tools/classes.ps1

class MetaNullWiki {
    static [hashtable]$Commands = @{}

    static [System.Management.Automation.FunctionInfo] GetModuleCommand([string]$Module, [string]$CommandName) {
        if( -not ([MetaNullWiki]::Commands.Keys -contains $Module )) {
            [MetaNullWiki]::Commands += @{ $Module = @{}}
        }
        if( -not ([MetaNullWiki]::Commands.$Module.$CommandName )) {
            $Command = Get-Command -Module $Module ($CommandName -ireplace '^([a-z]+-)',"`$1$((Get-Module $Module).Prefix)")
            if($Command) {
                [MetaNullWiki]::Commands.$Module.$CommandName = $Command
            }
        }
        return [MetaNullWiki]::Commands.$Module.$CommandName
    }

    static [System.Management.Automation.FunctionInfo] Wiki([string]$CommandName) {
        return [MetaNullWiki]::GetModuleCommand('MetaNullWiki',$CommandName)
    }
    static [System.Management.Automation.FunctionInfo] Utils([string]$CommandName) {
        return [MetaNullWiki]::GetModuleCommand('MetaNullUtils',$CommandName)
    }
    static [System.Management.Automation.FunctionInfo] ConfluencePS([string]$CommandName) {
        return [MetaNullWiki]::GetModuleCommand('ConfluencePS',$CommandName)
    }
}
class WikiArtifact {
    [object]$Page
    [WikiPageProperties[]]$Properties
    [string[]]$Labels
    [string[]]$LinkCards

    WikiArtifact() {
        $this.InitFromHashtable(@{})
    }
    WikiArtifact([object]$Page) {
        $this.InitFromHashtable(@{Page = $Page})
    }
    WikiArtifact([object]$Page,[string[]]$Labels) {
        $this.InitFromHashtable(@{Page = $Page; Labels = $Labels})
    }
    WikiArtifact([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) {
        $this.InitFromHashtable(@{Page = $Page; Labels = $Labels; Properties = $Properties})
    }
    WikiArtifact([WikiArtifact]$Artifact) {
        $this.InitFromHashtable(@{Page = $Artifact.Page; Labels = $Artifact.Labels; Properties = $Artifact.Properties})
    }

    [void] SetPage([object]$Page) {
        if($null -ne $Page) {
            $this.Page = $Page.PSObject.Copy()
        }
    }
    [void] SetProperties([WikiPageProperties[]]$Properties) {
        if($null -ne $Properties) {
            $this.Properties = $Properties.Clone()
        } else {
            $this.Properties = @()
        }
    }
    [void] SetLabels([string[]]$Labels) {
        if($null -ne $Labels) {
            $this.Labels = $Labels.Clone()
        } else {
            $this.Labels = @()
        }
    }
    [void] SetLinkCards([string[]]$LinkCards) {
        if($null -ne $LinkCards) {
            $this.LinkCards = $LinkCards.Clone()
        } else {
            $this.LinkCards = @()
        }
    }

    [void] InitFromHashtable([hashtable]$properties) {
        foreach ($Property in $Properties.Keys) {
            switch($Property) {
                'Page' {
                    $this.SetPage($Properties.Page)
                    if($properties.Keys -notcontains 'Properties') {
                        # Load 'properties' from the page, unless if they are explicitly provided
                        $this.LoadPageProperties()
                        $this.LoadLinkCards()
                    }
                }
                'Properties' {
                    $this.SetProperties($Properties.Properties)
                }
                'Labels' {
                    $this.SetLabels($Properties.Labels)
                }
                'LinkCards' {
                    $this.SetLinkCards($Properties.LinkCards)
                }
                default {
                    [System.ArgumentException]::New("No such property: $($Property).")
                }
            }
        }
    }

    [WikiArtifact] Clone() {
        return $this.PSObject.Copy()
    }

    [void] LoadPageProperties() {
        if($this.Page) {
            $this.Properties = [WikiPageProperties]::CreateFromMacros([WikiMacro]::CreateFromHtml($this.Page.Body))
        } else {
            $this.Properties = [WikiPageProperties[]]@()
        }
    }

    [void] LoadLinkCards() {
        if($this.Page) {
            $this.LinkCards = $this.Page.Body | &([MetaNullWiki]::Wiki('Select-LinkCard')) | Select-Object -Unique | Sort-Object
        } else {
            $this.LinkCards = [string[]]@()
        }
    }

    [WikiPageProperties[]] GetPageProperties([string]$Id) {
        return $this.Properties | Where-Object { $_.Id -eq $Id }
    }

    [string] ToString() {
        return "[$($this.Page.ID)] [$($this.GetType().Name -replace '^Wiki')] $($this.Page.Title)"
    }

    # Calculated Properties
    # They are added to the object definition directly, unlike what is done via the WikiArtifact.types.ps1.xml
    # The purpose is to add properties that are calculated through functions provided by the MetaNullWiki and MetaNullUtils modules.
    # Depending on the way the modules are loaded (from local, form PSGallery, with/without a Prefix...) the way to call them may change
    # To avoid this issue, the calculated properties are added at runtime; and the module functions are called through Get-Command
    static [hashtable[]] $MemberDefinitions = @(
        @{
            MemberName = 'xType'
            MemberType = 'ScriptProperty'
            Value = {
                $this.GetType().Name -replace '^Wiki'
            }
        }
        @{
            MemberName = 'xAllPropertyText'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Foreach-Object {
                    foreach($p in $_.Properties.GetEnumerator()) {
                        $p.Value | Foreach-Object {
                            $_
                        }
                    }
                }
            }
        }
        @{
            MemberName = 'xAllPropertyLink'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Foreach-Object {
                    foreach($p in $_.Properties.GetEnumerator()) {
                        $p.Value | Foreach-Object {
                            $_
                        }
                    }
                } | &([MetaNullWiki]::Wiki('Select-LinkCard')) | Select-Object -Unique
            }
        }
        @{
            MemberName = 'xAllPropertyVersionLink'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Foreach-Object {
                    foreach($p in $_.Properties.GetEnumerator()) {
                        $p.Value | Foreach-Object {
                            $_
                        }
                    }
                } | &([MetaNullWiki]::Wiki('Select-VersionLinkCard')) | Where-Object {
                    $_.Version -ne '0.0.0.0'
                } | Select-Object -Unique
            }
        }
    )
    static WikiArtifact() {
        $TypeName = [WikiArtifact].Name
        foreach ($Definition in [WikiArtifact]::MemberDefinitions) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }
}
class WikiContainer : WikiArtifact {

