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