1

I am looping through multiple XML files in a directory. Each file has identical structure:

<input>
    <filePattern>
        <marketCode>cbf_d</marketCode>
        <format>txt</format>
    </filePattern>
</input>
<input>
    <filePattern>
        <marketCode>lvd_cbf_b</marketCode>
        <format>csv</format>
    </filePattern>
</input>
<input>
    <filePattern>
        <marketCode>cbf_a</marketCode>
        <format>zip</format>
    </filePattern>
</input>

My purpose is to loop through each input node within a file, extract InnerText values from marketCode and format and then concatenate them into a single string:

Get-ChildItem -Path 'C:\test\cbf\*merge*' -Recurse |
ForEach-Object {
    $xml_file = $_ 
    
    $content = [xml](Get-Content $xml_file)
    $nodes = $content.SelectNodes('//input')    

    
    foreach ($node in $nodes) {
        $marketCode = $node.SelectNodes('//input/filePattern/marketCode').InnerText
        $format = $node.SelectNodes('//input/filePattern/format').InnerText
        
                                    
    }
    #Trying to cancatenate InnerText values here:
    $marketCode + '_' + $format
    
        
}

The output that I get:

cbf_d
lvd_cbf_b
cbf_a
_
txt
csv
zip

The output that I expect:

cbf_d_txt
lvd_cbf_b_csv
cbf_a_zip

How to properly concatenate values of InnerText?

kamokoba
  • 497
  • 9
  • 17

2 Answers2

2

By using //input you are resetting your base path to "global" - which means ignoring your context node $node. The solution is to use a relative path (relative to $node) in your SelectNodes expressions. Furthermore, you only concatenated the strings once per file - and not once per <input> element - so the concatenation operation needs to be shifted into the loop.

Get-ChildItem -Path 'C:\test\cbf\*merge*' -Recurse |
ForEach-Object {
    $xml_file = $_ 
    
    $content = [xml](Get-Content $xml_file)
    $nodes = $content.SelectNodes('//input')    

    
    foreach ($node in $nodes) {
        $marketCode = $node.SelectNodes('filePattern/marketCode').InnerText
        $format = $node.SelectNodes('filePattern/format').InnerText
        #Trying to concatenate InnerText values here:
        $marketCode + '_' + $format
    }
}

The output is as desired.

zx485
  • 28,498
  • 28
  • 50
  • 59
1

To complement zx485's helpful answer, which explains the problems with your attempt well and offers an effective solution, with a more concise solution that uses the Select-Xml cmdlet:

Get-ChildItem C:\test\cbf\*merge* -Recurse |
  Select-Xml '//input/filePattern' | 
    ForEach-Object { $_.Node.marketCode, $_.Node.format -join '_' }
  • Select-Xml can perform XPath queries directly on input files, without having to parse the file content into an XML DOM ([xml] (System.Xml.XmlDocument)) manually.

  • It outputs SelectXmlInfo wrapper objects with metadata, whose .Node property contains the matching node(s).

    • As an aside: Given that the metadata is often not needed, it would be convenient if Select-Xml could be instructed to return the nodes only, which is the subject of GitHub feature request #13669.
  • Thanks to PowerShell's convenient adaption of the XML DOM, you can access child elements on the nodes returned simply as properties (e.g., .marketCode to access the <marketCode> child element), and if those elements only have text content (only a child text node), the text is directly returned (no need for .InnerText).

mklement0
  • 382,024
  • 64
  • 607
  • 775