Reputation: 16476
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
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
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