    WikiContainer() : base() {}
    WikiContainer([object]$Page) : base($Page) {}
    WikiContainer([object]$Page,[string[]]$Labels) : base($Page,$Labels) {}
    WikiContainer([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {}
    WikiContainer([WikiArtifact]$Artifact) : base($Artifact) {}
    WikiContainer([WikiContainer]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {}



    # Calculated Properties
    static [hashtable[]] $ContainerMemberDefinitions = @(
        @{
            MemberName = 'xContainerName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Container }
            }
        }
        @{
            MemberName = 'xContainerType'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Type }
            }
        }
        @{
            MemberName = 'xProductName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Product }
            }
        }
        @{
            MemberName = 'xLinkProduct'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Product } | &([MetaNullWiki]::Wiki('Select-LinkCard'))
            }
        }
        @{
            MemberName = 'xFramework'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.'Main frameworks' }
            }
        }
        @{
            MemberName = 'xVersionLinkFramework'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.'Main frameworks' } | &([MetaNullWiki]::Wiki('Select-VersionLinkCard'))
            }
        }
        @{
            MemberName = 'xLibrary'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.'Frameworks & libraries' }
            }
        }
        @{
            MemberName = 'xVersionLinkLibrary'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.'Frameworks & libraries' } | &([MetaNullWiki]::Wiki('Select-VersionLinkCard'))
            }
        }
        @{
            MemberName = 'xAuthentication'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Authentication }
            }
        }
        @{
            MemberName = 'xLinkAuthentication'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Authentication } | &([MetaNullWiki]::Wiki('Select-LinkCard'))
            }
        }
        @{
            MemberName = 'xAuthorization'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Authorization }
            }
        }
        @{
            MemberName = 'xLinkAuthorization'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'container' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Authorization } | &([MetaNullWiki]::Wiki('Select-LinkCard'))
            }
        }
    )
    static WikiContainer() {
        $TypeName = [WikiContainer].Name
        foreach ($Definition in [WikiContainer]::ContainerMemberDefinitions) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }
}
class WikiDomain : WikiArtifact {

