tags:

views:

647

answers:

2
+3  Q: 

node-set in xpath

Hello, everyone:

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

+8  A: 

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>
Dimitre Novatchev
A: 

Dear Dimitre:

Thank you so much! Your solution works great.

The reason I use "//" is the element could be repeated used in different structure and i couldn't tell the exact one.

Thanks again for the help!

Jing, you are welcome! In case you found the answer useful, you can accept it (click on the check mark. :)
Dimitre Novatchev