Src/Private/Format-HtmlCell.ps1

function Format-HtmlCell {
    <#
    .SYNOPSIS
        Dynamically builds Graphviz HTML table cell (<TR><TD>) markup from structured input or an image source.
 
    .DESCRIPTION
        Accepts a Hashtable, OrderedDictionary, PSCustomObject, or string array and converts
        each entry into one or more <TR><TD>...</TD></TR> rows. The resulting string is ready
        to be passed directly to Format-HtmlTable as the -TableRowContent parameter, or
        embedded in any Graphviz HTML label.
 
        Input type behaviour (Text parameter set):
          - Hashtable / OrderedDictionary : each key-value pair becomes one row rendered as "Key: Value".
          - PSCustomObject : each property becomes one row rendered as "Name: Value".
          - string[] : each string becomes one single-cell row.
 
        When the Image parameter set is used (i.e. -ImageSrc is supplied), the function produces
        a fixed-size or auto-sized image cell:
          '<TR><TD STYLE="..." ALIGN="..." fixedsize="true" width="..." height="..." colspan="1"><img src="..."/></TD></TR>'
        or, without -FixedSize:
          '<TR><TD STYLE="..." ALIGN="..." colspan="1"><img src="..."/></TD></TR>'
 
    .EXAMPLE
        # Build cell markup from a hashtable and wrap it in a table
        $cells = Format-HtmlCell -Rows @{ OS = 'Windows'; Version = '2019' }
        $table = Format-HtmlTable -TableRowContent $cells
        Node 'MyServer' @{ label = $table; shape = 'plain' }
 
    .EXAMPLE
        # Build cell markup from a PSCustomObject
        $info = [PSCustomObject][ordered]@{ CPU = '8 vCPU'; RAM = '32 GB' }
        $cells = Format-HtmlCell -Rows $info -FontBold -CellBackgroundColor '#EEF2FF'
        Format-HtmlTable -TableRowContent $cells
 
    .EXAMPLE
        # Build a two-column flat list from a string array
        Format-HtmlCell -Rows @('192.168.1.0/24', '10.0.0.0/8') -Align 'Left' -FontSize 12
 
    .EXAMPLE
        # Build an auto-sized image cell
        Format-HtmlCell -ImageSrc '/icons/server.png' -CellStyle SOLID
 
    .EXAMPLE
        # Build a fixed-size image cell
        Format-HtmlCell -ImageSrc '/icons/server.png' -CellStyle ROUNDED -FixedSize -Width 48 -Height 48
 
    .NOTES
        Version: 0.2.0
        Author: Jonathan Colon
        Bluesky: @jcolonfpr.bsky.social
        Github: rebelinux
 
    .PARAMETER Rows
        The input data to convert into table cells. Accepts Hashtable, OrderedDictionary,
        PSCustomObject, or a string array. Used in the Text parameter set.
 
    .PARAMETER ImageSrc
        Path to the image file to embed as an <img> element inside the TD cell.
        Selects the Image parameter set.
 
    .PARAMETER CellStyle
        STYLE attribute applied to the TD cell when using the Image parameter set.
        Accepted values: ROUNDED, RADIAL, SOLID, INVISIBLE, INVIS, DOTTED, DASHED.
        Default is 'SOLID'.
 
    .PARAMETER FixedSize
        When set, applies fixedsize="true" together with -Width and -Height to the TD cell.
        Only valid in the Image parameter set.
 
    .PARAMETER Width
        Cell width in pixels. Requires -FixedSize. Only valid in the Image parameter set.
 
    .PARAMETER Height
        Cell height in pixels. Requires -FixedSize. Only valid in the Image parameter set.
 
    .PARAMETER Align
        Horizontal alignment of cell content. Accepted values: 'Center', 'Left', 'Right'.
        Default is 'Center'.
 
    .PARAMETER CellBackgroundColor
        Background color of each <TD> cell (hex or named color). Default is '#FFFFFF'.
        Only used in the Text parameter set.
 
    .PARAMETER CellBorder
        Width of the HTML cell border. Default is 0. Only used in the Text parameter set.
 
    .PARAMETER CellPadding
        Padding inside each cell. Default is 5. Only used in the Text parameter set.
 
    .PARAMETER ColSpan
        Number of columns each cell should span. Default is 1.
 
    .PARAMETER FontBold
        Renders cell text in bold. Only used in the Text parameter set.
 
    .PARAMETER FontColor
        Font color (hex or named color). Default is '#000000'. Only used in the Text parameter set.
 
    .PARAMETER FontItalic
        Renders cell text in italic. Only used in the Text parameter set.
 
    .PARAMETER FontName
        Font face name. Default is 'Segoe Ui'. Only used in the Text parameter set.
 
    .PARAMETER FontOverline
        Renders cell text with an overline. Only used in the Text parameter set.
 
    .PARAMETER FontSize
        Font size in points. Default is 14. Only used in the Text parameter set.
 
    .PARAMETER FontStrikeThrough
        Renders cell text with a strikethrough. Only used in the Text parameter set.
 
    .PARAMETER FontSubscript
        Renders cell text as subscript. Only used in the Text parameter set.
 
    .PARAMETER FontSuperscript
        Renders cell text as superscript. Only used in the Text parameter set.
 
    .PARAMETER FontUnderline
        Renders cell text with underline. Only used in the Text parameter set.
 
    .PARAMETER IconDebug
        When $true, highlights each cell with a red background (Text) or renders the image
        source path as plain text (Image) for layout debugging. Aliased as DraftMode.
 
    .PARAMETER Port
        Graphviz port name attached to each cell via the PORT attribute. Default is 'EdgeDot'.
        Only used in the Text parameter set.
    #>


    [CmdletBinding(DefaultParameterSetName = 'Text')]
    [OutputType([System.String])]
    param(
        [Parameter(
            ParameterSetName = 'Text',
            Mandatory,
            HelpMessage = 'Input data: Hashtable, OrderedDictionary, PSCustomObject, or string array.'
        )]
        [Alias('InputObject')]
        $Rows,

        [Parameter(
            ParameterSetName = 'Image',
            Mandatory,
            HelpMessage = 'Path to the image file to embed as an <img> element.'
        )]
        [string] $ImageSrc,

        [Parameter(
            ParameterSetName = 'Image',
            Mandatory = $false,
            HelpMessage = 'STYLE attribute applied to the TD cell.'
        )]
        [ValidateSet('ROUNDED', 'RADIAL', 'SOLID', 'INVISIBLE', 'INVIS', 'DOTTED', 'DASHED')]
        [string] $CellStyle = 'SOLID',

        [Parameter(
            ParameterSetName = 'Image',
            Mandatory = $false,
            HelpMessage = 'Applies fixedsize="true" with the given Width and Height to the TD cell.'
        )]
        [switch] $FixedSize,

        [Parameter(
            ParameterSetName = 'Image',
            Mandatory = $false,
            HelpMessage = 'Cell width in pixels. Requires -FixedSize.'
        )]
        [int] $Width,

        [Parameter(
            ParameterSetName = 'Image',
            Mandatory = $false,
            HelpMessage = 'Cell height in pixels. Requires -FixedSize.'
        )]
        [int] $Height,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Horizontal alignment of cell content.'
        )]
        [ValidateSet('Center', 'Left', 'Right')]
        [string] $Align = 'Center',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Background color of each TD cell.'
        )]
        [string] $CellBackgroundColor = '#FFFFFF',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Width of the HTML cell border.'
        )]
        [int] $CellBorder = 0,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Padding inside each cell.'
        )]
        [int] $CellPadding = 5,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Number of columns each cell should span.'
        )]
        [int] $ColSpan = 1,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text in bold.'
        )]
        [switch] $FontBold,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Font color (hex or named color).'
        )]
        [string] $FontColor = '#000000',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text in italic.'
        )]
        [switch] $FontItalic,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Font face name.'
        )]
        [string] $FontName = 'Segoe Ui',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text with an overline.'
        )]
        [switch] $FontOverline,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Font size in points.'
        )]
        [int] $FontSize = 14,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text with a strikethrough.'
        )]
        [switch] $FontStrikeThrough,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text as subscript.'
        )]
        [switch] $FontSubscript,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text as superscript.'
        )]
        [switch] $FontSuperscript,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Renders cell text with underline.'
        )]
        [switch] $FontUnderline,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Highlights cells in red for layout debugging.'
        )]
        [Alias('DraftMode')]
        [bool] $IconDebug = $false,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Graphviz port name attached to each cell via the PORT attribute.'
        )]
        [string] $Port = 'EdgeDot'
    )

    $debugBg = if ($IconDebug) { '#FFCCCC' } else { $CellBackgroundColor }

    $tr = [System.Text.StringBuilder]::new()

    # Build a single <TR><TD>...</TD></TR> string using concatenation to avoid any
    # ambiguity between HTML attribute quotes and PowerShell's -f format operator.
    function buildTD ([string]$tdPort, [string]$tdBg, [string]$tdAlign, [int]$tdSpan, [int]$tdPad, [int]$tdBorder, [string]$tdContent) {
        return '<TR><TD PORT="' + $tdPort + '" BGCOLOR="' + $tdBg + '" ALIGN="' + $tdAlign + '" COLSPAN="' + $tdSpan + '" CELLPADDING="' + $tdPad + '" BORDER="' + $tdBorder + '">' + $tdContent + '</TD></TR>'
    }

    # Build an image <TR><TD><img .../></TD></TR> cell.
    function buildImgTD ([string]$tdStyle, [string]$tdAlign, [int]$tdSpan, [string]$tdSrc, [bool]$fixed, [int]$w, [int]$h) {
        if ($fixed) {
            return '<TR><TD STYLE="' + $tdStyle + '" ALIGN="' + $tdAlign + '" fixedsize="true" width="' + $w + '" height="' + $h + '" colspan="' + $tdSpan + '"><img src="' + $tdSrc + '"/></TD></TR>'
        } else {
            return '<TR><TD STYLE="' + $tdStyle + '" ALIGN="' + $tdAlign + '" colspan="' + $tdSpan + '"><img src="' + $tdSrc + '"/></TD></TR>'
        }
    }

    # Image parameter set: emit a single image cell and return early.
    if ($PSCmdlet.ParameterSetName -eq 'Image') {
        if ($IconDebug) {
            [void]$tr.Append('<TR><TD STYLE="SOLID" ALIGN="' + $Align + '" colspan="' + $ColSpan + '">' + $ImageSrc + '</TD></TR>')
        } else {
            [void]$tr.Append((buildImgTD $CellStyle $Align $ColSpan $ImageSrc ([bool]$FixedSize) $Width $Height))
        }
        return $tr.ToString()
    }

    switch ($Rows.GetType().Name) {
        'Hashtable' {
            foreach ($entry in $Rows.GetEnumerator()) {
                $label = $entry.Key + ': ' + $entry.Value
                $text = Format-HtmlFontProperty -Text $label -FontSize $FontSize -FontColor $FontColor -FontName $FontName -FontBold:$FontBold -FontItalic:$FontItalic -FontUnderline:$FontUnderline -FontOverline:$FontOverline -FontSubscript:$FontSubscript -FontSuperscript:$FontSuperscript -FontStrikeThrough:$FontStrikeThrough
                [void]$tr.Append((buildTD $entry.Key $debugBg $Align $ColSpan $CellPadding $CellBorder $text))
            }
        }

        'OrderedDictionary' {
            foreach ($entry in $Rows.GetEnumerator()) {
                $label = $entry.Key + ': ' + $entry.Value
                $text = Format-HtmlFontProperty -Text $label -FontSize $FontSize -FontColor $FontColor -FontName $FontName -FontBold:$FontBold -FontItalic:$FontItalic -FontUnderline:$FontUnderline -FontOverline:$FontOverline -FontSubscript:$FontSubscript -FontSuperscript:$FontSuperscript -FontStrikeThrough:$FontStrikeThrough
                [void]$tr.Append((buildTD $entry.Key $debugBg $Align $ColSpan $CellPadding $CellBorder $text))
            }
        }

        'PSCustomObject' {
            foreach ($prop in $Rows.PSObject.Properties) {
                $label = $prop.Name + ': ' + $prop.Value
                $text = Format-HtmlFontProperty -Text $label -FontSize $FontSize -FontColor $FontColor -FontName $FontName -FontBold:$FontBold -FontItalic:$FontItalic -FontUnderline:$FontUnderline -FontOverline:$FontOverline -FontSubscript:$FontSubscript -FontSuperscript:$FontSuperscript -FontStrikeThrough:$FontStrikeThrough
                [void]$tr.Append((buildTD $prop.Name $debugBg $Align $ColSpan $CellPadding $CellBorder $text))
            }
        }

        default {
            # string[] or any other enumerable
            foreach ($item in @($Rows)) {
                $text = Format-HtmlFontProperty -Text ([string]$item) -FontSize $FontSize -FontColor $FontColor -FontName $FontName -FontBold:$FontBold -FontItalic:$FontItalic -FontUnderline:$FontUnderline -FontOverline:$FontOverline -FontSubscript:$FontSubscript -FontSuperscript:$FontSuperscript -FontStrikeThrough:$FontStrikeThrough
                [void]$tr.Append((buildTD $Port $debugBg $Align $ColSpan $CellPadding $CellBorder $text))
            }
        }
    }

    return $tr.ToString()
}