Types/PSAdapter.Class/PSXmlAdapter.class.ps1

class PSXmlAdapterQueryBuilder : Microsoft.PowerShell.Cmdletization.QueryBuilder {
    [Collections.IDictionary] $QueryOption = [Ordered]@{}
    [Collections.Generic.List[PSObject]] $QueryFilterList = [Collections.Generic.List[PSObject]]::new()
    [PSXmlAdapter] $Adapter
    PSXmlAdapterQueryBuilder([PSXmlAdapter]$adapter) {
        $this.Adapter = $adapter
    }
    AddQueryOption([string] $name, [object] $value) {
        $this.QueryOption[$name] = $value
        $this.Adapter.Cmdlet.WriteVerbose("Added query option '$name' with value '$value'")
    }
    ExcludeByProperty([string] $propertyName, [Collections.IEnumerable]$ExcludePropertyValue, [object] $propertyValue, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "ExcludeByProperty"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Excluded by property '$propertyName' with value '$propertyValue'")
    }
    FilterByProperty([string] $propertyName, [Collections.IEnumerable]$AllowedPropertyValue, [bool] $wildcardsEnabled, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "FilterByProperty"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Filtered property value '$propertyName' with value '$AllowedPropertyValue'")
    }
    FilterByMinPropertyValue([string]$propertyName, [object]$MinValue, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "FilterByMinPropertyValue"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Filtered by MinValue value '$propertyName' with value '$MinValue'")
    }
    FilterByMaxPropertyValue([string]$propertyName, [object]$MaxValue, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "FilterByMaxPropertyValue"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Filtered by MaxValue '$propertyName' with value '$MaxValue'")
    }
    [bool] MatchesFilters([object]$Instance, [Microsoft.PowerShell.Cmdletization.QueryBuilder]$QueryBuilder) {
        :nextQueryFilter foreach ($queryFilterItem in $QueryBuilder.QueryFilterList) {            
            $InstancePropertyValue = $Instance.$($queryFilterItem.PropertyName)
            switch ($queryFilterItem.FilterType) {
                FilterByProperty {
                    if ($queryFilterItem.WildcardsEnabled) {
                        foreach ($wildcard in $queryFilterItem.AllowedPropertyValue) {
                            if ($InstancePropertyValue -like $wildcard) {                                
                                continue nextQueryFilter
                            }
                        }
                        return $false
                    } else {
                        if ($InstancePropertyValue -notin $queryFilterItem.AllowedPropertyValue) {
                            if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                                $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is not in the allowed list")                             
                            }
                            return $false
                        }
                    }
                    
                    
                }
                FilterByMinPropertyValue {
                    if ($InstancePropertyValue -lt $queryFilterItem.MinValue) {
                        if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                            $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is less than the minimum value")                                    
                        }
                        return $false
                    }
                }
                FilterByMaxPropertyValue {
                    if ($InstancePropertyValue -gt $queryFilterItem.MaxValue) {
                        if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                            $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is greater than the maximum value")                                    
                        }
                        return $false
                    }
                }
                ExcludeByProperty {
                    if ($queryFilterItem.WildcardsEnabled) {
                        foreach ($wildcard in $queryFilterItem.ExcludePropertyValue) {
                            if ($InstancePropertyValue -like $wildcard) {                                
                                return $false
                            }
                        }
                    } else {
                        if ($InstancePropertyValue -in $queryFilterItem.ExcludePropertyValue) {
                            if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                                $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is in the exclude list")                                    
                            }
                            return $false
                        }
                    }                    
                }
            }            
        }
        return $true
    }
}
class PSXmlAdapter : Microsoft.PowerShell.Cmdletization.CmdletAdapter[object] {    
    [Microsoft.PowerShell.Cmdletization.QueryBuilder] GetQueryBuilder() {
        $this.Cmdlet.WriteVerbose("Getting query builder")
        $queryBuilder = [PSXmlAdapterQueryBuilder]::new($this)        
        return $queryBuilder
    }
    [string[]] $myInvocationPrivateDataKeys
    
    BeginProcessing() {
        $myNamePattern = [Regex]::Escape($this.Cmdlet.MyInvocation.InvocationName)
        $this.myInvocationPrivateDataKeys = $this.PrivateData.Keys -match "^$myNamePattern"
        if ($this.myInvocationPrivateDataKeys) {
            $this.Cmdlet.WriteVerbose("Found private data keys: $($this.myInvocationPrivateDataKeys -join ', ')")
        }
        $this.Cmdlet.WriteVerbose("Beginning processing")
    }
    StopProcessing() {
        $this.Cmdlet.WriteVerbose("Stopping processing")
    }

