Andrew Mairose
Andrew Mairose

Reputation: 10985

XPath doesn't match when desired element contains child elements?

I have the following XPath expression:

//a[@attribute='my-attribute']

When I have the following element in the HTML that XPath is searching, it matches as expected:

<a attribute="my-attribute">Some text</a>

But if there is an <svg> tag under that element, XPath returns no match:

<a attribute="my-attribute">
    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"
        viewBox="0 0 24 24" focusable="false"></svg>
</a>

Why doesn't XPath match in this case? Is there a way I can modify my expression to make it match?

EDIT:

Apparently it has to do with the namespace on the <svg> element. Using the local-name() function makes it match in the XPath tester I'm using:

//*[local-name()='a' and @attribute='my-attribute']

However, this still doesn't match when running through Selenium WebDriver. Any idea of how to get this working with Selenium?

Upvotes: 1

Views: 1125

Answers (2)

Paul Margus
Paul Margus

Reputation: 41

Following is a possible solution in vb.net.

Public Class XmlNodeListWithNamespace
    ' see https://stackoverflow.com/questions/55385520/xpath-doesnt-match-when-desired-element-contains-child-elements
    ' @JaSON I would have thought the same thing,
    ' but removing the xmlns attribute from the svg tag
    ' causes the //a[@attribute='my-attribute'] expression to match.
    ' – Andrew Mairose
    ' Mar 28 at 13:07 "Asked 5 months ago  Active 5 months ago" implies 2019-03-28 13:07.

    ' Therefore, I first considered deleting all occurrences of
    '       xmlns="" and xmlns="http://www.w3.org/1999/xhtml"
    ' I did this using the following Replacement.
    ' gstrHtml = Regex.Replace(
    '            input:=gstrHtml,
    '            pattern:=" *xmlns=""[^""]*""",
    '            replacement:="",
    '            options:=RegexOptions.IgnoreCase
    '        )

    ' However, the solution below retains the namespace, while avoiding unsightly xpath strings.

    ''' <summary>
    ''' For a given xpath, returns an XmlNodeList, taking account of the xmlns namespace.
    ''' </summary>
    ''' <param name="oXmlDocument">The current XML document.</param>
    ''' <param name="xpath">A normal xpath string, without any namespace qualifier.</param>
    ''' <returns>The XmlNodeList for the given xpath.</returns>
    Public Shared Function NodeList(
        oXmlDocument As XmlDocument,
        xpath As String
    ) As XmlNodeList

        Dim strXpath As String = xpath

        ' Insert Namespace Qualifier.  For example, 
        '    "//pre"                                            becomes "//x:pre"
        '    "/html/body/form/div/pre"                          becomes "/x:html/x:body/x:form/x:div/x:pre"
        '    "//div[@id='nv_bot_contents']/pre"                 becomes "//x:div[@id='nv_bot_contents']/x:pre"
        '    "//div[@id='nv_bot_contents']/pre[@data-xxx='X2']" becomes "//x:div[@id='nv_bot_contents']/x:pre[@data-xxx='X2']"
        '    "//div[@id='nv_bot_contents']/pre[@data-xxx]"      becomes "//x:div[@id='nv_bot_contents']/x:pre[@data-xxx]"
        '    "//pre[@data-xxx]"                                 becomes "//x:pre[@data-xxx]"
        strXpath = Regex.Replace(
                        input:=strXpath,
                        pattern:="(/)(\w+)",
                        replacement:="$1x:$2"
                    )

        ' See https://stackoverflow.com/questions/40796231/how-does-xpath-deal-with-xml-namespaces/40796315#40796315
        Dim oXmlNamespaceManager As New XmlNamespaceManager(nameTable:=oXmlDocument.NameTable)
        oXmlNamespaceManager.AddNamespace("x", "http://www.w3.org/1999/xhtml")

        Dim oXmlNodeList As XmlNodeList = oXmlDocument.SelectNodes(
            xpath:=strXpath,
            nsmgr:=oXmlNamespaceManager
        )

        Return oXmlNodeList

    End Function

End Class

Sample invocation:

Dim oXmlNodeList As XmlNodeList =
            XmlNodeListWithNamespace.NodeList(
                oXmlDocument:=oXmlDocument,
                xpath:="//pre"
            )

Upvotes: 0

kjhughes
kjhughes

Reputation: 111491

You may be confused by how the XPath hosting environment is presenting the selected a elements.

Adding an svg element to the a element will not affect what's selected by

//a[@attribute='my-attribute']

In the case of

<a attribute="my-attribute">Some text</a>

the a element has a string value consisting of more than just white space characters, but with

<a attribute="my-attribute">
    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"
        viewBox="0 0 24 24" focusable="false"></svg>
</a>

the a element has a string value that consists only of whites space, so for text results of the selection, you wouldn't see anything selected.

If you evaluate count(//a[@attribute='my-attribute']), you'll likely see the same results for both cases.

Upvotes: 3

Related Questions