Waqar Janjua
Waqar Janjua

Reputation: 6123

Need help in xpath

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

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

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

Pavel Veller
Pavel Veller

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

Related Questions