smo
smo

Reputation: 89

XSLT: How to copy elements from parent node and delete them afterwards

I have the following XML:

<entry>
<form type="variant">
    <form type="hyperlemma">
        <orth>wordA</orth>
    </form>
    <orth>wordB</orth>
    <gramGrp>
        <gram>vb</gram>
        <gram>inf</gram>
    </gramGrp>
    <cit>
        <form type="lemma">
            <orth>wordC</orth>
        </form>
    </cit>
    <form type="graphical_variant">
        <form type="hyperlemma">
            <orth>wordD</orth>
        </form>
        <orth>wordE</orth>
        <cit>
            <orth>∅</orth>
        </cit>
    </form> 
</form>
</entry>

Child nodes of <form type="variant"> - namely <orth>wordB</orth> and <cit> - are supposed to go in a second <form type="graphical_variant"> that is newly added right before the first one.

The desired output is

<entry>
<form type="variant">
    <form type="hyperlemma">
        <orth>wordA</orth>
    </form>
    <gramGrp>
        <gram>vb</gram>
        <gram>inf</gram>
    </gramGrp>
    <form type="graphical_variant">
        <orth>wordB</orth>
        <cit>
        <form type="lemma">
            <orth>wordC</orth>
        </form>
    </cit>
    </form>
    <form type="graphical_variant">
        <form type="hyperlemma">
            <orth>wordD</orth>
        </form>
        <orth>wordE</orth>
        <cit>
            <orth>∅</orth>
        </cit>
    </form> 
</form>
</entry>

Applying this XSLT, templates 1 and 2 are doing the copying

<!--TEMPLATE1-->
<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" />
    </xsl:copy>
</xsl:template>
<!--TEMPLATE2-->
<xsl:template match="form[@type='graphical_variant']">
    <form type="graphical_variant">
        <xsl:apply-templates select="../orth" />
        <!--in case there are several <cit> elements-->
        <xsl:for-each select="../cit">
            <xsl:apply-templates select="." />
        </xsl:for-each>
    </form>
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
</xsl:template>
<!--TEMPLATE3-->
<xsl:template match="form[@type='variant']/cit" />
<!--TEMPLATE4-->
<xsl:template match="form[@type='variant']/orth"/>

But as soon as I try to delete those copied elements from <form type="variant"> using templates 3 and 4, they are deleted from the newly created element <form type="graphical_variant">, too.

This may be a stupid question, but why is that so? By referring to form[@type='variant'] (template3), I thought <cit> was going to be deleted there only. I'd be glad if someone could explain what I'm doing wrong - and perhaps even provide a solution!

I forgot to mention: it is XSLT 2.0.

Upvotes: 0

Views: 1087

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 116992

The problem with your approach is that when you do:

<xsl:apply-templates select="../orth" />

you are applying templates to orth elements that are siblings of the current node - i.e. children of the <form type="variant"> element. There's only template that matches these orth elements - it's your TEMPLATE4, and that template is empty.

Same thing happens with your:

<xsl:for-each select="../cit">
    <xsl:apply-templates select="." />
</xsl:for-each>

That's just an awkward way of saying:

<xsl:apply-templates select="../cit" />

which applies the empty TEMPLATE3 to these nodes.

How to fix this:

There are three ways you could approach this:

  1. Instead of applying templates, copy the nodes to where you want them:

    <!--TEMPLATE1--> 
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" />
        </xsl:copy> 
    </xsl:template>
    <!--TEMPLATE2--> 
    <xsl:template match="form[@type='graphical_variant']">
        <form type="graphical_variant">
            <xsl:copy-of select="../orth" />
            <xsl:copy-of select="../cit" />
        </form>
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy> 
    </xsl:template>
    <!--TEMPLATE3--> 
    <xsl:template match="form[@type='variant']/cit" />
    <!--TEMPLATE4--> 
    <xsl:template match="form[@type='variant']/orth"/>
    
  2. Apply different templates (using mode) when you want the nodes to be copied.

  3. Be more selective when applying the templates (this, IMHO, is the preferable approach):

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="form[@type='variant']">
        <xsl:copy>
            <!-- remove orth and cit from here -->
            <xsl:apply-templates select="@*|node() except (orth|cit)"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="form[@type='graphical_variant']">
        <form type="graphical_variant">
            <!-- add orth and cit here -->
            <xsl:apply-templates select="../orth" />
            <xsl:apply-templates select="../cit" />
        </form>
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

Upvotes: 2

alamar
alamar

Reputation: 19313

<xsl:apply-templates select="." />

Here you go, it matches to empty templates towards the bottom of your example.

Simple, however not optimal solution would be

<xsl:apply-templates select="." mode="noignore" />

And then make 's that match your desired elements with mode="noignore" and actually copy them instead of suppressing.

Upvotes: 0

Related Questions