用于匹配元素和属性的XPath

时间:2017-03-14 14:44:25

标签: xml powershell xpath attributes element

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

返回:

/root/child1/child2/child3
/root/child1/child2Part2

应该回归:

/root/child1/child2/attribute1
/root/child1/child2/child3
/root/child1/child2Part2

您尝试了什么?

我尝试过匹配:

  • //@*|*[.='{0}'] - 返回匹配的元素,但返回所有属性。
  • //*|@*[.='{0}'] - 返回匹配的属性,但返回所有元素。
  • //*[.='{0}']|@*[.='{0}']" - 返回匹配的元素。
  • //@*[.='{0}']|*[.='{0}']" - 返回匹配的属性。
  • //(@*|*)[.='{0}']" - 抛出异常。

2 个答案:

答案 0 :(得分:1)

使用以下XPath解决了问题: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 } } }

n

答案 1 :(得分:1)

您的推导XPath表达式的方法有三个缺陷,如您对问题的评论中所示。

  1. 它不处理在同一级别存在多个具有相同名称的元素的情况。
  2. 它无法正确处理值中的引号。
  3. 它不处理XML名称空间。
  4. 这是我对一个解决这些问题的函数的看法(我还给了它一个我认为在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]