Release/PScribo-v0.7.6.7-Bundle.ps1
#region PScribo Bundle v0.7.6.7 #requires -Version 3 <# The MIT License (MIT) Copyright (c) 2015 Iain Brighton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #> $localized = DATA { # en-US ConvertFrom-StringData @' ImportingFile = Importing file '{0}'. InvalidDirectoryPathError = Path '{0}' is not a valid directory path.' NoScriptBlockProvidedError = No PScribo section script block is provided (have you put the open curly brace on the next line?). InvalidHtmlColorError = Invalid Html color '{0}' specified. InvalidHtmlBackgroundColorError = Invalid Html background color '{0}' specified. UndefinedTableHeaderStyleError = Undefined table header style '{0}' specified. UndefinedTableRowStyleError = Undefined table row style '{0}' specified. UndefinedAltTableRowStyleError = Undefined table alternating row style '{0}' specified. InvalidTableBorderColorError = Invalid table border color '{0}' specified. UndefinedStyleError = Undefined style '{0}' specified. OpenPackageError = Error opening package '{0}'. Ensure the file in not in use by another process. MaxHeadingLevelWarning = Html5 supports a maximum of 5 heading levels. TableHeadersWithNoColumnsWarning = Table headers have been specified with no table columns/properties. Headers will be ignored. TableHeadersCountMismatchWarning = The number of table headers specified does not match the number of specified columns/properties. Headers will be ignored. ListTableColumnCountWarning = Table columns widths in list format must be 2. Column widths will be ignored. TableColumnWidthMismatchWarning = The specified number of table columns and column widths do not match. Column widths will be ignored. TableColumnWidthSumWarning = The table column widths total '{0}'%. Total column width must equal 100%. Column widths will be ignored. TableWidthOverflowWarning = The table width overflows the page margin and has been adjusted to '{0}'%. DocumentProcessingStarted = Document '{0}' processing started. DocumentInvokePlugin = Invoking '{0}' plugin. DocumentOptions = Setting global document options. DocumentOptionSpaceSeparator = Setting default space separator to '{0}'. DocumentOptionUppercaseHeadings = Enabling uppercase headings. DocumentOptionUppercaseSections = Enabling uppercase sections. DocumentOptionSectionNumbering = Enabling section/heading numbering. DocumentOptionPageTopMargin = Setting page top margin to '{0}'mm. DocumentOptionPageRightMargin = Setting page right margin to '{0}'mm. DocumentOptionPageBottomMargin = Setting page bottom margin to '{0}'mm. DocumentOptionPageLeftMargin = Setting page left margin to '{0}'mm. DocumentOptionPageSize = Setting page size to '{0}'. DocumentOptionPageHeight = Setting page height to '{0}'mm. DocumentOptionPageWidth = Setting page width to '{0}'mm. DocumentOptionDefaultFont = Setting default font(s) to '{0}'. ProcessingBlankLine = Processing blank line. ProcessingImage = Processing image '{0}'. ProcessingLineBreak = Processing line break. ProcessingPageBreak = Processing page break. ProcessingParagraph = Processing paragraph '{0}'. ProcessingSection = Processing section '{0}'. ProcessingSectionStarted = Processing section '{0}' started. ProcessingSectionCompleted = Processing section '{0}' completed. PluginProcessingSection = Processing {0} '{1}'. ProcessingStyle = Setting document style '{0}'. ProcessingTable = Processing table '{0}'. ProcessingTableStyle = Setting table style '{0}'. ProcessingTOC = Processing table of contents '{0}'. ProcessingDocumentPart = Processing document part '{0}'. WritingDocumentPart = Writing document part '{0}'. GeneratingPackageRelationships = Generating package relationships. PluginUnsupportedSection = Unsupported section '{0}'. DocumentProcessingCompleted = Document '{0}' processing completed. TotalProcessingTime = Total processing time '{0:N2}' seconds. SavingFile = Saving file '{0}'. '@; } function BlankLine { <# .SYNOPSIS Initializes a new PScribo blank line object. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline, Position = 0)] [System.UInt32] $Count = 1 ) begin { #region BlankLine Private Functions function New-PScriboBlankLine { <# .SYNOPSIS Initializes a new PScribo blank line break. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(ValueFromPipeline)] [System.UInt32] $Count = 1 ) process { $typeName = 'PScribo.BlankLine'; $pscriboDocument.Properties['BlankLines']++; $pscriboBlankLine = [PSCustomObject] @{ Id = [System.Guid]::NewGuid().ToString(); LineCount = $Count; Type = $typeName; } return $pscriboBlankLine; } } #end function New-PScriboBlankLine #endregion BlankLine Private Functions } #end begin process { WriteLog -Message $localized.ProcessingBlankLine; return (New-PScriboBlankLine @PSBoundParameters); } #end process } #end function BlankLine function ConvertPtToMm { <# .SYNOPSIS Convert points into millimeters #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('pt')] [System.Single] $Point ) process { return [System.Math]::Round(($Point / 72) * 25.4, 2); } } function ConvertPxToMm { <# .SYNOPSIS Convert pixels into millimeters (default 96dpi) #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('px')] [System.Single] $Pixel, [Parameter()] [System.Int16] $Dpi = 96 ) process { return [System.Math]::Round((25.4 / $Dpi) * $Pixel, 2); } } function ConvertInToMm { <# .SYNOPSIS Convert inches into millimeters #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('in')] [System.Single] $Inch ) process { return [System.Math]::Round($Inch * 25.4, 2); } } function ConvertMmToIn { <# .SYNOPSIS Convert millimeters into inches #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('mm','Millimetre')] [System.Single] $Millimeter ) process { return [System.Math]::Round($Millimeter / 25.4, 2); } } function ConvertMmToPt { <# .SYNOPSIS Convert millimeters into points #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('mm','Millimetre')] [System.Single] $Millimeter ) return ((ConvertMmToIn $Millimeter) / 0.0138888888888889); } function ConvertMmToTwips { <# .SYNOPSIS Convert millimeters into twips .NOTES 1 twip = 1/20th pt #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('mm','Millimetre')] [System.Single] $Millimeter ) process { return (ConvertMmToIn -Millimeter $Millimeter) * 1440; } } function ConvertMmToOctips { <# .SYNOPSIS Convert millimeters into octips .NOTES 1 "octip" = 1/8th pt #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('mm','Millimetre')] [System.Single] $Millimeter ) process { return (ConvertMmToIn -Millimeter $Millimeter) * 576; } } function ConvertMmToEm { <# .SYNOPSIS Convert millimeters into em #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('mm','Millimetre')] [System.Single] $Millimeter ) process { return [System.Math]::Round($Millimeter / 4.23333333333333, 2); } } function ConvertMmToPx { <# .SYNOPSIS Convert millimeters into pixels (default 96dpi) #> [CmdletBinding()] [OutputType([System.Single])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Alias('mm','Millimetre')] [System.Single] $Millimeter, [Parameter()] [System.Int16] $Dpi = 96 ) process { $pixels = [System.Int16] ((ConvertMmToIn -Millimeter $Millimeter) * $Dpi); if ($pixels -lt 1) { return 1; } else { return $pixels; } } } function Document { <# .SYNOPSIS Initializes a new PScribo document object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## PScribo document name [Parameter(Mandatory, Position = 0)] [System.String] $Name, ## PScribo document DSL script block containing Section, Paragraph and/or Table etc. commands. [Parameter(Position = 1)] [System.Management.Automation.ScriptBlock] $ScriptBlock = $(throw $localized.NoScriptBlockProvidedError) ) begin { $pluginName = 'Document'; #region Document Private Functions function New-PScriboDocument { <# .SYNOPSIS Initializes a new PScript document object. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## PScribo document name [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name ) process { WriteLog -Message ($localized.DocumentProcessingStarted -f $Name); $typeName = 'PScribo.Document'; $pscriboDocument = [PSCustomObject] @{ Id = $Name.Replace(' ', '').ToUpper(); Type = $typeName; Name = $Name; Sections = New-Object -TypeName System.Collections.ArrayList; Options = New-Object -TypeName System.Collections.Hashtable([System.StringComparer]::InvariantCultureIgnoreCase); Properties = New-Object -TypeName System.Collections.Hashtable([System.StringComparer]::InvariantCultureIgnoreCase); Styles = New-Object -TypeName System.Collections.Hashtable([System.StringComparer]::InvariantCultureIgnoreCase); TableStyles = New-Object -TypeName System.Collections.Hashtable([System.StringComparer]::InvariantCultureIgnoreCase); DefaultStyle = $null; DefaultTableStyle = $null; TOC = New-Object -TypeName System.Collections.ArrayList; } GlobalOption -MarginTopAndBottom 72 -MarginLeftAndRight 54 -PageSize A4 -Verbose:$false -DefaultFont 'Calibri','Candara','Segoe','Segoe UI','Optima','Arial','Sans-Serif'; ## Set "default" styles Style -Name Normal -Default; Style -Name Title -Size 28 -Color 0072af; Style -Name TOC -Size 16 -Color 0072af; Style -Name 'Heading 1' -Size 16 -Color 0072af; Style -Name 'Heading 2' -Size 14 -Color 0072af; Style -Name 'Heading 3' -Size 12 -Color 0072af; Style -Name TableDefaultHeading -Size 11 -Color fff -Bold -BackgroundColor 4472c4; Style -Name TableDefaultRow -Size 11; Style -Name TableDefaultAltRow -BackgroundColor d0ddee; Style -Name Footer -Size 8 -Color 0072af; TableStyle TableDefault -BorderWidth 1 -BorderColor 2a70be -HeaderStyle TableDefaultHeading -RowStyle TableDefaultRow -AlternateRowStyle TableDefaultAltRow -Default; return $pscriboDocument; } #end process } #end function NewPScriboDocument function Process-PScriboSection { <# .SYNOPSIS Processes the document/TOC section versioning each level, i.e. 1.2.2.3 .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] param ( ) function Process-PScriboSectionLevel { <# .SYNOPSIS Nested function that processes each document/TOC nested section #> [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateNotNull()] [PSCustomObject] $Section, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Number ) if ($pscriboDocument.Options['ForceUppercaseSection']) { $Section.Name = $Section.Name.ToUpper(); } ## Set this section's level $Section.Number = $Number; $Section.Level = $Number.Split('.').Count -1; ### Add to the TOC $tocEntry = [PScustomObject] @{ Id = $Section.Id; Number = $Number; Level = $Section.Level; Name = $Section.Name; } [ref] $null = $pscriboDocument.TOC.Add($tocEntry); ## Set sub-section level seed $minorNumber = 1; foreach ($s in $Section.Sections) { if ($s.Type -like '*.Section' -and -not $s.IsExcluded) { $sectionNumber = ('{0}.{1}' -f $Number, $minorNumber).TrimStart('.'); ## Calculate section version Process-PScriboSectionLevel -Section $s -Number $sectionNumber; $minorNumber++; } } #end foreach section } #end function Process-PScriboSectionLevel $majorNumber = 1; foreach ($s in $pscriboDocument.Sections) { if ($s.Type -like '*.Section') { if ($pscriboDocument.Options['ForceUppercaseSection']) { $s.Name = $s.Name.ToUpper(); } if (-not $s.IsExcluded) { Process-PScriboSectionLevel -Section $s -Number $majorNumber; $majorNumber++; } } #end if } #end foreach } #end function process-psscribosection #endregion Document Private Functions } #end begin process { $stopwatch = [Diagnostics.Stopwatch]::StartNew(); $pscriboDocument = New-PScriboDocument -Name $Name; ## Call the Document script block foreach ($result in & $ScriptBlock) { [ref] $null = $pscriboDocument.Sections.Add($result); } Process-PScriboSection; WriteLog -Message ($localized.DocumentProcessingCompleted -f $pscriboDocument.Name); $stopwatch.Stop(); WriteLog -Message ($localized.TotalProcessingTime -f $stopwatch.Elapsed.TotalSeconds); return $pscriboDocument; } #end process } #end function Document function Export-Document { <# .SYNOPSIS Exports a PScribo document object to one or more output formats. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( ## PScribo document object [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Document, ## Output formats [Parameter(Mandatory)] [ValidateNotNull()] [System.String[]] $Format, ## Output file path [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Path = (Get-Location -PSProvider FileSystem), ## PScribo document export option [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.Collections.Hashtable] $Options ) begin { try { $Path = Resolve-Path $Path -ErrorAction SilentlyContinue; } catch { } if (-not (Test-Path $Path -PathType Container)) { ## Check $Path is a directory throw ($localized.InvalidDirectoryPathError -f $Path); } } process { foreach ($f in $Format) { WriteLog -Message ($localized.DocumentInvokePlugin -f $f) -Plugin 'Export'; ## Call specified output plugin #try { ## Dynamically generate the output format function name $outputFormat = 'Out{0}' -f $f; & $outputFormat -Document $Document -Path $Path; # -ErrorAction Stop; #} #catch [System.Management.Automation.CommandNotFoundException] { # Write-Warning ('Output format "{0}" is unsupported.' -f $f); #} } # end foreach } #end process } #end function Export-Document function GlobalOption { <# .SYNOPSIS Initializes a new PScribo global options/settings. .NOTES Options are reset upon each invocation. #> [CmdletBinding(DefaultParameterSetName = 'Margin')] param ( ## Forces document header to be displayed in upper case. [System.Management.Automation.SwitchParameter] $ForceUppercaseHeader, ## Forces all section headers to be displayed in upper case. [System.Management.Automation.SwitchParameter] $ForceUppercaseSection, ## Enable section/heading numbering [System.Management.Automation.SwitchParameter] $EnableSectionNumbering, ## Default space replacement separator [Parameter(ValueFromPipelineByPropertyName)] [Alias('Separator')] [AllowNull()] [ValidateLength(0,1)] [System.String] $SpaceSeparator, ## Default page top, bottom, left and right margin (pt) [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Margin')] [System.UInt16] $Margin = 72, ## Default page top and bottom margins (pt) [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'CustomMargin')] [System.UInt16] $MarginTopAndBottom, ## Default page left and right margins (pt) [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'CustomMargin')] [System.UInt16] $MarginLeftAndRight, ## Default page size [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('A4','Legal','Letter')] [System.String] $PageSize = 'A4', ## Default document font(s) [Parameter(ValueFromPipelineByPropertyName)] [System.String[]] $DefaultFont = @('Calibri','Candara','Segoe','Segoe UI','Optima','Arial','Sans-Serif') ) process { $localized.DocumentOptions | WriteLog; if ($SpaceSeparator) { WriteLog -Message ($localized.DocumentOptionSpaceSeparator -f $SpaceSeparator); $pscriboDocument.Options['SpaceSeparator'] = $SpaceSeparator; } if ($ForceUppercaseHeader) { $localized.DocumentOptionUppercaseHeadings | WriteLog; $pscriboDocument.Options['ForceUppercaseHeader'] = $true; $pscriboDocument.Name = $pscriboDocument.Name.ToUpper(); } #end if ForceUppercaseHeader if ($ForceUppercaseSection) { $localized.DocumentOptionUppercaseSections | WriteLog; $pscriboDocument.Options['ForceUppercaseSection'] = $true; } #end if ForceUppercaseSection if ($EnableSectionNumbering) { $localized.DocumentOptionSectionNumbering | WriteLog; $pscriboDocument.Options['EnableSectionNumbering'] = $true; } if ($DefaultFont) { WriteLog -Message ($localized.DocumentOptionDefaultFont -f ([System.String]::Join(', ', $DefaultFont))); $pscriboDocument.Options['DefaultFont'] = $DefaultFont; } if ($PSCmdlet.ParameterSetName -eq 'CustomMargin') { if ($MarginTopAndBottom -eq 0) { $MarginTopAndBottom = 72; } if ($MarginLeftAndRight -eq 0) { $MarginTopAndBottom = 72; } $pscriboDocument.Options['MarginTop'] = ConvertPtToMm -Point $MarginTopAndBottom; $pscriboDocument.Options['MarginBottom'] = $pscriboDocument.Options['MarginTop']; $pscriboDocument.Options['MarginLeft'] = ConvertPtToMm -Point $MarginLeftAndRight; $pscriboDocument.Options['MarginRight'] = $pscriboDocument.Options['MarginLeft']; } else { $pscriboDocument.Options['MarginTop'] = ConvertPtToMm -Point $Margin; $pscriboDocument.Options['MarginBottom'] = $pscriboDocument.Options['MarginTop']; $pscriboDocument.Options['MarginLeft'] = $pscriboDocument.Options['MarginTop']; $pscriboDocument.Options['MarginRight'] = $pscriboDocument.Options['MarginTop']; } WriteLog -Message ($localized.DocumentOptionPageTopMargin -f $pscriboDocument.Options['MarginTop']); WriteLog -Message ($localized.DocumentOptionPageRightMargin -f $pscriboDocument.Options['MarginRight']); WriteLog -Message ($localized.DocumentOptionPageBottomMargin -f $pscriboDocument.Options['MarginBottom']); WriteLog -Message ($localized.DocumentOptionPageLeftMargin -f $pscriboDocument.Options['MarginLeft']); ## Convert page size ($localized.DocumentOptionPageSize -f $PageSize) | WriteLog; switch ($PageSize) { 'A4' { $pscriboDocument.Options['PageWidth'] = 210.0; $pscriboDocument.Options['PageHeight'] = 297.0; } 'Legal' { $pscriboDocument.Options['PageWidth'] = 215.9; $pscriboDocument.Options['PageHeight'] = 355.6; } 'Letter' { $pscriboDocument.Options['PageWidth'] = 215.9; $pscriboDocument.Options['PageHeight'] = 279.4; } } #end switch ($localized.DocumentOptionPageHeight -f $pscriboDocument.Options['PageHeight']) | WriteLog; ($localized.DocumentOptionPageWidth -f $pscriboDocument.Options['PageWidth']) | WriteLog; } #end process } #end function GlobalOption function LineBreak { <# .SYNOPSIS Initializes a new PScribo line break object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Id = [System.Guid]::NewGuid().ToString() ) begin { #region LineBreak Private Functions function New-PScriboLineBreak { <# .SYNOPSIS Initializes a new PScribo line break object. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Id = [System.Guid]::NewGuid().ToString() ) process { $typeName = 'PScribo.LineBreak'; $pscriboDocument.Properties['LineBreaks']++; $pscriboLineBreak = [PSCustomObject] @{ Id = $Id; Type = $typeName; } return $pscriboLineBreak; } } #end function New-PScriboLineBreak #endregion LineBreak Private Functions } #end begin process { WriteLog -Message $localized.ProcessingLineBreak; return (New-PScriboLineBreak @PSBoundParameters); } #end process } #end function LineBreak function WriteLog { <# .SYNOPSIS Writes message to the verbose, warning or debug streams. Output is prefixed with the time and PScribo plugin name. #> [CmdletBinding(DefaultParameterSetName = 'Verbose')] param ( ## Message to send to the Verbose stream [Parameter(ValueFromPipeline, ParameterSetName = 'Verbose')] [Parameter(ValueFromPipeline, ParameterSetName = 'Warning')] [Parameter(ValueFromPipeline, ParameterSetName = 'Debug')] [ValidateNotNullOrEmpty()] [System.String] $Message, ## PScribo plugin name [Parameter()] [System.String] $Plugin, ## Redirect message to the Warning stream [Parameter(ParameterSetName = 'Warning')] [System.Management.Automation.SwitchParameter] $IsWarning, ## Redirect message to the Debug stream [Parameter(ParameterSetName = 'Debug')] [System.Management.Automation.SwitchParameter] $IsDebug, ## Padding/indent section level [Parameter(ValueFromPipeline, ParameterSetName = 'Verbose')] [Parameter(ValueFromPipeline, ParameterSetName = 'Warning')] [Parameter(ValueFromPipeline, ParameterSetName = 'Debug')] [ValidateNotNullOrEmpty()] [System.Int16] $Indent ) process { if ([System.String]::IsNullOrEmpty($Plugin)) { ## Attempt to resolve the plugin name from the parent scope if (Test-Path -Path Variable:\pluginName) { $Plugin = Get-Variable -Name pluginName -ValueOnly; } else { $Plugin = 'Unknown'; } } ## Center plugin name $pluginPaddingSize = [System.Math]::Floor((10 - $Plugin.Length) / 2); $pluginPaddingString = ''.PadRight($pluginPaddingSize); $Plugin = '{0}{1}' -f $pluginPaddingString, $Plugin; $Plugin = $Plugin.PadRight(10) $date = Get-Date; $sectionLevelPadding = ''.PadRight($Indent); $formattedMessage = '[ {0} ] [{1}] - {2}{3}' -f $date.ToString('HH:mm:ss:fff'), $Plugin, $sectionLevelPadding, $Message; switch ($PSCmdlet.ParameterSetName) { 'Warning' { Write-Warning -Message $formattedMessage; } 'Debug' { Write-Debug -Message $formattedMessage; } Default { Write-Verbose -Message $formattedMessage; } } } #end process } #end function WriteLog function PageBreak { <# .SYNOPSIS Creates a PScribo page break object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Id = [System.Guid]::NewGuid().ToString() ) begin { #region PageBreak Private Functions function New-PScriboPageBreak { <# .SYNOPSIS Creates a PScribo page break object. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Id = [System.Guid]::NewGuid().ToString() ) process { $typeName = 'PScribo.PageBreak'; $pscriboDocument.Properties['PageBreaks']++; $pscriboPageBreak = [PSCustomObject] @{ Id = $Id; Type = $typeName; } return $pscriboPageBreak; } } #end function New-PScriboPageBreak #endregion PageBreak Private Functions } #end begin process { WriteLog -Message $localized.ProcessingPageBreak; return (New-PScriboPageBreak -Id $Id); } } #end function PageBreak function Paragraph { <# .SYNOPSIS Initializes a new PScribo paragraph object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## Paragraph Id and Xml element name [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Paragraph text. If empty $Name/Id will be used. [Parameter(ValueFromPipelineByPropertyName, Position = 1)] [AllowNull()] [System.String] $Text = $null, ## Output value override, i.e. for Xml elements. If empty $Text will be used. [Parameter(ValueFromPipelineByPropertyName, Position = 2)] [AllowNull()] [System.String] $Value = $null, ## Paragraph style Name/Id reference. [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Style = $null, ## No new line - ONLY IMPLEMENTED FOR TEXT OUTPUT [System.Management.Automation.SwitchParameter] $NoNewLine, ## Override the bold style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Bold, ## Override the italic style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Italic, ## Override the underline style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Underline, ## Override the font name(s) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $Font, ## Override the font size (pt) [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.UInt16] $Size = $null, ## Override the font color/colour [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Color = $null, ## Tab indent [Parameter(ValueFromPipelineByPropertyName)] [System.Int32] [ValidateRange(0,10)] $Tabs = 0 ) begin { #region Paragraph Private Functions function New-PScriboParagraph { <# .SYNOPSIS Initializes a new PScribo paragraph object. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## Paragraph Id (and Xml) element name [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Paragraph text. If empty $Name/Id will be used. [Parameter(ValueFromPipelineByPropertyName, Position = 1)] [AllowNull()] [System.String] $Text = $null, ## Ouptut value override, i.e. for Xml elements. If empty $Text will be used. [Parameter(ValueFromPipelineByPropertyName, Position = 2)] [AllowNull()] [System.String] $Value = $null, ## Paragraph style Name/Id reference. [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Style = $null, ## No new line - ONLY IMPLEMENTED FOR TEXT OUTPUT [Switch] $NoNewLine, ## Override the bold style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Bold, ## Override the italic style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Italic, ## Override the underline style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Underline, ## Override the font name(s) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $Font, ## Override the font size (pt) [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.UInt16] $Size = $null, ## Override the font color/colour [Parameter(ValueFromPipelineByPropertyName)] [Alias('Colour')] [AllowNull()] [System.String] $Color = $null, ## Tab indent [Parameter()] [ValidateRange(0,10)] [System.Int32] $Tabs = 0 ) begin { if (-not ([string]::IsNullOrEmpty($Text))) { $Name = $Name.Replace(' ', $pscriboDocument.Options['SpaceSeparator']).ToUpper(); } if ($Color) { $Color = Resolve-PScriboStyleColor -Color $Color; } } #end begin process { $typeName = 'PScribo.Paragraph'; $pscriboDocument.Properties['Paragraphs']++; $pscriboParagraph = [PSCustomObject] @{ Id = $Name; Text = $Text; Type = $typeName; Style = $Style; Value = $Value; NewLine = !$NoNewLine; Tabs = $Tabs; Bold = $Bold; Italic = $Italic; Underline = $Underline; Font = $Font; Size = $Size; Color = $Color; } return $pscriboParagraph; } #end process } #end function New-PScriboParagraph #endregion Paragraph Private Functions } #end begin process { if ($Name.Length -gt 40) { $paragraphDisplayName = '{0}[..]' -f $Name.Substring(0,36); } else { $paragraphDisplayName = $Name; } WriteLog -Message ($localized.ProcessingParagraph -f $paragraphDisplayName); return (New-PScriboParagraph @PSBoundParameters); } #end process } #end function Paragraph function Section { <# .SYNOPSIS Initializes a new PScribo section object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## PScribo section heading/name. [Parameter(Mandatory, Position = 0)] [System.String] $Name, ## PScribo document script block. [Parameter(Position = 1)] [ValidateNotNull()] [System.Management.Automation.ScriptBlock] $ScriptBlock = $(throw $localized.NoScriptBlockProvidedError), ## PScribo style applied to document section. [Parameter()] [System.String] [AllowNull()] $Style = $null, ## Section is excluded from TOC/section numbering. [Parameter()] [System.Management.Automation.SwitchParameter] $ExcludeFromTOC ) begin { #region Section Private Functions function New-PScriboSection { <# .SYNOPSIS Initializes new PScribo section object. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## PScribo section heading/name. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## PScribo style applied to document section. [Parameter()] [AllowNull()] [System.String] $Style = $null, ## Section is excluded from TOC/section numbering. [Parameter()] [System.Management.Automation.SwitchParameter] $IsExcluded ) process { $typeName = 'PScribo.Section'; $pscriboDocument.Properties['Sections']++; $pscriboSection = [PSCustomObject] @{ Id = $Name.Replace(' ', $pscriboDocument.Options['SpaceSeparator']).ToUpper(); Level = 0; Number = ''; Name = $Name; Type = $typeName; Style = $Style; IsExcluded = $IsExcluded; Sections = (New-Object -TypeName System.Collections.ArrayList); } return $pscriboSection; } #end process } #end function new-pscribosection #endregion Section Private Functions } #end begin process { WriteLog -Message ($localized.ProcessingSectionStarted -f $Name); $pscriboSection = New-PScriboSection -Name $Name -Style $Style -IsExcluded:$ExcludeFromTOC; foreach ($result in & $ScriptBlock) { [ref] $null = $pscriboSection.Sections.Add($result); } WriteLog -Message ($localized.ProcessingSectionCompleted -f $Name); return $pscriboSection; } #end process } #end function Section function Resolve-PScriboStyleColor { <# .SYNOPSIS Resolves a HTML color format or Word color constant to a RGB value #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline, Position = 0)] [ValidateNotNull()] [System.String] $Color ) begin { # http://www.jadecat.com/tuts/colorsplus.html $wordColorConstants = @{ AliceBlue = 'F0F8FF'; AntiqueWhite = 'FAEBD7'; Aqua = '00FFFF'; Aquamarine = '7FFFD4'; Azure = 'F0FFFF'; Beige = 'F5F5DC'; Bisque = 'FFE4C4'; Black = '000000'; BlanchedAlmond = 'FFEBCD'; Blue = '0000FF'; BlueViolet = '8A2BE2'; Brown = 'A52A2A'; BurlyWood = 'DEB887'; CadetBlue = '5F9EA0'; Chartreuse = '7FFF00'; Chocolate = 'D2691E'; Coral = 'FF7F50'; CornflowerBlue = '6495ED'; Cornsilk = 'FFF8DC'; Crimson = 'DC143C'; Cyan = '00FFFF'; DarkBlue = '00008B'; DarkCyan = '008B8B'; DarkGoldenrod = 'B8860B'; DarkGray = 'A9A9A9'; DarkGreen = '006400'; DarkKhaki = 'BDB76B'; DarkMagenta = '8B008B'; DarkOliveGreen = '556B2F'; DarkOrange = 'FF8C00'; DarkOrchid = '9932CC'; DarkRed = '8B0000'; DarkSalmon = 'E9967A'; DarkSeaGreen = '8FBC8F'; DarkSlateBlue = '483D8B'; DarkSlateGray = '2F4F4F'; DarkTurquoise = '00CED1'; DarkViolet = '9400D3'; DeepPink = 'FF1493'; DeepSkyBlue = '00BFFF'; DimGray = '696969'; DodgerBlue = '1E90FF'; Firebrick = 'B22222'; FloralWhite = 'FFFAF0'; ForestGreen = '228B22'; Fuchsia = 'FF00FF'; Gainsboro = 'DCDCDC'; GhostWhite = 'F8F8FF'; Gold = 'FFD700'; Goldenrod = 'DAA520'; Gray = '808080'; Green = '008000'; GreenYellow = 'ADFF2F'; Honeydew = 'F0FFF0'; HotPink = 'FF69B4'; IndianRed = 'CD5C5C'; Indigo = '4B0082'; Ivory = 'FFFFF0'; Khaki = 'F0E68C'; Lavender = 'E6E6FA'; LavenderBlush = 'FFF0F5'; LawnGreen = '7CFC00'; LemonChiffon = 'FFFACD'; LightBlue = 'ADD8E6'; LightCoral = 'F08080'; LightCyan = 'E0FFFF'; LightGoldenrodYellow = 'FAFAD2'; LightGreen = '90EE90'; LightGrey = 'D3D3D3'; LightPink = 'FFB6C1'; LightSalmon = 'FFA07A'; LightSeaGreen = '20B2AA'; LightSkyBlue = '87CEFA'; LightSlateGray = '778899'; LightSteelBlue = 'B0C4DE'; LightYellow = 'FFFFE0'; Lime = '00FF00'; LimeGreen = '32CD32'; Linen = 'FAF0E6'; Magenta = 'FF00FF'; Maroon = '800000'; McMintGreen = 'BED6C9'; MediumAuqamarine = '66CDAA'; MediumBlue = '0000CD'; MediumOrchid = 'BA55D3'; MediumPurple = '9370D8'; MediumSeaGreen = '3CB371'; MediumSlateBlue = '7B68EE'; MediumSpringGreen = '00FA9A'; MediumTurquoise = '48D1CC'; MediumVioletRed = 'C71585'; MidnightBlue = '191970'; MintCream = 'F5FFFA'; MistyRose = 'FFE4E1'; Moccasin = 'FFE4B5'; NavajoWhite = 'FFDEAD'; Navy = '000080'; OldLace = 'FDF5E6'; Olive = '808000'; OliveDrab = '688E23'; Orange = 'FFA500'; OrangeRed = 'FF4500'; Orchid = 'DA70D6'; PaleGoldenRod = 'EEE8AA'; PaleGreen = '98FB98'; PaleTurquoise = 'AFEEEE'; PaleVioletRed = 'D87093'; PapayaWhip = 'FFEFD5'; PeachPuff = 'FFDAB9'; Peru = 'CD853F'; Pink = 'FFC0CB'; Plum = 'DDA0DD'; PowderBlue = 'B0E0E6'; Purple = '800080'; Red = 'FF0000'; RosyBrown = 'BC8F8F'; RoyalBlue = '4169E1'; SaddleBrown = '8B4513'; Salmon = 'FA8072'; SandyBrown = 'F4A460'; SeaGreen = '2E8B57'; Seashell = 'FFF5EE'; Sienna = 'A0522D'; Silver = 'C0C0C0'; SkyBlue = '87CEEB'; SlateBlue = '6A5ACD'; SlateGray = '708090'; Snow = 'FFFAFA'; SpringGreen = '00FF7F'; SteelBlue = '4682B4'; Tan = 'D2B48C'; Teal = '008080'; Thistle = 'D8BFD8'; Tomato = 'FF6347'; Turquoise = '40E0D0'; Violet = 'EE82EE'; Wheat = 'F5DEB3'; White = 'FFFFFF'; WhiteSmoke = 'F5F5F5'; Yellow = 'FFFF00'; YellowGreen = '9ACD32'; }; } #end begin process { $pscriboColor = $Color; if ($wordColorConstants.ContainsKey($pscriboColor)) { return $wordColorConstants[$pscriboColor].ToLower(); } elseif ($pscriboColor.Length -eq 6 -or $pscriboColor.Length -eq 3) { $pscriboColor = '#{0}' -f $pscriboColor; } elseif ($pscriboColor.Length -eq 7 -or $pscriboColor.Length -eq 4) { if (-not ($pscriboColor.StartsWith('#'))) { return $null; } } if ($pscriboColor -notmatch '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$') { return $null; } return $pscriboColor.TrimStart('#').ToLower(); } #end process } #end function ResolvePScriboColor function Test-PScriboStyleColor { <# .SYNOPSIS Tests whether a color string is a valid HTML color. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory, ValueFromPipeline, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Color ) process { if (Resolve-PScriboStyleColor -Color $Color) { return $true; } else { return $false; } } #end process } #end function test-pscribostylecolor function Test-PScriboStyle { <# .SYNOPSIS Tests whether a style has been defined. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory, ValueFromPipeline, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Name ) process { return $PScriboDocument.Styles.ContainsKey($Name); } } #end function Test-PScriboStyle function Style { <# .SYNOPSIS Defines a new PScribo formatting style. .DESCRIPTION Creates a standard format formatting style that can be applied to PScribo document keywords, e.g. a combination of font style, font weight and font size. .NOTES Not all plugins support all options. #> [CmdletBinding()] param ( ## Style name [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Font size (pt) [Parameter(ValueFromPipelineByPropertyName, Position = 1)] [System.UInt16] $Size = 11, ## Font color/colour [Parameter(ValueFromPipelineByPropertyName)] [Alias('Colour')] [ValidateNotNullOrEmpty()] [System.String] $Color = '000', ## Background color/colour [Parameter(ValueFromPipelineByPropertyName)] [Alias('BackgroundColour')] [ValidateNotNullOrEmpty()] [System.String] $BackgroundColor, ## Bold typeface [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Bold, ## Italic typeface [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Italic, ## Underline typeface [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Underline, ## Text alignment [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Left','Center','Right','Justify')] [System.String] $Align = 'Left', ## Set as default style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Default, ## Style id [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Id = $Name -Replace(' ',''), ## Font name (array of names for HTML output) [Parameter(ValueFromPipelineByPropertyName)] [System.String[]] $Font ) begin { #region Style Private Functions function Add-PScriboStyle { <# .SYNOPSIS Initializes a new PScribo style object. #> [CmdletBinding()] param ( ## Style name [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Style id [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Id = $Name -Replace(' ',''), ## Font size (pt) [Parameter(ValueFromPipelineByPropertyName)] [System.UInt16] $Size = 11, ## Font name (array of names for HTML output) [Parameter(ValueFromPipelineByPropertyName)] [System.String[]] $Font, ## Font color/colour [Parameter(ValueFromPipelineByPropertyName)] [Alias('Colour')] [ValidateNotNullOrEmpty()] [System.String] $Color = 'Black', ## Background color/colour [Parameter(ValueFromPipelineByPropertyName)] [Alias('BackgroundColour')] [ValidateNotNullOrEmpty()] [System.String] $BackgroundColor, ## Bold typeface [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Bold, ## Italic typeface [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Italic, ## Underline typeface [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Underline, ## Text alignment [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Left','Center','Right','Justify')] [string] $Align = 'Left', ## Html CSS class id. Overrides Style.Id in HTML output. [Parameter(ValueFromPipelineByPropertyName)] [System.String] $CssClassId = '', ## Set as default style [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Default ) #end param begin { if (-not (Test-PScriboStyleColor -Color $Color)) { throw ($localized.InvalidHtmlColorError -f $Color); } if ($BackgroundColor) { if (-not (Test-PScriboStyleColor -Color $BackgroundColor)) { throw ($localized.InvalidHtmlBackgroundColorError -f $BackgroundColor); } else { $BackgroundColor = Resolve-PScriboStyleColor -Color $BackgroundColor; } } if (-not ($Font)) { $Font = $pscriboDocument.Options['DefaultFont']; } } #end begin process { $pscriboDocument.Properties['Styles']++; $style = [PSCustomObject] @{ Id = $Id; Name = $Name; Font = $Font; Size = $Size; Color = (Resolve-PScriboStyleColor -Color $Color).ToLower(); BackgroundColor = $BackgroundColor.ToLower(); Bold = $Bold; Italic = $Italic; Underline = $Underline; Align = $Align; } $pscriboDocument.Styles[$Id] = $style; if ($Default) { $pscriboDocument.DefaultStyle = $style.Id; } } #end process } #end function Add-PScriboStyle #endregion Style Private Functions } process { WriteLog -Message ($localized.ProcessingStyle -f $Id); Add-PScriboStyle @PSBoundParameters; } #end process } #end function Style function Set-Style { <# .SYNOPSIS Sets the style for an individual table row or cell. #> [CmdletBinding()] [OutputType([System.Object])] param ( ## PSCustomObject to apply the style to [Parameter(Mandatory, ValueFromPipeline)] [System.Object[]] [Ref] $InputObject, ## PScribo style Id to apply [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $Style, ## Property name(s) to apply the selected style to. Leave blank to apply the style to the entire row. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $Property = '', ## Passes the modified object back to the pipeline [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $PassThru ) #end param begin { if (-not (Test-PScriboStyle -Name $Style)) { Write-Error ($localized.UndefinedStyleError -f $Style); return; } } process { foreach ($object in $InputObject) { foreach ($p in $Property) { ## If $Property not set, __Style will apply to the whole row. $propertyName = '{0}__Style' -f $p; $object | Add-Member -MemberType NoteProperty -Name $propertyName -Value $Style -Force; } } if ($PassThru) { return $object; } } #end process } #end function Set-Style function Table { <# .SYNOPSIS Defines a new PScribo document table. #> [CmdletBinding(DefaultParameterSetName = 'InputObject')] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## Table name/Id [Parameter(ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name = ([System.Guid]::NewGuid().ToString()), # Array of Hashtables [Parameter(Mandatory, ParameterSetName = 'Hashtable')] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.OrderedDictionary[]] $Hashtable, # Array of objects [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')] [Alias('CustomObject','Object')] [ValidateNotNullOrEmpty()] [System.Object[]] $InputObject, # Array of Hashtable key names or Object/PSCustomObject property names to include, in display order. # If not supplied then all Hashtable keys or all PSCustomObject properties will be used. [Parameter(ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'InputObject')] [Parameter(ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'Hashtable')] [Alias('Properties')] [AllowNull()] [System.String[]] $Columns = $null, ## Column widths as percentages. Total should not exceed 100. [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.UInt16[]] $ColumnWidths, # Array of custom table header strings in display order. [Parameter(ValueFromPipelineByPropertyName, Position = 2)] [AllowNull()] [System.String[]] $Headers = $null, ## Table style [Parameter(ValueFromPipelineByPropertyName, Position = 3)] [ValidateNotNullOrEmpty()] [System.String] $Style = 'TableDefault', # List view (no headers) [System.Management.Automation.SwitchParameter] $List, ## Table width (%), 0 = Autofit [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(0,100)] [System.UInt16] $Width = 100, ## Indent table [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(0,10)] [System.UInt16] $Tabs ) #end param begin { #region Table Private Functions function New-PScriboTable { <# .SYNOPSIS Initializes a new PScribo table object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## Table name/Id [Parameter(ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name = ([System.Guid]::NewGuid().ToString()), ## Table columns/display order [Parameter(Mandatory)] [AllowNull()] [System.String[]] $Columns, ## Table columns widths [Parameter(Mandatory)] [AllowNull()] [System.UInt16[]] $ColumnWidths, ## Collection of PScriboTableObjects for table rows [Parameter(Mandatory)] [ValidateNotNull()] [System.Collections.ArrayList] $Rows, ## Table style [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Style, ## List view [System.Management.Automation.SwitchParameter] $List, ## Table width (%), 0 = Autofit [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(0,100)] [System.UInt16] $Width = 100, ## Indent table [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(0,10)] [System.UInt16] $Tabs ) #end param process { $typeName = 'PScribo.Table'; $pscriboDocument.Properties['Tables']++; $pscriboTable = [PSCustomObject] @{ Id = $Name.Replace(' ', $pscriboDocument.Options['SpaceSeparator']).ToUpper(); Name = $Name; Type = $typeName; # Headers = $Headers; ## Headers are stored as they may be required when formatting output, i.e. Word tables Columns = $Columns; ColumnWidths = $ColumnWidths; Rows = $Rows; List = $List; Style = $Style; Width = $Width; Tabs = $Tabs; } return $pscriboTable; } #end process } #end function new-pscribotable function New-PScriboTableRow { <# .SYNOPSIS Defines a new PScribo document table row from an object or hashtable. #> [CmdletBinding(DefaultParameterSetName='InputObject')] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## PSCustomObject to create PScribo table row [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')] [ValidateNotNull()] [System.Object] $InputObject, ## PSCutomObject properties to include in the table row [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'InputObject')] [AllowNull()] [System.String[]] $Properties, # Custom table header strings (in Display Order). Used for property names. [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'InputObject')] [AllowNull()] [System.String[]] $Headers = $null, ## Array of ordered dictionaries (hashtables) to create PScribo table row [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Hashtable')] [AllowNull()] [System.Collections.Specialized.OrderedDictionary] $Hashtable ) begin { Write-Debug ('Using parameter set "{0}.' -f $PSCmdlet.ParameterSetName); } #end begin process { switch ($PSCmdlet.ParameterSetName) { 'Hashtable'{ if (-not $Hashtable.Contains('__Style')) { $Hashtable['__Style'] = $null; } ## Create and return custom object from hashtable return ([PSCustomObject] $Hashtable); } #end Hashtable Default { $objectProperties = [Ordered] @{ }; if ($Properties -notcontains '__Style') { $Properties += '__Style'; } ## Build up hashtable of required property names for ($i = 0; $i -lt $Properties.Count; $i++) { $propertyName = $Properties[$i]; $propertyStyleName = '{0}__Style' -f $propertyName; if ($InputObject.PSObject.Properties[$propertyStyleName]) { if ($Headers) { ## Rename the style property to match the header $headerStyleName = '{0}__Style' -f $Headers[$i]; $objectProperties[$headerStyleName] = $InputObject.$propertyStyleName; } else { $objectProperties[$propertyStyleName] = $InputObject.$propertyStyleName; } } if ($Headers -and $PropertyName -notlike '*__Style') { if ($InputObject.PSObject.Properties[$propertyName]) { $objectProperties[$Headers[$i]] = $InputObject.$propertyName; } } else { if ($InputObject.PSObject.Properties[$propertyName]) { $objectProperties[$propertyName] = $InputObject.$propertyName; } else { $objectProperties[$propertyName] = $null; } } } #end for ## Create and return custom object return ([PSCustomObject] $objectProperties); } #end Default } #end switch } #end process } #end function New-PScriboTableRow #endregion Table Private Functions Write-Debug ('Using parameter set "{0}".' -f $PSCmdlet.ParameterSetName); [System.Collections.ArrayList] $rows = New-Object -TypeName System.Collections.ArrayList; WriteLog -Message ($localized.ProcessingTable -f $Name); if ($Headers -and (-not $Columns)) { WriteLog -Message $localized.TableHeadersWithNoColumnsWarning -IsWarning; $Headers = $Columns; } #end if elseif (($null -ne $Columns) -and ($null -ne $Headers)) { ## Check the number of -Headers matches the number of -Properties if ($Headers.Count -ne $Columns.Count) { WriteLog -Message $localized.TableHeadersCountMismatchWarning -IsWarning; $Headers = $Columns; } } #end if if ($ColumnWidths) { $columnWidthsSum = $ColumnWidths | Measure-Object -Sum | Select-Object -ExpandProperty Sum; if ($columnWidthsSum -ne 100) { WriteLog -Message ($localized.TableColumnWidthSumWarning -f $columnWidthsSum) -IsWarning; $ColumnWidths = $null; } elseif ($List -and $ColumnWidths.Count -ne 2) { WriteLog -Message $localized.ListTableColumnCountWarning -IsWarning; $ColumnWidths = $null; } elseif (($PSCmdlet.ParameterSetName -eq 'Hashtable') -and (-not $List) -and ($Hashtable[0].Keys.Count -ne $ColumnWidths.Count)) { WriteLog -Message $localized.TableColumnWidthMismatchWarning -IsWarning; $ColumnWidths = $null; } elseif (($PSCmdlet.ParameterSetName -eq 'InputObject') -and (-not $List) -and ($Columns.Count -ne $ColumnWidths.Count)) { WriteLog -Message $localized.TableColumnWidthMismatchWarning -IsWarning; $ColumnWidths = $null; } } #end if columnwidths } #end begin process { if ($null -eq $Columns) { ## Use all available properties switch ($PSCmdlet.ParameterSetName) { 'Hashtable' { $Columns = $Hashtable | Select-Object -First 1 -ExpandProperty Keys | Where-Object { $_ -notlike '*__Style' }; } Default { ## Pipeline objects are not available in the begin scriptblock $object = $InputObject | Select-Object -First 1; if ($object -is [System.Management.Automation.PSCustomObject]) { $Columns = $object.PSObject.Properties | Where-Object Name -notlike '*__Style' | Select-Object -ExpandProperty Name; } else { $Columns = Get-Member -InputObject $object -MemberType Properties | Where-Object Name -notlike '*__Style' | Select-Object -ExpandProperty Name; } } #end default } #end switch parametersetname } # end if not columns switch ($PSCmdlet.ParameterSetName) { 'Hashtable' { foreach ($nestedHashtable in $Hashtable) { $customObject = New-PScriboTableRow -Hashtable $nestedHashtable; [ref] $null = $rows.Add($customObject); } #end foreach nested hashtable entry } #end hashtable Default { foreach ($object in $InputObject) { $customObject = New-PScriboTableRow -InputObject $object -Properties $Columns -Headers $Headers; [ref] $null = $rows.Add($customObject); } #end foreach inputobject } #end default } #end switch } #end process end { ## Reset the column names as the object have been rewritten with their headers if ($Headers) { $Columns = $Headers; } $table = @{ Name = $Name; Columns = $Columns; ColumnWidths = $ColumnWidths; Rows = $rows; List = $List; Style = $Style; Width = $Width; Tabs = $Tabs; } return (New-PScriboTable @table); } #end end } #end function Table function TableStyle { <# .SYNOPSIS Defines a new PScribo table formatting style. .DESCRIPTION Creates a standard table formatting style that can be applied to the PScribo table keyword, e.g. a combination of header and row styles and borders. .NOTES Not all plugins support all options. #> [CmdletBinding()] param ( ## Table Style name/id [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [Alias('Name')] [System.String] $Id, ## Header Row Style Id [Parameter(ValueFromPipelineByPropertyName, Position = 1)] [ValidateNotNullOrEmpty()] [System.String] $HeaderStyle = 'Default', ## Row Style Id [Parameter(ValueFromPipelineByPropertyName, Position = 2)] [ValidateNotNullOrEmpty()] [System.String] $RowStyle = 'Default', ## Header Row Style Id [Parameter(ValueFromPipelineByPropertyName, Position = 3)] [AllowNull()] [Alias('AlternatingRowStyle')] [System.String] $AlternateRowStyle = 'Default', ## Table border size/width (pt) [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Border')] [AllowNull()] [System.Single] $BorderWidth = 0, ## Table border colour [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Border')] [ValidateNotNullOrEmpty()] [Alias('BorderColour')] [System.String] $BorderColor = '000', ## Table cell top padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingTop = 1.0, ## Table cell left padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingLeft = 4.0, ## Table cell bottom padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingBottom = 0.0, ## Table cell right padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingRight = 4.0, ## Table alignment [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Left','Center','Right')] [System.String] $Align = 'Left', ## Set as default table style [System.Management.Automation.SwitchParameter] $Default ) #end param begin { #region TableStyle Private Functions function Add-PScriboTableStyle { <# .SYNOPSIS Defines a new PScribo table formatting style. .DESCRIPTION Creates a standard table formatting style that can be applied to the PScribo table keyword, e.g. a combination of header and row styles and borders. .NOTES Not all plugins support all options. #> [CmdletBinding()] param ( ## Table Style name/id [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [Alias('Name')] [System.String] $Id, ## Header Row Style Id [Parameter(ValueFromPipelineByPropertyName, Position = 1)] [ValidateNotNullOrEmpty()] [System.String] $HeaderStyle = 'Normal', ## Row Style Id [Parameter(ValueFromPipelineByPropertyName, Position = 2)] [ValidateNotNullOrEmpty()] [System.String] $RowStyle = 'Normal', ## Header Row Style Id [Parameter(ValueFromPipelineByPropertyName, Position = 3)] [AllowNull()] [Alias('AlternatingRowStyle')] [System.String] $AlternateRowStyle = 'Normal', ## Table border size/width (pt) [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Border')] [AllowNull()] [System.Single] $BorderWidth = 0, ## Table border colour [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Border')] [ValidateNotNullOrEmpty()] [Alias('BorderColour')] [System.String] $BorderColor = '000', ## Table cell top padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingTop = 1.0, ## Table cell left padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingLeft = 4.0, ## Table cell bottom padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingBottom = 0.0, ## Table cell right padding (pt) [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Single] $PaddingRight = 4.0, ## Table alignment [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Left','Center','Right')] [System.String] $Align = 'Left', ## Set as default table style [System.Management.Automation.SwitchParameter] $Default ) #end param begin { if ($BorderWidth -gt 0) { $borderStyle = 'Solid'; } else {$borderStyle = 'None'; } if (-not ($pscriboDocument.Styles.ContainsKey($HeaderStyle))) { throw ($localized.UndefinedTableHeaderStyleError -f $HeaderStyle); } if (-not ($pscriboDocument.Styles.ContainsKey($RowStyle))) { throw ($localized.UndefinedTableRowStyleError -f $RowStyle); } if (-not ($pscriboDocument.Styles.ContainsKey($AlternateRowStyle))) { throw ($localized.UndefinedAltTableRowStyleError -f $AlternateRowStyle); } if (-not (Test-PScriboStyleColor -Color $BorderColor)) { throw ($localized.InvalidTableBorderColorError -f $BorderColor); } } #end begin process { $pscriboDocument.Properties['TableStyles']++; $tableStyle = [PSCustomObject] @{ Id = $Id.Replace(' ', $pscriboDocument.Options['SpaceSeparator']); Name = $Id; HeaderStyle = $HeaderStyle; RowStyle = $RowStyle; AlternateRowStyle = $AlternateRowStyle; PaddingTop = ConvertPtToMm $PaddingTop; PaddingLeft = ConvertPtToMm $PaddingLeft; PaddingBottom = ConvertPtToMm $PaddingBottom; PaddingRight = ConvertPtToMm $PaddingRight; Align = $Align; BorderWidth = ConvertPtToMm $BorderWidth; BorderStyle = $borderStyle; BorderColor = Resolve-PScriboStyleColor -Color $BorderColor; } $pscriboDocument.TableStyles[$Id] = $tableStyle; if ($Default) { $pscriboDocument.DefaultTableStyle = $tableStyle.Id; } } #end process } #end function Add-PScriboTableStyle #endregion TableStyle Private Functions } process { WriteLog -Message ($localized.ProcessingTableStyle -f $Id); Add-PScriboTableStyle @PSBoundParameters; } } #end function tablestyle function TOC { <# .SYNOPSIS Initializes a new PScribo Table of Contents (TOC) object. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Name = 'Contents' ) begin { #region TOC Private Functions function New-PScriboTOC { <# .SYNOPSIS Initializes a new PScribo Table of Contents (TOC) object. .NOTES This is an internal function and should not be called directly. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Name = 'Contents' ) process { $typeName = 'PScribo.TOC'; if ($pscriboDocument.Options['ForceUppercaseSection']) { $Name = $Name.ToUpper(); } $pscriboDocument.Properties['TOCs']++; $pscriboTOC = [PSCustomObject] @{ Id = [System.Guid]::NewGuid().ToString(); Name = $Name; Type = $typeName; } return $pscriboTOC; } #end process } #end function New-PScriboLTOC #endregion TOC Private Functions } #end begin process { WriteLog -Message ($localized.ProcessingTOC -f $Name); return (New-PScriboTOC @PSBoundParameters); } } #end function TOC function OutHtml { <# .SYNOPSIS Html output plugin for PScribo. .DESCRIPTION Outputs a Html file representation of a PScribo document object. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( ## PScribo document object to convert to a text document [Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject] $Document, ## Output directory path for the .html file [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Path, ### Hashtable of all plugin supported options [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.Collections.Hashtable] $Options ) begin { $pluginName = 'Html'; #region OutHtml Private Functions function GetHtmlStyle { <# .SYNOPSIS Generates html stylesheet style attributes from a PScribo document style. #> [CmdletBinding()] [OutputType([System.String])] param ( ## PScribo document style [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Style ) process { $styleBuilder = New-Object -TypeName System.Text.StringBuilder; [ref] $null = $styleBuilder.AppendFormat(" font-family: '{0}';", $Style.Font -Join "','"); [ref] $null = $styleBuilder.AppendFormat(' font-size: {0:0.00}em;', $Style.Size / 12); [ref] $null = $styleBuilder.AppendFormat(' text-align: {0};', $Style.Align.ToLower()); if ($Style.Bold) { [ref] $null = $styleBuilder.Append(' font-weight: bold;'); } else { [ref] $null = $styleBuilder.Append(' font-weight: normal;'); } if ($Style.Italic) { [ref] $null = $styleBuilder.Append(' font-style: italic;'); } if ($Style.Underline) { [ref] $null = $styleBuilder.Append(' text-decoration: underline;'); } if ($Style.Color.StartsWith('#')) { [ref] $null = $styleBuilder.AppendFormat(' color: {0};', $Style.Color.ToLower()); } else { [ref] $null = $styleBuilder.AppendFormat(' color: #{0};', $Style.Color); } if ($Style.BackgroundColor) { if ($Style.BackgroundColor.StartsWith('#')) { [ref] $null = $styleBuilder.AppendFormat(' background-color: {0};', $Style.BackgroundColor.ToLower()); } else { [ref] $null = $styleBuilder.AppendFormat(' background-color: #{0};', $Style.BackgroundColor.ToLower()); } } return $styleBuilder.ToString(); } } #end function GetHtmlStyle function GetHtmlTableStyle { <# .SYNOPSIS Generates html stylesheet style attributes from a PScribo document table style. #> [CmdletBinding()] [OutputType([System.String])] param ( ## PScribo document table style [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $TableStyle ) process { $tableStyleBuilder = New-Object -TypeName 'System.Text.StringBuilder'; [ref] $null = $tableStyleBuilder.AppendFormat(' padding: {0}em {1}em {2}em {3}em;', (ConvertMmToEm $TableStyle.PaddingTop), (ConvertMmToEm $TableStyle.PaddingRight), (ConvertMmToEm $TableStyle.PaddingBottom), (ConvertMmToEm $TableStyle.PaddingLeft)); [ref] $null = $tableStyleBuilder.AppendFormat(' border-style: {0};', $TableStyle.BorderStyle.ToLower()); if ($TableStyle.BorderWidth -gt 0) { [ref] $null = $tableStyleBuilder.AppendFormat(' border-width: {0}em;', (ConvertMmToEm $TableStyle.BorderWidth)); if ($TableStyle.BorderColor.Contains('#')) { [ref] $null = $tableStyleBuilder.AppendFormat(' border-color: {0};', $TableStyle.BorderColor); } else { [ref] $null = $tableStyleBuilder.AppendFormat(' border-color: #{0};', $TableStyle.BorderColor); } } [ref] $null = $tableStyleBuilder.Append(' border-collapse: collapse;'); ## <table align="center"> is deprecated in Html5 if ($TableStyle.Align -eq 'Center') { [ref] $null = $tableStyleBuilder.Append(' margin-left: auto; margin-right: auto;'); } elseif ($TableStyle.Align -eq 'Right') { [ref] $null = $tableStyleBuilder.Append(' margin-left: auto; margin-right: 0;'); } return $tableStyleBuilder.ToString(); } } #end function outhtmltablestyle function GetHtmlTableDiv { <# .SYNOPSIS Generates Html <div style=..><table style=..> tags based upon table width, columns and indentation .NOTES A <div> is required to ensure that the table stays within the "page" boundaries/margins. #> param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table ) process { $divBuilder = New-Object -TypeName 'System.Text.StringBuilder'; if ($Table.Tabs -gt 0) { [ref] $null = $divBuilder.AppendFormat('<div style="margin-left: {0}em;">' -f (ConvertMmToEm -Millimeter (12.7 * $Table.Tabs))); } else { [ref] $null = $divBuilder.Append('<div>' -f (ConvertMmToEm -Millimeter (12.7 * $Table.Tabs))); } if ($Table.List) { [ref] $null = $divBuilder.AppendFormat('<table class="{0}-list"', $Table.Style.ToLower()); } else { [ref] $null = $divBuilder.AppendFormat('<table class="{0}"', $Table.Style.ToLower()); } $styleElements = @(); if ($Table.Width -gt 0) { $styleElements += 'width:{0}%;' -f $Table.Width; } if ($Table.ColumnWidths) { $styleElements += 'table-layout: fixed;'; $styleElements += 'word-break: break-word;' } if ($styleElements.Count -gt 0) { [ref] $null = $divBuilder.AppendFormat(' style="{0}">', [String]::Join(' ', $styleElements)); } else { [ref] $null = $divBuilder.Append('>'); } return $divBuilder.ToString(); } } #end function GetHtmlTableDiv function GetHtmlTableColGroup { <# .SYNOPSIS Generates Html <colgroup> tags based on table column widths #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table ) process { $colGroupBuilder = New-Object -TypeName 'System.Text.StringBuilder'; if ($Table.ColumnWidths) { [ref] $null = $colGroupBuilder.Append('<colgroup>'); foreach ($columnWidth in $Table.ColumnWidths) { if ($null -eq $columnWidth) { [ref] $null = $colGroupBuilder.Append('<col />'); } else { [ref] $null = $colGroupBuilder.AppendFormat('<col style="max-width:{0}%; min-width:{0}%; width:{0}%" />', $columnWidth); } } [ref] $null = $colGroupBuilder.AppendLine('</colgroup>'); } return $colGroupBuilder.ToString(); } } #end function GetHtmlTableDiv function OutHtmlTOC { <# .SYNOPSIS Generates Html table of contents. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $TOC ) process { $tocBuilder = New-Object -TypeName 'System.Text.StringBuilder'; [ref] $null = $tocBuilder.AppendFormat('<h1 class="TOC">{0}</h1>', $TOC.Name); [ref] $null = $tocBuilder.AppendLine('<table style="width: 100%;">'); foreach ($tocEntry in $Document.TOC) { $sectionNumberIndent = ' ' * $tocEntry.Level; if ($Document.Options['EnableSectionNumbering']) { [ref] $null = $tocBuilder.AppendFormat('<tr><td>{0}</td><td>{1}<a href="#{2}" style="text-decoration: none;">{3}</a></td></tr>', $tocEntry.Number, $sectionNumberIndent, $tocEntry.Id, $tocEntry.Name).AppendLine(); } else { [ref] $null = $tocBuilder.AppendFormat('<tr><td>{0}<a href="#{1}" style="text-decoration: none;">{2}</a></td></tr>', $sectionNumberIndent, $tocEntry.Id, $tocEntry.Name).AppendLine(); } } [ref] $null = $tocBuilder.AppendLine('</table>'); return $tocBuilder.ToString(); } #end process } #end function OutHtmlTOC function OutHtmlBlankLine { <# .SYNOPSIS Outputs html PScribo.Blankline. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $BlankLine ) process { $blankLineBuilder = New-Object -TypeName System.Text.StringBuilder; for ($i = 0; $i -lt $BlankLine.LineCount; $i++) { [ref] $null = $blankLineBuilder.Append('<br />'); } return $blankLineBuilder.ToString(); } #end process } #end function OutHtmlBlankLine function OutHtmlStyle { <# .SYNOPSIS Generates an in-line HTML CSS stylesheet from a PScribo document styles and table styles. #> [CmdletBinding()] [OutputType([System.String])] param ( ## PScribo document styles [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] $Styles, ## PScribo document tables styles [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] $TableStyles ) process { $stylesBuilder = New-Object -TypeName 'System.Text.StringBuilder'; [ref] $null = $stylesBuilder.AppendLine('<style type="text/css">'); ## Add HTML page layout styling options [ref] $null = $stylesBuilder.AppendLine('html { height: 100%; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover; background: #f8f8f8; }'); [ref] $null = $stylesBuilder.Append("page { background: white; width: $($Document.Options['PageWidth'])mm; display: block; margin-top: 1em; margin-left: auto; margin-right: auto; margin-bottom: 1em; "); [ref] $null = $stylesBuilder.AppendLine('border-style: solid; border-width: 1px; border-color: #c6c6c6; }'); [ref] $null = $stylesBuilder.AppendLine('@media print { body, page { margin: 0; box-shadow: 0; } }'); [ref] $null = $stylesBuilder.AppendLine('hr { margin-top: 1.0em; }'); foreach ($style in $Styles.Keys) { ## Build style $htmlStyle = GetHtmlStyle -Style $Styles[$style]; [ref] $null = $stylesBuilder.AppendFormat(' .{0} {{{1} }}', $Styles[$style].Id, $htmlStyle).AppendLine(); } foreach ($tableStyle in $TableStyles.Keys) { $tStyle = $TableStyles[$tableStyle]; $tableStyleId = $tStyle.Id.ToLower(); $htmlTableStyle = GetHtmlTableStyle -TableStyle $tStyle; $htmlHeaderStyle = GetHtmlStyle -Style $Styles[$tStyle.HeaderStyle]; $htmlRowStyle = GetHtmlStyle -Style $Styles[$tStyle.RowStyle]; $htmlAlternateRowStyle = GetHtmlStyle -Style $Styles[$tStyle.AlternateRowStyle]; ## Generate Standard table styles [ref] $null = $stylesBuilder.AppendFormat(' table.{0} {{{1} }}', $tableStyleId, $htmlTableStyle).AppendLine(); [ref] $null = $stylesBuilder.AppendFormat(' table.{0} th {{{1}{2} }}', $tableStyleId, $htmlHeaderStyle, $htmlTableStyle).AppendLine(); [ref] $null = $stylesBuilder.AppendFormat(' table.{0} tr:nth-child(odd) td {{{1}{2} }}', $tableStyleId, $htmlRowStyle, $htmlTableStyle).AppendLine(); [ref] $null = $stylesBuilder.AppendFormat(' table.{0} tr:nth-child(even) td {{{1}{2} }}', $tableStyleId, $htmlAlternateRowStyle, $htmlTableStyle).AppendLine(); ## Generate List table styles [ref] $null = $stylesBuilder.AppendFormat(' table.{0}-list {{{1} }}', $tableStyleId, $htmlTableStyle).AppendLine(); [ref] $null = $stylesBuilder.AppendFormat(' table.{0}-list td:nth-child(1) {{{1}{2} }}', $tableStyleId, $htmlHeaderStyle, $htmlTableStyle).AppendLine(); [ref] $null = $stylesBuilder.AppendFormat(' table.{0}-list td:nth-child(2) {{{1}{2} }}', $tableStyleId, $htmlRowStyle, $htmlTableStyle).AppendLine(); } #end foreach style [ref] $null = $stylesBuilder.AppendLine('</style>'); return $stylesBuilder.ToString().TrimEnd(); } #end process } #end function OutHtmlStyle function OutHtmlSection { <# .SYNOPSIS Output formatted Html section. #> [CmdletBinding()] [OutputType([System.String])] param ( ## Section to output [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Section ) process { [System.Text.StringBuilder] $sectionBuilder = New-Object System.Text.StringBuilder; if ($Document.Options['EnableSectionNumbering']) { [string] $sectionName = '{0} {1}' -f $Section.Number, $Section.Name; } else { [string] $sectionName = '{0}' -f $Section.Name; } [int] $headerLevel = $Section.Number.Split('.').Count; ## Html <h5> is the maximum supported level if ($headerLevel -ge 5) { WriteLog -Message $localized.MaxHeadingLevelWarning -IsWarning; $headerLevel = 5; } if ([string]::IsNullOrEmpty($Section.Style)) { $className = $Document.DefaultStyle; } else { $className = $Section.Style; } [ref] $null = $sectionBuilder.AppendFormat('<a name="{0}"><h{1} class="{2}">{3}</h{1}></a>', $Section.Id, $headerLevel, $className, $sectionName.TrimStart()); foreach ($s in $Section.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}[..]' -f $s.Id.Substring(0,36); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { [ref] $null = $sectionBuilder.Append((OutHtmlSection -Section $s)); } 'PScribo.Paragraph' { [ref] $null = $sectionBuilder.Append((OutHtmlParagraph -Paragraph $s)); } 'PScribo.LineBreak' { [ref] $null = $sectionBuilder.Append((OutHtmlLineBreak)); } 'PScribo.PageBreak' { [ref] $null = $sectionBuilder.Append((OutHtmlPageBreak)); } 'PScribo.Table' { [ref] $null = $sectionBuilder.Append((OutHtmlTable -Table $s)); } 'PScribo.BlankLine' { [ref] $null = $sectionBuilder.Append((OutHtmlBlankLine -BlankLine $s)); } Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach return $sectionBuilder.ToString(); } #end process } # end function OutHtmlSection function GetHtmlParagraphStyle { <# .SYNOPSIS Generates html style attribute from PScribo paragraph style overrides. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Paragraph ) process { $paragraphStyleBuilder = New-Object -TypeName System.Text.StringBuilder; if ($Paragraph.Tabs -gt 0) { ## Default to 1/2in tab spacing $tabEm = ConvertMmToEm -Millimeter (12.7 * $Paragraph.Tabs); [ref] $null = $paragraphStyleBuilder.AppendFormat(' margin-left: {0}em;', $tabEm); } if ($Paragraph.Font) { [ref] $null = $paragraphStyleBuilder.AppendFormat(" font-family: '{0}';", $Paragraph.Font -Join "','"); } if ($Paragraph.Size -gt 0) { [ref] $null = $paragraphStyleBuilder.AppendFormat(' font-size: {0:0.00}em;', $Paragraph.Size / 12); } if ($Paragraph.Bold -eq $true) { [ref] $null = $paragraphStyleBuilder.Append(' font-weight: bold;'); } if ($Paragraph.Italic -eq $true) { [ref] $null = $paragraphStyleBuilder.Append(' font-style: italic;'); } if ($Paragraph.Underline -eq $true) { [ref] $null = $paragraphStyleBuilder.Append(' text-decoration: underline;'); } if (-not [System.String]::IsNullOrEmpty($Paragraph.Color) -and $Paragraph.Color.StartsWith('#')) { [ref] $null = $paragraphStyleBuilder.AppendFormat(' color: {0};', $Paragraph.Color.ToLower()); } elseif (-not [System.String]::IsNullOrEmpty($Paragraph.Color)) { [ref] $null = $paragraphStyleBuilder.AppendFormat(' color: #{0};', $Paragraph.Color.ToLower()); } return $paragraphStyleBuilder.ToString().TrimStart(); } #end process } #end function GetHtmlParagraphStyle function OutHtmlParagraph { <# .SYNOPSIS Output formatted Html paragraph. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Paragraph ) process { [System.Text.StringBuilder] $paragraphBuilder = New-Object -TypeName 'System.Text.StringBuilder'; $text = $Paragraph.Text; if ([System.String]::IsNullOrEmpty($text)) { $text = $Paragraph.Id; } $customStyle = GetHtmlParagraphStyle -Paragraph $Paragraph; if ([System.String]::IsNullOrEmpty($Paragraph.Style) -and [System.String]::IsNullOrEmpty($customStyle)) { [ref] $null = $paragraphBuilder.AppendFormat('<div>{0}</div>', $text); } elseif ([System.String]::IsNullOrEmpty($customStyle)) { [ref] $null = $paragraphBuilder.AppendFormat('<div class="{0}">{1}</div>', $Paragraph.Style, $text); } else { [ref] $null = $paragraphBuilder.AppendFormat('<div style="{1}">{2}</div>', $Paragraph.Style, $customStyle, $text); } return $paragraphBuilder.ToString(); } #end process } #end OutHtmlParagraph function GetHtmlTableList { <# .SYNOPSIS Generates list html <table> from a PScribo.Table row object. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table, [Parameter(Mandatory)] [System.Object] $Row ) process { $listTableBuilder = New-Object -TypeName System.Text.StringBuilder; [ref] $null = $listTableBuilder.Append((GetHtmlTableDiv -Table $Table)); [ref] $null = $listTableBuilder.Append((GetHtmlTableColGroup -Table $Table)); [ref] $null = $listTableBuilder.Append('<tbody>'); for ($i = 0; $i -lt $Table.Columns.Count; $i++) { $propertyName = $Table.Columns[$i]; [ref] $null = $listTableBuilder.AppendFormat('<tr><td>{0}</td>', $propertyName); $propertyStyle = '{0}__Style' -f $propertyName; if ($row.PSObject.Properties[$propertyStyle]) { $propertyStyleHtml = (GetHtmlStyle -Style $Document.Styles[$Row.$propertyStyle]); if ([string]::IsNullOrEmpty($Row.$propertyName)) { [ref] $null = $listTableBuilder.AppendFormat('<td style="{0}"> </td></tr>', $propertyStyleHtml); } else { [ref] $null = $listTableBuilder.AppendFormat('<td style="{0}">{1}</td></tr>', $propertyStyleHtml, $Row.($propertyName)); } } else { if ([string]::IsNullOrEmpty($Row.$propertyName)) { [ref] $null = $listTableBuilder.Append('<td> </td></tr>'); } else { [ref] $null = $listTableBuilder.AppendFormat('<td>{0}</td></tr>', $Row.$propertyName); } } } #end for each property [ref] $null = $listTableBuilder.AppendLine('</tbody></table></div>'); return $listTableBuilder.ToString(); } #end process } #end function GetHtmlTableList function GetHtmlTable { <# .SYNOPSIS Generates html <table> from a PScribo.Table object. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table ) process { $standardTableBuilder = New-Object -TypeName System.Text.StringBuilder; [ref] $null = $standardTableBuilder.Append((GetHtmlTableDiv -Table $Table)); [ref] $null = $standardTableBuilder.Append((GetHtmlTableColGroup -Table $Table)); ## Table headers [ref] $null = $standardTableBuilder.Append('<thead><tr>'); for ($i = 0; $i -lt $Table.Columns.Count; $i++) { [ref] $null = $standardTableBuilder.AppendFormat('<th>{0}</th>', $Table.Columns[$i]); } [ref] $null = $standardTableBuilder.Append('</tr></thead>'); ## Table body [ref] $null = $standardTableBuilder.AppendLine('<tbody>'); foreach ($row in $Table.Rows) { [ref] $null = $standardTableBuilder.Append('<tr>'); foreach ($propertyName in $Table.Columns) { $propertyStyle = '{0}__Style' -f $propertyName; if ($row.PSObject.Properties[$propertyStyle]) { ## Cell styles override row styles $propertyStyleHtml = (GetHtmlStyle -Style $Document.Styles[$row.$propertyStyle]).Trim(); [ref] $null = $standardTableBuilder.AppendFormat('<td style="{0}">{1}</td>', $propertyStyleHtml, $row.$propertyName); } elseif (($row.PSObject.Properties['__Style']) -and (-not [System.String]::IsNullOrEmpty($row.__Style))) { ## We have a row style $rowStyleHtml = (GetHtmlStyle -Style $Document.Styles[$row.__Style]).Trim(); [ref] $null = $standardTableBuilder.AppendFormat('<td style="{0}">{1}</td>', $rowStyleHtml, $row.$propertyName); } else { if ($null -ne $row.$propertyName) { ## Check that the property has a value [ref] $null = $standardTableBuilder.AppendFormat('<td>{0}</td>', $row.$propertyName); } else { [ref] $null = $standardTableBuilder.Append('<td> </td>'); } } #end if $row.PropertyStyle } #end foreach property [ref] $null = $standardTableBuilder.AppendLine('</tr>'); } #end foreach row [ref] $null = $standardTableBuilder.AppendLine('</tbody></table></div>'); return $standardTableBuilder.ToString(); } #end process } #end function GetHtmlTableList function OutHtmlTable { <# .SYNOPSIS Output formatted Html <table> from PScribo.Table object. .NOTES One table is output per table row with the -List parameter. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table ) process { [System.Text.StringBuilder] $tableBuilder = New-Object -TypeName 'System.Text.StringBuilder'; if ($Table.List) { ## Create a table for each row for ($r = 0; $r -lt $Table.Rows.Count; $r++) { $row = $Table.Rows[$r]; if ($r -gt 0) { ## Add a space between each table to mirror Word output rendering [ref] $null = $tableBuilder.AppendLine('<p />'); } [ref] $null = $tableBuilder.Append((GetHtmlTableList -Table $Table -Row $row)); } #end foreach row } else { [ref] $null = $tableBuilder.Append((GetHtmlTable -Table $Table)); } #end if return $tableBuilder.ToString(); #Write-Output ($tableBuilder.ToString()) -NoEnumerate; } #end process } #end function outhtmltable function OutHtmlLineBreak { <# .SYNOPSIS Output formatted Html line break. #> [CmdletBinding()] [OutputType([System.String])] param ( ) process { return '<hr />'; } } #end function OutHtmlLineBreak function OutHtmlPageBreak { <# .SYNOPSIS Output formatted Html page break. #> [CmdletBinding()] [OutputType([System.String])] param ( ) process { [System.Text.StringBuilder] $pageBreakBuilder = New-Object 'System.Text.StringBuilder'; [ref] $null = $pageBreakBuilder.Append('</div></page>'); $topMargin = ConvertMmToEm $Document.Options['MarginTop']; $leftMargin = ConvertMmToEm $Document.Options['MarginLeft']; $bottomMargin = ConvertMmToEm $Document.Options['MarginBottom']; $rightMargin = ConvertMmToEm $Document.Options['MarginRight']; [ref] $null = $pageBreakBuilder.AppendFormat('<page><div class="{0}" style="padding-top: {1}em; padding-left: {2}em; padding-bottom: {3}em; padding-right: {4}em;">', $Document.DefaultStyle, $topMargin, $leftMargin, $bottomMargin, $rightMargin).AppendLine(); return $pageBreakBuilder.ToString(); } } #end function OutHtmlPageBreak #endregion OutHtml Private Functions } #end begin process { $stopwatch = [System.Diagnostics.Stopwatch]::StartNew(); WriteLog -Message ($localized.DocumentProcessingStarted -f $Document.Name); [System.Text.StringBuilder] $htmlBuilder = New-Object System.Text.StringBuilder; [ref] $null = $htmlBuilder.AppendLine('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'); [ref] $null = $htmlBuilder.AppendLine('<html xmlns="http://www.w3.org/1999/xhtml">'); [ref] $null = $htmlBuilder.AppendLine('<head><title>{0}</title>' -f $Document.Name); [ref] $null = $htmlBuilder.AppendLine('{0}</head><body><page>' -f (OutHtmlStyle -Styles $Document.Styles -TableStyles $Document.TableStyles)); $topMargin = ConvertMmToEm $Document.Options['MarginTop']; $leftMargin = (ConvertMmToEm $Document.Options['MarginLeft']); $bottomMargin = (ConvertMmToEm $Document.Options['MarginBottom']); $rightMargin = ConvertMmToEm $Document.Options['MarginRight']; [ref] $null = $htmlBuilder.AppendFormat('<div class="{0}" style="padding-top: {1}em; padding-left: {2}em; padding-bottom: {3}em; padding-right: {4}em;">', $Document.DefaultStyle, $topMargin, $leftMargin, $bottomMargin, $rightMargin).AppendLine(); foreach ($s in $Document.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}[..]' -f $s.Id.Substring(0,36); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { [ref] $null = $htmlBuilder.Append((OutHtmlSection -Section $s)); } 'PScribo.Paragraph' { [ref] $null = $htmlBuilder.Append((OutHtmlParagraph -Paragraph $s)); } 'PScribo.Table' { [ref] $null = $htmlBuilder.Append((OutHtmlTable -Table $s)); } 'PScribo.LineBreak' { [ref] $null = $htmlBuilder.Append((OutHtmlLineBreak)); } 'PScribo.PageBreak' { [ref] $null = $htmlBuilder.Append((OutHtmlPageBreak)); } ## Page breaks are implemented as line breaks with extra padding 'PScribo.TOC' { [ref] $null = $htmlBuilder.Append((OutHtmlTOC -TOC $s)); } 'PScribo.BlankLine' { [ref] $null = $htmlBuilder.Append((OutHtmlBlankLine -BlankLine $s)); } Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach section $stopwatch.Stop(); WriteLog -Message ($localized.DocumentProcessingCompleted -f $Document.Name); $destinationPath = Join-Path $Path ('{0}.html' -f $Document.Name); WriteLog -Message ($localized.SavingFile -f $destinationPath); $htmlBuilder.ToString().TrimEnd() | Out-File -FilePath $destinationPath -Force -Encoding utf8; [ref] $null = $htmlBuilder; WriteLog -Message ($localized.TotalProcessingTime -f $stopwatch.Elapsed.TotalSeconds); Write-Output (Get-Item -Path $destinationPath); } #end process } #end function OutHtml function OutText { <# .SYNOPSIS Text output plugin for PScribo. .DESCRIPTION Outputs a text file representation of a PScribo document object. #> [CmdletBinding()] param ( ## ThePScribo document object to convert to a text document [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Document, ## Output directory path for the .txt file [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.String] $Path, ### Hashtable of all plugin supported options [Parameter()] [AllowNull()] [System.Collections.Hashtable] $Options ) begin { $pluginName = 'Text'; #region OutText Private Functions function New-PScriboTextOptions { <# .SYNOPSIS Sets the text plugin specific formatting/output options. .NOTES All plugin options should be prefixed with the plugin name. #> [CmdletBinding()] param ( ## Text/output width. 0 = none/no wrap. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Int32] $TextWidth = 120, ## Document header separator character. [Parameter(ValueFromPipelineByPropertyName)] [ValidateLength(1,1)] [System.String] $HeaderSeparator = '=', ## Document section separator character. [Parameter(ValueFromPipelineByPropertyName)] [ValidateLength(1,1)] [System.String] $SectionSeparator = '-', ## Document section separator character. [Parameter(ValueFromPipelineByPropertyName)] [ValidateLength(1,1)] [System.String] $LineBreakSeparator = '_', ## Default header/section separator width. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Int32] $SeparatorWidth = $TextWidth, ## Text encoding [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('ASCII','Unicode','UTF7','UTF8')] [System.String] $Encoding = 'ASCII' ) process { ## Flag that the Text options have been set. There should be a flag that is checked ## by the plugin if the user has set plugin-specific options. $options = @{ TextWidth = $TextWidth; HeaderSeparator = $HeaderSeparator; SectionSeparator = $SectionSeparator; LineBreakSeparator = $LineBreakSeparator; SeparatorWidth = $SeparatorWidth; Encoding = $Encoding; } return $options; } #end process } #end function New-PScriboTextOptions function OutTextTOC { <# .SYNOPSIS Output formatted Table of Contents #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $TOC ) begin { ## Fix Set-StrictMode if (Test-Path -Path Variable:\Options) { $options = Get-Variable -Name Options -ValueOnly; } else { $options = New-PScriboTextOptions; } } process { $tocBuilder = New-Object -TypeName System.Text.StringBuilder; [ref] $null = $tocBuilder.AppendLine($TOC.Name); [ref] $null = $tocBuilder.AppendLine(''.PadRight($options.SeparatorWidth, $options.SectionSeparator)); $maxSectionNumberLength = ([System.String] ($Document.TOC.Number | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum)).Length; foreach ($tocEntry in $Document.TOC) { $sectionNumberPaddingLength = $maxSectionNumberLength - $tocEntry.Number.Length; $sectionNumberIndent = ''.PadRight($tocEntry.Level, ' '); $sectionPadding = ''.PadRight($sectionNumberPaddingLength, ' '); [ref] $null = $tocBuilder.AppendFormat('{0}{1} {2}{3}', $tocEntry.Number, $sectionPadding, $sectionNumberIndent, $tocEntry.Name).AppendLine(); } #end foreach TOC entry return $tocBuilder.ToString(); } #end process } #end function OutTextTOC function OutTextBlankLine { <# .SYNOPSIS Output formatted text blankline. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $BlankLine ) process { $blankLineBuilder = New-Object -TypeName System.Text.StringBuilder; for ($i = 0; $i -lt $BlankLine.LineCount; $i++) { [ref] $null = $blankLineBuilder.AppendLine(); } return $blankLineBuilder.ToString(); } #end process } #end function OutHtmlBlankLine function OutTextSection { <# .SYNOPSIS Output formatted text section. #> [CmdletBinding()] param ( ## Section to output [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Section ) begin { ## Fix Set-StrictMode if (Test-Path -Path Variable:\Options) { $options = Get-Variable -Name Options -ValueOnly; } else { $options = New-PScriboTextOptions; } } process { $sectionBuilder = New-Object -TypeName System.Text.StringBuilder; if ($Document.Options['EnableSectionNumbering']) { [string] $sectionName = '{0} {1}' -f $Section.Number, $Section.Name; } else { [string] $sectionName = '{0}' -f $Section.Name; } [ref] $null = $sectionBuilder.AppendLine(); [ref] $null = $sectionBuilder.AppendLine($sectionName.TrimStart()); [ref] $null = $sectionBuilder.AppendLine(''.PadRight($options.SeparatorWidth, $options.SectionSeparator)); foreach ($s in $Section.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}..' -f $s.Id.Substring(0,38); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { [ref] $null = $sectionBuilder.Append((OutTextSection -Section $s)); } 'PScribo.Paragraph' { [ref] $null = $sectionBuilder.Append(($s | OutTextParagraph)); } 'PScribo.PageBreak' { [ref] $null = $sectionBuilder.AppendLine((OutTextPageBreak)); } ## Page breaks implemented as line break with extra padding 'PScribo.LineBreak' { [ref] $null = $sectionBuilder.AppendLine((OutTextLineBreak)); } 'PScribo.Table' { [ref] $null = $sectionBuilder.AppendLine(($s | OutTextTable)); } 'PScribo.BlankLine' { [ref] $null = $sectionBuilder.AppendLine(($s | OutTextBlankLine)); } Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach return $sectionBuilder.ToString(); } #end process } #end function outtextsection function OutTextParagraph { <# .SYNOPSIS Output formatted paragraph text. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Paragraph ) begin { ## Fix Set-StrictMode if (Test-Path -Path Variable:\Options) { $options = Get-Variable -Name Options -ValueOnly; } else { $options = New-PScriboTextOptions; } } process { $padding = ''.PadRight(($Paragraph.Tabs * 4), ' '); if ([string]::IsNullOrEmpty($Paragraph.Text)) { $text = "$padding$($Paragraph.Id)"; } else { $text = "$padding$($Paragraph.Text)"; } $formattedText = OutStringWrap -InputObject $text -Width $Options.TextWidth; if ($Paragraph.NewLine) { return "$formattedText`r`n"; } else { return $formattedText; } } #end process } #end outtextparagraph function OutTextLineBreak { <# .SYNOPSIS Output formatted line break text. #> [CmdletBinding()] param ( ) begin { ## Fix Set-StrictMode if (Test-Path -Path Variable:\Options) { $options = Get-Variable -Name Options -ValueOnly; } else { $options = New-PScriboTextOptions; } } process { ## Use the specified output width if ($options.TextWidth -eq 0) { $options.TextWidth = $Host.UI.RawUI.BufferSize.Width -1; } $lb = ''.PadRight($options.SeparatorWidth, $options.LineBreakSeparator); return "$(OutStringWrap -InputObject $lb -Width $options.TextWidth)`r`n"; } #end process } #end function OutTextLineBreak function OutTextPageBreak { <# .SYNOPSIS Output formatted line break text. #> [CmdletBinding()] param ( ) process { return "$(OutTextLineBreak)`r`n"; } #end process } #end function OutTextLineBreak function OutTextTable { <# .SYNOPSIS Output formatted text table. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Table ) begin { ## Fix Set-StrictMode if (Test-Path -Path Variable:\Options) { $options = Get-Variable -Name Options -ValueOnly; } else { $options = New-PScriboTextOptions; } } process { ## Use the specified output width if ($options.TextWidth -eq 0) { $options.TextWidth = $Host.UI.RawUI.BufferSize.Width -1; } if ($Table.List) { $text = ($Table.Rows | Select-Object -Property * -ExcludeProperty '*__Style' | Format-List | Out-String -Width $options.TextWidth).Trim(); } else { ## Don't trim tabs for table headers ## Tables set to AutoSize as otherwise, rendering is different between PoSh v4 and v5 $text = ($Table.Rows | Select-Object -Property * -ExcludeProperty '*__Style' | Format-Table -Wrap -AutoSize | Out-String -Width $options.TextWidth).Trim("`r`n"); } # Ensure there's a space before and after the table. return "`r`n$text`r`n"; } #end process } #end function outtexttable function OutStringWrap { <# .SYNOPSIS Outputs objects to strings, wrapping as required. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [Object[]] $InputObject, [Parameter()] [ValidateNotNull()] [System.Int32] $Width = $Host.UI.RawUI.BufferSize.Width ) begin { ## 2 is the minimum, therefore default to wiiiiiiiiiide! if ($Width -lt 2) { $Width = 4096; } WriteLog -Message ('Wrapping text at "{0}" characters.' -f $Width) -IsDebug; } process { foreach ($object in $InputObject) { $textBuilder = New-Object -TypeName System.Text.StringBuilder; $text = (Out-String -InputObject $object).TrimEnd("`r`n"); for ($i = 0; $i -le $text.Length; $i += $Width) { if (($i + $Width) -ge ($text.Length -1)) { [ref] $null = $textBuilder.Append($text.Substring($i)); } else { [ref] $null = $textBuilder.AppendLine($text.Substring($i, $Width)); } } #end for return $textBuilder.ToString(); $textBuilder = $null; } #end foreach } #end process } #end function OutStringWrap #endregion OutText Private Functions } process { $stopwatch = [Diagnostics.Stopwatch]::StartNew(); WriteLog -Message ($localized.DocumentProcessingStarted -f $Document.Name); ## Create default options if not specified if ($null -eq $Options) { $Options = New-PScriboTextOptions; } [System.Text.StringBuilder] $textBuilder = New-Object System.Text.StringBuilder; foreach ($s in $Document.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}[..]' -f $s.Id.Substring(0,36); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { [ref] $null = $textBuilder.Append((OutTextSection -Section $s)); } 'PScribo.Paragraph' { [ref] $null = $textBuilder.Append(($s | OutTextParagraph)); } 'PScribo.PageBreak' { [ref] $null = $textBuilder.AppendLine((OutTextPageBreak)); } 'PScribo.LineBreak' { [ref] $null = $textBuilder.AppendLine((OutTextLineBreak)); } 'PScribo.Table' { [ref] $null = $textBuilder.AppendLine(($s | OutTextTable)); } 'PScribo.TOC' { [ref] $null = $textBuilder.AppendLine(($s | OutTextTOC)); } 'PScribo.BlankLine' { [ref] $null = $textBuilder.AppendLine(($s | OutTextBlankLine)); } Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach $stopwatch.Stop(); WriteLog -Message ($localized.DocumentProcessingCompleted -f $Document.Name); $destinationPath = Join-Path -Path $Path ('{0}.txt' -f $Document.Name); WriteLog -Message ($localized.SavingFile -f $destinationPath); Set-Content -Value ($textBuilder.ToString()) -Path $destinationPath -Encoding $Options.Encoding; [ref] $null = $textBuilder; WriteLog -Message ($localized.TotalProcessingTime -f $stopwatch.Elapsed.TotalSeconds); ## Return the file reference to the pipeline Write-Output (Get-Item -Path $destinationPath); } #end process } #end function OutText function OutWord { <# .SYNOPSIS Microsoft Word output plugin for PScribo. .DESCRIPTION Outputs a Word document representation of a PScribo document object. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( ## ThePScribo document object to convert to a text document [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Document, ## Output directory path for the .txt file [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.String] $Path, ### Hashtable of all plugin supported options [Parameter()] [AllowNull()] [System.Collections.Hashtable] $Options ) begin { $pluginName = 'Word'; #region OutWord Private Functions function ConvertToWordColor { <# .SYNOPSIS Converts an HTML color to RRGGBB value as Word does not support short Html color codes #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Color ) process { $Color = $Color.TrimStart('#'); if ($Color.Length -eq 3) { $Color = '{0}{0}{1}{1}{2}{2}' -f $Color[0], $Color[1],$Color[2]; } return $Color.ToUpper(); } } #end function ConvertToWordColor function OutWordSection { <# .SYNOPSIS Output formatted Word section (paragraph). #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Section, [Parameter(Mandatory)] [System.Xml.XmlElement] $RootElement, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $p = $RootElement.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $pPr = $p.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); if (-not [System.String]::IsNullOrEmpty($Section.Style)) { #if (-not $Section.IsExcluded) { ## If it's excluded we need a non-Heading style :( Could explicitly set the style on the run? $pStyle = $pPr.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle.SetAttribute('val', $xmlnsMain, $Section.Style); #} } $spacing = $pPr.AppendChild($XmlDocument.CreateElement('w', 'spacing', $xmlnsMain)); ## Increment heading spacing by 2pt for each section level, starting at 8pt for level 0, 10pt for level 1 etc $spacingPt = (($Section.Level * 2) + 8) * 20; [ref] $null = $spacing.SetAttribute('before', $xmlnsMain, $spacingPt); [ref] $null = $spacing.SetAttribute('after', $xmlnsMain, $spacingPt); $r = $p.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $t = $r.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); if ($Document.Options['EnableSectionNumbering']) { [string] $sectionName = '{0} {1}' -f $Section.Number, $Section.Name; } else { [string] $sectionName = '{0}' -f $Section.Name; } [ref] $null = $t.AppendChild($XmlDocument.CreateTextNode($sectionName)); foreach ($s in $Section.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}[..]' -f $s.Id.Substring(0,36); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { $s | OutWordSection -RootElement $RootElement -XmlDocument $XmlDocument; } 'PScribo.Paragraph' { [ref] $null = $RootElement.AppendChild((OutWordParagraph -Paragraph $s -XmlDocument $XmlDocument)); } 'PScribo.PageBreak' { [ref] $null = $RootElement.AppendChild((OutWordPageBreak -PageBreak $s -XmlDocument $xmlDocument)); } 'PScribo.LineBreak' { [ref] $null = $RootElement.AppendChild((OutWordLineBreak -LineBreak $s -XmlDocument $xmlDocument)); } 'PScribo.Table' { OutWordTable -Table $s -XmlDocument $xmlDocument -Element $RootElement; } 'PScribo.BlankLine' { OutWordBlankLine -BlankLine $s -XmlDocument $xmlDocument -Element $RootElement; } Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach } #end process } #end function OutWordSection function OutWordParagraph { <# .SYNOPSIS Output formatted Word paragraph. #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Paragraph, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $p = $XmlDocument.CreateElement('w', 'p', $xmlnsMain); $pPr = $p.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); if ($Paragraph.Tabs -gt 0) { $ind = $pPr.AppendChild($XmlDocument.CreateElement('w', 'ind', $xmlnsMain)); [ref] $null = $ind.SetAttribute('left', $xmlnsMain, (720 * $Paragraph.Tabs)); } if (-not [System.String]::IsNullOrEmpty($Paragraph.Style)) { $pStyle = $pPr.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle.SetAttribute('val', $xmlnsMain, $Paragraph.Style); } $spacing = $pPr.AppendChild($XmlDocument.CreateElement('w', 'spacing', $xmlnsMain)); [ref] $null = $spacing.SetAttribute('before', $xmlnsMain, 0); [ref] $null = $spacing.SetAttribute('after', $xmlnsMain, 0); $r = $p.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $rPr = $r.AppendChild($XmlDocument.CreateElement('w', 'rPr', $xmlnsMain)); ## Apply custom paragraph styles to the run.. if ($Paragraph.Font) { $rFonts = $rPr.AppendChild($XmlDocument.CreateElement('w', 'rFonts', $xmlnsMain)); [ref] $null = $rFonts.SetAttribute('ascii', $xmlnsMain, $Paragraph.Font[0]); [ref] $null = $rFonts.SetAttribute('hAnsi', $xmlnsMain, $Paragraph.Font[0]); } if ($Paragraph.Size -gt 0) { $sz = $rPr.AppendChild($XmlDocument.CreateElement('w', 'sz', $xmlnsMain)); [ref] $null = $sz.SetAttribute('val', $xmlnsMain, $Paragraph.Size * 2); } if ($Paragraph.Bold -eq $true) { [ref] $null = $rPr.AppendChild($XmlDocument.CreateElement('w', 'b', $xmlnsMain)); } if ($Paragraph.Italic -eq $true) { [ref] $null = $rPr.AppendChild($XmlDocument.CreateElement('w', 'i', $xmlnsMain)); } if ($Paragraph.Underline -eq $true) { $u = $rPr.AppendChild($XmlDocument.CreateElement('w', 'u', $xmlnsMain)); [ref] $null = $u.SetAttribute('val', $xmlnsMain, 'single'); } if (-not [System.String]::IsNullOrEmpty($Paragraph.Color)) { $color = $rPr.AppendChild($XmlDocument.CreateElement('w', 'color', $xmlnsMain)); [ref] $null = $color.SetAttribute('val', $xmlnsMain, (ConvertToWordColor -Color $Paragraph.Color)); } $t = $r.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); [ref] $null = $t.SetAttribute('space', 'http://www.w3.org/XML/1998/namespace', 'preserve'); ## needs to be xml:space="preserve" NOT w:space... if ([System.String]::IsNullOrEmpty($Paragraph.Text)) { [ref] $null = $t.AppendChild($XmlDocument.CreateTextNode($Paragraph.Id)); } else { [ref] $null = $t.AppendChild($XmlDocument.CreateTextNode($Paragraph.Text)); } return $p; } #end process } #end function OutWordParagraph function OutWordPageBreak { <# .SYNOPSIS Output formatted Word page break. #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $PageBreak, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $p = $XmlDocument.CreateElement('w', 'p', $xmlnsMain); $r = $p.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $br = $r.AppendChild($XmlDocument.CreateElement('w', 'br', $xmlnsMain)); [ref] $null = $br.SetAttribute('type', $xmlnsMain, 'page'); return $p; } } #end function OutWordPageBreak function OutWordLineBreak { <# .SYNOPSIS Output formatted Word line break. #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $LineBreak, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $p = $XmlDocument.CreateElement('w', 'p', $xmlnsMain); $pPr = $p.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $pBdr = $pPr.AppendChild($XmlDocument.CreateElement('w', 'pBdr', $xmlnsMain)); $bottom = $pBdr.AppendChild($XmlDocument.CreateElement('w', 'bottom', $xmlnsMain)); [ref] $null = $bottom.SetAttribute('val', $xmlnsMain, 'single'); [ref] $null = $bottom.SetAttribute('sz', $xmlnsMain, 6); [ref] $null = $bottom.SetAttribute('space', $xmlnsMain, 1); [ref] $null = $bottom.SetAttribute('color', $xmlnsMain, 'auto'); return $p; } } #end function OutWordLineBreak function GetWordTable { <# .SYNOPSIS Creates a scaffold Word <w:tbl> element #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $tableStyle = $Document.TableStyles[$Table.Style]; $tbl = $XmlDocument.CreateElement('w', 'tbl', $xmlnsMain); $tblPr = $tbl.AppendChild($XmlDocument.CreateElement('w', 'tblPr', $xmlnsMain)); if ($Table.Tabs -gt 0) { $tblInd = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblInd', $xmlnsMain)); [ref] $null = $tblInd.SetAttribute('w', $xmlnsMain, (720 * $Table.Tabs)); } if ($Table.ColumnWidths) { $tblLayout = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblLayout', $xmlnsMain)); [ref] $null = $tblLayout.SetAttribute('type', $xmlnsMain, 'fixed'); } elseif ($Table.Width -eq 0) { $tblLayout = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblLayout', $xmlnsMain)); [ref] $null = $tblLayout.SetAttribute('type', $xmlnsMain, 'autofit'); } if ($Table.Width -gt 0) { $tblW = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblW', $xmlnsMain)); [ref] $null = $tblW.SetAttribute('type', $xmlnsMain, 'pct'); $tableWidthRenderPct = $Table.Width; if ($Table.Tabs -gt 0) { ## We now need to deal with tables being pushed outside the page margin $pageWidthMm = $Document.Options['PageWidth'] - ($Document.Options['PageMarginLeft'] + $Document.Options['PageMarginRight']); $indentWidthMm = ConvertPtToMm -Point ($Table.Tabs * 36); $tableRenderMm = (($pageWidthMm / 100) * $Table.Width) + $indentWidthMm; if ($tableRenderMm -gt $pageWidthMm) { ## We've over-flowed so need to work out the maximum percentage $maxTableWidthMm = $pageWidthMm - $indentWidthMm; $tableWidthRenderPct = [System.Math]::Round(($maxTableWidthMm / $pageWidthMm) * 100, 2); WriteLog -Message ($localized.TableWidthOverflowWarning -f $tableWidthRenderPct) -IsWarning; } } [ref] $null = $tblW.SetAttribute('w', $xmlnsMain, $tableWidthRenderPct * 50); } $spacing = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'spacing', $xmlnsMain)); [ref] $null = $spacing.SetAttribute('before', $xmlnsMain, 72); [ref] $null = $spacing.SetAttribute('after', $xmlnsMain, 72); #$tblLook = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblLook', $xmlnsMain)); #[ref] $null = $tblLook.SetAttribute('val', $xmlnsMain, '04A0'); #[ref] $null = $tblLook.SetAttribute('firstRow', $xmlnsMain, 1); ## <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/> #$tblStyle = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblStyle', $xmlnsMain)); #[ref] $null = $tblStyle.SetAttribute('val', $xmlnsMain, $Table.Style); if ($tableStyle.BorderWidth -gt 0) { $tblBorders = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblBorders', $xmlnsMain)); foreach ($border in @('top','bottom','start','end','insideH','insideV')) { $b = $tblBorders.AppendChild($XmlDocument.CreateElement('w', $border, $xmlnsMain)); [ref] $null = $b.SetAttribute('sz', $xmlnsMain, (ConvertMmToOctips $tableStyle.BorderWidth)); [ref] $null = $b.SetAttribute('val', $xmlnsMain, 'single'); [ref] $null = $b.SetAttribute('color', $xmlnsMain, (ConvertToWordColor -Color $tableStyle.BorderColor)); } } $tblCellMar = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblCellMar', $xmlnsMain)); $top = $tblCellMar.AppendChild($XmlDocument.CreateElement('w', 'top', $xmlnsMain)); [ref] $null = $top.SetAttribute('w', $xmlnsMain, (ConvertMmToTwips $tableStyle.PaddingTop)); [ref] $null = $top.SetAttribute('type', $xmlnsMain, 'dxa'); $left = $tblCellMar.AppendChild($XmlDocument.CreateElement('w', 'start', $xmlnsMain)); [ref] $null = $left.SetAttribute('w', $xmlnsMain, (ConvertMmToTwips $tableStyle.PaddingLeft)); [ref] $null = $left.SetAttribute('type', $xmlnsMain, 'dxa'); $bottom = $tblCellMar.AppendChild($XmlDocument.CreateElement('w', 'bottom', $xmlnsMain)); [ref] $null = $bottom.SetAttribute('w', $xmlnsMain, (ConvertMmToTwips $tableStyle.PaddingBottom)); [ref] $null = $bottom.SetAttribute('type', $xmlnsMain, 'dxa'); $right = $tblCellMar.AppendChild($XmlDocument.CreateElement('w', 'end', $xmlnsMain)); [ref] $null = $right.SetAttribute('w', $xmlnsMain, (ConvertMmToTwips $tableStyle.PaddingRight)); [ref] $null = $right.SetAttribute('type', $xmlnsMain, 'dxa'); $tblGrid = $tbl.AppendChild($XmlDocument.CreateElement('w', 'tblGrid', $xmlnsMain)); $columnCount = $Table.Columns.Count; if ($Table.List) { $columnCount = 2; } for ($i = 0; $i -lt $Table.Columns.Count; $i++) { $gridCol = $tblGrid.AppendChild($XmlDocument.CreateElement('w', 'gridCol', $xmlnsMain)); } return $tbl; } #end process } #end function GetWordTable function OutWordTable { <# .SYNOPSIS Output formatted Word table. .NOTES Specifies that the current row should be repeated at the top each new page on which the table is displayed. E.g, <w:tblHeader />. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table, ## Root element to append the table(s) to. List view will create multiple tables [Parameter(Mandatory)] [ValidateNotNull()] [System.Xml.XmlElement] $Element, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $tableStyle = $Document.TableStyles[$Table.Style]; $headerStyle = $Document.Styles[$tableStyle.HeaderStyle]; if ($Table.List) { for ($r = 0; $r -lt $Table.Rows.Count; $r++) { $row = $Table.Rows[$r]; if ($r -gt 0) { ## Add a space between each table as Word renders them together.. [ref] $null = $Element.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); } ## Create <tr><tc></tc></tr> for each property $tbl = $Element.AppendChild((GetWordTable -Table $Table -XmlDocument $XmlDocument)); $properties = @($row.PSObject.Properties); for ($i = 0; $i -lt $properties.Count; $i++) { $propertyName = $properties[$i].Name; ## Ignore __Style properties if (-not $propertyName.EndsWith('__Style')) { $tr = $tbl.AppendChild($XmlDocument.CreateElement('w', 'tr', $xmlnsMain)); $tc1 = $tr.AppendChild($XmlDocument.CreateElement('w', 'tc', $xmlnsMain)); $tcPr1 = $tc1.AppendChild($XmlDocument.CreateElement('w', 'tcPr', $xmlnsMain)); if ($null -ne $Table.ColumnWidths) { ## TODO: Refactor out $columnWidthTwips = ConvertMmToTwips -Millimeter $Table.ColumnWidths[0]; $tcW1 = $tcPr1.AppendChild($XmlDocument.CreateElement('w', 'tcW', $xmlnsMain)); [ref] $null = $tcW1.SetAttribute('w', $xmlnsMain, $Table.ColumnWidths[0] * 50); [ref] $null = $tcW1.SetAttribute('type', $xmlnsMain, 'pct'); } if ($headerStyle.BackgroundColor) { [ref] $null = $tc1.AppendChild((GetWordTableStyleCellPr -Style $headerStyle -XmlDocument $XmlDocument)); } $p1 = $tc1.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $pPr1 = $p1.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $pStyle1 = $pPr1.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle1.SetAttribute('val', $xmlnsMain, $tableStyle.HeaderStyle); $r1 = $p1.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $t1 = $r1.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); [ref] $null = $t1.AppendChild($XmlDocument.CreateTextNode($propertyName)); $tc2 = $tr.AppendChild($XmlDocument.CreateElement('w', 'tc', $xmlnsMain)); $tcPr2 = $tc2.AppendChild($XmlDocument.CreateElement('w', 'tcPr', $xmlnsMain)); if ($null -ne $Table.ColumnWidths) { ## TODO: Refactor out $tcW2 = $tcPr2.AppendChild($XmlDocument.CreateElement('w', 'tcW', $xmlnsMain)); [ref] $null = $tcW2.SetAttribute('w', $xmlnsMain, $Table.ColumnWidths[1] * 50); [ref] $null = $tcW2.SetAttribute('type', $xmlnsMain, 'pct'); } $p2 = $tc2.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $cellPropertyStyle = '{0}__Style' -f $propertyName; if ($row.PSObject.Properties[$cellPropertyStyle]) { if (-not (Test-Path -Path Variable:\cellStyle)) { $cellStyle = $Document.Styles[$row.$cellPropertyStyle]; } elseif ($cellStyle.Id -ne $row.$cellPropertyStyle) { ## Retrieve the style if we don't already have it $cellStyle = $Document.Styles[$row.$cellPropertyStyle]; } if ($cellStyle.BackgroundColor) { [ref] $null = $tc2.AppendChild((GetWordTableStyleCellPr -Style $cellStyle -XmlDocument $XmlDocument)); } if ($row.$cellPropertyStyle) { $pPr2 = $p2.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $pStyle2 = $pPr2.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle2.SetAttribute('val', $xmlnsMain, $row.$cellPropertyStyle); } } $r2 = $p2.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $t2 = $r2.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); [ref] $null = $t2.AppendChild($XmlDocument.CreateTextNode($row.($propertyName))); } } #end for each property } #end foreach row } #end if Table.List else { $tbl = $Element.AppendChild((GetWordTable -Table $Table -XmlDocument $XmlDocument)); $tr = $tbl.AppendChild($XmlDocument.CreateElement('w', 'tr', $xmlnsMain)); $trPr = $tr.AppendChild($XmlDocument.CreateElement('w', 'trPr', $xmlnsMain)); [ref] $rblHeader = $trPr.AppendChild($XmlDocument.CreateElement('w', 'tblHeader', $xmlnsMain)); ## Flow headers across pages for ($i = 0; $i -lt $Table.Columns.Count; $i++) { $tc = $tr.AppendChild($XmlDocument.CreateElement('w', 'tc', $xmlnsMain)); if ($headerStyle.BackgroundColor) { $tcPr = $tc.AppendChild((GetWordTableStyleCellPr -Style $headerStyle -XmlDocument $XmlDocument)); } else { $tcPr = $tc.AppendChild($XmlDocument.CreateElement('w', 'tcPr', $xmlnsMain)); } $tcW = $tcPr.AppendChild($XmlDocument.CreateElement('w', 'tcW', $xmlnsMain)); if (($Table.ColumnWidths -ne $null) -and ($Table.ColumnWidths[$i] -ne $null)) { [ref] $null = $tcW.SetAttribute('w', $xmlnsMain, $Table.ColumnWidths[$i] * 50); [ref] $null = $tcW.SetAttribute('type', $xmlnsMain, 'pct'); } else { [ref] $null = $tcW.SetAttribute('w', $xmlnsMain, 0); [ref] $null = $tcW.SetAttribute('type', $xmlnsMain, 'auto'); } $p = $tc.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $pPr = $p.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $pStyle = $pPr.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle.SetAttribute('val', $xmlnsMain, $tableStyle.HeaderStyle); $r = $p.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $t = $r.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); [ref] $null = $t.AppendChild($XmlDocument.CreateTextNode($Table.Columns[$i])); } #end for Table.Columns $isAlternatingRow = $false; foreach ($row in $Table.Rows) { $tr = $tbl.AppendChild($XmlDocument.CreateElement('w', 'tr', $xmlnsMain)); foreach ($propertyName in $Table.Columns) { $cellPropertyStyle = '{0}__Style' -f $propertyName; if ($row.PSObject.Properties[$cellPropertyStyle]) { ## Cell style overrides row/default styles $cellStyleName = $row.$cellPropertyStyle; } elseif (-not [System.String]::IsNullOrEmpty($row.__Style)) { ## Row style overrides default style $cellStyleName = $row.__Style; } else { ## Use the table row/alternating style.. $cellStyleName = $tableStyle.RowStyle; if ($isAlternatingRow) { $cellStyleName = $tableStyle.AlternateRowStyle; } } if (-not (Test-Path -Path Variable:\cellStyle)) { $cellStyle = $Document.Styles[$cellStyleName]; } elseif ($cellStyle.Id -ne $cellStyleName) { ## Retrieve the style if we don't already have it $cellStyle = $Document.Styles[$cellStyleName]; } $tc = $tr.AppendChild($XmlDocument.CreateElement('w', 'tc', $xmlnsMain)); if ($cellStyle.BackgroundColor) { [ref] $null = $tc.AppendChild((GetWordTableStyleCellPr -Style $cellStyle -XmlDocument $XmlDocument)); } $p = $tc.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $pPr = $p.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $pStyle = $pPr.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle.SetAttribute('val', $xmlnsMain, $cellStyleName); $r = $p.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $t = $r.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); [ref] $null = $t.AppendChild($XmlDocument.CreateTextNode($row.($propertyName))); } #end foreach property $isAlternatingRow = !$isAlternatingRow; } #end foreach row } #end if not Table.List } #end process } #end function OutWordTable function OutWordTOC { <# .SYNOPSIS Output formatted Word table of contents. #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $TOC, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $sdt = $XmlDocument.CreateElement('w', 'sdt', $xmlnsMain); $sdtPr = $sdt.AppendChild($XmlDocument.CreateElement('w', 'sdtPr', $xmlnsMain)); $docPartObj = $sdtPr.AppendChild($XmlDocument.CreateElement('w', 'docPartObj', $xmlnsMain)); $docObjectGallery = $docPartObj.AppendChild($XmlDocument.CreateElement('w', 'docPartGallery', $xmlnsMain)); [ref] $null = $docObjectGallery.SetAttribute('val', $xmlnsMain, 'Table of Contents'); [ref] $null = $docPartObj.AppendChild($XmlDocument.CreateElement('w', 'docPartUnique', $xmlnsMain)); $sdtEndPr = $sdt.AppendChild($XmlDocument.CreateElement('w', 'stdEndPr', $xmlnsMain)); $sdtContent = $sdt.AppendChild($XmlDocument.CreateElement('w', 'stdContent', $xmlnsMain)); $p1 = $sdtContent.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $pPr1 = $p1.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $pStyle1 = $pPr1.AppendChild($XmlDocument.CreateElement('w', 'pStyle', $xmlnsMain)); [ref] $null = $pStyle1.SetAttribute('val', $xmlnsMain, 'TOC'); $r1 = $p1.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $t1 = $r1.AppendChild($XmlDocument.CreateElement('w', 't', $xmlnsMain)); [ref] $null = $t1.AppendChild($XmlDocument.CreateTextNode($TOC.Name)); $p2 = $sdtContent.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $pPr2 = $p2.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $tabs2 = $pPr2.AppendChild($XmlDocument.CreateElement('w', 'tabs', $xmlnsMain)); $tab2 = $tabs2.AppendChild($XmlDocument.CreateElement('w', 'tab', $xmlnsMain)); [ref] $null = $tab2.SetAttribute('val', $xmlnsMain, 'right'); [ref] $null = $tab2.SetAttribute('leader', $xmlnsMain, 'dot'); [ref] $null = $tab2.SetAttribute('pos', $xmlnsMain, '9016'); #10790?! $r2 = $p2.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); ##TODO: Refactor duplicate code $fldChar1 = $r2.AppendChild($XmlDocument.CreateElement('w', 'fldChar', $xmlnsMain)); [ref] $null = $fldChar1.SetAttribute('fldCharType', $xmlnsMain, 'begin'); $r3 = $p2.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $instrText = $r3.AppendChild($XmlDocument.CreateElement('w', 'instrText', $xmlnsMain)); [ref] $null = $instrText.SetAttribute('space', 'http://www.w3.org/XML/1998/namespace', 'preserve'); [ref] $null = $instrText.AppendChild($XmlDocument.CreateTextNode(' TOC \o "1-3" \h \z \u ')); $r4 = $p2.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); $fldChar2 = $r4.AppendChild($XmlDocument.CreateElement('w', 'fldChar', $xmlnsMain)); [ref] $null = $fldChar2.SetAttribute('fldCharType', $xmlnsMain, 'separate'); $p3 = $sdtContent.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); $r5 = $p3.AppendChild($XmlDocument.CreateElement('w', 'r', $xmlnsMain)); #$rPr3 = $r3.AppendChild($XmlDocument.CreateElement('w', 'rPr', $xmlnsMain)); $fldChar3 = $r5.AppendChild($XmlDocument.CreateElement('w', 'fldChar', $xmlnsMain)); [ref] $null = $fldChar3.SetAttribute('fldCharType', $xmlnsMain, 'end'); return $sdt; } #end process } #end function OutWordTOC function OutWordBlankLine { <# .SYNOPSIS Output formatted Word xml blank line (paragraph). #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $BlankLine, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument, [Parameter(Mandatory)] [System.Xml.XmlElement] $Element ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; for ($i = 0; $i -lt $BlankLine.LineCount; $i++) { [ref] $null = $Element.AppendChild($XmlDocument.CreateElement('w', 'p', $xmlnsMain)); } } } #end function OutWordLineBreak function GetWordStyle { <# .SYNOPSIS Generates Word Xml style element from a PScribo document style. #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( ## PScribo document style [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Style, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument, [Parameter(Mandatory)] [ValidateSet('Paragraph','Character')] [System.String] $Type ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; if ($Type -eq 'Paragraph') { $styleId = $Style.Id; $styleName = $Style.Name; $linkId = '{0}Char' -f $Style.Id; } else { $styleId = '{0}Char' -f $Style.Id; $styleName = '{0} Char' -f $Style.Name; $linkId = $Style.Id; } $documentStyle = $XmlDocument.CreateElement('w', 'style', $xmlnsMain); [ref] $null = $documentStyle.SetAttribute('type', $xmlnsMain, $Type.ToLower()); if ($Style.Id -eq $Document.DefaultStyle) { ## Set as default style [ref] $null = $documentStyle.SetAttribute('default', $xmlnsMain, 1); $uiPriority = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'uiPriority', $xmlnsMain)); [ref] $null = $uiPriority.SetAttribute('val', $xmlnsMain, 1); } elseif (($Style.Id -eq 'Footer') -or ($Style.Id -eq 'Header')) { ## Semi hide the styles named Footer and Header [ref] $null = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'semiHidden', $xmlnsMain)); } elseif (($document.TableStyles.Values | ForEach-Object { $_.HeaderStyle; $_.RowStyle; $_.AlternateRowStyle; }) -contains $Style.Id) { ## Semi hide styles behind table styles (except default style!) [ref] $null = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'semiHidden', $xmlnsMain)); } [ref] $null = $documentStyle.SetAttribute('styleId', $xmlnsMain, $styleId); $documentStyleName = $documentStyle.AppendChild($xmlDocument.CreateElement('w', 'name', $xmlnsMain)); [ref] $null = $documentStyleName.SetAttribute('val', $xmlnsMain, $styleName); $basedOn = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'basedOn', $xmlnsMain)); [ref] $null = $basedOn.SetAttribute('val', $XmlnsMain, 'Normal'); $link = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'link', $xmlnsMain)); [ref] $null = $link.SetAttribute('val', $XmlnsMain, $linkId); $next = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'next', $xmlnsMain)); [ref] $null = $next.SetAttribute('val', $xmlnsMain, 'Normal'); $qFormat = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'qFormat', $xmlnsMain)); $pPr = $documentStyle.AppendChild($XmlDocument.CreateElement('w', 'pPr', $xmlnsMain)); $keepNext = $pPr.AppendChild($XmlDocument.CreateElement('w', 'keepNext', $xmlnsMain)); $keepLines = $pPr.AppendChild($XmlDocument.CreateElement('w', 'keepLines', $xmlnsMain)); $spacing = $pPr.AppendChild($XmlDocument.CreateElement('w', 'spacing', $xmlnsMain)); [ref] $null = $spacing.SetAttribute('before', $xmlnsMain, 0); [ref] $null = $spacing.SetAttribute('after', $xmlnsMain, 0); ## Set the <w:jc> (justification) element $jc = $pPr.AppendChild($XmlDocument.CreateElement('w', 'jc', $xmlnsMain)); if ($Style.Align.ToLower() -eq 'justify') { [ref] $null = $jc.SetAttribute('val', $xmlnsMain, 'distribute'); } else { [ref] $null = $jc.SetAttribute('val', $xmlnsMain, $Style.Align.ToLower()); } if ($Style.BackgroundColor) { $shd = $pPr.AppendChild($XmlDocument.CreateElement('w', 'shd', $xmlnsMain)); [ref] $null = $shd.SetAttribute('val', $xmlnsMain, 'clear'); [ref] $null = $shd.SetAttribute('color', $xmlnsMain, 'auto'); [ref] $null = $shd.SetAttribute('fill', $xmlnsMain, (ConvertToWordColor -Color $Style.BackgroundColor)); } [ref] $null = $documentStyle.AppendChild((GetWordStyleRunPr -Style $Style -XmlDocument $XmlDocument)); return $documentStyle; } #end process } #end function GetWordStyle function GetWordTableStyle { <# .SYNOPSIS Generates Word Xml table style element from a PScribo document table style. #> [CmdletBinding()] param ( ## PScribo document style [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $TableStyle, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $style = $XmlDocument.CreateElement('w', 'style', $xmlnsMain); [ref] $null = $style.SetAttribute('type', $xmlnsMain, 'table'); [ref] $null = $style.SetAttribute('styleId', $xmlnsMain, $TableStyle.Id); $name = $style.AppendChild($XmlDocument.CreateElement('w', 'name', $xmlnsMain)); [ref] $null = $name.SetAttribute('val', $xmlnsMain, $TableStyle.Id); $tblPr = $style.AppendChild($XmlDocument.CreateElement('w', 'tblPr', $xmlnsMain)); $tblStyleRowBandSize = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblStyleRowBandSize', $xmlnsMain)); [ref] $null = $tblStyleRowBandSize.SetAttribute('val', $xmlnsMain, 1); if ($tableStyle.BorderWidth -gt 0) { $tblBorders = $tblPr.AppendChild($XmlDocument.CreateElement('w', 'tblBorders', $xmlnsMain)); foreach ($border in @('top','bottom','start','end','insideH','insideV')) { $b = $tblBorders.AppendChild($XmlDocument.CreateElement('w', $border, $xmlnsMain)); [ref] $null = $b.SetAttribute('sz', $xmlnsMain, (ConvertMmToOctips $tableStyle.BorderWidth)); [ref] $null = $b.SetAttribute('val', $xmlnsMain, 'single'); [ref] $null = $b.SetAttribute('color', $xmlnsMain, (ConvertToWordColor -Color $tableStyle.BorderColor)); } } [ref] $null = $style.AppendChild((GetWordTableStylePr -Style $Document.Styles[$TableStyle.HeaderStyle] -Type Header -XmlDocument $XmlDocument)); [ref] $null = $style.AppendChild((GetWordTableStylePr -Style $Document.Styles[$TableStyle.RowStyle] -Type Row -XmlDocument $XmlDocument)); [ref] $null = $style.AppendChild((GetWordTableStylePr -Style $Document.Styles[$TableStyle.AlternateRowStyle] -Type AlternateRow -XmlDocument $XmlDocument)); return $style; } } #end function GetWordTableStyle function GetWordStyleParagraphPr { <# .SYNOPSIS Generates Word paragraph (pPr) formatting properties #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory)] [System.Object] $Style, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $pPr = $XmlDocument.CreateElement('w', 'pPr', $xmlnsMain); $spacing = $pPr.AppendChild($XmlDocument.CreateElement('w', 'spacing', $xmlnsMain)); [ref] $null = $spacing.SetAttribute('before', $xmlnsMain, 0); [ref] $null = $spacing.SetAttribute('after', $xmlnsMain, 0); $keepNext = $pPr.AppendChild($XmlDocument.CreateElement('w', 'keepNext', $xmlnsMain)); $keepLines = $pPr.AppendChild($XmlDocument.CreateElement('w', 'keepLines', $xmlnsMain)); $jc = $pPr.AppendChild($XmlDocument.CreateElement('w', 'jc', $xmlnsMain)); if ($Style.Align.ToLower() -eq 'justify') { [ref] $null = $jc.SetAttribute('val', $xmlnsMain, 'distribute'); } else { [ref] $null = $jc.SetAttribute('val', $xmlnsMain, $Style.Align.ToLower()); } return $pPr; } #end process } #end function GetWordTableCellPr function GetWordStyleRunPrColor { <# .SYNOPSIS Generates Word run (rPr) text colour formatting property only. .NOTES This is only required to override the text colour in table rows/headers as I can't get this (yet) applied via the table style? #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory)] [System.Object] $Style, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $rPr = $XmlDocument.CreateElement('w', 'rPr', $xmlnsMain); $color = $rPr.AppendChild($XmlDocument.CreateElement('w', 'color', $xmlnsMain)); [ref] $null = $color.SetAttribute('val', $xmlnsMain, (ConvertToWordColor -Color $Style.Color)); return $rPr; } } #end function GetWordStyleRunPrColor function GetWordStyleRunPr { <# .SYNOPSIS Generates Word run (rPr) formatting properties #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory)] [System.Object] $Style, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $rPr = $XmlDocument.CreateElement('w', 'rPr', $xmlnsMain); $rFonts = $rPr.AppendChild($XmlDocument.CreateElement('w', 'rFonts', $xmlnsMain)); [ref] $null = $rFonts.SetAttribute('ascii', $xmlnsMain, $Style.Font[0]); [ref] $null = $rFonts.SetAttribute('hAnsi', $xmlnsMain, $Style.Font[0]); if ($Style.Bold) { [ref] $null = $rPr.AppendChild($XmlDocument.CreateElement('w', 'b', $xmlnsMain)); } if ($Style.Underline) { [ref] $null = $rPr.AppendChild($XmlDocument.CreateElement('w', 'u', $xmlnsMain)); } if ($Style.Italic) { [ref] $null = $rPr.AppendChild($XmlDocument.CreateElement('w', 'i', $xmlnsMain)); } $color = $rPr.AppendChild($XmlDocument.CreateElement('w', 'color', $xmlnsMain)); [ref] $null = $color.SetAttribute('val', $xmlnsMain, (ConvertToWordColor -Color $Style.Color)); $sz = $rPr.AppendChild($XmlDocument.CreateElement('w', 'sz', $xmlnsMain)); [ref] $null = $sz.SetAttribute('val', $xmlnsMain, $Style.Size * 2); return $rPr; } #end process } #end function GetWordStyleRunPr function GetWordTableStyleCellPr { <# .SYNOPSIS Generates Word table cell (tcPr) formatting properties #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory)] [System.Object] $Style, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $tcPr = $XmlDocument.CreateElement('w', 'tcPr', $xmlnsMain); if ($Style.BackgroundColor) { $shd = $tcPr.AppendChild($XmlDocument.CreateElement('w', 'shd', $xmlnsMain)); [ref] $null = $shd.SetAttribute('val', $xmlnsMain, 'clear'); [ref] $null = $shd.SetAttribute('color', $xmlnsMain, 'auto'); [ref] $null = $shd.SetAttribute('fill', $xmlnsMain, (ConvertToWordColor -Color $Style.BackgroundColor)); } return $tcPr; } #end process } #end function GetWordTableCellPr function GetWordTableStylePr { <# .SYNOPSIS Generates Word table style (tblStylePr) formatting properties for specified table style type #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory)] [System.Object] $Style, [Parameter(Mandatory)] [ValidateSet('Header','Row','AlternateRow')] [System.String] $Type, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $tblStylePr = $XmlDocument.CreateElement('w', 'tblStylePr', $xmlnsMain); $tblPr = $tblStylePr.AppendChild($XmlDocument.CreateElement('w', 'tblPr', $xmlnsMain)); switch ($Type) { 'Header' { $tblStylePrType = 'firstRow'; } 'Row' { $tblStylePrType = 'band2Horz'; } 'AlternateRow' { $tblStylePrType = 'band1Horz'; } } [ref] $null = $tblStylePr.SetAttribute('type', $xmlnsMain, $tblStylePrType); [ref] $null = $tblStylePr.AppendChild((GetWordStyleParagraphPr -Style $Style -XmlDocument $XmlDocument)); [ref] $null = $tblStylePr.AppendChild((GetWordStyleRunPr -Style $Style -XmlDocument $XmlDocument)); [ref] $null = $tblStylePr.AppendChild((GetWordTableStyleCellPr -Style $Style -XmlDocument $XmlDocument)); return $tblStylePr; } #end process } #end function GetWordTableStylePr function GetWordSectionPr { <# .SYNOPSIS Outputs Office Open XML section element to set page size and margins. #> [CmdletBinding()] [OutputType([System.Xml.XmlElement])] param ( [Parameter(Mandatory)] [System.Single] $PageWidth, [Parameter(Mandatory)] [System.Single] $PageHeight, [Parameter(Mandatory)] [System.Single] $PageMarginTop, [Parameter(Mandatory)] [System.Single] $PageMarginLeft, [Parameter(Mandatory)] [System.Single] $PageMarginBottom, [Parameter(Mandatory)] [System.Single] $PageMarginRight, [Parameter(Mandatory)] [System.Xml.XmlDocument] $XmlDocument ) process { $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $sectPr = $XmlDocument.CreateElement('w', 'sectPr', $xmlnsMain); $pgSz = $sectPr.AppendChild($XmlDocument.CreateElement('w', 'pgSz', $xmlnsMain)); [ref] $null = $pgSz.SetAttribute('w', $xmlnsMain, (ConvertMmToTwips -Millimeter $PageWidth)); [ref] $null = $pgSz.SetAttribute('h', $xmlnsMain, (ConvertMmToTwips -Millimeter $PageHeight)); [ref] $null = $pgSz.SetAttribute('orient', $xmlnsMain, 'portrait'); $pgMar = $sectPr.AppendChild($XmlDocument.CreateElement('w', 'pgMar', $xmlnsMain)); [ref] $null = $pgMar.SetAttribute('top', $xmlnsMain, (ConvertMmToTwips -Millimeter $PageMarginTop)); [ref] $null = $pgMar.SetAttribute('bottom', $xmlnsMain, (ConvertMmToTwips -Millimeter $PageMarginBottom)); [ref] $null = $pgMar.SetAttribute('left', $xmlnsMain, (ConvertMmToTwips -Millimeter $PageMarginLeft)); [ref] $null = $pgMar.SetAttribute('right', $xmlnsMain, (ConvertMmToTwips -Millimeter $PageMarginRight)); return $sectPr; } #end process } #end GetWordSectionPr function OutWordStylesDocument { <# .SYNOPSIS Outputs Office Open XML style document #> [CmdletBinding()] [OutputType([System.Xml.XmlDocument])] param ( ## PScribo document styles [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] $Styles, ## PScribo document tables styles [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] $TableStyles ) process { ## Create the Style.xml document $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $xmlDocument = New-Object -TypeName 'System.Xml.XmlDocument'; [ref] $null = $xmlDocument.AppendChild($xmlDocument.CreateXmlDeclaration('1.0', 'utf-8', 'yes')); $documentStyles = $xmlDocument.AppendChild($xmlDocument.CreateElement('w', 'styles', $xmlnsMain)); ## Create default style $defaultStyle = $documentStyles.AppendChild($xmlDocument.CreateElement('w', 'style', $xmlnsMain)); [ref] $null = $defaultStyle.SetAttribute('type', $xmlnsMain, 'paragraph'); [ref] $null = $defaultStyle.SetAttribute('default', $xmlnsMain, '1'); [ref] $null = $defaultStyle.SetAttribute('styleId', $xmlnsMain, 'Normal'); $defaultStyleName = $defaultStyle.AppendChild($xmlDocument.CreateElement('w', 'name', $xmlnsMain)); [ref] $null = $defaultStyleName.SetAttribute('val', $xmlnsMain, 'Normal'); [ref] $null = $defaultStyle.AppendChild($xmlDocument.CreateElement('w', 'qFormat', $xmlnsMain)); foreach ($style in $Styles.Values) { $documentParagraphStyle = GetWordStyle -Style $style -XmlDocument $xmlDocument -Type Paragraph; [ref] $null = $documentStyles.AppendChild($documentParagraphStyle); $documentCharacterStyle = GetWordStyle -Style $style -XmlDocument $xmlDocument -Type Character; [ref] $null = $documentStyles.AppendChild($documentCharacterStyle); } foreach ($tableStyle in $TableStyles.Values) { $documentTableStyle = GetWordTableStyle -TableStyle $tableStyle -XmlDocument $xmlDocument; [ref] $null = $documentStyles.AppendChild($documentTableStyle); } return $xmlDocument; } #end process } #end function OutWordStyleDocument function OutWordSettingsDocument { <# .SYNOPSIS Outputs Office Open XML settings document #> [CmdletBinding()] [OutputType([System.Xml.XmlDocument])] param ( [Parameter()] [System.Management.Automation.SwitchParameter] $UpdateFields ) process { ## Create the Style.xml document $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; # <w:settings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" # xmlns:o="urn:schemas-microsoft-com:office:office" # xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" # xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" # xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" # xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" # xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" # xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" # xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" # mc:Ignorable="w14 w15"> $settingsDocument = New-Object -TypeName 'System.Xml.XmlDocument'; [ref] $null = $settingsDocument.AppendChild($settingsDocument.CreateXmlDeclaration('1.0', 'utf-8', 'yes')); $settings = $settingsDocument.AppendChild($settingsDocument.CreateElement('w', 'settings', $xmlnsMain)); ## Set compatibility mode to Word 2013 $compat = $settings.AppendChild($settingsDocument.CreateElement('w', 'compat', $xmlnsMain)); $compatSetting = $compat.AppendChild($settingsDocument.CreateElement('w', 'compatSetting', $xmlnsMain)); [ref] $null = $compatSetting.SetAttribute('name', $xmlnsMain, 'compatibilityMode'); [ref] $null = $compatSetting.SetAttribute('uri', $xmlnsMain, 'http://schemas.microsoft.com/office/word'); [ref] $null = $compatSetting.SetAttribute('val', $xmlnsMain, 15); if ($UpdateFields) { $wupdateFields = $settings.AppendChild($settingsDocument.CreateElement('w', 'updateFields', $xmlnsMain)); [ref] $null = $wupdateFields.SetAttribute('val', $xmlnsMain, 'true'); } return $settingsDocument; } #end process } #end function OutWordSettingsDocument #endregion OutWord Private Functions } process { $stopwatch = [Diagnostics.Stopwatch]::StartNew(); WriteLog -Message ($localized.DocumentProcessingStarted -f $Document.Name); $xmlnsMain = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; $xmlDocument = New-Object -TypeName 'System.Xml.XmlDocument'; [ref] $null = $xmlDocument.AppendChild($xmlDocument.CreateXmlDeclaration('1.0', 'utf-8', 'yes')); $documentXml = $xmlDocument.AppendChild($xmlDocument.CreateElement('w', 'document', $xmlnsMain)); [ref] $null = $xmlDocument.DocumentElement.SetAttribute('xmlns:xml', 'http://www.w3.org/XML/1998/namespace'); $body = $documentXml.AppendChild($xmlDocument.CreateElement('w', 'body', $xmlnsMain)); ## Setup the document page size/margins $sectionPrParams = @{ PageHeight = $Document.Options['PageHeight']; PageWidth = $Document.Options['PageWidth']; PageMarginTop = $Document.Options['MarginTop']; PageMarginBottom = $Document.Options['MarginBottom']; PageMarginLeft = $Document.Options['MarginLeft']; PageMarginRight = $Document.Options['MarginRight']; } [ref] $null = $body.AppendChild((GetWordSectionPr @sectionPrParams -XmlDocument $xmlDocument)); foreach ($s in $Document.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}[..]' -f $s.Id.Substring(0,36); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { $s | OutWordSection -RootElement $body -XmlDocument $xmlDocument; } 'PScribo.Paragraph' { [ref] $null = $body.AppendChild((OutWordParagraph -Paragraph $s -XmlDocument $xmlDocument)); } 'PScribo.PageBreak' { [ref] $null = $body.AppendChild((OutWordPageBreak -PageBreak $s -XmlDocument $xmlDocument)); } 'PScribo.LineBreak' { [ref] $null = $body.AppendChild((OutWordLineBreak -LineBreak $s -XmlDocument $xmlDocument)); } 'PScribo.Table' { OutWordTable -Table $s -XmlDocument $xmlDocument -Element $body; } 'PScribo.TOC' { [ref] $null = $body.AppendChild((OutWordTOC -TOC $s -XmlDocument $xmlDocument)); } 'PScribo.BlankLine' { OutWordBlankLine -BlankLine $s -XmlDocument $xmlDocument -Element $body; } Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach ## Generate the Word 'styles.xml' document part $stylesXml = OutWordStylesDocument -Styles $Document.Styles -TableStyles $Document.TableStyles; ## Generate the Word 'settings.xml' document part if (($Document.Properties['TOCs']) -and ($Document.Properties['TOCs'] -gt 0)) { ## We have a TOC so flag to update the document when opened $settingsXml = OutWordSettingsDocument -UpdateFields; } else { $settingsXml = OutWordSettingsDocument; } $destinationPath = Join-Path -Path $Path ('{0}.docx' -f $Document.Name); Add-Type -AssemblyName WindowsBase; try { $package = [System.IO.Packaging.Package]::Open($destinationPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite); } catch { WriteLog -Message ($localized.OpenPackageError -f $destinationPath) -IsWarning; throw $_; } ## Create document.xml part $documentUri = New-Object System.Uri('/word/document.xml', [System.UriKind]::Relative); WriteLog -Message ($localized.ProcessingDocumentPart -f $documentUri); $documentPart = $package.CreatePart($documentUri, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml'); $streamWriter = New-Object System.IO.StreamWriter($documentPart.GetStream([System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite)); $xmlWriter = [System.Xml.XmlWriter]::Create($streamWriter); WriteLog -Message ($localized.WritingDocumentPart -f $documentUri); $xmlDocument.Save($xmlWriter); $xmlWriter.Close(); $streamWriter.Close(); ## Create styles.xml part $stylesUri = New-Object System.Uri('/word/styles.xml', [System.UriKind]::Relative); WriteLog -Message ($localized.ProcessingDocumentPart -f $stylesUri); $stylesPart = $package.CreatePart($stylesUri, 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml'); $streamWriter = New-Object System.IO.StreamWriter($stylesPart.GetStream([System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite)); $xmlWriter = [System.Xml.XmlWriter]::Create($streamWriter); WriteLog -Message ($localized.WritingDocumentPart -f $stylesUri); $stylesXml.Save($xmlWriter); $xmlWriter.Close(); $streamWriter.Close(); ## Create settings.xml part $settingsUri = New-Object System.Uri('/word/settings.xml', [System.UriKind]::Relative); WriteLog -Message ($localized.ProcessingDocumentPart -f $settingsUri); $settingsPart = $package.CreatePart($settingsUri, 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml'); $streamWriter = New-Object System.IO.StreamWriter($settingsPart.GetStream([System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite)); $xmlWriter = [System.Xml.XmlWriter]::Create($streamWriter); WriteLog -Message ($localized.WritingDocumentPart -f $settingsUri); $settingsXml.Save($xmlWriter); $xmlWriter.Close(); $streamWriter.Close(); ## Create the Package relationships WriteLog -Message $localized.GeneratingPackageRelationships; [ref] $null = $package.CreateRelationship($documentUri, [System.IO.Packaging.TargetMode]::Internal, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', 'rId1'); [ref] $null = $documentPart.CreateRelationship($stylesUri, [System.IO.Packaging.TargetMode]::Internal, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', 'rId1'); [ref] $null = $documentPart.CreateRelationship($settingsUri, [System.IO.Packaging.TargetMode]::Internal, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings', 'rId2'); WriteLog -Message ($localized.SavingFile -f $destinationPath); $package.Flush(); $package.Close(); $stopwatch.Stop(); WriteLog -Message ($localized.DocumentProcessingCompleted -f $Document.Name); WriteLog -Message ($localized.TotalProcessingTime -f $stopwatch.Elapsed.TotalSeconds); ## Return the file reference to the pipeline Write-Output (Get-Item -Path $destinationPath); } #end process } #end function OutWord function OutXml { <# .SYNOPSIS Xml output plugin for PScribo. .DESCRIPTION Outputs a xml representation of a PScribo document object. #> [CmdletBinding()] param ( ## ThePScribo document object to convert to a xml document [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Document, ## Output directory path for the .xml file [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.String] $Path, ### Hashtable of all plugin supported options [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.Collections.Hashtable] $Options ) begin { #region OutXml Private Functions function OutXmlSection { <# .SYNOPSIS Output formatted Xml section. #> [CmdletBinding()] param ( ## PScribo document section [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $Section ) process { $sectionId = ($Section.Id -replace '[^a-z0-9-_\.]','').ToLower(); $element = $xmlDocument.CreateElement($sectionId); [ref] $null = $element.SetAttribute("name", $Section.Name); foreach ($s in $Section.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}..' -f $s.Id.Substring(0,38); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { [ref] $null = $element.AppendChild((OutXmlSection -Section $s)); } 'PScribo.Paragraph' { [ref] $null = $element.AppendChild((OutXmlParagraph -Paragraph $s)); } 'PScribo.Table' { [ref] $null = $element.AppendChild((OutXmlTable -Table $s)); } 'PScribo.PageBreak' { } ## Page breaks are not implemented for Xml output 'PScribo.LineBreak' { } ## Line breaks are not implemented for Xml output 'PScribo.BlankLine' { } ## Blank lines are not implemented for Xml output 'PScribo.TOC' { } ## TOC is not implemented for Xml output Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach return $element; } #end process } #end function outxmlsection function OutXmlParagraph { <# .SYNOPSIS Output formatted Xml paragraph. #> [CmdletBinding()] param ( ## PScribo paragraph object [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Paragraph ) process { if (-not ([string]::IsNullOrEmpty($Paragraph.Value))) { ## Value override specified $paragraphId = ($Paragraph.Id -replace '[^a-z0-9-_\.]','').ToLower(); $paragraphElement = $xmlDocument.CreateElement($paragraphId); [ref] $null = $paragraphElement.AppendChild($xmlDocument.CreateTextNode($Paragraph.Value)); } #end if elseif ([string]::IsNullOrEmpty($Paragraph.Text)) { ## No Id/Name specified, therefore insert as a comment $paragraphElement = $xmlDocument.CreateComment((' {0} ' -f $Paragraph.Id)); } #end elseif else { ## Create an element with the Id/Name $paragraphId = ($Paragraph.Id -replace '[^a-z0-9-_\.]','').ToLower(); $paragraphElement = $xmlDocument.CreateElement($paragraphId); [ref] $null = $paragraphElement.AppendChild($xmlDocument.CreateTextNode($Paragraph.Text)); } #end else return $paragraphElement; } #end process } #end function outxmlparagraph function OutXmlTable { <# .SYNOPSIS Output formatted Xml table. #> [CmdletBinding()] param ( ## PScribo table object [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Table ) process { $tableId = ($Table.Id -replace '[^a-z0-9-_\.]','').ToLower(); $tableElement = $element.AppendChild($xmlDocument.CreateElement($tableId)); [ref] $null = $tableElement.SetAttribute('name', $Table.Name); foreach ($row in $Table.Rows) { $groupElement = $tableElement.AppendChild($xmlDocument.CreateElement('group')); foreach ($property in $row.PSObject.Properties) { if (-not ($property.Name).EndsWith('__Style')) { $propertyId = ($property.Name -replace '[^a-z0-9-_\.]','').ToLower(); $rowElement = $groupElement.AppendChild($xmlDocument.CreateElement($propertyId)); ## Only add the Name attribute if there's a difference if ($property.Name -ne $propertyId) { [ref] $null = $rowElement.SetAttribute('name', $property.Name); } [ref] $null = $rowElement.AppendChild($xmlDocument.CreateTextNode($row.($property.Name))); } #end if } #end foreach property } #end foreach row return $tableElement; } #end process } #end outxmltable #endregion OutXml Private Functions } process { $pluginName = 'Xml'; $stopwatch = [System.Diagnostics.Stopwatch]::StartNew(); WriteLog -Message ($localized.DocumentProcessingStarted -f $Document.Name); $documentName = $Document.Name; $xmlDocument = New-Object -TypeName System.Xml.XmlDocument; [ref] $null = $xmlDocument.AppendChild($xmlDocument.CreateXmlDeclaration('1.0', 'utf-8', 'yes')); $documentId = ($Document.Id -replace '[^a-z0-9-_\.]','').ToLower(); $element = $xmlDocument.AppendChild($xmlDocument.CreateElement($documentId)); [ref] $null = $element.SetAttribute("name", $documentName); foreach ($s in $Document.Sections.GetEnumerator()) { if ($s.Id.Length -gt 40) { $sectionId = '{0}[..]' -f $s.Id.Substring(0,36); } else { $sectionId = $s.Id; } $currentIndentationLevel = 1; if ($null -ne $s.PSObject.Properties['Level']) { $currentIndentationLevel = $s.Level +1; } WriteLog -Message ($localized.PluginProcessingSection -f $s.Type, $sectionId) -Indent $currentIndentationLevel; switch ($s.Type) { 'PScribo.Section' { [ref] $null = $element.AppendChild((OutXmlSection -Section $s)); } 'PScribo.Paragraph' { [ref] $null = $element.AppendChild((OutXmlParagraph -Paragraph $s)); } 'PScribo.Table' { [ref] $null = $element.AppendChild((OutXmlTable -Table $s)); } 'PScribo.PageBreak'{ } ## Page breaks are not implemented for Xml output 'PScribo.LineBreak' { } ## Line breaks are not implemented for Xml output 'PScribo.BlankLine' { } ## Blank lines are not implemented for Xml output 'PScribo.TOC' { } ## TOC is not implemented for Xml output Default { WriteLog -Message ($localized.PluginUnsupportedSection -f $s.Type) -IsWarning; } } #end switch } #end foreach $stopwatch.Stop(); WriteLog -Message ($localized.DocumentProcessingCompleted -f $Document.Name); $destinationPath = Join-Path $Path ('{0}.xml' -f $Document.Name); WriteLog -Message ($localized.SavingFile -f $destinationPath); $xmlDocument.Save($destinationPath); WriteLog -Message ($localized.TotalProcessingTime -f $stopwatch.Elapsed.TotalSeconds); ## Return the file reference to the pipeline Write-Output (Get-Item -Path $destinationPath); } #end process } #end function outxml #endregion PScribo Bundle v0.7.6.7 |