Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get all the parents of a child XML Node in powershell

Tags:

powershell

xml

So I've been working on an XML file that has a folder structure stored in it and I want to be able to validate that the folder structure exists in the file system. While there are many ways to try and do this "Resolve-Path" "Get-ChildItems" etc ... I want to be able to build the path that's in the XML structure and put it into a string or something to I can just run Test-Path against it. Sounds easy enough right? Well probably for some seasoned Powershell veterans it might be but I'm new to PS and having some difficulty wrapping my head around this.

Here's a sample of XML with the Directory Structure

<config>
    <local>
        <setup>
            <folder name="FolderA" type="root">
                <folder name="FolderC"/>
                <folder name="FolderB">
                    <folder name="FolderD" type="thisone"/>
                </folder>
            </folder>
        </setup>
    </local>
</config>

If the child node was set to FolderD the ultimate goal is to build a string that resembles something like this

FolderA\FolderB\FolderD

It seems obvious to me that in order to get the full path you'd have to start at the end and walk the node tree backwards, and right there I get lost. The intention is to select only one path out of the multiple ones that potentially could exist. So for instance I want to check the path for FolderD. So I'd have to build just that path \FolderA\FolderB\FolderD and ignore the rest.

like image 504
todd1215 Avatar asked Mar 12 '23 14:03

todd1215


2 Answers

The simplest approach is probably:

  • identify the target leaf node
  • walk up the hierarchy in a loop and build the path from the name attribute values until the type=root node is hit
$xmlDoc = [xml] @'
<config>
    <local>
        <setup>
            <folder name="FolderA" type="root">
                <folder name="FolderC"/>
                <folder name="FolderB">
                    <folder name="FolderD" type="thisone"/>
                </folder>
            </folder>
        </setup>
    </local>
</config>
'@

# Get the leaf element of interest.
$leafNode = $xmlDoc.SelectSingleNode('//folder[@type="thisone"]')

# Simply build up the path by walking up the node hierarchy
# until an element with attribute type="root" is found.
$path = $leafNode.name
$node = $leafNode
while ($node.type -ne 'root') {
  $node = $node.ParentNode
  $path = $node.name + '\' + $path         #'# (ignore this - fixes syntax highglighting)
} 

# Output the resulting path:
#  FolderA\FolderB\FolderD
$path

Original answer, based on the original form of the question: May still be of interest with respect to building paths from element attributes top-down, using recursion.

With recursion, you can still employ a top-down approach:

$xmlDoc = [xml] @'
<folder name="FolderA">
    <folder name="FolderC"/>
    <folder name="FolderB">
        <folder name="FolderD"/>
    </folder>
</folder>
'@

# Define a function that walks the specified XML element's hierarchy to 
# down its leaf elements, building up a path of the values of the 
# specified attribute, and outputting the path for each leaf element.
function get-LeafAttribPaths($xmlEl, $attrName, $parentPath) {  
  $path = if ($parentPath) { join-path $parentPath $xmlEl.$attrName } else
                           { $xmlEl.$attrName }
  if ($xmlEl.ChildNodes.Count) { # interior element -> recurse over children
    foreach ($childXmlEl in $xmlEl.ChildNodes) {
      get-LeafAttribPaths $childXmlEl $attrName $path 
    }
  } else { # leaf element -> output the built-up path
    $path
  }
}

# Invoke the function with the document element of the XML document
# at hand and the name of the attribute from whose values to build the path.
$paths = get-LeafAttribPaths $xmlDoc.DocumentElement 'name'

# Output the resulting paths.
# With the above sample input:
#  FolderA\FolderC
#  FolderA\FolderB\FolderD
$paths

# Test paths for existence.
Test-Path $paths
like image 75
mklement0 Avatar answered Apr 28 '23 00:04

mklement0


I would write it that way:

$xmlDoc = [xml]'<folder name="FolderA">
    <folder name="FolderC"/>
    <folder name="FolderB">
        <folder name="FolderD"/>
    </folder>
</folder>'


function Get-XmlPath($node, $pathPrefix){
    $children = $node.SelectNodes('folder[@name]')
    $path = [System.IO.Path]::Combine($pathPrefix, $node.name)
    if ($children.Count) {
        foreach($child in $children) {
            Get-XmlPath $child $path
        }
    } else {
        $path
    }
}

Get-XmlPath $xmlDoc.DocumentElement | % { [PSCustomObject]@{Path = $_; Exist = Test-Path $_ } }

In my system it yields:

Path                        Exist
----                        -----
FolderA\FolderC             False
FolderA\FolderB\FolderD     False

Updated to reflect new xml structure

You can narrow search to selected node as follows:

Get-XmlPath $xmlDoc.SelectSingleNode('//folder[@type="root"]') | % { [PSCustomObject]@{Path = $_; Exist = Test-Path $_ } }

The key is to start from correct node. You can try other selectors as well if this don't suit:

#start from first folder node
$xmlDoc.SelectSingleNode('//folder')
#start from first folder node with root attribute
$xmlDoc.SelectSingleNode('//folder[@type="root"]')
like image 38
Paweł Dyl Avatar answered Apr 27 '23 23:04

Paweł Dyl