    WikiDomain() : base() {}
    WikiDomain([object]$Page) : base($Page) {}
    WikiDomain([object]$Page,[string[]]$Labels) : base($Page,$Labels) {}
    WikiDomain([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {}
    WikiDomain([WikiArtifact]$Artifact) : base($Artifact) {}
    WikiDomain([WikiDomain]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {}

    # Calculated Properties
    static [hashtable[]] $DomainMemberDefinitions = @(
        @{
            MemberName = 'xDomainName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'domain' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Domain } | &([MetaNullWiki]::Wiki('Select-Text')) | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xGUID'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'domain' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.GUID }
            }
        }
    )
    static WikiDomain() {
        $TypeName = [WikiDomain].Name
        foreach ($Definition in [WikiDomain]::DomainMemberDefinitions) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }
}
class WikiKeyDataEntity : WikiArtifact {

    WikiKeyDataEntity() : base() {}
    WikiKeyDataEntity([object]$Page) : base($Page) {}
    WikiKeyDataEntity([object]$Page,[string[]]$Labels) : base($Page,$Labels) {}
    WikiKeyDataEntity([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {}
    WikiKeyDataEntity([WikiArtifact]$Artifact) : base($Artifact) {}
    WikiKeyDataEntity([WikiKeyDataEntity]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {}
}
class WikiMacro {
    [string]$Name
    [hashtable]$Parameters
    [hashtable]$Attributes
    [string]$Body

    WikiMacro() {
        $this.InitFromHashtable(@{})
    }
    WikiMacro([string]$Name,[hashtable]$Parameters,[hashtable]$Attributes,[string]$Body) {
        $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters; Attributes = $Attributes; Body = $Body })
    }
    WikiMacro([string]$Name,[hashtable]$Parameters,[hashtable]$Attributes) {
        $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters; Attributes = $Attributes })
    }
    WikiMacro([string]$Name,[hashtable]$Parameters,[string]$Body) {
        $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters; Body = $Body })
    }
    WikiMacro([string]$Name, [string]$Body) {
        $this.InitFromHashtable(@{Name = $Name; Body = $Body })
    }
    WikiMacro([string]$Name,[hashtable]$Parameters) {
        $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters})
    }

    [void] SetName([string]$Name) {
        $this.Name = $Name
    }
    [void] SetParameters([hashtable]$Parameters) {
        $this.Parameters = $Parameters.Clone()
    }
    [void] SetAttributes([hashtable]$Attributes) {
        $this.Attributes = $Attributes.Clone()
    }
    [void] SetBody([string]$Body) {
        $this.Body = $Body
    }

    [void] InitFromHashtable([hashtable]$properties) {
        foreach ($Property in $Properties.Keys) {
            if($Properties.$Property -is [hashtable]) {
                $this.$Property = $Properties.$Property.Clone()
            } else {
                $this.$Property = $Properties.$Property
            }
        }
    }

    [WikiMacro] Clone() {
        return $this.PSObject.Copy()
    }


    static [WikiMacro[]] CreateFromHtml([string]$Html) {
        return [WikiMacro]::CreateFromXDocument(($Html | &([MetaNullWiki]::Wiki('ConvertTo-XDocument'))))
    }

    static [WikiMacro[]] CreateFromXDocument([object]$Document) {
        $Output = @()

        $Document | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath '//ac:structured-macro' | Foreach-Object {
            $Name = $_ | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath './@ac:name' | Select-Object -ExpandProperty Node -First 1 | Select-Object -ExpandProperty '#text'
            $Attributes = @{}
            $_ | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath './@*' | Foreach-Object {
                $Attributes += @{  $_.Node.Name = $_.Node.Value }
            }
            $Parameters = @{}
            $_ | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath './ac:parameter' | Foreach-Object {
                $Parameters += @{  $_.Node.Name = $_.Node.InnerXml }
            }
            $Body = $_ | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath './ac:rich-text-body' | Select-Object -ExpandProperty Node -First 1 | Select-Object -ExpandProperty InnerXml
            $Output += ([WikiMacro]::new($Name,$Parameters,$Attributes,$Body))
        }
        return [WikiMacro[]]($Output)
    }

    [string] ToString() {
        return "[Macro] Name:$($this.Name), Parameters:{$(($this.Parameters.Keys -join ', '))}, Body:$($this.Body)"
    }

    [string] ToHtml() {
        $Html = @()
        $Html += "<ac:structured-macro ac:name=""$($this.Name)"" data-layout=""default"""
        foreach($attrib in $this.Attributes.GetEnumerator()) {
            if($attrib.Value) {
                $Html += " $($attrib.Name)=""$($attrib.Value)"""
            } else {
                $Html += " $($attrib.Name)"
            }
        }
        $Html += ">"
        foreach($param in $this.Parameters.GetEnumerator()) {
            if($param.Value) {
                $Html += "<ac:parameter ac:name=""$($param.Name)"">$($param.Value)</ac:parameter>"
            } else {
                $Html += "<ac:parameter ac:name=""$($param.Name)""/>"
            }
        }
        $this.Body | ForEach-Object {
            $Html += "<ac:rich-text-body>"
            $Html += $this.Body
            $Html += "</ac:rich-text-body>"
        }
        $Html += "</ac:structured-macro>"
        return ($Html -join '')
    }

}
class WikiPageProperties {
    [string]$Id
    [hashtable]$Properties
    [WikiMacro]$Macro

