PowerShell: HTML rendering views

Introduction

Not long ago I had a task to render from PowerShell script various HTML reports for future reference by e-mail. Search for ready decisions did not bring in very much. Someone attaches Razor, someone your house hard a Bicycle engines.
A modest list of requirements was:
    the
  1. Code vyuha should be in separate files.
  2. the
  3. Inside the vyuha, there must be support nesting, and inserts code into PowerShell.
  4. the
  5. Should work on any host with PowerShell 2.0 without any additional configuration.

Since nothing like this has been found, it was implemented a simple (and powerful) rendering engine vyuha in the style of classic Asp.




implementation Details

Studying the issue (as does PowerShell) I drew attention to the syntax of PowerShell calculation expressions inside of strings. For example, the expression " $($env:COMPUTERNAME)" is a runtime interpreted and we will get something like MYCOMPUTER.

It already actually is templating in its simplest form. It allows you to render a fairly complex view function:

the
$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


As you can see, the PowerShell parser allows for nested code insertions prisoner in $() to a string, it is very convenient to implement branches and loops.

This method can already be used for small tasks, though there are drawbacks:
    the
  1. Code view function contained in the script code, not in a separate file.
  2. the
  3. is Not possible to use nested view function.
  4. the Syntax is not entirely clear, and often for missed brackets or quotation marks stress you have to check it out. the

  5. in the text inserts to encode the double quote " as "".


The first two drawbacks can be solved simply – the template is moved to a separate file in the Views subfolder, and write a function to render the model:

the
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 + '"')
}


Then it can be called so:

the
RenderViewNativePowerShell 'Test_ps.html' $Model


In this nested view function. Here is the code test_ps.html:
the
$( RenderViewNativePowerShell 'header_ps.html' $Model )

<div class="test">
<ul>
$( foreach($client in $Model.Clients) {"
<li>
$( $client )
</li>
"})
</ul>
</div>


To some this may seem enough, but I decided to overcome the remaining weaknesses – go the use of parentheses ASP <%...%> because this syntax is supported by many text editors, and layout of the page looks much more readable.
So the main idea of implementation is quite simple: to replace all the brackets <%...%> in their PowerShell equivalents $(...). Some difficulty was the fact that the replacement needs to be ambiguous to account for the nested view function, as they should be in the “...” blocks.

After some agony having this function:

the
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 + '"')
}


In addition to the required replacement of <%%> in their PowerShell equivalents also, you will replace “ with “” in text blocks.



In conclusion, it remains to note that the source code with some tests and examples posted on GitHub.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

The use of Lisp in production

FreeBSD + PostgreSQL: tuning the database server

As we did a free Noodle for iOS and how we plan to earn