Commands/Common/Write-SVG.ps1
function Write-SVG { <# .SYNOPSIS Writes a SVG element .DESCRIPTION Writes a Scalable Vector Graphics element. .Notes While this function can be used directly, it is designed to be the core function that other SVG creation functions call. #> param( # The name of the SVG element. [Parameter(Mandatory)] [string] $ElementName, # A dictionary of attributes. [Parameter(ValueFromPipelineByPropertyName)] [Collections.IDictionary] $Attribute = [Ordered]@{}, # A dictionary of data. [Parameter(ValueFromPipelineByPropertyName)] [Collections.IDictionary] $Data = [Ordered]@{}, # A dictionary of content. [Parameter(ValueFromPipelineByPropertyName)] [PSObject] $Content, # A dictionary or object containing event handlers. # Each key or property name will be the name of the event # Each value will be the handler. [Parameter(ValueFromPipelineByPropertyName)] $On, # An output path. [Parameter(ValueFromPipelineByPropertyName)] [string] $OutputPath ) begin { $myCmd = $MyInvocation.MyCommand ${?<CamelCaseSpace>} = [Regex]::new('(?<CamelCaseSpace>(?<=[a-z])(?=[A-Z]))') } process { $elementCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand("SVG.$elementName", 'Function') if ($Attribute['Style'] -and $Attribute['Style'] -isnot [string]) { if ($Attribute['Style'] -is [Collections.IDictionary]) { $Attribute['Style'] = @(foreach ($kv in $Attribute['Style'].GetEnumerator()) { "$($kv.Key):$($kv.Value)" }) -join ';' } else { $Attribute['Style'] = @(foreach ($prop in $Attribute['Style'].psobject.properties) { "$($prop.Name):$($kv.Value)" }) -join ';' } } $elementText = "<$elementName " :nextParameter foreach ($kv in $Attribute.GetEnumerator()) { if ($myCmd.Parameters[$kv.Key]) { continue } $paramValue = $kv.Value $paramName = $kv.Key if ($paramName -eq 'Viewbox' -and $paramValue.Length -eq 2) { $paramValue = @(0,0) + $paramValue } if ($paramValue -is [timespan]) { $paramValue = "$($paramValue.TotalSeconds)s" } foreach ($attr in $elementCmd.Parameters[$kv.Key].Attributes) { if ($attr.Key -eq 'SVG.AttributeName') { if ($inputObject -and $inputObject.psobject.properties[$attr.Key]) { $inputObject.psobject.properties.Remove($attr.Key) } $elementText += "$($attr.Value)='$([Web.HttpUtility]::HtmlAttributeEncode($paramValue))' " continue nextParameter } } $elementText += "$($kv.Key)='$([Web.HttpUtility]::HtmlAttributeEncode($kv.Value))' " } if ($data -and $data.Count) { foreach ($kv in $data.GetEnumerator()) { $dataKey = ${?<CamelCaseSpace>}.Replace($kv.Key, '-').Replace('_','-') $dataKey = "data-$($dataKey.ToLower())" $dataValue = [Web.HttpUtility]::HtmlAttributeEncode($kv.Value) $elementText += "$dataKey='$dataValue' " } } if ($On) { $eventNames = @( if ($on -is [Collections.IDictionary]) { $on.Keys } else { $on.psobject.properties.name } ) foreach ($eventName in $eventNames) { $svgEventName = $eventName -replace '^On' -replace '^[_-]' $eventValue = if ($on -is [Collections.IDictionary]) { $on[$eventName] } else { $on.$eventName } $elementText += "on" + $svgEventName.ToLower() + "=`"" + $( [Web.HttpUtility]::HtmlAttributeEncode($eventValue) ) + '" ' } } if ($elementName -eq 'svg') { $elementText += 'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"' } $elementText = $elementText -replace '\s{0,1}$' if (-not $content) { $elementText += " />" } else { $isCData = $false foreach ($attr in $elementCmd.Parameters.Content.Attributes) { if ($attr.Key -eq 'SVG.IsCData' -and $attr.Value -eq 'true') { $isCData = $true } } $elementText += ">" $elementText += foreach ($pieceOfContent in $Content) { if ($isCData -and -not ($pieceOfContent -as [xml.xmlelement]) -and ($pieceOfContent -notmatch '^\s{0,}\<') ) { [Security.SecurityElement]::Escape("$pieceOfContent") } elseif ($pieceOfContent.Outerxml) { $pieceOfContent.Outerxml } else { "$pieceOfContent" } } $elementText += "</$elementName>" } $elementXml = $elementText -as [xml] $svgOutput = if ($elementXml -and $elementXml.$ElementName) { $o = $elementXml.$ElementName if ($o -is [string]) { $o = $elementXml } $o.pstypenames.clear() $o.pstypenames.add('SVG.Element') $o } else { $elementText } $myParams = @{} + $PSBoundParameters if ($myParams['OutputPath']) { $unresolvedOutput = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) if ($unresolvedOutput -and $svgOutput.ParentNode.Save) { $svgOutput.ParentNode.PreserveWhitespace = $true $memoryStream = [io.memorystream]::new() $streamWriter = [io.streamWriter]::new($memoryStream) $writerSettings = [Xml.XmlWriterSettings]::new() $writerSettings.Encoding = [Text.Encoding]::UTF8 $writerSettings.Indent = $true $writer = [Xml.XmlWriter]::Create($streamWriter, $writerSettings) $svgOutput.ParentNode.Save($writer) [Text.Encoding]::UTF8.GetString($memoryStream.ToArray()) | Set-Content -Path $OutputPath -Encoding UTF8 $writer.Dispose() $streamWriter.Dispose() $memoryStream.Dispose() Get-Item $OutputPath } elseif ($unresolvedOutput -and $svgOutput) { $svgOutput | Set-Content -Path $OutputPath Get-Item $OutputPath } } else { $svgOutput } } } |