Reputation: 6873
I have two questions regarding XML...
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?
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
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