    WikiPageProperties() {
        $this.InitFromHashtable(@{})
    }
    WikiPageProperties([hashtable]$Properties) {
        $this.InitFromHashtable(@{Properties = $Properties })
    }
    WikiPageProperties([hashtable]$Properties, [string] $Id) {
        $this.InitFromHashtable(@{Properties = $Properties; Id = $Id })
    }
    WikiPageProperties([string]$Id, [hashtable]$Properties, [WikiMacro]$Macro) {
        $this.InitFromHashtable(@{Properties = $Properties; Id = $Id; Macro = $Macro })
    }


    [void] SetId([string]$Id) {
        $this.Id = $Id
    }
    [void] SetProperties([hashtable]$Properties) {
        $this.Properties = $Properties.Clone()
    }
    [void] SetMacro([string]$Macro) {
        $this.Macro = $Macro.Clone()
    }


    [void] InitFromHashtable([hashtable]$properties) {
        foreach ($Property in $Properties.Keys) {
            switch($Property) {
                'Macro' {
                    $this.$Property = $Properties.$Property.Clone()
                }
                'Properties' {
                    $this.$Property = $Properties.$Property.Clone()
                }
                'Id' {
                    $this.$Property = $Properties.$Property
                }
                default {
                    [System.ArgumentException]::New("No such property: $($Property).")
                }
            }
        }
    }

    [WikiPageProperties] Clone() {
        return $this.PSObject.Copy()
    }


    static [WikiPageProperties[]] CreateFromHtml([string]$Html) {
        return [WikiPageProperties]::CreateFromXDocument(($Html | &([MetaNullWiki]::Wiki('ConvertTo-XDocument'))))
    }
    static [WikiPageProperties[]] CreateFromXDocument([object]$Document) {
        return [WikiPageProperties]::CreateFromMacros([WikiMacro]::CreateFromXDocument($Document))
    }
    static [WikiPageProperties[]] CreateFromMacros([WikiMacro[]]$Macros) {
        $Output = @()

        $Macros | Where-Object {
            $_.Name -eq 'details'
        } | Foreach-Object {
            $Macro = $_.Clone()
            $Id = $_.Parameters.id
            $Properties = @{}
            $_.Body | &([MetaNullWiki]::Wiki('ConvertTo-XDocument')) | &([MetaNullWiki]::Wiki('Select-XPath')) '//tr' | Foreach-Object {
                $Key = ($_ | &([MetaNullWiki]::Wiki('Select-XPath')) '(./td|./th)[position()=1]').Node.InnerText
                # $Value = ($_ | &([MetaNullWiki]::Wiki('Select-XPath')) '(./td|./th)[position()=2]/p').Node.InnerXml
                $Value = ($_ | &([MetaNullWiki]::Wiki('Select-XPath')) '(./td|./th)[position()=2]/p' | &([MetaNullWiki]::Wiki('Clear-Placeholder')) | Where-Object {$_})
                $Properties += @{ $Key = $Value }
            }
            $Output += [WikiPageProperties]::new($Id,$Properties,$Macro)
        }
        return [WikiPageProperties[]]($Output)
    }

