PowerShell: рендеринг HTML представлений

    Вступление

    Не так давно передо мной возникла задача рендерить из PowerShell скрипта различные HTML отчеты для дальнейшей отсылки по e-mail. Поиск готовых решений дал не очень много. Кто-то подключает Razor, кто-то свои самописные сложноватые велосипеды движки.
    Скромный список требований был такой:
    1. Код вьюх должен быть в отдельных файлах.
    2. Внутри вьюх должна быть поддержка вложенности, и вставок кода на PowerShell.
    3. Должен работать на любых хостах с PowerShell 2.0 без дополнительных настроек.

    Так как ничего подобного не удалось найти, то был реализован простой (и одновременно мощный) движок рендеринга вьюх в стиле классического Asp.




    Подробности реализации

    Изучая вопрос (как и сам PowerShell) я обратил внимание на синтаксис вычисления PowerShell выражений внутри строк. Например выражение " $($env:COMPUTERNAME)" будет во время выполнения интерпретировано и на выходе мы получим что-то вроде MYCOMPUTER.

    Это уже собственно и есть темплейтинг в простейшем виде. Он позволяет рендерить достаточно сложные вьюхи:

    $Model = @{}
    $Model.Title = 'Hello, this is a test'
    $Model.Clients = @('Ivan', 'Sergiy', 'John')
    	
    $html = "<h1>
    	$($Model.Title)
    </h1>
    <div class=""test"">
    	<ul>
    		 $( foreach($client in $Model.Clients) {"
    			<li>
    				$( $client )
    			</li>
    		"})
    	</ul>
    </div>"
    
    $html
    


    Как видим, парсер PowerShell позволяет использовать вложенные вставки кода заключенного в $() в строки, это очень удобно для реализации ветвлений и циклов.

    Данный метод уже можно использовать для небольших задач, хотя есть и недостатки:
    1. Код вьюхи содержится в коде скрипта, а не в отдельном файле.
    2. Нет возможности использовать вложенные вьюхи.
    3. Синтаксис немного не совсем нагляден, и часто из-за пропущенной скобки или кавычки приходится напряженно все проверять.
    4. Необходимо в текстовых вставках кодировать двойную кавычку " как "".


    Первые два недостатка решаются довольно просто – темплейт перемещается в отдельный файл в подпапку Views, и пишется функция для рендера модели:

    function RenderViewNativePowerShell(
    	[Parameter(Mandatory=$true)][string] $viewName,
    	[Parameter(Mandatory=$true)][Object] $model
    )
    {
    	$viewFileName = Resolve-Path ('Views\' + $viewName)
    	$templateContent = Get-Content $viewFileName | Out-String
    	return $ExecutionContext.InvokeCommand.ExpandString('"' + $templateContent + '"')
    }
    


    После чего ее можно вызывать так:

    RenderViewNativePowerShell 'Test_ps.html' $Model
    


    При этом поддерживаются вложенные вьюхи. Вот так выглядит код test_ps.html:
    $( RenderViewNativePowerShell 'header_ps.html' $Model )
    
    <div class=""test"">
    	<ul>
    		 $( foreach($client in $Model.Clients) {"
    			<li>
    				$( $client )
    			</li>
    		"})
    	</ul>
    </div>
    


    Кому-то этого может показаться достаточно, но я решил побороть оставшиеся недостатки – перейти на использование скобок ASP <%...%>, так как этот синтаксис поддерживается во многих текстовых редакторах, и верстка страницы выглядит намного читабельнее.
    Итак, основная идея реализации довольно проста: взять и заменить все скобки <%...%> на их PowerShell эквиваленты $(…). Некоторая сложность состояла в том, что замена должна быть неоднозначной, чтобы учитывать вложенные вьюхи, так как они должны быть в “…” блоках.

    После некоторых мучений возникла такая функция:

    function RenderView(
    	[Parameter(Mandatory=$true)][string] $viewName,
    	[Parameter(Mandatory=$true)][Object] $model
    )
    {
    	$viewFileName = Resolve-Path ("Views\" + $viewName)
    	$templateContent = Get-Content $viewFileName | Out-String
    	
    	$rx = New-Object System.Text.RegularExpressions.Regex('(<%.*?%>)', [System.Text.RegularExpressions.RegexOptions]::Singleline)
    	$res = @()
    	$splitted = $rx.split($templateContent);
    	foreach($part in $splitted)
    	{
    		if ($part.StartsWith('<%') -and $part.EndsWith('%>')) #transform <%...%> blocks
    		{	
    			$expr = $part.Substring(2, $part.Length-4) #remove <%%> quotes
    			$normExpr = $expr.Replace('`n','').Replace('`r','').Trim();
    			
    			$startClosure = '$('
    			$endClosure = ')'
    			if ($normExpr.endswith('{')) {
    				$endClosure = '"'
    			}
    			if ($normExpr.startsWith('}')) {
    				$startClosure = '"'
    			}
    			$res += @($startClosure + $expr + $endClosure)
    		}
    		else #encode text blocks
    		{	
    			$expr = $part.Replace('"', '""');
    			$res += @($expr)
    		}
    	}
    	$viewExpr = $res -join ''
    	return $ExecutionContext.InvokeCommand.ExpandString('"' + $viewExpr + '"')
    }
    


    Кроме требуемой замены <%%> на их PowerShell эквиваленты также выполняется замена “ на “” в текстовых блоках.
    В итоге наша вьюха выглядит довольно неплохо в Visual Studio:



    В заключение остается отметить, что исходный код с некоторыми тестами и примерами выложен на GitHub.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 4

      –1
      Я правильно понимаю, что вы для разбора html применяете регулярные выражения?
        +1
        Регулярное выражение применяется чтобы найти и обработать блоки <%...%>. А какого типа темплейт не важно HTML, XML или просто текст.
        0
        Спасибо, интересно. Мне, правда, всегда хватало возможностей ConvertTo-Html. Была в свое время идея для более сложного случая конвертировать в XML с помощью Export-Clixml, а потом прикрутить вьюху с помощью XLST. Для рассылки по почте вряд ли пригодно, правда.
          0
          Посмотрите тут. Конкретно «Creating HTML Reports in PowerShell» Толково, и не придется изобретать велосипед

          Only users with full accounts can post comments. Log in, please.