匹配元素和属性的 XPath
XPath to match Elements and Attributes
匹配属性和元素的正确 XPath 语法是什么?
更多信息
我创建了以下函数来查找包含给定值的元素和属性:
function Get-XPathToValue {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[xml]$Xml
,
[Parameter(Mandatory)]
[string]$Value
)
process {
$Xml.SelectNodes("//*[.='{0}']" -f ($Value -replace "'","''")) | %{
$xpath = ''
$elem = $_
while (($elem -ne $null) -and ($elem.NodeType -ne 'Document')) {
$xpath = '/' + $elem.Name + $xpath
$elem = $elem.SelectSingleNode('..')
}
$xpath
}
}
}
这匹配元素,但不匹配属性。
通过将 $Xml.SelectNodes("//*[.='{0}']"
替换为 $Xml.SelectNodes("//@*[.='{0}']"
我可以匹配属性,但不能匹配元素。
例子
[xml]$sampleXml = @"
<root>
<child1>
<child2 attribute1='hello'>
<ignoreMe>what</ignoreMe>
<child3>hello</child3>
<ignoreMe2>world</ignoreMe2>
</child2>
<child2Part2 attribute2="ignored">hello</child2Part2>
</child1>
<notMe>
<norMe>Not here</norMe>
</notMe>
</root>
"@
Get-XPathToValue -Xml $sampleXml -Value 'hello'
Returns:
/root/child1/child2/child3
/root/child1/child2Part2
应该Return:
/root/child1/child2/attribute1
/root/child1/child2/child3
/root/child1/child2Part2
你试过什么?
我尝试匹配:
//@*|*[.='{0}']
- returns 匹配元素,但所有属性。
//*|@*[.='{0}']
- returns 匹配属性,但所有元素。
//*[.='{0}']|@*[.='{0}']"
- returns 个匹配元素。
//@*[.='{0}']|*[.='{0}']"
- returns 匹配属性。
//(@*|*)[.='{0}']"
- 抛出异常。
使用以下 XPath 解决了问题://@*[.='{0}']|//*[.='{0}']
即
function Get-XPathToValue {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[xml]$Xml
,
[Parameter(Mandatory)]
[string]$Value
)
process {
$Xml.SelectNodes("//@*[.='{0}']|//*[./text()='{0}']" -f ($Value -replace "'","''")) | %{
$xpath = ''
$elem = $_
while (($elem -ne $null) -and ($elem.NodeType -ne 'Document')) {
$prefix = ''
if($elem.NodeType -eq 'Attribute'){$prefix = '@'}
$xpath = '/' + $prefix + $elem.Name + $xpath
$elem = $elem.SelectSingleNode('..')
}
$xpath
}
}
}
您的 XPath 表达式的派生方法存在三个缺陷,如您问题的评论中所示。
- 不处理同层有多个同名元素的情况
- 它没有正确处理值中的引号。
- 它不处理 XML 个命名空间。
这是我对解决这些问题的函数的看法(我还给它起了一个我认为在 cmdlet 命名方案中更合适的名称):
function Convert-ValueToXpath {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[xml]$Xml
,
[Parameter(Mandatory)]
[string]$Value
)
process {
$escapedValue = "concat('', '" + ($value -split "'" -join "', ""'"", '") + "')"
$Xml.SelectNodes("(//*|//@*)[normalize-space() = {0}]" -f $escapedValue) | % {
$xpath = ''
$elem = $_
while ($true) {
if ($elem.NodeType -eq "Attribute") {
$xpath = '/@' + $elem.Name
$elem = $elem.OwnerElement
} elseif ($elem.ParentNode) {
$precedingExpr = "./preceding-sibling::*[local-name() = '$($elem.LocalName)' and namespace-uri() = '$($elem.NamespaceURI)']"
$pos = $elem.SelectNodes($precedingExpr).Count + 1
$xpath = '/' + $elem.Name + "[" + $pos + "]" + $xpath
$elem = $elem.ParentNode
} else {
break;
}
}
$xpath
}
}
}
对于您的示例输入,我得到了这些 XPath:
/root[1]/child1[1]/child2[1]/@attribute1
/root[1]/child1[1]/child2[1]/child3[1]
/root[1]/child1[1]/child2Part2[1]
匹配属性和元素的正确 XPath 语法是什么?
更多信息
我创建了以下函数来查找包含给定值的元素和属性:
function Get-XPathToValue {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[xml]$Xml
,
[Parameter(Mandatory)]
[string]$Value
)
process {
$Xml.SelectNodes("//*[.='{0}']" -f ($Value -replace "'","''")) | %{
$xpath = ''
$elem = $_
while (($elem -ne $null) -and ($elem.NodeType -ne 'Document')) {
$xpath = '/' + $elem.Name + $xpath
$elem = $elem.SelectSingleNode('..')
}
$xpath
}
}
}
这匹配元素,但不匹配属性。
通过将 $Xml.SelectNodes("//*[.='{0}']"
替换为 $Xml.SelectNodes("//@*[.='{0}']"
我可以匹配属性,但不能匹配元素。
例子
[xml]$sampleXml = @"
<root>
<child1>
<child2 attribute1='hello'>
<ignoreMe>what</ignoreMe>
<child3>hello</child3>
<ignoreMe2>world</ignoreMe2>
</child2>
<child2Part2 attribute2="ignored">hello</child2Part2>
</child1>
<notMe>
<norMe>Not here</norMe>
</notMe>
</root>
"@
Get-XPathToValue -Xml $sampleXml -Value 'hello'
Returns:
/root/child1/child2/child3
/root/child1/child2Part2
应该Return:
/root/child1/child2/attribute1
/root/child1/child2/child3
/root/child1/child2Part2
你试过什么?
我尝试匹配:
//@*|*[.='{0}']
- returns 匹配元素,但所有属性。//*|@*[.='{0}']
- returns 匹配属性,但所有元素。//*[.='{0}']|@*[.='{0}']"
- returns 个匹配元素。//@*[.='{0}']|*[.='{0}']"
- returns 匹配属性。//(@*|*)[.='{0}']"
- 抛出异常。
使用以下 XPath 解决了问题://@*[.='{0}']|//*[.='{0}']
即
function Get-XPathToValue {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[xml]$Xml
,
[Parameter(Mandatory)]
[string]$Value
)
process {
$Xml.SelectNodes("//@*[.='{0}']|//*[./text()='{0}']" -f ($Value -replace "'","''")) | %{
$xpath = ''
$elem = $_
while (($elem -ne $null) -and ($elem.NodeType -ne 'Document')) {
$prefix = ''
if($elem.NodeType -eq 'Attribute'){$prefix = '@'}
$xpath = '/' + $prefix + $elem.Name + $xpath
$elem = $elem.SelectSingleNode('..')
}
$xpath
}
}
}
您的 XPath 表达式的派生方法存在三个缺陷,如您问题的评论中所示。
- 不处理同层有多个同名元素的情况
- 它没有正确处理值中的引号。
- 它不处理 XML 个命名空间。
这是我对解决这些问题的函数的看法(我还给它起了一个我认为在 cmdlet 命名方案中更合适的名称):
function Convert-ValueToXpath {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[xml]$Xml
,
[Parameter(Mandatory)]
[string]$Value
)
process {
$escapedValue = "concat('', '" + ($value -split "'" -join "', ""'"", '") + "')"
$Xml.SelectNodes("(//*|//@*)[normalize-space() = {0}]" -f $escapedValue) | % {
$xpath = ''
$elem = $_
while ($true) {
if ($elem.NodeType -eq "Attribute") {
$xpath = '/@' + $elem.Name
$elem = $elem.OwnerElement
} elseif ($elem.ParentNode) {
$precedingExpr = "./preceding-sibling::*[local-name() = '$($elem.LocalName)' and namespace-uri() = '$($elem.NamespaceURI)']"
$pos = $elem.SelectNodes($precedingExpr).Count + 1
$xpath = '/' + $elem.Name + "[" + $pos + "]" + $xpath
$elem = $elem.ParentNode
} else {
break;
}
}
$xpath
}
}
}
对于您的示例输入,我得到了这些 XPath:
/root[1]/child1[1]/child2[1]/@attribute1 /root[1]/child1[1]/child2[1]/child3[1] /root[1]/child1[1]/child2Part2[1]