匹配元素和属性的 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

你试过什么?

我尝试匹配:

使用以下 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 表达式的派生方法存在三个缺陷,如您问题的评论中所示。

  1. 不处理同层有多个同名元素的情况
  2. 它没有正确处理值中的引号。
  3. 它不处理 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]