Source/Classes/ObjectParser.ps1
<#
.SYNOPSIS PowerShell Object Node Class .DESCRIPTION This class provides general properties and method to recursively iterate through to PowerShell Object Graph nodes. *** commented help will follow *** #> [Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssignments', 'Name', Justification = 'False positive')] param() Class PSNode { static [int]$DefaultMaxDepth = 10 [Int]$MaxDepth = [PSNode]::DefaultMaxDepth $Key # The dictionary key or property name of the node $Index # The list index of the node [Type]$Type [Int]$Depth hidden $_Value [PSNode]$ParentNode [PSNode]$RootNode = $this # This will be overwritten by the Append method hidden [Bool]$WarnedMaxDepth # Warn one per item branch PSNode($Object) { if ($Object -is [PSNode]) { $this._Value = $Object._Value } else { $this._Value = $Object } if ($Null -ne $Object) { $this.Type = $Object.GetType() } } static [PSNode] ParseInput($Object) { if ($Object -is [PSNode]) { return $Object } elseif ($Object -is [Management.Automation.PSCustomObject]) { return [PSObjectNode]$Object } elseif ($Object -is [ComponentModel.Component]) { return [PSObjectNode]$Object } elseif ($Object -is [Collections.IDictionary]) { return [PSDictionaryNode]$Object } elseif ($Object -is [Collections.ICollection]) { return [PSListNode]$Object } else { return [PSLeafNode]$Object } } static [PSNode] ParseInput($Object, [int]$MaxDepth) { $Node = [PSNode]::parseInput($Object) $Node.MaxDepth = $MaxDepth return $Node } [PSNode] Append($Object) { $Node = [PSNode]::ParseInput($Object) $Node.Depth = $this.Depth + 1 $Node.ParentNode = $this $Node.RootNode = $this.RootNode return $Node } hidden [System.Collections.Generic.List[PSNode]] get_Path() { if ($this.ParentNode) { $Path = $this.ParentNode.get_Path() } else { $Path = [System.Collections.Generic.List[PSNode]]::new() } $Path.Add($this) return $Path } hidden [String] get_PathName() { return -Join @($this.get_Path()).foreach{ if ($Null -ne $_.Key) { if ($_.Key -is [ValueType]) { ".$($_.Key)" } elseif ($_.Key -isnot [String]) { ".[$($_.Key.GetType())]'$($_.Key)'" } elseif ($_.Key -Match '^[_,a-z]+\w*$') { ".$($_.Key)" } else { ".'$($_.Key)'" } } elseif ($Null -ne $_.Index) { "[$($_.Index)]" } } } } Class PSLeafNode : PSNode { PSLeafNode($Object) : base($Object) { } } Class PSCollectionNode : PSNode { PSCollectionNode($Object) : base($Object) { } hidden [bool]MaxDepthReached() { $MaxDepthReached = $this.Depth -ge $this.RootNode.MaxDepth if ($MaxDepthReached -and -not $this.WarnedMaxDepth) { Write-Warning "$($this.Path) reached the maximum depth of $($this.RootNode.MaxDepth)." $this.WarnedMaxDepth = $true } return $MaxDepthReached } } Class PSListNode : PSCollectionNode { PSListNode($Object) : base($Object) { } hidden [Int]get_Count() { return $this._Value.get_Count() } hidden [Array]get_Keys() { if ($this._Value.Length) { return 0..($this._Value.Length - 1) } return @() } hidden [Array]get_Values() { return $this._Value } [Bool]Contains($Index) { return $Index -ge 0 -and $Index -lt $this.get_Count() } [Object]GetItem($Index) { return $this._Value[$Index] } SetItem($Index, $Value) { $this._Value[$Index] = $Value } [PSNode[]]get_ChildNodes() { if ($this.MaxDepthReached()) { return @() } return @( for ($Index = 0; $Index -lt $this._Value.Get_Count(); $Index++) { $Node = $this.Append($this._Value[$Index]) $Node.Index = $Index $Node } ) } [Object]GetChildNode([Int]$Index) { if ($this.MaxDepthReached()) { return $Null } if ($Index -lt 0 -or $Index -ge $this._Value.get_Count()) { return $Null } # Return $Null if index is out of bound $Node = $this.Append($this._Value[$Index]) $Node.Index = $Index return $Node } } Class PSMapNode : PSCollectionNode { PSMapNode($Object) : base($Object) { } } Class PSDictionaryNode : PSMapNode { PSDictionaryNode($Object) : base($Object) { } hidden [Int]get_Count() { return $this._Value.get_Count() } hidden [Array]get_Keys() { return $this._Value.get_Keys() } hidden [Array]get_Values() { return $this._Value.get_Values() } [Bool]Contains($Key) { return $this._Value.Contains($Key) } [Object]GetItem($Key) { return $this._Value[$Key] } SetItem($Key, $Value) { $this._Value[$Key] = $Value } [PSNode[]]get_ChildNodes() { if ($this.MaxDepthReached()) { return @() } return @( foreach($Key in $this._Value.get_Keys()) { $Node = $this.Append($this._Value[$Key]) $Node.Key = $Key $Node } ) } [Object]GetChildNode($Key) { if ($this.MaxDepthReached()) { return $Null } if (-not $this._Value.Contains($Key)) { return $Null } # Return $Null if index is out of bound $Node = $this.Append($this._Value[$Key]) $Node.Key = $Key return $Node } } Class PSObjectNode : PSMapNode { PSObjectNode($Object) : base($Object) { } hidden [Int]get_Count() { return @($this._Value.PSObject.Properties).get_Count() } hidden [Array]get_Keys() { return $this._Value.PSObject.Properties.Name } hidden [Array]get_Values() { return $this._Value.PSObject.Properties.Value } [Bool]Contains($Name) { return $this._Value.PSObject.Properties[$Name] } [Object]GetItem($Name) { return $this._Value.PSObject.Properties[$Name].Value } SetItem($Name, $Value) { $this._Value.PSObject.Properties[$Name].Value = $Value } [PSNode[]]get_ChildNodes() { if ($this.MaxDepthReached()) { return @() } return @( foreach($Property in $this._Value.PSObject.Properties) { $Node = $this.Append($Property.Value) $Node.Key = $Property.Name $Node } ) } [Object]GetChildNode([String]$Name) { if ($this.MaxDepthReached()) { return $Null } if ($Name -NotIn $this._Value.PSObject.Properties.Name) { return $Null } # Return $Null if index is out of bound $Node = $this.Append($this._Value.PSObject.Properties[$Name].Value) $Node.Key = $Name return $Node } } Update-TypeData -Force -TypeName PSNode -MemberName Value -MemberType ScriptProperty -Value { return ,$this._Value } -SecondValue { Throw 'Node values are readonly. To change a node value, use the SetItem() method of its parent and reload the child node(s) if required.' } Update-TypeData -Force -TypeName PSNode -MemberName Count -MemberType ScriptProperty -Value { return $this.get_Count() } Update-TypeData -Force -TypeName PSNode -MemberName Keys -MemberType ScriptProperty -Value { return ,$this.get_Keys() } Update-TypeData -Force -TypeName PSNode -MemberName Values -MemberType ScriptProperty -Value { return ,$this.get_Values() } Update-TypeData -Force -TypeName PSNode -MemberName ChildNodes -MemberType ScriptProperty -Value { return ,$this.get_ChildNodes() } Update-TypeData -Force -TypeName PSNode -MemberName Path -MemberType ScriptProperty -Value { return ,$this.get_Path() } Update-TypeData -Force -TypeName PSNode -MemberName PathName -MemberType ScriptProperty -Value { return $this.get_PathName() } |