Harold Sota
Harold Sota

Reputation: 7566

XSL select all node where not node of another node

Thse is my xml

<element1> <subel1/> </element1> <element2> <subel2/> </element2> <element3> <subel3/> </element3> <criteria> <subel3/> </criteria>

how i can select all node with xsl that not are in criteria subnodes? like these

 <subel1/> <subel2/>

How is this done?

If the xml is formated as:

<element1> 
<el> subel1 </el>
</element1>
 <element2> 
<el> subel2 
</el> 
</element2> 
<element3> 
<el> subel3 </el> 
</element3> 
<criteria> 
<subel3/> 
</criteria>

Upvotes: 1

Views: 7252

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243579

This transformation:

<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:for-each select=
  "/*/*[not(self::criteria)]/*">
   <xsl:copy-of select=
   "self::node()[not(/*/criteria/*[name()=name(current())])]"/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document (based on the provided XML fragment, but wrapped the XML fragmentwithin a top element and added one more child to criteria to make the problem less trivial):

<t>
    <element1>
        <subel1/>
    </element1>
    <element2>
        <subel2/>
    </element2>
    <element3>
        <subel3/>
    </element3>
    <criteria>
        <subel3/>
        <subel1/>
    </criteria>
</t>

produces the wanted result (no other published answer at the time of writing this produces a correct result):

<subel2/>

Step-by-step explanation:

  1. The XPath expression:

    /*/*[not(self::criteria)]/*

selects every element whose parent is an element not named "criteria" and which is a child of the top element of the document.

  1. Within the <xsl:for-each> instruction we check if the current node has a name that is not one of the names of any children of criteria and only copy such a node:

self::node()[not(/*/criteria/*[name()=name(current())])]

This XPath expression selects the current node ( self::node() ) only if there doesn't exist a child of /*/criteria, whose name is the same as the name of the current node ( not(/*/criteria/*[name()=name(current())]) ).

Here we use the fact that not(someNode-Set) is false() only if the nodeset someNode-Set is empty.

Upvotes: 6

Casey Jordan
Casey Jordan

Reputation: 1254

The easiest way to do this is to construct an xpath expression which cross checks against the criteria nodes. For instance:

Step 1: The following will select all nodes that are not in the criteria area (So all nodes that do not have a parent named "criteria")

//*[name(..) != 'criteria'] 

Step 2: Filter out any nodes that also appear in critera section

//*[name(..) != 'criteria' and not(name(//criteria/*) = name(.))]

Now that last statement will select all the nodes that don't match a criteria node. But you just want sub nodes, so we can modify the selector to only grab elements that are "sub elements" or leaf elements that have no child nodes.

//*[name(..) != 'criteria' and not(name(//criteria/*) = name(.)) and not(*)]

So here is the breakdown of each of our conditionals one more time:

name(..) != 'criteria' -- Limits to nodes that are not in the criteria section

not(name(//criteria/*) = name(.)) -- Limits to nodes that do not have the same name as a node in the criteria section

and not(*) - Limits to nodes that do not have any child nodes (so the leaf nodes that you want.)

So if you were do something like:

<xsl:for-each select="//*[name(..) != 'criteria' and not(name(//criteria/*) = name(.)) and not(*)]">
 <xsl:value-of select="name(current())"/> :
</xsl:for-each>

For your above example this would print:

subel1 : subel2

Hope this helps.

Cheers,

Casey

Upvotes: 2

user414661
user414661

Reputation: 1042

arent you missing the xmlcode? would be easier to help if you put in the code and in the editor mark it as sourcecode (the button with "101 010")

Upvotes: 1

Related Questions