locked
XML Nodes by attribute RRS feed

  • Question

  • [edit] I did forget to mention this is my first in powershell.

    How can I change the hard coded index "$table.AXIS[2]" to use the Attribute 'id' for AXIS selection.

    Something along the lines of $table.AXIS[@id="z"] would be great.

    Or is there a better way ...

    [xml]$xmlf = @"
    <FORMAT>
      <TABLE>
        <AXIS id="x">
        </AXIS>
        <AXIS id="y">
          <LABEL index="0" value="A" />
          <LABEL index="1" value="B" />
          <LABEL index="2" value="C" />
          <LABEL index="3" value="D" />
          <LABEL index="4" value="E" />
        </AXIS>
        <AXIS id="z">
          <ITEM row="1">
            <LOCATION address="0x18A36" />
          </ITEM>
          <ITEM row="2">
            <LOCATION address="0x18A38" />
          </ITEM>
          <ITEM row="3">
            <LOCATION address="0x18AF6" />
          </ITEM>
          <ITEM row="4">
            <LOCATION address="0x18AF8" />
          </ITEM>
          <ITEM row="5">
            <LOCATION address="0x18A3A" />
          </ITEM>
        </AXIS>
      </TABLE>
    </FORMAT>
    "@
    
    foreach($table in $xmlf.FORMAT.TABLE) {
      foreach($label in $table.AXIS[1].LABEL) {
        foreach($item in $table.AXIS[2].ITEM) {
          if($item.row -ne $null) {
            if($label.index -eq $item.row - 1) {
              Write-Host($label.value + " " + $item.LOCATION.address)
              # Much processing will be done here with the collected elements.
            }
          }
        }
      }
    }

    I do not have control over the XML format, it is what it is.
    I have removed a ton of unneeded elements/attributes for size and simplicity sake.
    There could be hundreds of tables.
    Each table will have 3 AXIS (x,y,z).
    There could be hundreds of ITEM elements, the only ones that matter have a 'row' attribute.

    The out put is ...
    A 0x18A36
    B 0x18A38
    C 0x18AF6
    D 0x18AF8
    E 0x18A3A

    Thank you


    Monday, January 6, 2020 5:17 PM

All replies

  • The following selects only ITEM elements that have a "row" attribute.

    $xml.SelectNodes('//ITEM[@row]')


    \_(ツ)_/

    Monday, January 6, 2020 5:24 PM
  • The following selects only ITEM elements that have a "row" attribute.

    $xml.SelectNodes('//ITEM[@row]')


    \_(ツ)_/

    Thank you,

    Educational ...

    That changes the code to

    foreach($table in $xmlf.FORMAT.TABLE) {
      foreach($label in $table.AXIS[1].LABEL) {
        foreach($item in $table.AXIS[2].SelectNodes('//ITEM[@row]')) {
          if($label.index -eq $item.row - 1) {
            Write-Host($label.value + " " + $item.LOCATION.address)
            # Much processing will be done here with the collected elements.
          }
        }
      }
    }

    Leaving the original question still to be unanswered.

    [edit]Broken code obviously.
    Monday, January 6, 2020 6:16 PM
  • No. Just replace all of your code with my code and it will give you all nodes with "row" as an attribute. That is what you asked for,

    There is no need for any of the code you have posted.  Just use XPath.


    \_(ツ)_/

    Monday, January 6, 2020 7:49 PM
  • I would have to say you didn't read the question at all ... because you are so very far off.

    What you provided does NOT produce the output as I have shown.

    In fact your code gives every ITEM node in the whole file that has a row attribute, not just the ITEM nodes of the current table. Nor does it get the LABEL value from that same table that gets associated with ITEM row of that table.

    Monday, January 6, 2020 9:32 PM
  • Your request is a bit vague. Are you asking for all of the "table" nodes that have a child with a "row" attribute?

    When we are trying to describe a query against a hierarchical data set then it is necessary to describe the hierarchical relationship that we wish to target. Once we can make a statement that correctly describes the target then we can create a query that returns the nodes required.

    Start by stating what you want as a result and then the criteria for the query.  To implement use the statement to build the XPath then modify it to pull the correct ancestor nodes and dereference to access the required target.

    The query I posted was intended to demonstrate how to use an attribute to select only nodes that contained that attribute.  This was in response to you original question: "Something along the lines of $table.AXIS[@id="z"] would be great."

    I know that complex declarative logical thinking is a bit of a challenge for non-programmers and for many classical programmers but XML/XPath is a declarative language scheme the same as SQL but with a twist;  it can query hierarchical data that has embedded relations.

    IN any data extraction query in any system it is usual to use the most specific selector first then modify the query for each less specific filter.  Your question makes me assume that the "row" is most specific.  That then makes the result of the "row" query contain all nodes of interest.  Now just add the next most specific filter and you are on you way to a result.  After all candidate nodes are collected then pick the values you want to display.


    \_(ツ)_/

    Tuesday, January 7, 2020 5:31 AM
  • You also have failed to determine a relation between the LABEL element and the LOCATION element. What is their relation. There are no parallel attributes.

    As a next step the following selects all AXIS nodes that conform and returns the LABEL nodes.  To then get the related nodes and assign we need to be able to state the relationship.

    The following gets the related LABEL nodes.

    $xml.SelectNodes('//AXIS[ITEM[@row]]').ParentNode.SelectNodes('//LABEL')

    The following returns the location nodes:

    $xml.SelectNodes('//AXIS[ITEM[@row]]').ParentNode.SelectNodes('//LOCATION')

    Once we can state the relationship then it is possible to devise a query that can combine the two.  We could also enumerate the parent nodes and use the objects to extract the values if we had a relation to base this on.


    \_(ツ)_/

    Tuesday, January 7, 2020 5:52 AM
  • The following are the required nodes grouped by parent.

    $xml.SelectNodes('//AXIS[ITEM[@row]]').ParentNode|
        ForEach-Object{
            $labels = $_.SelectNodes('//LABEL')
            $locations = $_.SelectNodes('//LOCATION')
        }

    Once you can define the relationship between them then you are set assuming that your XML is only what you say it is.


    \_(ツ)_/


    • Edited by jrv Tuesday, January 7, 2020 5:57 AM
    Tuesday, January 7, 2020 5:56 AM