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.
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 noMove_Exelement 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 theRulesnodes inside the passed element you should use this XPath:'//.Rules'