TreeControl/SRxXmlTreeControl.ps1

class SRxXmlTreeControl : SRxTreeControl {

    [bool] $Verbose = $false

    hidden [System.Collections.Hashtable] $_byXPath = @{}

    SRxXmlTreeControl() : base() { 

        $this.SetSideBorder(
            [SRxBorderSides]::Right,
            [System.Drawing.SystemColors]::ControlDark,
            [float]1.0
        )

        $this.Padding = [System.Windows.Forms.Padding]::new(0, 0, 1, 0)
    }

    [void] LoadFromXml($xmlElement, [string]$rootXPath, $xPathFn) {
        try {
            $this.ClearModel()
            $this._byXPath.Clear()
            if ($null -eq $xmlElement) { $this.SyncToUI(); return }

            $rootId = $this.reserveId($rootXPath)
            $this.AddNode($rootId, "", $this.headText($xmlElement, $true), (
                $this.newTag($rootXPath, $xmlElement)))

            $this.addXmlChildren($xmlElement, $rootXPath, $rootId, $xPathFn)
            $this.SyncToUI()
            if ($this.Verbose) { Write-Host ("SRxXmlTreeControl - model built: {0} nodes" -f $this.Count()) }
        }
        catch {
            Write-Host "SRxXmlTreeControl.LoadFromXml - FAILED (model is PARTIAL): $($_.Exception.Message)"
            try { $this.SyncToUI() } catch { }
        }
    }

    hidden [void] addXmlChildren($xElement, [string]$parentXPath, [string]$parentId, $xPathFn) {
        foreach ($xEl in $xElement.ChildNodes) {

            if ($xEl.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }

            $xPth = ""
            if ($null -ne $xPathFn) {
                try { $xPth = [string](& $xPathFn $xEl $parentXPath) } catch { $xPth = "" }
            }
            if ([string]::IsNullOrEmpty($xPth)) { $xPth = ($parentXPath + "/" + [string]$xEl.LocalName) }

            $hasKids = $false
            try { $hasKids = [bool]$xEl.HasChildNodes } catch { $hasKids = $false }

            $id = $this.reserveId($xPth)
            $this.AddNode($id, $parentId, $this.headText($xEl, $hasKids), ($this.newTag($xPth, $xEl)))

            if ($hasKids) { $this.addXmlChildren($xEl, $xPth, $id, $xPathFn) }
        }
    }

    hidden [string] headText($xEl, [bool]$hasKids) {
        try {
            $outer = [string]$xEl.OuterXml
            if ($hasKids) { return ($outer.Split(">")[0] + ">") }
            return $outer
        } catch { return "" }
    }

    hidden [object] newTag([string]$xPth, $xEl) {
        $outer = ""
        try { $outer = [string]$xEl.OuterXml } catch { }
        return (New-Object PSObject -Property @{
            xPath                = $xPth
            Filter               = @{}
            SavedFilter          = @{}
            ActiveScopeChanges   = @{}
            ActiveScopeFilter    = @{}
            OuterXml             = $outer
            DeprecatedOnScope_In = -1
        })
    }

    hidden [string] reserveId([string]$xPth) {
        $id = $xPth
        if ($this._byXPath.ContainsKey($xPth)) {
            $n = [int]$this._byXPath[$xPth]
            $n++
            $this._byXPath[$xPth] = $n
            $id = ("{0}#{1}" -f $xPth, $n)
        } else {
            $this._byXPath[$xPth] = 0
        }
        return $id
    }

    [string] GetXPath([string]$id) {
        $n = $this.GetNode($id)
        if ($null -eq $n -or $null -eq $n.Tag) { return "" }
        try { return [string]$n.Tag.xPath } catch { return "" }
    }

    [string] IdForXPath([string]$xPth) {
        if ($this.GetNode($xPth)) { return $xPth }
        foreach ($id in $this.AllIds()) {
            if ($this.GetXPath($id) -eq $xPth) { return $id }
        }
        return ""
    }

    [string[]] AllIds() {
        $out = New-Object System.Collections.ArrayList
        foreach ($rid in $this.RootIds()) { $this.collectIds($rid, $out) }
        return @($out)
    }
    hidden [void] collectIds([string]$id, $acc) {
        [void]$acc.Add($id)
        foreach ($c in $this.ChildIds($id)) { $this.collectIds($c, $acc) }
    }

    [object] SelectedTag() {
        $id = $this.SelectedId()
        if ([string]::IsNullOrEmpty($id)) { return $null }
        $n = $this.GetNode($id)
        if ($null -eq $n) { return $null }
        return $n.Tag
    }

    [object] FindUiNode([string]$id) {
        try {
            $found = $this.InnerTree().Nodes.Find($id, $true)
            if ($found.Count -gt 0) { return $found[0] }
        } catch { }
        return $null
    }

    [void] ApplyCustomizationOverlay([hashtable]$styleById) {
        try {
            $this.PushOverlay('customizations')
            $this.ClearOverlayStyles('customizations')
            if ($null -ne $styleById) {
                foreach ($id in $styleById.Keys) {
                    $this.SetOverlayStyle('customizations', [string]$id, [hashtable]$styleById[$id])
                }
            }
            $this.applyAppearance()
        } catch { Write-Host "ApplyCustomizationOverlay - $($_.Exception.Message)" }
    }

    [void] ClearCustomizationOverlay() { $this.PopOverlay('customizations') }

    [void] ExpandRoot() {
        try {
            $roots = $this.RootIds()
            if ($roots.Count -eq 0) { return }
            $ui = $this.FindUiNode([string]$roots[0])
            if ($null -ne $ui -and -not $ui.IsExpanded) { $ui.Expand() }
        } catch { Write-Host "ExpandRoot - $($_.Exception.Message)" }
    }
}