Languages/XSL/XSL-Language.ps1
function Language.XSL { <# .SYNOPSIS XSL PipeScript Language Definition. .DESCRIPTION Allows PipeScript to generate and interact with XSL. Multiline comments blocks like this ```<!--{}-->``` will be treated as blocks of PipeScript. #> [ValidatePattern('\.xsl$')] param() $this = $myInvocation.MyCommand if (-not $this.Self) { $languageDefinition = New-Module { param() $FilePattern = '\.xsl$' # We start off by declaring a number of regular expressions: $startComment = '<\!--' # * Start Comments ```<!--``` $endComment = '-->' # * End Comments ```-->``` $Whitespace = '[\s\n\r]{0,}' # * StartPattern ```$StartComment + '{' + $Whitespace``` $startPattern = "(?<PSStart>${startComment}\{$Whitespace)" # * EndPattern ```$whitespace + '}' + $EndComment``` $endPattern = "(?<PSEnd>$Whitespace\}${endComment}\s{0,})" # XSL will render each output similarily to XML $ForeachObject = { $in = $_ # Strings or primitive types are rendered inline if (($in -is [string]) -or ($in.GetType -and $in.GetType().IsPrimitive)) { $in } elseif ($in.ChildNodes) { # If there were child nodes foreach ($inChildNode in $in.ChildNodes) { # output them (unless they were declarations) if ($inChildNode.NodeType -ne 'XmlDeclaration') { $inChildNode.OuterXml } } } else { # otherwise, we attempt to conver the object to xml $inXml = (ConvertTo-Xml -Depth 100 -InputObject $in) foreach ($inChildNode in $inXml.ChildNodes) { if ($inChildNode.NodeType -ne 'XmlDeclaration') { $inChildNode.OuterXml } } } } $Interpreter = { param() # Remove any empty arguments $argList = @($args -ne $null) # And create a transform object $xslTransformer = [xml.Xsl.XslCompiledTransform]::new() $xslTransformer | Add-Member Arguments $argList -Force # Attempt to turn args into XSL-friendly arguments: $xslFriendlyArgs = @(foreach ($arg in $argList) { if ($arg -match '^\.[\\/]') { # * Resolve relative paths if (Test-path $arg) { # (that exist) (Get-Item $arg).FullName # to their fullname. } else { "$($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($arg))" } } elseif ($arg -as [xml]) { # * Cast as xml $arg -as [xml] # if we can } else { $arg # or return directly } }) # The first arg should be the file/xml document $xslFile, $otherArgs = $xslFriendlyArgs # the rest should be transform options $xslTransformer | Add-Member XslFile $xslFile -Force $otherArgs = @($otherArgs) try { $xslTransformer.Load($xslFile) } catch { $xslTransformer | Add-Member Error $_ -Force return $xslTransformer } # If we only had the XSL, return the "ready-to-go" transform. if ($xslFriendlyArgs.Length -eq 1) { return $xslTransformer } # If we had one other argument, return a string if ($otherArgs.Length -eq 1) { $stringBuilder = [Text.StringBuilder]::new() $xmlWriter = [Xml.XmlWriter]::create($stringBuilder) $xslTransformer.Transform($otherArgs[0], $xmlWriter) $xmlWriter.Close() return "$stringBuilder" } # Invoke the transformer (if this fails, the error will bubble up) $xslTransformer.Transform.Invoke($otherArgs) } function TranslateAssignmentStatement { param($assignmentStatement) if ($assignmentStatement.Right.Expression -is [Management.Automation.Language.StringConstantExpressionAst] -and $assignmentStatement.Operator -eq 'Equals') { "<xsl:variable name=`"$($assignmentStatement.Left)`">$($assignmentStatement.Right.Expression.Value)</xsl:variable>" } } function TranslateSwitchStatement { param($switchStatement) @" <xsl:variable name='`$_'>$($this.TranslateFromPowerShell($switchStatement.Condition))</xsl:variable> <xsl:choose>$( @( foreach ($switchClause in $switchStatement.Clauses) { "<xsl:when test=`"$( $this.TranslateFromPowerShell($switchClause.Item1) -replace '"', '\"' )`"> $($this.TranslateFromPowerShell($switchClause.Item2) -replace '"', '\"') </xsl:when>" } if ($switchStatement.Default) { "<xsl:otherwise>$($this.TranslateFromPowerShell($switchStatement.Default) -replace '"', '\"')</xsl:otherwise>" } ) -join ((' ' * 4) + [Environment]::NewLine) )</xsl:choose> "@ } function TranslateIfStatement { param($ifStatement) if ($ifStatement.Clauses.Count -eq 1 -and -not $ifStatement.ElseClause){ @" <xsl:if test="$( $this.TranslateFromPowerShell($ifStatement.Clauses.Item1) -replace '"', '\"' )"> $($this.TranslateFromPowerShell($ifStatement.Clauses.Item2) -replace '"', '\"') </xsl:if> "@ } else { @" <xsl:choose>$( @( foreach ($ifClause in $ifStatement.Clauses) { "<xsl:when test=`"$( $this.TranslateFromPowerShell($ifClause.Item1) -replace '"', '\"' )`"> $($this.TranslateFromPowerShell($ifClause.Item2) -replace '"', '\"') </xsl:when>" } if ($ifStatement.ElseClause) { "<xsl:otherwise>$($this.TranslateFromPowerShell($ifClause.Item2) -replace '"', '\"')</xsl:otherwise>" } ) -join ((' ' * 4) + [Environment]::NewLine) )</xsl:choose> "@ } } function TranslateForeachStatement { param($ForeachStatementAst) @" <xsl:for-each select="$( $this.TranslateFromPowerShell($ForeachStatementAst.Condition) -replace '"', '\"' )"> <xsl:variable name='$($ForeachStatementAst.Variable.VariablePath.UserPath)' select='.' /> $($this.TranslateFromPowerShell($ForeachStatementAst.Body) -replace '"', '\"') </xsl:for-each> "@ } function TranslateFromPowerShell { <# .SYNOPSIS Performs Limited PowerShell to XSL Translation .DESCRIPTION Performs Limited PowerShell to XSL Translation. While XSL is a much more restricted language than PowerShell, certain statements translate fairly cleanly: |Powershell|XSL| |-|-| |`foreach`|`<xsl:for-each />`/`<xsl:variable />`| |`if|`<xsl:if />`/`<xls:choose />`/`<xsl:otherwise />| |`switch`|`<xsl:choose>`| |`Sort-Object|`<xsl:sort/>`| String Constants can be embedded inline. .EXAMPLE $PSLanguage.XSL.TranslateFromPowerShell({if($x -eq "true") { "y" }) #> param($inputObject) if (-not $inputObject) { return } switch ($inputObject) { {$_ -is [Management.Automation.Language.CommandExpressionAst]} { $this.TranslateFromPowerShell($inputObject.Expression) } {$_ -is [Management.Automation.Language.CommandAst]} { } {$_ -is [Management.Automation.Language.PipelineAst]} { foreach ($element in $inputObject.PipelineElements) { $this.TranslateFromPowerShell($element) } } {$_ -is [Management.Automation.Language.StatementBlockAst]} { foreach ($statement in $inputObject.Statements) { $this.TranslateFromPowerShell($statement) } } {$_ -is [Management.Automation.Language.ForeachStatementAst]} { $foreachStatement = $_ $this.TranslateForeachStatement($foreachStatement) } {$_ -is [Management.Automation.Language.IfStatementAst]} { $ifStatement = $_ $this.TranslateIfStatement($ifStatement) } {$_ -is [Management.Automation.Language.StringConstantExpressionAst]} { $_.Value } } } $LanguageName = 'XSL' Export-ModuleMember -Variable * -Function * -Alias * } -AsCustomObject $languageDefinition.pstypenames.clear() $languageDefinition.pstypenames.add("Language") $languageDefinition.pstypenames.add("Language.XSL") $this.psobject.properties.add([PSNoteProperty]::new('Self',$languageDefinition)) } $this.Self } |