linuxxmlpowershellxpathread-eval-print-loop

How to print a list of xml elements and their properties in the Powershell REPL console?


Referencing:

https://www.red-gate.com/simple-talk/sysadmin/powershell/powershell-data-basics-xml/

and:

https://stackoverflow.com/a/65264118/4531180

how is a list of elements and their properties printed?

PS /home/nicholas/powershell> 
PS /home/nicholas/powershell> $doc = new-object System.Xml.XmlDocument
PS /home/nicholas/powershell> $file = resolve-path('./bookstore.xml') 
PS /home/nicholas/powershell> $doc.load($file)                                           
PS /home/nicholas/powershell> 
PS /home/nicholas/powershell> $doc.bookstore.book[1].author.first-name
ParserError: 
Line |
   1 |  $doc.bookstore.book[1].author.first-name
     |                                     ~~~~~
     | Unexpected token '-name' in expression or statement.

PS /home/nicholas/powershell> 
PS /home/nicholas/powershell> $doc.bookstore.book[1].author           

first-name last-name
---------- ---------
Margaret   Atwood

PS /home/nicholas/powershell> 
PS /home/nicholas/powershell> $doc.bookstore

bk          book
--          ----
urn:samples {book, book, book, book}

PS /home/nicholas/powershell> 

Solution

  • To address the for-display formatting problem:

    If you look closely at the sample output from your own answer, you'll see that even though the display is mostly helpful, the value of the author property is author instead of showing the (presumed) first-name and last-name child-element values.

    The problem is that PowerShell's default output formatting represents child elements that have:

    by the element's name only.

    Especially with deeply nested elements this results in unhelpful output.

    Workarounds, possibly in combination:

    See the examples below.


    # Sample XML document
    $xmlDoc = [xml] @"
    <?xml version="1.0"?>
    <bookstore>
       <book id="bk101">
          <author>
            <first-name>Matthew</first-name>
            <last-name>Gambardella</last-name>
          </author>
          <title>XML Developer's Guide</title>
          <genre>Computer</genre>
          <price>44.95</price>
          <publish_date>2000-10-01</publish_date>
          <description>An in-depth look at creating applications with XML.</description>
       </book>
       <book id="bk102">
          <author>
            <first-name>Kim</first-name>
            <last-name>Rall</last-name>
          </author>
          <title>Midnight Rain</title>
          <genre>Fantasy</genre>
          <price>5.95</price>
          <publish_date>2000-12-16</publish_date>
          <description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.</description>
       </book>
    </bookstore>
    "@
    

    To get a helpful representation of all <book> elements including the <author> child elements' <first-name> and <last-name> child elements via Select-Object and a calculated property:

    $xmldoc.bookstore.book | Select-Object id, 
       @{ n='author'; e={ $_.author.'first-name' + ' ' + $_.author.'last-name'} }, 
       title, genre, price, publish_date, description
    

    This yields (note how the author property now lists first and last name):

    id           : bk101
    author       : Matthew Gambardella
    title        : XML Developer's Guide
    genre        : Computer
    price        : 44.95
    publish_date : 2000-10-01
    description  : An in-depth look at creating applications with XML.
    
    id           : bk102
    author       : Kim Rall
    title        : Midnight Rain
    genre        : Fantasy
    price        : 5.95
    publish_date : 2000-12-16
    description  : A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.
    

    To get a helpful representation of all <book> elements via pretty-printed XML, via an auxiliary System.Xml.Linq.XDocument instance:

    # Load the assembly that contains XDocument.
    # Note: Required in Windows PowerShell only, and only once per session.
    Add-Type -AssemblyName System.Xml.Linq
    
    $xmldoc.bookstore.book | ForEach-Object {
      ([System.Xml.Linq.XDocument] $_.OuterXml).ToString()
    }
    

    This yields (a pretty-printed XML representation):

    <book id="bk101">
      <author>
        <first-name>Matthew</first-name>
        <last-name>Gambardella</last-name>
      </author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
    </book>
    <book id="bk102">
      <author>
        <first-name>Kim</first-name>
        <last-name>Rall</last-name>
      </author>
      <title>Midnight Rain</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-12-16</publish_date>
      <description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.</description>
    </book>
    

    Note that you could wrap the formatting code in a simple (filter) function named Format-Xml that you could put in your $PROFILE file (in Windows PowerShell, also place the Add-Type -AssemblyName System.Xml.Linq there, above it):

    filter Format-Xml { ([System.Xml.Linq.XDocument] $_.OuterXml).ToString() }
    

    Now the formatting is as simple as:

    $xmldoc.bookstore.book | Format-Xml