Gordon
Gordon

Reputation: 6873

XML 'name' attribute, and XML variable type with SelectNodes

I have two questions regarding XML...

  1. Given an XML node like this

    <Product name="Dave">
    

    and assigned to a variable with SelectSingleNode, $xmlNode.name will return Dave. However, if the XML is

    <Product id="Dave">
    

    Then $xmlNode.name will return Product.

    Since "name' can refer to either an attribute of the node, or the name of the node itself, does this argue against using Name as an attribute at all, and best practice is to use something like ID as here? Or is this just not really likely to be an issue, m and if name makes more sense then run with it?

  2. Given a bunch of Product nodes, and code like this

    $tempProducts = $temp.xml.SelectNodes('//Product')
    

    Write-Host "$($tempProducts.GetType())" will suggest that the type of the resulting variable is

    System.Xml.XPathNodeList, System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

    And yet, [System.Xml.XPathNodeList]$tempProducts = $temp.xml.SelectNodes('//Product') will throw an error,

    Unable to find type [System.Xml.XPathNodeList].

This comes up as I have been typing my variables in a large script with the thinking that it might save me some debug time. But this one condition seems to be giving me trouble.

Any thoughts?

Upvotes: 2

Views: 1058

Answers (1)

mklement0
mklement0

Reputation: 439892

Re 1:

PowerShell adapts XML elements for convenience, by surfacing both their child nodes and their attributes as regular object properties.

This generally makes working with XML in PowerShell very convenient, but has its pitfalls:

If there's a naming conflict with the properties of the underlying System.Xml.XmlElement type, the adapted properties take precedence.

This happened with the Name attribute in your sample XML: PowerShell exposes it as a .Name property, which shadows the XMlElement's .Name property that reports the tag name.

A simple workaround is to call the property accessor method, .get_Name() to get the shadowed XmlElement property:

$node = ([xml] '<xml><Product Name="Dave" /></xml>').SelectSingleNode('//Product')

$node.Name       # adapted property: Name *attribute* -> 'Dave'
$node.get_Name() # native property: tag name -> 'Product'

That said, if you control the format of the XML, it is preferable to avoid such conflicts to begin with.


Re 2:

.SelectNodes() perhaps surprisingly returns a type that is derived from the documented System.Xml.XmlNodeList return type and that derived type is itself not public: System.Xml.XPathNodeList is internal.

That is not a problem in practice, given that what matters is that it can act as a System.Xml.XmlNodeList instance, but can be confusing.

Given an instance of a type, you can inspect the type's derivation (chain of base classes) as follows:

$nodeList = ([xml] '<xml><Product Name="Dave" /><Product Name="Jane" /></xml>').SelectNodes('//Product')

$type = $nodeList.GetType()
do {
 $type.FullName 
} while ($type = $type.BaseType)

The above yields:

System.Xml.XPathNodeList
System.Xml.XmlNodeList
System.Object

Upvotes: 4

Related Questions