找到xml节点的完整xpath

时间:2014-06-04 16:57:59

标签: xml powershell

假设以下PowerShell代码行

$node = Select-Xml -Path $filePath -XPath "//*[@$someAttribute]"

如何获取节点的xpath?我想我可以使用其ParentNode属性遍历,但是有更好的方法吗?

3 个答案:

答案 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)"