假设以下PowerShell代码行
$node = Select-Xml -Path $filePath -XPath "//*[@$someAttribute]"
如何获取节点的xpath?我想我可以使用其ParentNode属性遍历,但是有更好的方法吗?
答案 0 :(得分:2)
我认为PowerShell中没有内置任何内容可以执行您想要的操作。但是,向上递归并不太难。像这样的函数应该有效:
function Get-XPath($n) {
if ( $n.GetType().Name -ne 'XmlDocument' ) {
"{0}/{1}" -f (Get-XPath $n.ParentNode), $n.Name
}
}
答案 1 :(得分:0)
为了稍微扩展Ansgar的答案,可以使代码找到不扩展到XML根的部分Xpath。只需要子节点和“超级父母”(未知级别的父级)的某些明确属性(我的代码中的名称)。
Function Get-XPath($node_xml, [string]$parent_type){
if ($node_xml.GetType().Name -ne 'XmlDocument' -and $node_xml.Name -ne "$parent_type"){
"{0}/{1}" -f (Get-XPath $node_xml.ParentNode $parent_type), $node_xml.name
}
}
答案 2 :(得分:0)
Ansgar Wiechers' helpful answer提供了一种优雅的递归功能。
在尝试消除其某些限制的同时,以下函数为基础:
它在同名兄弟姐妹之间正确反映节点的 index 以便可靠地将其作为目标;例如,如果给定节点是名为foo
的元素,并且在其之前还有另外两个同级foo
元素,则返回的路径以{{1}结尾}
它不仅支持 element 节点,还支持属性和 text / CDATA 节点。
通过使用.../foo[3]
方法访问类型本地属性,它避免了PowerShell添加的属性的潜在名称冲突,这些属性提供了基于名称的直接对XML DOM的访问-请参见{{3 }}以获得背景信息。
get_*()
以下是其用法示例:
# Given a [System.Xml.XmlNode] instance, returns the path to it
# inside its document in XPath form.
# Supports element, attribute, and text/CDATA nodes.
function Get-NodeXPath {
param (
[ValidateNotNull()]
[System.Xml.XmlNode] $node
)
if ($node -is [System.Xml.XmlDocument]) { return '' } # Root reached
$isAttrib = $node -is [System.Xml.XmlAttribute]
# IMPORTANT: Use get_*() accessors for all type-native property access,
# to prevent name collision with Powershell's adapted-DOM ETS properties.
# Get the node's name.
$name = if ($isAttrib) {
'@' + $node.get_Name()
} elseif ($node -is [System.Xml.XmlText] -or $node -is [System.Xml.XmlCDataSection]) {
'text()'
} else { # element
$node.get_Name()
}
# Count any preceding siblings with the same name.
# Note: To avoid having to provide a namespace manager, we do NOT use
# an XPath query to get the previous siblings.
$prevSibsCount = 0; $prevSib = $node.get_PreviousSibling()
while ($prevSib) {
if ($prevSib.get_Name() -ceq $name) { ++$prevSibsCount }
$prevSib = $prevSib.get_PreviousSibling()
}
# Determine the (1-based) index among like-named siblings, if applicable.
$ndx = if ($prevSibsCount) { '[{0}]' -f (1 + $prevSibsCount) }
# Determine the owner / parent element.
$ownerOrParentElem = if ($isAttrib) { $node.get_OwnerElement() } else { $node.get_ParentNode() }
# Recurse upward and concatenate with "/"
"{0}/{1}" -f (Get-NodeXPath $ownerOrParentElem), ($name + $ndx)
}
以上结果:
$xml = @'
<foo>
<bar name='b1'>bar1</bar>
<other>...</other>
<bar name='b2'>bar2</bar>
</foo>
'@
# Get a reference to the 2nd <bar> element:
$node = (Select-Xml -XPath '//bar[@name="b2"]' -Content $xml).Node
# Output the retrieved element's XML text.
"original node: $($node.OuterXml)"
# Obtain the path to that element as an XPath path.
$nodePath = Get-NodeXPath $node
# Output the path.
"path: $nodePath"
# Test the resulting path to see if it finds the original node:
$node = (Select-Xml -XPath $nodePath -Content $xml).Node
"re-queried node: $($node.OuterXml)"