    [string] ToString() {
        return "[PageProperties] Id:$($this.Id), Properties:{$(($this.Properties.Keys -join ', '))}"
    }

}
class WikiProduct : WikiArtifact {

    WikiProduct() : base() {}
    WikiProduct([object]$Page) : base($Page) {}
    WikiProduct([object]$Page,[string[]]$Labels) : base($Page,$Labels) {}
    WikiProduct([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {}
    WikiProduct([WikiArtifact]$Artifact) : base($Artifact) {}
    WikiProduct([WikiProduct]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {}

    # Calculated Properties
    static [hashtable[]] $ProductMemberDefinitions = @(
        @{
            MemberName = 'xProductName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'info' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Name } | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xProductAlias'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'info' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Title } | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xDomainName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'info' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Domain } | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xLinkDomain'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'info' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Domain }  | &([MetaNullWiki]::Wiki('Select-LinkCard'))
            }
        }
        @{
            MemberName = 'xStatus'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'info' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Status } | &([MetaNullWiki]::Wiki('Select-Text'))
            }
        }
        @{
            MemberName = 'xDescription'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'info' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Description } | &([MetaNullWiki]::Wiki('Select-Text')) | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xProjectManager'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'pmo' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.PM }
            }
        }
    )
    static WikiProduct() {
        $TypeName = [WikiProduct].Name
        foreach ($Definition in [WikiProduct]::ProductMemberDefinitions) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }
}
class WikiTechnology : WikiArtifact {

    WikiTechnology() : base() {}
    WikiTechnology([object]$Page) : base($Page) {}
    WikiTechnology([object]$Page,[string[]]$Labels) : base($Page,$Labels) {}
    WikiTechnology([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {}
    WikiTechnology([WikiArtifact]$Artifact) : base($Artifact) {}
    WikiTechnology([WikiTechnology]$Technology) : base($Technology.Page,$Technology.Labels,$Technology.Properties) {}

    [WikiVersionPriority] AssessVersion([version]$Version) {
        if($null -eq $version -or $version -eq '0.0.0.0') {
            return  [WikiVersionPriority]::Unknown
        }
        if($null -ne $this.xVersionVulnerable -and $this.xVersionVulnerable -ne '0.0.0.0' -and $Version -le $this.xVersionVulnerable) {
            # Smaller than Vulnerable = Vulnerable
            return [WikiVersionPriority]::Vulnerable
        } elseif($null -ne $this.xVersionOutdated -and $this.xVersionOutdated -ne '0.0.0.0' -and $Version -le $this.xVersionOutdated) {
            # Smaller than Outdated = Outdated
            return [WikiVersionPriority]::NotSupported
        } else {
            if($null -ne $this.xVersionRecommended -and $this.xVersionRecommended -ne '0.0.0.0' -and $Version -ge $this.xVersionRecommended) {
                # Greater than Recommended = Recommended
                return [WikiVersionPriority]::UpToDate
            } elseif($null -ne $this.xVersionAccepted -and $this.xVersionAccepted -ne '0.0.0.0' -and $Version -ge $this.xVersionAccepted) {
                # Greater than Accepted = Accepted
                return [WikiVersionPriority]::Supported
            }
        }
        # In any other case = Outdated
        return  [WikiVersionPriority]::NotSupported
    }
    [bool] IsSupported([version] $Version) {
        return ($this.AssessVersion($Version) -in ([WikiVersionPriority]::UpToDate,[WikiVersionPriority]::Supported))
    }
    [bool] IsVulnerable([version] $Version) {
        return ($this.AssessVersion($Version) -in ([WikiVersionPriority]::Vulnerable))
    }
    [bool] IsUnknown([version] $Version) {
        return ($this.AssessVersion($Version) -in ([WikiVersionPriority]::Unknown))
    }

    # Calculated Properties
    static [hashtable[]] $WikiTechnologyMemberDefinitions = @(
        @{
            MemberName = 'xTechnologyName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Technology } | &([MetaNullWiki]::Wiki('Select-Text')) | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xTechnologyVendor'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Vendor } | &([MetaNullWiki]::Wiki('Select-Text')) | &([MetaNullWiki]::Utils('ConvertFrom-HtmlEncoded'))
            }
        }
        @{
            MemberName = 'xTechnologyType'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Type } | &([MetaNullWiki]::Wiki('Select-Text'))
            }
        }
        @{
            MemberName = 'xTechnologyStatus'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Status } | &([MetaNullWiki]::Wiki('Select-Text'))
            }
        }

        @{
            MemberName = 'xVersionRecommended'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology-version' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Recommended } | &([MetaNullWiki]::Utils('ConvertTo-Version'))
            }
        }
        @{
            MemberName = 'xVersionAccepted'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology-version' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Accepted } | &([MetaNullWiki]::Utils('ConvertTo-Version'))
            }
        }
        @{
            MemberName = 'xVersionOutdated'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology-version' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Outdated } | &([MetaNullWiki]::Utils('ConvertTo-Version'))
            }
        }
        @{
            MemberName = 'xVersionVulnerable'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Properties | Where-Object { $_.Id -eq 'technology-version' } | Select-Object -ExpandProperty Properties | Foreach-Object { $_.Vulnerable } | &([MetaNullWiki]::Utils('ConvertTo-Version'))
            }
        }
    )
    static WikiTechnology() {
        $TypeName = [WikiTechnology].Name
        foreach ($Definition in [WikiTechnology]::WikiTechnologyMemberDefinitions) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }
}
class WikiVersionLink {
    [string]$Title
    [version]$Version
    [string]$VersionString
    [string]$Body

