graham.reeds
graham.reeds

Reputation: 16476

Using an XPath expression within SelectNodes

I've seen this question where Dimitre Novatchev shows a way of replicating ends-with with an XPath 1.0 expression. However I am having trouble implementing it in context of within a SelectNodes call.

Previously I was using

XmlElement root = doc.DocumentElement;
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("x", root.NamespaceURI);    
XmlNodeList nodeList = doc.SelectNodes("//x:*[contains(name(.), '-notification')]", nsmgr);

Which returned all the nodes I wanted plus one I didn't which had an additional 's' on the end (has-more-notifications).

So I tried using Dimitre expression which gave me:

XmlNodeList nodeList = doc.SelectNodes("//x:*[substring(name(.), string-length(name(.)) - string-length('-notification') +1)]", nsmgr);

Which fails miserably giving me the root node of notification-data-response.

This is my first foray into XPath and it seems to be like regex - you either understand it or you don't.

How do I implement the expression so it returns only the nodes that end with -notification?

UPDATE

A sample of the input:

<?xml version="1.0" encoding="UTF-8"?>
<notification-data-response xmlns="http://checkout.google.com/schema/2" serial-number="16ceae10-a9f1-4ff0-a77b-c3407f2d684a">
    <notifications>
        <new-order-notification serial-number="653417067275702-00001-7">
        </new-order-notification>
        <order-state-change-notification serial-number="653417067275702-00005-1">
        </order-state-change-notification>
        <risk-information-notification serial-number="653417067275702-00005-5">
        </risk-information-notification>
        <authorization-amount-notification serial-number="653417067275702-00005-6">
        </authorization-amount-notification>
    </notifications>
    <continue-token>CP6u9NeQJxC2y72h-MiUARgG</continue-token>
    <has-more-notifications>false</has-more-notifications>
</notification-data-response>

Upvotes: 1

Views: 1303

Answers (2)

Vamsi Mohan Jayanti
Vamsi Mohan Jayanti

Reputation: 666

you have to use local-name() function if you want to avoid namespace prefixes. Something like this should give you all nodes ending with -notification

//node()[substring(local-name(), string-length(local-name()) - string-length('-notification')+ 1, string-length(local-name()))= '-notification']

Ok .. I tested this here . you can verify too .. XPath is an open standard so all tools should be able to give similar response.

http://www.xpathtester.com/test

INPUT

 <?xml version="1.0"?>
<notification-data-response xmlns:x="test">
    <TPI_ADDRESSES>
        <ISPRIMARY>Y</ISPRIMARY>
        <data1>
            <x:wewe-notification/>
        </data1>
        <data2>
            <x:wewe-notification/>
        </data2>
    </TPI_ADDRESSES>
</notification-data-response>

Output

<?xml version="1.0" encoding="UTF-8"?>

<root>
  <x:wewe-notification xmlns:x="test"/>
  <x:wewe-notification xmlns:x="test"/>
</root>

ignore the root node as it is generated to present a valid formatable response.

Now let me break down and explain the xpath to you:

"//" -- denotes that you are searching doing a wild search or we call full scan.. like you don't care at what level target node comes.

"node()" -- is a reference to any and every node or the current node ....

so now : "//node()" -- together denote that you are going to evaluate all the nodes in the xml.

What do you evaluate ?

the name -- you want to find if the name of the node contains "-notification" in it. For that you use the substring function in LHS

substring(local-name(), string-length(local-name()) - string-length('-notification')+ 1, string-length(local-name()))

RHS is you search string = '-notification'

local-name() = gives the name of the node that you are evaluating at any point excluding prefix string-length() - length of the node name

Upvotes: 2

StuartLC
StuartLC

Reputation: 107247

Dimitre's workaround works nicely. You just need to compare the substringed 'ending' with the string constant that you are looking for, i.e.:

SelectNodes("//x:*['-notification'=substring(name(.), string-length(name(.)) 
           - string-length('-notification') +1)]")

Out of interest, if you can avoid the // wildcard by being more specific with the path, you'll improve the performance of your query significantly on a large document.

Edit

Here's how I tested this (in MS Visual Studio's xslt parser, which is 1.0):

<root>
    <nodeendsin-notification></nodeendsin-notification>
    <alsonodeendsin-notification></alsonodeendsin-notification>
    <nopethisis-notifications></nopethisis-notifications>
    <idontcontainatall></idontcontainatall>
</root>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="/root">
        <root>
            <xsl:apply-templates/>
        </root>
    </xsl:template>
    <xsl:template match="*['-notification'=substring(name(.), string-length(name(.)) - string-length('-notification') +1)]">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@* | node()">
    </xsl:template>
</xsl:stylesheet>

Returns just the nodes ending in 'notification'

<root>
    <nodeendsin-notification></nodeendsin-notification>
    <alsonodeendsin-notification></alsonodeendsin-notification>
</root>

Upvotes: 0

Related Questions