Reputation: 6873
Given xml of
$xml = [Xml]@"
<Package>
<Copy_Ex>
</Copy_Ex>
<Move_Ex>
<Rules>
<Rule>
<Property>os</Property>
<Operator>-eq</Operator>
<Value>Windows10</Value>
</Rule>
</Rules>
<Task>
<Rules>
<Rule>
<Property>lastWriteTime</Property>
<Operator>-gt</Operator>
<Value>6W</Value>
</Rule>
</Rules>
</Task>
</Move_Ex>
</Package>
"@
I want to be able to select a single node from the xml, say <Move_Ex>
, and pass it to a function, then independently access the two <Rules>
nodes. I can do that now with
function Test {
param (
[Xml.XmlElement]$task
)
if ($nodeRules = $task.SelectSingleNode('//Rules')) {
Write-PxXmlToConsole $nodeRules
}
if ($taskRules = $task.SelectSingleNode('//Task/Rules')) {
Write-PxXmlToConsole $taskRules
}
}
$task = $xml.SelectSingleNode('//Move_Ex')
Test $task
But //
is searching the entire document, NOT just the [Xml.XmlElement]
that I passed. So if I have a different <Rules>
node in the <Copy_Ex>
node, THAT is what gets returned by the first SelectSingleNode()
. What I think I need is a way to identify the root node of the passed element, not the entire document. But I can't seem to find a way to do that consistently. My understanding is that while //
finds the sequence of nodes anywhere in the document, /
only finds it relative to the root node. Which should mean that
$task = $xml.SelectSingleNode('/Move_Ex')
finds only that <Move_Ex>
in the root, and if I had another one somewhere else I would be fine. However, that doesn't return anything at all, which has me worried I don't really understand how either /
or //
works, which makes the chances of getting what I need to work unlikely.
I have looked at the documentation for [Xml.XmlElement]
and GetElementsByTagName()
seems to be finding just the elements in my passed element. Write-Host "$($task.GetElementsByTagName('Rules').Count)"
in the function returns a 2
, so not finding any <Rules>
node I have put in <Copy_Ex>
.
I also tried just dot referencing things, so
Write-PxXmlToConsole $task.Rules
Write-PxXmlToConsole $task.Task.Rules
And that seems to be working also. But again, ONLY when the element being passed is selected with //
rather than /
, and I worry that in a much more complex XML with hundreds of <Rules>
nodes I won't be getting consistent results.
So, two questions...
1: What is the difference between /
and //
in this situation, and why isn't /Move_Ex
working as "expected" in $task = $xml.SelectSingleNode('/Move_Ex')
?
2: Is the dot referencing approach the "correct" way to limit my access to just the passed Element? Or is there another/better way?
I should note here that I did verify that //
does break down if make the XML more realistic. So with this XML
$xml = [Xml]@"
<Package>
<Copy_Ex>
<Rules>
<Rule>
<Property>os</Property>
<Operator>-eq</Operator>
<Value>WindowsXP</Value>
</Rule>
</Rules>
<PreTask>
<Move_Ex>
<Rules>
<Rule>
<Property>os</Property>
<Operator>-eq</Operator>
<Value>WindowsVista</Value>
</Rule>
</Rules>
<Task>
<Rules>
<Rule>
<Property>lastWriteTime</Property>
<Operator>-gt</Operator>
<Value>8W</Value>
</Rule>
</Rules>
</Task>
</Move_Ex>
</PreTask>
</Copy_Ex>
<Move_Ex>
<Rules>
<Rule>
<Property>os</Property>
<Operator>-eq</Operator>
<Value>Windows10</Value>
</Rule>
</Rules>
<Task>
<Rules>
<Rule>
<Property>lastWriteTime</Property>
<Operator>-gt</Operator>
<Value>6W</Value>
</Rule>
</Rules>
</Task>
</Move_Ex>
</Package>
"@
Where I need to select only the <Move_Ex>
in the <Package>
node
$task = $xml.SelectSingleNode('/Move_Ex')
selects nothing, while
$task = $xml.SelectSingleNode('//Move_Ex')
selects the <Move_Ex>
node nested in <Copy_Ex>
, which is not at all what I want and need.
Note that all Write-PxXmlToConsole
does is exactly that.
function Write-PxXmlToConsole ($xml) {
$stringWriter = New-Object System.IO.StringWriter
$xmlWriter = New-Object System.Xml.XmlTextWriter $stringWriter
$xmlWriter.Formatting = "indented"
$xml.WriteTo($xmlWriter)
$xmlWriter.Flush()
$stringWriter.Flush()
Write-Host $stringWriter.ToString()
Write-Host
}
EDIT: With respect to my first question, and based on what @Prophet has already said, I realized I was conflating Root NODE and Root ELEMENT, and now I see this in my related links. Some good additional info about Root node, root element, document element, etc.
Upvotes: 0
Views: 489
Reputation: 33361
Maybe I'm missing something or misunderstanding your question, but it seems to me that the answer is very simple:
The difference between /
and //
:
/
will go to the direct child of the passed argument while //
will look for any element below the passed argument.
By default XPath searches starting from the root node. This is why '/Move_Ex'
returns nothing. There is no Move_Ex
element directly below the root.
To start searching from current node, not from the root, you should put a dot .
at the prefix of the XPath expression.
So, to get the Rules
nodes inside the passed element you should use this XPath: '//.Rules'
Upvotes: 1