Utilities/Resolve-InnerPathFromDictionary.ps1
|
# ══════════════════════════════════════════════════════════════════════════════ # 📦 FUNCTION: Resolve-InnerPathFromDictionary # 🧠 Sovereign memory path resolver with symbolic and filtered access # 🏷️ Slots: Memory | Lookup | Graph # 🛠️ Authors: Shadow PhanTom 🤖/☠️🧁👾️ • Neural Alchemist ⚗️☣️🐲 # ══════════════════════════════════════════════════════════════════════════════ function Resolve-InnerPathFromDictionary { param ( [Parameter(Mandatory)] $Dictionary, [Parameter(Mandatory)] [string]$Path, [object]$Default, [string]$SignalLevel = "Critical", [string[]]$SignalTags = $null ) $hasDefault = $PSBoundParameters.ContainsKey('Default') $opSignal = [Signal]::Start("Resolve-InnerPathFromDictionary", $Dictionary) | Select-Object -Last 1 # ░▒▓█ SYMBOL MAP █▓▒░ $symbolMap = @{ "%" = "Jacket" "*" = "Pointer" "@" = "Result" "$" = "Signal" "#" = "Grid" "~" = "Control" # Reserved "^" = "XPathTail" "|" = "DefaultOnPath" # Provisional, not in use ":" = "Dimension" "&" = "Binding" "!" = "Polarity" "[" = "Meta" } # ░▒▓█ XPATH TAIL █▓▒░ # If '^' exists, everything after it is treated as an XPath-tail mini language $basePath = $Path $xpathTail = $null $caretIndex = $Path.IndexOf('^') if ($caretIndex -ge 0) { $basePath = $Path.Substring(0, $caretIndex) $xpathTail = $Path.Substring($caretIndex + 1) } function Split-PathPreservingQuotes { param([string]$Path) $segments = @() $buffer = "" $inQuote = $false $quoteChar = $null for ($i = 0; $i -lt $Path.Length; $i++) { $c = $Path[$i] if ($inQuote) { if ($c -eq $quoteChar) { # End quote $inQuote = $false continue } # Allow escaped quotes \" or \' if ($c -eq '\' -and $i + 1 -lt $Path.Length -and $Path[$i+1] -eq $quoteChar) { $buffer += $quoteChar $i++ continue } $buffer += $c continue } # Start of quoted segment if ($c -eq '"' -or $c -eq "'") { $inQuote = $true $quoteChar = $c continue } # Dot = segment boundary if ($c -eq '.') { if ($buffer.Length -gt 0) { $segments += $buffer $buffer = "" } continue } # Normal character $buffer += $c } # Final segment if ($buffer.Length -gt 0) { $segments += $buffer } return $segments } function Handle-NullWithDefault { param ( [Parameter(Mandatory)] [Signal]$OperationSignal, [Parameter(Mandatory)] [bool]$HasDefault, $Default, [Parameter(Mandatory)] [string]$DefaultForNullMessage, [Parameter(Mandatory)] [string]$NullMessage, [string]$SignalLevel = "Critical", [string[]]$SignalTags = $null ) if ($HasDefault) { $OperationSignal.SetResult($Default) $OperationSignal.LogInformation($DefaultForNullMessage, $SignalTags) } else { $OperationSignal.LogMessage($SignalLevel, $NullMessage, $SignalTags) } return $OperationSignal } function Expand-Symbols { param ([string[]]$segments) return $segments | ForEach-Object { if ($symbolMap.ContainsKey($_)) { $symbolMap[$_] } else { $_ } } } function ConvertFrom-Xml { param ( [Parameter(Mandatory)] [System.Xml.XmlNode]$Node ) # If the node is a simple text node if ($Node.ChildNodes.Count -eq 1 -and $Node.FirstChild.NodeType -eq 'Text') { return $Node.InnerText } $hash = @{} # Attributes (optional but useful) foreach ($attr in $Node.Attributes) { $hash["@${($attr.Name)}"] = $attr.Value } foreach ($child in $Node.ChildNodes | Where-Object NodeType -eq 'Element') { $value = ConvertFrom-Xml -Node $child if ($hash.ContainsKey($child.Name)) { # Promote to array if ($hash[$child.Name] -isnot [System.Collections.IList]) { $hash[$child.Name] = @($hash[$child.Name]) } $hash[$child.Name] += $value } else { $hash[$child.Name] = $value } } return [pscustomobject]$hash } function Convert-SimpleXPathTailToXPath { param([Parameter(Mandatory)][string]$Tail) # Allow optional leading '.' after '^' $t = $Tail -replace '^\.', '' # Telemetry.Data -> Telemetry/Data $t = $t -replace '\.', '/' # [Name=Trace] -> [@Name='Trace'] $t = [regex]::Replace($t, '\[(?<k>[^=\]]+?)=(?<v>[^\]]+?)\]', { param($m) $k = $m.Groups['k'].Value.Trim() $v = $m.Groups['v'].Value.Trim().Trim("'`"") # strip quotes if present "[@$k='$v']" }) # Default to searching anywhere in the doc if ($t -notmatch '^(\/|\/\/)') { $t = "//$t" } return $t } function Invoke-ResolveXPathTail { param ( [Parameter(Mandatory)] [Signal]$OperationSignal, [Parameter(Mandatory)] $Current, [Parameter(Mandatory)] [string]$XPathTail, [Parameter(Mandatory)] [bool]$HasDefault, $Default, [string]$SignalLevel = "Critical", [string[]]$SignalTags = $null ) $isXml = ($Current -is [xml]) -or ($Current -is [System.Xml.XmlDocument]) -or ($Current -is [System.Xml.XmlNode]) if (-not $isXml) { $message = "XPath tail '^' encountered but current is not XML. Type: $($Current.GetType().FullName). Tail: $XPathTail" return Handle-NullWithDefault ` -OperationSignal $OperationSignal ` -HasDefault $HasDefault ` -Default $Default ` -DefaultForNullMessage "$message Applying default." ` -NullMessage $message ` -SignalLevel $SignalLevel ` -SignalTags $SignalTags } $xpath = Convert-SimpleXPathTailToXPath -Tail $XPathTail try { $selected = $Current.SelectNodes($xpath) if ($null -eq $selected) { $message = "XPath returned null. XPath: $xpath" return Handle-NullWithDefault ` -OperationSignal $OperationSignal ` -HasDefault $HasDefault ` -Default $Default ` -DefaultForNullMessage "$message Applying default." ` -NullMessage $message ` -SignalLevel $SignalLevel ` -SignalTags $SignalTags } $asArray = @() foreach ($n in $selected) { $asArray += $n } if ($asArray.Count -eq 0) { $message = "XPath matched 0 nodes. XPath: $xpath" return Handle-NullWithDefault ` -OperationSignal $OperationSignal ` -HasDefault $HasDefault ` -Default $Default ` -DefaultForNullMessage "$message Applying default." ` -NullMessage $message ` -SignalLevel $SignalLevel ` -SignalTags $SignalTags } elseif ($asArray.Count -eq 1) { # $OperationSignal.LogVerbose("🧬 XPath applied: $xpath") return $asArray[0] } else { # $OperationSignal.LogVerbose("🧬 XPath applied (multiple results): $xpath") return $asArray } } catch { $message = "Exception during XPath select: $_ (XPath: $xpath)" return Handle-NullWithDefault ` -OperationSignal $OperationSignal ` -HasDefault $HasDefault ` -Default $Default ` -DefaultForNullMessage "$message Applying default." ` -NullMessage $message ` -SignalLevel $SignalLevel ` -SignalTags $SignalTags } } try { $char = $basePath -Contains '\:' ? '\:' : '\.' if ($char -eq '\:') { $char = $char } $rawSegments = $basePath -split '\.' $rawSegments = Split-PathPreservingQuotes -Path $basePath $segments = Expand-Symbols $rawSegments $current = $Dictionary $lastSegmentName = "" foreach ($segment in $segments) { if ($null -eq $current) { return Handle-NullWithDefault -DefaultForNullMessage "Null encountered while traversing '$segment', applying default." -NullMessage "Null encountered while traversing '$segment'" -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } $processed = $false switch ($segment) { "Pointer" { if ($current -is [Signal]) { $current = $current.Pointer # $opSignal.LogVerbose("🔗 Dereferenced *Pointer") $processed = $true } else { $opSignal.LogWarning("Expected Signal for *Pointer, got $($current.GetType().Name)", $SignalTags) } } "Result" { if ($current -is [Signal]) { $current = $current.Result # $opSignal.LogVerbose("🎯 Dereferenced @Result") $processed = $true } else { $opSignal.LogWarning("Expected Signal for @Result, got $($current.GetType().Name)", $SignalTags) } } "Signal" { # This segment is structural, let it use the below logic to determine how to return the Signal #$processed = $true continue } "Jacket" { if ($current -is [Signal]) { $current = $current.Jacket # $opSignal.LogVerbose("🧥 Accessed %Jacket") $processed = $true } else { $opSignal.LogWarning("Expected Signal for %Jacket, got $($current.GetType().Name)", $SignalTags) } } "Grid" { if ($current -is [Graph]) { $current = $current.Grid # $opSignal.LogVerbose("🧩 Accessed #Grid") $processed = $true } else { $opSignal.LogWarning("Expected Graph for #Grid, got $($current.GetType().Name)", $SignalTags) } } } if ($processed) { $lastSegmentName = $segment continue } $parsed = Invoke-ParseFilterSegment $segment if ($parsed.IsFilter) { $arrayKey = $parsed.ArrayKey $array = $null if ($current -is [System.Collections.IDictionary] -and $current.ContainsKey($arrayKey)) { $array = $current[$arrayKey] } elseif ($current -is [pscustomobject] -and $current.PSObject.Properties.Name -contains $arrayKey) { $array = $current.$arrayKey } else { $opSignal.LogCritical("Array key '$arrayKey' not found.") return $opSignal } $match = Resolve-FilteredArrayItem -Array $array -Filters $parsed.Filters -Signal $opSignal if ($null -eq $match) { return $opSignal } $current = $match continue } $key = $parsed.Raw if ($current -is [System.Collections.IDictionary] -and $current.Contains($key)) { $current = $current[$key] } elseif ($current -is [hashtable] -and $current.Contains($key)) { $current = $current[$key] } elseif ($current -is [pscustomobject] -and $current.PSObject.Properties.Name -contains $key) { $current = $current.$key } elseif ($current -is [System.Collections.IEnumerable] -and -not ($current -is [string])) { $found = $null # Todo: Review: Leaving this for reference, made change on 12-30-25 to not let it just convert it to an int since a string converts to a non-null int on the test. # if ($key -is [int] -or ($null -ne $key -as [int])) { $inVal = $key -as [int] if ($segment -is [int] -or ($null -ne $inVal)) { $index = [int]$segment $list = @($current) # Ensure it's indexable if ($index -ge 0 -and $index -lt $list.Count) { $current = $list[$index] } else { $opSignal.LogMessage($SignalLevel, "Index '$index' out of bounds (0..$($list.Count - 1)).", $SignalTags) return $opSignal } } else { if ($key -eq "Count") { $found = $current.Count } else { # Determine selector property + value $propertyName = 'Name' $propertyValue = $key if ($key -match '^(?<prop>[^=]+)=(?<val>.+)$') { $propertyName = $matches['prop'] $propertyValue = $matches['val'] } foreach ($item in $current) { if ($null -eq $item) { continue } # Allow selecting signals too (Name is common) if ($item -is [Signal]) { if ($propertyName -eq 'Name' -and $item.Name -eq $propertyValue) { $found = $item break } continue } $prop = $item.PSObject.Properties[$propertyName] if ($null -ne $prop -and "$($prop.Value)" -eq $propertyValue) { $found = $item break } } } if ($found) { $current = $found } else { $message = if ($current -is [string]) { $snippet = $current.Substring(0, [Math]::Min(40, $current.Length)) "Unsupported traversal type: String ('$snippet') (lastSegmentName: $lastSegmentName)" } else { $typeName = if ($null -eq $current) { '<null>' } else { $current.GetType().FullName } "Not Found segment name in type: $typeName, lastSegmentName: $lastSegmentName, key: $key" } return Handle-NullWithDefault -DefaultForNullMessage " $message, applying default." -NullMessage $message -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } } } elseif ($current -is [pscustomobject]) { $props = $current.PSObject.Properties.Name $match = $current.PSObject.Properties.Match($key) if (-not $match -or $null -eq ($value = $current.$key)) { $message = "Cannot access '$key' on 'PSCustomObject' (lastSegmentName: $lastSegmentName, available: $($props -join ', '))" return Handle-NullWithDefault -DefaultForNullMessage " $message, applying default." -NullMessage $message -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } $current = $value } elseif ($current.GetType().IsClass -and $current.GetType().Namespace -ne "System") { $type = $current.GetType() $props = $type.GetProperties() | ForEach-Object Name $methods = $type.GetMethods() | ForEach-Object Name $prop = $type.GetProperty($key) # 1) Normal property path if ($null -ne $prop) { $value = $prop.GetValue($current) if ($null -eq $value) { $message = "Cannot access '$key' on '$($type.Name)' (lastSegmentName: $lastSegmentName, available props: $($props -join ', '))" return Handle-NullWithDefault -DefaultForNullMessage " $message, applying default." -NullMessage $message -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } $current = $value continue } # 2) Method-backed getter fallback (e.g. get_LevelValue()) $getterNames = @( "get_$key", # PowerShell/C# property getter convention "Get$key", # optional friendly convention "$key" # Optional Method only way ) $getter = $null foreach ($name in $getterNames) { $getter = $type.GetMethod($name, [Type[]]@()) if ($null -ne $getter) { break } } if ($null -ne $getter) { $value = $getter.Invoke($current, @()) if ($null -eq $value) { $message = "Getter '$($getter.Name)()' returned null for '$key' on '$($type.Name)' (lastSegmentName: $lastSegmentName)" return Handle-NullWithDefault -DefaultForNullMessage " $message, applying default." -NullMessage $message -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } $current = $value continue } # 3) Not found (property or getter) $message = "Cannot access '$key' on '$($type.Name)' (lastSegmentName: $lastSegmentName, available props: $($props -join ', '), available methods: $($methods -join ', '))" return Handle-NullWithDefault -DefaultForNullMessage " $message, applying default." -NullMessage $message -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } <# elseif ($current.GetType().IsClass -and $current.GetType().Namespace -ne "System") { $type = $current.GetType() $prop = $type.GetProperty($key) $props = $type.GetProperties() | ForEach-Object Name if ($null -eq $prop -or $null -eq ($value = $prop.GetValue($current))) { $message = "Cannot access '$key' on '$($type.Name)' (lastSegmentName: $lastSegmentName, available: $($props -join ', '))" return Handle-NullWithDefault -DefaultForNullMessage " $message, applying default." -NullMessage $message -SignalLevel $SignalLevel -SignalTags $SignalTags -HasDefault $hasDefault -Default $Default -OperationSignal $opSignal } $current = $value } #> else { $opSignal.LogMessage($SignalLevel, "Unsupported traversal type: $($current.GetType().FullName) (lastSegmentName: $lastSegmentName)", $SignalTags) return $opSignal } $lastSegmentName = $segment } if ($xpathTail) { $result = Invoke-ResolveXPathTail ` -OperationSignal $opSignal ` -Current $current ` -XPathTail $xpathTail ` -HasDefault $hasDefault ` -Default $Default ` -SignalLevel $SignalLevel ` -SignalTags $SignalTags | Select-Object -Last 1 # If a Signal comes back, it's an early-exit failure # TODO: Change this to merge with $opsignal and null $current if ($result -is [Signal]) { return $result } $convertedResult = ConvertFrom-Xml -Node $result $current = $convertedResult } if ($null -eq $current -and $HasDefault) { $opSignal.SetResult($Default) } else { $opSignal.SetResult($current) } # $opSignal.LogInformation("✅ Successfully resolved path '$Path'") } catch { $opSignal.LogMessage($SignalLevel, "Exception during path resolution: $_ (lastSegmentName: $lastSegmentName)", $SignalTags, $_) } return $opSignal } |