    WikiVersionLink() {
        $this.InitFromHashtable(@{})
    }
    WikiVersionLink([string]$Title,[version]$Version) {
        $this.InitFromHashtable(@{Title = $Title; Version = $Version})
    }

    [void] SetTitle([string]$Title) {
        $this.Title = $Title
    }
    [void] SetVersion([version]$Version) {
        $this.Version = $Version.Clone()
    }
    
    [void] InitFromHashtable([hashtable]$properties) {
        foreach ($Property in $Properties.Keys) {
            if($Properties.$Property -is [hashtable]) {
                $this.$Property = $Properties.$Property.Clone()
            } else {
                $this.$Property = $Properties.$Property
            }
        }
    }

    [WikiVersionLink] Clone() {
        return $this.PSObject.Copy()
    }

    static [WikiVersionLink[]] CreateFromHtml([string]$Html) {
        return [WikiVersionLink]::CreateFromXDocument(($Html | &([MetaNullWiki]::Wiki('ConvertTo-XDocument'))))
    }

    static [WikiVersionLink] CreateFromCurrentNode([object]$Node) {
        $Output = [WikiVersionLink]::empty
        $Node | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath 'self::ac:link' | Foreach-Object {
            $Title = $_ | &([MetaNullWiki]::Wiki('Select-XPath')) './ri:page/@ri:content-title' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value
            $VersionString = $_ | &([MetaNullWiki]::Wiki('Select-XPath')) '(./following-sibling::text()[1]|./following-sibling::code/text())' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value
            if($VersionString) {
                $VersionString = $VersionString.Trim()
            }
            $Version = &([MetaNullWiki]::Utils('ConvertTo-Version')) -InputString $VersionString
            <##> $Body = $_ | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty OuterXml
            $VersionLink = [WikiVersionLink]::new()
            $VersionLink.Title = $Title
            $VersionLink.Version = $Version
            <##> $VersionLink.VersionString = $VersionString
            <##> $VersionLink.Body = $Body
            $Output = $VersionLink
            # $Output += ([WikiVersionLink]::new($Title,$Version))
        }

        return [WikiVersionLink]($Output)
    }

    static [WikiVersionLink[]] CreateFromXDocument([object]$Document) {
        $Output = @()
        $Document | &([MetaNullWiki]::Wiki('Select-XPath')) -XPath '//ac:link' | Foreach-Object { 
            $Output += [WikiVersionLink]::CreateFromCurrentNode($_)
        }
        return $Output
    }

    [string] ToString() {
        return "[VersionLink] Link:$($this.Title), Version:$($this.Version), VersionString:$($this.VersionString)"
    }

}
enum WikiVersionPriority {
    UpToDate
    Supported
    NotSupported
    Vulnerable
    Unknown
}