    [Ordered] SelectXmlSplatter() {
        $pathKeys = $this.MyInvocationPrivateDataKeys -match '(?<!x)Path$'
        $xPathKey = @($this.MyInvocationPrivateDataKeys -match 'XPath$')[0]
        $myNamespaceKey = $this.MyInvocationPrivateDataKeys -match 'Namespace$'
        $selectXmlSplat = [ordered]@{}
        
        if ($myNamespaceKey) {
            $myNamespaceInfo = $this.PrivateData[$myNamespaceKey]
            try {
                if ($myNamespaceInfo -match '^\s{0,}@{') {
                    $dataBlock = [ScriptBlock]::Create("data {$myNamespaceInfo}")
                    if ($dataBlock.Ast.EndBlock.Statements.Count -eq 1 -and 
                        $dataBlock.Ast.EndBlock.Statements[0].PipelineElements.Count -eq 1 -and 
                        $dataBlock.Ast.EndBlock.Statements[0].PipelineElements[0].Expression -is [Management.Automation.Language.HashTableAst]) {
                        $selectXmlSplat.Namespace = $dataBlock.Invoke()
                    }
                } elseif ($myNamespaceInfo -match '^\s{0,}\{') {
                    $selectXmlSplat.Namespace = $myNamespaceInfo | ConvertFrom-Json -AsHashtable                    
                }
            } catch {
                Write-Debug "Failed to parse namespace info: $myNamespaceInfo"

            }        
        }
        elseif ($this.ClassName -match '.+?[\p{P}=].+?http') {
            $selectXmlSplat.Namespace = @{}
            foreach ($section in $this.ClassName -split ';') {
                $prefix, $xmlns = $section -split '\p{P}', 2
                $selectXmlSplat.Namespace[$prefix] = $xmlns -replace '[''"]'
            }
        }

        if (-not $pathKeys) {
            $this.Cmdlet.WriteVerbose("No path keys found, defaulting to '*.xml'")
            $pathKeys = "$($this.Cmdlet.MyInvocation.InvocationName)_Path"
            $this.PrivateData[$pathKeys] = '*.xml'
        }
        if (-not $xPathKey -and $pathKeys) {
            $this.Cmdlet.WriteVerbose("No XPath key found, defaulting to '/'")
            $xPathKey = "$($this.Cmdlet.MyInvocation.InvocationName)_XPath"
            $this.PrivateData[$xPathKey] = '/'
        }
        if ($pathKeys -and $xPathKey) {
            $selectXmlSplat.Path = $this.PrivateData[$pathKeys]
            $selectXmlSplat.XPath = $this.PrivateData[$xPathKey]        
            return $selectXmlSplat
        } else {
            return @{}
        }        
    }

    [PSObject] NewXmlElement([string]$ElementName, [Collections.IDictionary]$Dictionary) {
        $invocationName = $this.Cmdlet.MyInvocation.InvocationName
        $escapedInvocationName = '^' + ([Regex]::Escape($invocationName)) + '-'
        if ($script:debugPreference -eq 'Continue') {
            Write-Debug "Making Markup Element: $elementName"
        }
        $children = @(foreach ($parameterName in @($Dictionary.Keys)) {
            $myParameterPrivateData = 
                $this.PrivateData.Keys -match (
                    [Regex]::Escape($parameterName)
                ) -match $escapedInvocationName
            if ($script:debugPreference -eq 'Continue' -and $myParameterPrivateData) {
                Write-Debug "ParameterName: '$parameterName' has private data keys: $($myParameterPrivateData) "                            
            }
            if ($dictionary[$parameterName] -match '^\s{0,}\S+') {
                foreach ($elementNameKey in $myParameterPrivateData -match 'ElementName$') {
                    $elementNameValue = $this.PrivateData[$elementNameKey]
                    if ($elementNameValue -eq '.') {
                        [Security.SecurityElement]::Escape($dictionary[$parameterName])
                        $dictionary.Remove($parameterName)
                        continue
                    }
                    $childElementXml = foreach ($childElement in $dictionary[$parameterName]) {
                        $(
                            if ($childElement -is [switch] -and $childElement) {
                                "<$elementNameValue />"
                            } else {
                                "<$elementNameValue>" + 
                                    [Security.SecurityElement]::Escape($childElement) + 
                                "</$elementNameValue>"
                            }
                        ) -as [xml]
                    }                                
                    
                    if ($childElementXml) {
                        $childElementXml
                        $dictionary.Remove($parameterName)
                    }
                }
            }                        
            if ($dictionary[$parameterName] -is [xml] -or $dictionary[$parameterName] -is [xml[]]) {
                $dictionary[$parameterName]
                $dictionary.Remove($parameterName)
            } elseif (($dictionary[$parameterName] -as [xml[]])) {
                ($dictionary[$parameterName] -as [xml[]])
                $dictionary.Remove($parameterName)
            }            
        })
        $markupText = @(
        "<$ElementName"
            $elementAttributes = @(
                foreach ($keyValuePair in $dictionary.GetEnumerator()) {
                    $key = $keyValuePair.Key
                    $value = $keyValuePair.Value
                    if ($value -is [bool]) {
                        $value = $value.ToString().ToLower()
                    }
                    [Web.HttpUtility]::HtmlAttributeEncode($key) + '="' + [Web.HttpUtility]::HtmlAttributeEncode($Value) + '"'
                }
                $myXmlNamespaces = $this.PrivateData.Keys -match 'Namespace$' -match $escapedInvocationName
                if ($myXmlNamespaces) {
                    $xNamespaces = @($this.ClassName -split ';')
                    for ($xNamespaceIndex =0; $xNamespaceIndex -lt $xNamespaces.Length; $xNamespaceIndex++) {
                        $prefix, $xmlns = $xNamespaces[$xNamespaceIndex] -split '[\p{P}=]+', 2
                        if ($xNamespaceIndex -eq 0) {                            
                            "xmlns=`"$xmlns`""
                        } else {
                            "xmlns:$prefix=`"$xmlns`""
                        }
                    }
                }
            )
            if ($elementAttributes) {
                ' ' + ($elementAttributes -join ' ')
            }
        if ($children) {
            '>'
            Write-Verbose "Adding $($children.Count) children:"
            foreach ($child in $children) {
                if ($child.OuterXml) {
                    $child.OuterXml
                } else {
                    $child
                }
            }
            "</$ElementName>"
        } else {
            '/>'
        }
        ) -join ' '
        if ($markupText -as [xml]) {
            return $markupText -as [xml]
        } else {
            return $markupText
        }
    }

    
        
