Guasqueño
Guasqueño

Reputation: 447

Get a node list which sub-node value is not found somewhere else in the XML file

I want to make the following for-each work.

 <xsl:for-each select="DATA[not(SUB1 and SUB2) 
 or ((SUB1 or SUB2) and count(//PERSON[SUB1=current()/SUB1 and SUB2=current()/SUB2] )=0)]">

It is grammatically correct but it does not work. I tried adding a dot after current(), but it does not work either.

 <xsl:for-each select="DATA[not(SUB1 and SUB2) 
 or ((SUB1 or SUB2) and count(//PERSON[SUB1=current()/./SUB1 and SUB2=current()/./SUB2])=0 )]">

I have the feeling that the problem is the current(). It does not work with self:: either. I want the current()/SUB1 refers to the sub-node of DATA.

If I remove the test from <xsl:for-each> and put it in an <xsl:if> after the for-each I can make it work fine. It does not in the for-each statement.

<xsl:for-each select="DATA">
    <xsl:if test="not(SUB1 and SUB2) or ((SUB1 or SUB2) and count(//PERSON[SUB1=current()/SUB1 and SUB2=current()/SUB2])=0)">
         ....
    </xsl:if>
</xsl:for-each>

This is a test XML:

<ROOT>
    <PERSON>
        <SUB1>11</SUB1>
        <SUB2>AA</SUB2>
    </PERSON>
    <PERSON>
        <SUB1>22</SUB1>
        <SUB2>BB</SUB2>
    </PERSON>
    <DATA>
        <SUB1>99</SUB1>
        <SUB2>ZZ</SUB2>
    </DATA>
    <DATA>
        <SUB1>22</SUB1>
        <SUB2>BB</SUB2>
    </DATA>
    <DATA>
    </DATA>
</ROOT>

it should select these <DATA> blocks since one has no SUB sub-nodes and the other has but there is no person with those SUB values:

    <DATA>
        <SUB1>99</SUB1>
        <SUB2>ZZ</SUB2>
    </DATA>
    <DATA>
    </DATA>

Upvotes: 0

Views: 46

Answers (1)

Tony Graham
Tony Graham

Reputation: 8068

Using XSLT 2.0:

<xsl:template match="ROOT">
  <xsl:apply-templates select="DATA" />
</xsl:template>

<xsl:template
    match="DATA[empty(SUB1) and empty(SUB2)] |
           DATA[SUB1 or SUB2]
               [every $data in .
                 satisfies empty(/ROOT/PERSON[SUB1=$data/SUB1 and SUB2=$data/SUB2])]">
  <xsl:copy-of select="." />
</xsl:template>

<xsl:template match="DATA" />

Using XSLT 1.0 or XSLT 2.0 (and likely to be more efficient for a large number of PERSON):

<xsl:key name="person"
         match="PERSON"
         use="concat(SUB1, '&#xFFFC;', SUB2)" />

<xsl:template match="ROOT">
  <xsl:apply-templates select="DATA" />
</xsl:template>

<xsl:template
    match="DATA[not(SUB1) and not(SUB2)] |
           DATA[SUB1 or SUB2]
               [not(key('person', concat(SUB1, '&#xFFFC;', SUB2)))]">
  <xsl:copy-of select="." />
</xsl:template>

<xsl:template match="DATA" />

Upvotes: 1

Related Questions