Public/ConvertTo-ConfluenceHTML.ps1
|
function ConvertTo-ConfluenceHTML { <# .SYNOPSIS Converts simple Markdown to HTML for a Confluence page body. .DESCRIPTION ConvertTo-ConfluenceHTML performs a lightweight, regex-based Markdown conversion. Supported: headings (# to ######), **bold**, *italic*, "- " bullet lists, fenced code blocks (```), which may span several lines and whose content is HTML-encoded and left untouched by the other rules, and table rows (| a | b |), each converted to a single-cell table row. It is not a full Markdown parser; nested lists, links, images and inline code are left as they are. .PARAMETER InputContent The Markdown text to convert. .PARAMETER InputFormat The format of the input. Only Markdown is supported. .EXAMPLE ConvertTo-ConfluenceHTML -InputFormat Markdown -InputContent "# Title`n- one`n- two" Returns <h1>Title</h1> followed by <ul><li>one</li><li>two</li></ul>. .OUTPUTS System.String #> [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true, HelpMessage = 'The input content to be converted.')] [AllowEmptyString()] [string]$InputContent, [Parameter(Mandatory = $true, HelpMessage = "The format of the input content. Currently supported: 'Markdown'.")] [ValidateSet('Markdown')] [string]$InputFormat ) $TelemetryArgs = @{ ModuleName = $MyInvocation.MyCommand.Module.Name ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version CommandName = $MyInvocation.MyCommand.Name ExecutionID = [guid]::NewGuid().ToString() } Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer $telemetryFailed = $false try { switch ($InputFormat) { 'Markdown' { $HtmlContent = $InputContent -replace "`r`n", "`n" # Fenced code blocks first (they may span lines); keep them out of the other rules $codeBlocks = New-Object -TypeName System.Collections.Generic.List[string] $codeMatches = [regex]::Matches($HtmlContent, '(?s)```[^\n`]*\n?(.*?)```') for ($index = $codeMatches.Count - 1; $index -ge 0; $index--) { $match = $codeMatches[$index] $code = [System.Net.WebUtility]::HtmlEncode($match.Groups[1].Value.TrimEnd("`n")) $codeBlocks.Insert(0, "<pre><code>$code</code></pre>") $HtmlContent = $HtmlContent.Remove($match.Index, $match.Length).Insert($match.Index, "@@CODEBLOCK$index@@") } # Headings $HtmlContent = $HtmlContent -replace '(?m)^###### (.*)$', '<h6>$1</h6>' $HtmlContent = $HtmlContent -replace '(?m)^##### (.*)$', '<h5>$1</h5>' $HtmlContent = $HtmlContent -replace '(?m)^#### (.*)$', '<h4>$1</h4>' $HtmlContent = $HtmlContent -replace '(?m)^### (.*)$', '<h3>$1</h3>' $HtmlContent = $HtmlContent -replace '(?m)^## (.*)$', '<h2>$1</h2>' $HtmlContent = $HtmlContent -replace '(?m)^# (.*)$', '<h1>$1</h1>' # Bold, then italic $HtmlContent = $HtmlContent -replace '\*\*(.+?)\*\*', '<strong>$1</strong>' $HtmlContent = $HtmlContent -replace '\*(.+?)\*', '<em>$1</em>' # Unordered lists (consecutive items share one list) $HtmlContent = $HtmlContent -replace '(?m)^- (.*)$', '<ul><li>$1</li></ul>' $HtmlContent = $HtmlContent -replace '</ul>\n<ul>', '' # Table rows $HtmlContent = $HtmlContent -replace '(?m)^\|(.*)\|$', '<table><tr><td>$1</td></tr></table>' for ($index = 0; $index -lt $codeBlocks.Count; $index++) { $HtmlContent = $HtmlContent.Replace("@@CODEBLOCK$index@@", $codeBlocks[$index]) } return $HtmlContent } } } catch { $telemetryFailed = $true Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $_ throw } finally { if (-not $telemetryFailed) { Invoke-TelemetryCollection @TelemetryArgs -Stage End } } } |