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
4
  • refer stackoverflow.com/questions/45676709/… Commented Jan 24, 2019 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 Commented Jan 24, 2019 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. Commented Jan 24, 2019 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. Commented Jan 24, 2019 at 18:46

1 Answer 1

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
  }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.