Reputation: 6123
I have this xml:
<mappings>
<mapping>
<name iskey="true">234</name>
<aid iskey="true">bmz</aid>
<bid iskey="true">sim</bid>
<data>GSSS</data>
</mapping>
<mapping>
<aid iskey="true">bmz</aid>
<bid iskey="true">sim</bid>
<data>TS</data>
</mapping>
<mapping>
<aid iskey="true">bmz</aid>
<account>TS</account>
</mapping>
</mappings>
I need the xpath to select the node which has node
<aid iskey='true'>bmz</aid>
and no other node containing iskey attribute.
Upvotes: 2
Views: 130
Reputation: 243579
Use this shorter and simpler XPath expression:
/*/*
[aid[@iskey='true' and .='bmz']
and
not(*[not(self::aid)][@iskey])
]
Explanation:
This is a simple, specific case of the "Double Negation Principle" :)
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/*
[aid[@iskey='true' and .='bmz']
and
not(*[not(self::aid)][@iskey])
]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<mappings>
<mapping>
<name iskey="true">234</name>
<aid iskey="true">bmz</aid>
<bid iskey="true">sim</bid>
<data>GSSS</data>
</mapping>
<mapping>
<aid iskey="true">bmz</aid>
<bid iskey="true">sim</bid>
<data>TS</data>
</mapping>
<mapping>
<aid iskey="true">bmz</aid>
<account>TS</account>
</mapping>
</mappings>
the XPath expression is evaluated and the selected elements (just one in this case) are copied to the output:
<mapping>
<aid iskey="true">bmz</aid>
<account>TS</account>
</mapping>
Upvotes: 3
Reputation: 6105
After a few back and forth in comments it seems like you are looking to get a hold of the mapping
node (or any other node for that matter) that has the aid
child node with a certain attributes and value but no other child nodes with the @iskey
attribute. You do it like this:
//*[aid/@iskey='true' and aid/text()='bmz'][not(*[@iskey][local-name() != 'aid'])]
The predicate says exactly that: has the aid
child node with those values and doesn't have a child node with a name other than aid
that would happen to have an @iskey
attribute.
When I run this simple test stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select="//*[aid/@iskey='true' and aid/text()='bmz'][not(*[@iskey][local-name() != 'aid'])]"/>
</xsl:template>
</xsl:stylesheet>
on your input document I get the following in return:
<mapping>
<aid iskey="true">bmz</aid> <!-- I need this node -->
<account>TS</account>
</mapping>
UPDATE if you were looking to get a hold of nodes that only have one child node with the @iskey
, you can get away with:
//*[count(*[@iskey]) = 1]
I plugged it into my test stylesheet and it produced the same expected result:
<mapping>
<aid iskey="true">bmz</aid> <!-- I need this node -->
<account>TS</account>
</mapping>
Upvotes: 4