Last active
August 16, 2022 03:30
-
-
Save DBremen/06aeb0bcdcc1d57f04d6 to your computer and use it in GitHub Desktop.
Expand zen coding expression for PowerShell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires zenCoding.dll in \resources. Download via https://github.com/madskristensen/zencoding and compile. | |
function Get-ZenCode{ | |
[Alias("zenCode")] | |
[CmdletBinding()] | |
Param | |
( | |
[Parameter(Mandatory=$true, | |
Position=0)] | |
$zenCodeExpr, | |
[Parameter(ValueFromPipeline=$true, | |
Position=1)] | |
$InputObject, | |
[Parameter(Position=2)] | |
$outPath="", | |
[Parameter(Position=3)] | |
[switch]$show | |
) | |
if (-not ([Management.Automation.PSTypeName]'ZenCoding.HtmlParser').Type) | |
{ | |
Add-Type -Path "$(Split-Path $PSScriptRoot -Parent)\resources\ZenCoding.dll" | |
} | |
Add-Type -AssemblyName System.Web | |
$allData = @($Input) | |
if ($allData){ | |
$closingCurlyPositions = ([regex]::Matches($zenCodeExpr,'\$_.*?(?=\})(})')).Groups | | |
where {$_.Value -eq '}'} | select -ExpandProperty Index | sort -Descending | |
$txtWithinCurlies = [regex]::Matches($zenCodeExpr,'(?<=\{).*?(?=\})') | |
$pipelineVars = @([regex]::Matches($zenCodeExpr,'(\$_(\.\w+)*)')) | |
$firstIndex = ($pipelineVars | sort index | select -first 1).Index | |
$pipelineVars = $pipelineVars | select -ExpandProperty Value | |
$txtWithinCurlies = $txtWithinCurlies | select -ExpandProperty Value | |
$txtWithinCurliesCount = $txtWithinCurlies | group -AsHashTable -AsString | |
#if the expression contains a table, no unqualified pipelinVar ($_) and no headers, add the headers based on the property names | |
if ($zenCodeExpr -like '*table*' -and $pipelineVars -notcontains '$_' -and $zenCodeExpr -notlike '*>th*'){ | |
$headerIndex = ([regex]::Matches($zenCodeExpr,'(?<=\>)table.*?(>)').Groups | | |
where {$_.Value -eq '>' -and $_.Index -lt $firstIndex} | | |
sort Index -Descending).Index + 1 | |
$headerExpr = 'tr>' | |
$headerExpr += (($pipelineVars.SubString(3) | foreach { "th{$_}"}) -join '+') + '^' | |
$zenCodeExpr = $zenCodeExpr.Insert($headerIndex,$headerExpr) | |
$firstIndex += $headerExpr.Length | |
} | |
$txtWithinCurlies = $txtWithinCurlies.Trim() | where {$_ -like '*$_*'} | Get-Unique | |
$pipelineVars=$pipelineVars.Trim() | Get-Unique | |
$htReplacements = @{} | |
#foreach ($curlyPos in $closingCurlyPositions){ | |
# $zenCodeExpr = $zenCodeExpr.Insert($curlyPos+1,"*$($allData.count)") | |
#} | |
#add the multiplier and wrap the expression into parenthesis | |
$zenCodeExpr = $zenCodeExpr.Insert($zenCodeExpr.Substring(0,$firstIndex).LastIndexOf('>')+1,'(') | |
$zenCodeExpr += ")*$($allData.count)" | |
foreach ($var in $txtWithinCurlies){ | |
$guid = [guid]::NewGuid().Guid + '_' | |
$htReplacements.Add($guid,$var) | |
$zenCodeExpr = $zenCodeExpr -Replace ([regex]::Escape($var) + '\b') ,($guid + '$') | |
} | |
} | |
$zenCodeParser = New-Object ZenCoding.HtmlParser | |
$txt = $zenCodeParser.Parse($zenCodeExpr) | |
$i=1 | |
foreach ($item in $allData){ | |
foreach ($replacement in $htReplacements.GetEnumerator()) { | |
#cannot rely on zencoding numbering therefore use remove/insert instead of replacing all unique instances | |
$sb = [scriptblock]::Create($replacement.Value.Replace('$_','$item')) | |
$value=$sb.Invoke() | |
#do once for each instance of the placeholder within the template | |
$count = $txtWithinCurliesCount[$replacement.Value].Count | |
for($j=0;$j -lt $count;$j++){ | |
$firstIndex = $txt.IndexOf($replacement.Key) | |
#if the value is an array add a nested table with all its values | |
if ($value.Count -gt 1){ | |
$searchTxt = $txt.Substring(0,$firstIndex) | |
$startIndex = $searchTxt.LastIndexOf('<')+1 | |
$enclosingTag = $searchTxt.Substring($startIndex,$searchTxt.LastIndexOf('>')-$startIndex) | |
#object, insert nested table with headers based on properties | |
if($value | Get-Member CreateObjRef -MemberType Method){ | |
$tableExpr = 'table>tr>' | |
$props = ($value | Get-Member | where {$_.MemberType -like '*Property'}).Name | |
$tableExpr += (($props | foreach { "th{$_}"}) -join '+') + '^' | |
$tableExpr += '(tr>' + (($props | foreach { 'td{$_.' + "$_}" }) -join '+') + ')' | |
$replacementStr = $value | zenCode $tableExpr | |
} | |
#values handle according to enclosingtag | |
elseif($enclosingTag -eq 'td'){ | |
$replacementStr = $value | zenCode 'table>(tr>td{$_})' | |
} | |
#li assume ul | |
elseif ($enclosingTag -eq 'li'){ | |
$expr = 'ul{' + $replacement.Value.Replace('$_.','') + '}>(li{$_})' | |
$replacementStr = $value | zenCode $expr | |
} | |
#just repeat the element and indent the texst | |
else{ | |
$expr = '(' + $enclosingTag + '[style=margin-left:2em]{$_})' | |
$replacementStr = $value | zenCode $expr | |
} | |
$txt=$txt.Remove($firstIndex,$replacement.Key.Length+1).Insert($firstIndex,$replacementStr) | |
} | |
else{ | |
$txt=$txt.Remove($firstIndex,$replacement.Key.Length+1).Insert($firstIndex,$value) | |
} | |
} | |
} | |
$i++ | |
} | |
#apply indentation | |
$tempRoot = $false | |
#hex values do not work in xml | |
$escapedTxt= $txt.Replace('0x','hex').Replace('&','&') | |
try{ | |
[xml]$xml=$escapedTxt | |
} | |
catch{ | |
#missing unique root element, insert | |
[xml]$xml = "<tempRoot>$escapedTxt</tempRoot>" | |
$tempRoot = $true | |
} | |
$StringWriter = New-Object IO.StringWriter | |
$settings = New-Object XML.XmlWriterSettings | |
$settings.Indent = $true | |
$settings.IndentChars = "`t" | |
$settings.OmitXmlDeclaration = $true | |
$XmlWriter = [XMl.XmlTextWriter]::Create($stringWriter,$settings) | |
$xml.WriteContentTo($XmlWriter) | |
$XmlWriter.Flush() | |
$StringWriter.Flush() | |
#remove the root element | |
if ($tempRoot){ | |
$output = ($StringWriter.ToString() -replace '(?m)\s*</*tempRoot>\s*?','').Trim() -replace '(?m)^\t{1}','' | |
} | |
else{ | |
$output = $StringWriter.ToString() | |
} | |
#create a temp file if show flag is set and no outPath was provided | |
if ($show -and $outPath -eq ""){ | |
$outPath=[IO.Path]::GetTempFileName().Replace('.tmp','.html') | |
} | |
if ($outPath -ne ""){ | |
$output | Set-Content $outPath | |
} | |
else{ | |
$output | |
} | |
if ($show){ | |
Invoke-Item $outPath | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment