2

I am trying to select nodes from an XML file but am encountering issue that appear to be caused by the namespace.

The code below does not return anything. But if I remove the namespace from the XML file, I obtain the expected out.

MWE

$StandaloneXML = "test.xml"
# Load XML content
$NewStandaloneXML = New-Object -TypeName "System.XML.XMLDocument"
$NewStandaloneXML.Load($StandaloneXML)
# Get namespace
$Namespace = New-Object -TypeName "Xml.XmlNamespaceManager" -ArgumentList $NewStandaloneXML.NameTable
$Namespace.AddNamespace("jboss", $NewStandaloneXML.DocumentElement.NamespaceURI)
$NewStandaloneXML.SelectNodes("jboss:server/interfaces/interface", $Namespace)

XML

<?xml version="1.0" ?>
<server xmlns="urn:jboss:domain:4.2">
  <interfaces>
      <interface name="management">
          <inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
      </interface>
      <interface name="public">
          <inet-address value="${jboss.bind.address:127.0.0.1}"/>
      </interface>
  </interfaces>
</server>

Expected output

name       inet-address
----       ------------
management inet-address
public     inet-address
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
Akaizoku
  • 456
  • 5
  • 19
  • refer https://stackoverflow.com/questions/45676709/parsing-xml-with-namespace-with-powershell – Prasoon Karunan V Jan 24 '19 at 17:49
  • 1
    You must add the namespace prefix to all elements it applies to: `jboss:server/interfaces/interface` -> `/jboss:server/jboss:interfaces/jboss:interface` – Ansgar Wiechers Jan 24 '19 at 17:51
  • @AnsgarWiechers Thanks, that works. Why is it required? Is there no inheritance? I am struggling to find comprehensive documentation on the subject. – Akaizoku Jan 24 '19 at 18:04
  • 1
    XML nodes inherit the default namespace (and only the default namespace) from their parent. XPath path elements, however, don't. You could select nodes by their local name (`*[local-name()="nodename"]`), but `ns:nodename` is far more readable than that, don't you think? Also, [related](https://stackoverflow.com/q/40796231/1630171). – Ansgar Wiechers Jan 24 '19 at 18:46

1 Answers1

5

As @AnsgarWiechers stated, every node has to be prefixed by its namespace because there is no inheritance.

MWE

$StandaloneXML = "test.xml"
# Load XML content
$NewStandaloneXML = New-Object -TypeName "System.XML.XMLDocument"
$NewStandaloneXML.Load($StandaloneXML)
# Get namespace
$Namespace = New-Object -TypeName "Xml.XmlNamespaceManager" -ArgumentList $NewStandaloneXML.NameTable
$Namespace.AddNamespace("jboss", $NewStandaloneXML.DocumentElement.NamespaceURI)
$NewStandaloneXML.SelectNodes("jboss:server/jboss:interfaces/jboss:interface", $Namespace)

To make things easier, I have built a small function to automatically prefix each nodes in the XPath provided.

function Select-XMLNode {
  [CmdletBinding()]
  Param (
    [Parameter (
      Position    = 1,
      Mandatory   = $true,
      HelpMessage = "XML content"
    )]
    [ValidateNotNullOrEmpty()]
    [System.XML.XMLDocument]
    $XML,
    [Parameter (
      Position    = 2,
      Mandatory   = $true,
      HelpMessage = "XPath corresponding to the node"
    )]
    [ValidateNotNullOrEmpty()]
    [String]
    $XPath,
    [Parameter (
      Position    = 3,
      Mandatory   = $false,
      HelpMessage = "Namespace"
    )]
    [ValidateNotNullOrEmpty()]
    [String]
    $Namespace = $XML.DocumentElement.NamespaceURI
  )
  Begin {
    # Variables
    $Delimiter          = "/"
    $Alias              = "x"
    $SpecialCharacters  = [RegEx]::New('^[/.@]*')
    if ($XPath -match $SpecialCharacters) {
      $Prefix = $Matches[0]
      $XPath  = $XPath -replace $SpecialCharacters, ''
    }
  }
  Process {
    # Get namespace
    $NamespaceManager = New-Object -TypeName "Xml.XmlNamespaceManager" -ArgumentList $XML.NameTable
    $NamespaceManager.AddNamespace($Alias, $Namespace)
    # Split XPath to identify nodes
    $Nodes = $XPath.Split($Delimiter)
    $PrefixedNodes = New-Object -TypeName "System.Collections.ArrayList"
    # Prefix nodes with namespace (alias)
    foreach($Node in $Nodes) {
      if ($Node) {
        [Void]$PrefixedNodes.Add("${Alias}:${Node}")
      }
    }
    # Join prefixed-nodes to create new XPath with namespace
    $XPathWithNamespace = $PrefixedNodes -join $Delimiter
    # Check XPath prefix
    if ($Prefix) {
      $XPathWithNamespace = $Prefix + "" + $XPathWithNamespace
    }
    # Select and return nodes
    $SelectedNodes = $XML.SelectNodes($XPathWithNamespace, $NamespaceManager)
    return $SelectedNodes
  }
}
Akaizoku
  • 456
  • 5
  • 19