Jing
Jing

Reputation:

node-set in xpath

I am writing a xslt style sheet to transform a xml to another xml.

Here is the simplified version of original xml:

 <eml>
        <datatable>
                 <physical>
                     <distribution id="100"/>
                 </physical>
       </datatable>                 

       <software>
           <implementation>
              <distribution id="200"/>
            </implementation>
      </software>
     <additionalMetadata>
        <describes>100</describes>
        <describes>200</describes>
        <describes>300</describes>
        <describes>400</describes>
    </additionalMetadata>
   </eml>

I try to use a Xpath to select node-set of "describes" that doesn't have the value which equals the id value of //physical/distribution or software/implementation/distribution. In above case, I want to get the node-set:

   <deseribes>300</describes>
   <deseribes>400</describes>

(100 and 200 are attribute id values of //physical/distribution or software/implementation/distribution).

I wrote something like:

<xsl:with-param name="describes-list" 
                select="./describes[//physical/distribution/@id !=. and
                             //software/implementation/distribution/@id != .] "/>

It works on above example. However, the element of datatable and software are repeatable. So this xml is valid:

<eml>
    <datatable>
             <physical>
                 <distribution id="100"/>
             </physical>
   </datatable> 

  <datatable>
             <physical>
                 <distribution id="300"/>
             </physical>
   </datatable>                

   <software>
       <implementation>
          <distribution id="200"/>
       </implementation>
  </software>
 <additionalMetadata>
    <describes>100</describes>
    <describes>200</describes>
    <describes>300</describes>
    <describes>400</describes>
  </additionalMetadata>
</eml>

But my xslt doesn't work on the above example :(

Would you mind shedding some light on this? Thank you in advance!

Jing

Upvotes: 7

Views: 5920

Answers (1)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

This is an often commited mistake. Never use XPath's "!=" operator when one or both of the operands is/are node-set(s).

   value != node-set

by definition is true if there exists a node n in node-set, such that

   value is not equal to string(n)

What you want is that

   value is not equal to any node in node-set.

This can be expressed in the following way:

   value = node-set

is true if there exists at least one node n in node-set, such that:

   value = string(n)

Then

   not(value = node-set)

is true if there doesn't exist any node n in node-set, such that

   value = string(n)

Therefore, the following XPath expression will select the desired nodes:

 /*/*/describes[not(. = ../../*/physical/distribution/@id)
              and 
                not(. = ../../*/implementation/distribution/@id)]

I personally would prefer to have just one comparison of the context node to the union of the two node-sets:

 /*/*/describes
            [not(. = (../../*/physical/distribution/@id
                    | 
                      ../../*/implementation/distribution/@id
                     )
                 )
            ]

Please, do note that I avoid using the "//" abbreviation. It is typically very expensive (inefficient) and should be used only if we don't know the structure of the XML document.

And, of course, the above XPath expressions have to be eavaluated against the following XML document (the second one provided in the question):

<eml>
    <datatable>
        <physical>
            <distribution id="100"/>
        </physical>
    </datatable>
    <datatable>
        <physical>
            <distribution id="300"/>
        </physical>
    </datatable>
    <software>
        <implementation>
            <distribution id="200"/>
        </implementation>
    </software>
    <additionalMetadata>
        <describes>100</describes>
        <describes>200</describes>
        <describes>300</describes>
        <describes>400</describes>
    </additionalMetadata>
</eml>

Upvotes: 20

Related Questions