functions/Read-EBMarkdown.ps1

function Read-EBMarkdown {
    <#
    .SYNOPSIS
        Reads a markdown file and converts it to a page to be built into an ebook
     
    .DESCRIPTION
        Reads a markdown file and converts it to a page to be built into an ebook
     
    .PARAMETER Path
        Path to the file to read.
         
    .PARAMETER InlineStyles
        Hashtable mapping inline decorators to span classes.
        Used to enable inline style customizations.
        For example, when providing a hashtable like this:
        @{ 1 = 'spellcast' }
        It will convert this line:
        "Let me show you my #1#Fireball#1#!"
        into
        "Let me show you my <span class="spellcast">Fireball</span>!"
     
    .EXAMPLE
        PS C:\> Get-ChildItem *.md | Read-EBMarkdown
 
        Reads and converts all markdown files in he current folder
    #>

    [OutputType([EbookBuilder.Page])]
    [CmdletBinding()]
    param (
        [parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [string[]]
        $Path,
        
        [hashtable]
        $InlineStyles = @{ }
    )
    
    begin {
        function ConvertFrom-Markdown {
            [OutputType([EbookBuilder.Page])]
            [CmdletBinding()]
            param (
                [string]
                $Path,
                
                [int]
                $Index,
                
                [hashtable]
                $InlineStyles = @{ }
            )
            
            $lines = Get-Content -Path $Path -Encoding UTF8 | ConvertFrom-InlineStyle -InlineStyles $InlineStyles
            $stringBuilder = New-SBStringBuilder -Name ebook
            $PSDefaultParameterValues['Add-SBLine:Name'] = 'ebook'
            
            $inBlock = $false
            $inCode = $false
            $inBullet = $false
            $inNote = $false
            $inParagraph = $false
            
            $blockData = [pscustomobject]@{
                Attributes = @{ }
                Type       = $null
                Lines       = @()
                File       = $Path
            }
            $paragraph = @()
            $firstPar = $true
            
            foreach ($line in $lines) {
                #region Process Block Content
                if ($inBlock) {
                    if ($line -like '## <*') {
                        try { $firstPar = ConvertFrom-MdBlock -Type $blockData.Type -Lines $blockData.Lines -Attributes $blockData.Attributes -StringBuilder $stringBuilder }
                        catch { Stop-PSFFunction -Message 'Failed to convert block' -ErrorRecord $_ -Target $blockData -EnableException $true -Cmdlet $PSCmdlet }
                        $inBlock = $false
                    }
                    else { $blockData.Lines += $line }
                    
                    continue
                }
                #endregion Process Block Content
                
                #region Process Code Content
                if ($inCode) {
                    if ($line -like '``````*') {
                        $paragraph = @()
                        Add-SBLine '</pre>'
                        $inCode = $false
                        $firstPar = $true
                        continue
                    }
                    Add-SBLine $line
                    continue
                }
                #endregion Process Code Content
                
                #region Process Bullet Point
                if ($inBullet) {
                    if (-not $line.Trim()) {
                        Add-SBLine '<li class="defaultLI">{0}</li>' -Values ($paragraph -join ' ')
                        Add-SBLine '</ul>'
                        $paragraph = @()
                        $inBullet = $false
                        $firstPar = $true
                        continue
                    }
                    if ($line -notlike '+ *') {
                        $paragraph += $line
                        continue
                    }
                    
                    if ($paragraph) {
                        Add-SBLine '<li class="defaultLI">{0}</li>' -Values ($paragraph -join ' ')
                        $paragraph = @()
                    }
                    $paragraph += $line -replace '^\+ '
                    continue
                }
                #endregion Process Bullet Point
                
                #region Process Notes
                if ($inNote) {
                    if ($line.Trim()) {
                        $paragraph += $line -replace '^>\s{0,1}'
                        continue
                    }
                    
                    foreach ($text in ConvertFrom-EBMarkdown -Line $paragraph -ClassFirstParagraph noteFirstPar -ClassParagraph noteText -EmphasisClass noteEmphasis) {
                        Add-SBLine $text
                    }
                    Add-SBLine '<hr/></div>'
                    $inNote = $false
                    $firstPar = $true
                    $paragraph = @()
                    continue
                }
                #endregion Process Notes
                
                #region Process Paragraph
                if ($inParagraph) {
                    if ($line.Trim()) {
                        $paragraph += $line
                        continue
                    }
                    
                    $class = 'text'
                    if ($firstPar) {
                        $class = 'firstpar'
                        $firstPar = $false
                    }
                    
                    foreach ($text in ConvertFrom-EBMarkdown -Line $paragraph -ClassFirstParagraph $class -ClassParagraph $class) {
                        Add-SBLine $text
                    }
                    $paragraph = @()
                    $inParagraph = $false
                    continue
                }
                #endregion Process Paragraph
                
                #region Region Starters
                # Handle begin of a Block
                if ($line -like '## <*') {
                    $inBlock = $true
                    $blockData = New-Block -Line $line -Path $Path
                    continue
                }
                
                # Handle begin of a Code section
                if ($line -like '``````*') {
                    $inCode = $true
                    $firstPar = $true
                    Add-SBLine '<pre>'
                    continue
                }
                
                # Handle begin of a Bullet-Points section
                if ($line -like '+ *') {
                    $inBullet = $true
                    Add-SBLine '<ul>'
                    $paragraph += $line -replace '^\+ '
                    continue
                }
                
                # Handle begin of a Notes section
                if ($line -like '> *') {
                    $inNote = $true
                    Add-SBLine '<div class="notes"><hr/>'
                    $paragraph += $line -replace '^> '
                    continue
                }
                
                # Handle Chapter Title
                if ($line -like '# *') {
                    $null = $stringBuilder.AppendLine("<h2>$($line -replace '^# ')</h2>")
                    continue
                }
                
                # Handle begin of a Paragraph section
                if ($line.Trim()) {
                    $inParagraph = $true
                    $paragraph += $line
                }
                #endregion Region Starters
            }
            
            #region Cleanup
            
            #region Process Block Content
            if ($inBlock) {
                try { $firstPar = ConvertFrom-MdBlock -Type $blockData.Type -Lines $blockData.Lines -Attributes $blockData.Attributes -StringBuilder $stringBuilder }
                catch { Stop-PSFFunction -Message 'Failed to convert block' -ErrorRecord $_ -Target $blockData -EnableException $true -Cmdlet $PSCmdlet }
                $inBlock = $false
            }
            #endregion Process Block Content
            
            #region Process Code Content
            if ($inCode) {
                Add-SBLine '</pre>'
                $inCode = $false
                $firstPar = $true
            }
            #endregion Process Code Content
            
            #region Process Bullet Point
            if ($inBullet) {
                Add-SBLine '<li class="defaultLI">{0}</li>' -Values ($paragraph -join ' ')
                Add-SBLine '</ul>'
                $paragraph = @()
                $inBullet = $false
                $firstPar = $true
            }
            #endregion Process Bullet Point
            
            #region Process Notes
            if ($inNote) {
                foreach ($text in ConvertFrom-EBMarkdown -Line $paragraph -ClassFirstParagraph noteFirstPar -ClassParagraph noteText -EmphasisClass noteEmphasis) {
                    Add-SBLine $text
                }
                Add-SBLine '<hr/></div>'
                $inNote = $false
                $firstPar = $true
                $paragraph = @()
            }
            #endregion Process Notes
            
            #region Process Paragraph
            if ($inParagraph) {
                $class = 'text'
                if ($firstPar) {
                    $class = 'firstpar'
                    $firstPar = $false
                }
                
                foreach ($text in ConvertFrom-EBMarkdown -Line $paragraph -ClassFirstParagraph $class -ClassParagraph $class) {
                    Add-SBLine $text
                }
                $paragraph = @()
                $inParagraph = $false
            }
            #endregion Process Paragraph
            #endregion Cleanup
            
            New-Object EbookBuilder.Page -Property @{
                Index = $Index
                Name  = (Get-Item -Path $Path).BaseName
                Content = Close-SBStringBuilder -Name ebook
                SourceName = $Path
                TimeCreated = Get-Date
                MetaData = @{ }
            }
        }
        
        function ConvertFrom-InlineStyle {
            [OutputType([string])]
            [CmdletBinding()]
            param (
                [Parameter(ValueFromPipeline = $true)]
                [string[]]
                $Line,
                
                [hashtable]
                $InlineStyles = @{ }
            )
            
            begin {
                $replaceHash = @{ }
                
                foreach ($pair in $InlineStyles.GetEnumerator()) {
                    if ($pair.Value -is [string]) {
                        $replaceHash["#$($pair.Key)#(.+?)#$($pair.Key)#"] = '<span class="{0}">$1</span>' -f $pair.Value
                    }
                    else {
                        $newValue = '<span class="{0}">' -f $pair.Value.Class
                        if ($pair.Value.Prepend) { $newValue += $pair.Value.Prepend }
                        $newValue += '$1'
                        if ($pair.Value.Append) { $newValue += $pair.Value.Append }
                        $newValue += '</span>'
                        $replaceHash["#$($pair.Key)#(.+?)#$($pair.Key)#"] = $newValue
                    }
                }
            }
            process {
                foreach ($string in $Line) {
                    foreach ($pair in $replaceHash.GetEnumerator()) {
                        $string = $string -replace $pair.Key, $pair.Value
                    }
                    $string
                }
            }
        }
        
        function New-Block {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
            [CmdletBinding()]
            param (
                [string]
                $Line,
                
                [string]
                $Path
            )
            
            $type = $Line -replace '## <(\w+).+$', '$1'
            $attributes = @{ }
            $entries = $Line | Select-String '(\w+)="(.+?)"' -AllMatches
            foreach ($match in $entries.Matches) {
                $attributes[$match.Groups[1].Value] = $match.Groups[2].Value
            }
            
            [pscustomobject]@{
                Attributes = $attributes
                Type       = $type
                Lines       = @()
                File       = $Path
            }
        }
        
        $Index = 1
    }
    process {
        foreach ($pathItem in $Path) {
            Write-PSFMessage -Message "Processing: $pathItem"
            ConvertFrom-Markdown -Path $pathItem -Index $Index -InlineStyles $InlineStyles
            $Index++
        }
    }
}