    ProcessRecord([Microsoft.PowerShell.Cmdletization.QueryBuilder]$QueryBuilder) {
        $selectXmlSplat = $this.SelectXmlSplatter()
        $this.Cmdlet.WriteVerbose("Processing query builder")
        if ($selectXmlSplat) {                        
            :nextNode foreach ($node in Select-Xml @selectXmlSplat *>&1) {                
                if ($node.GetType -and $node.GetType().Name -match '\.(?<StreamType>.+?)Record$') {
                    $this."Write$($matches.StreamType)"($node)
                }
                elseif ($QueryBuilder.MatchesFilters($node.Node, $QueryBuilder)) {
                    $this.Cmdlet.WriteObject($node)
                }                
            }
        }        
    }
    ProcessRecord([Microsoft.PowerShell.Cmdletization.QueryBuilder]$QueryBuilder, 
        [Microsoft.PowerShell.Cmdletization.MethodInvocationInfo] $MethodInvocationInfo, 
        [bool]$PassThru) {
        $this.Cmdlet.WriteVerbose("Processing query and method")
        $selectXmlSplat = $this.SelectXmlSplatter()    
        if ($selectXmlSplat) {
            :nextNode foreach ($node in Select-Xml @selectXmlSplat) {
                if ($node.GetType -and $node.GetType().Name -match '\.(?<StreamType>.+?)Record$') {
                    $this."Write$($matches.StreamType)"($node)
                }
                elseif ($queryBuilder.MatchesFilters($node.Node, $QueryBuilder)) {
                    ProcessRecord($node.Node, $MethodInvocationInfo, $PassThru)
                }
            }
        }        
    }
    ProcessRecord([object]$Instance, [Microsoft.PowerShell.Cmdletization.MethodInvocationInfo] $methodInvocationInfo, [bool]$PassThru) {
        $this.Cmdlet.WriteVerbose("Processing instance and method")
        # If there is no method name, change the object
        if (-not $methodInvocationInfo.MethodName) {
            foreach ($parameter in $methodInvocationInfo.Parameters) {
                
            }
            $this.Cmdlet.WriteObject($Instance)
            return
        }        
    }
    ProcessRecord([Microsoft.PowerShell.Cmdletization.MethodInvocationInfo] $methodInvocationInfo) {
        $elementName = $methodInvocationInfo.MethodName
        $Dictionary = [Ordered]@{}
        foreach ($parameter in $methodInvocationInfo.Parameters.GetEnumerator()) {
            if (-not $parameter.Value) {
                continue
            }
            $Dictionary[$parameter.Name] = $parameter.Value            
            if ($parameter.Value -is [switch]) {
                $Dictionary[$parameter.Name] = $parameter.Value
            }
        }
        $this.Cmdlet.WriteObject($this.NewXmlElement($elementName, $Dictionary))
    }
    EndProcessing() {
        $this.Cmdlet.WriteVerbose("Ending processing")
    }
}