简体   繁体   中英

Move xml nodes between parents and delete duplicates - xslt 2.0

I have an xml as follows and would like to 1) move values in the Data node to Header if the match the attribute name or 2) delete nodes under Data if the name attribute is the same as an existing node in the header using xsl tranform, if possible. Any help would be greatly appreciated.

Initial xml:

<HEADER>
   <KEY name="child1" value="value1" />
   <KEY name="child2" value="value2" />
   <KEY name="child3" value="value3" />
   <KEY name="child4" value="value4" />
</HEADER>
<DATA>
   <KEY name="child1" value="value1.data" />
   <KEY name="child3" value="value3.data" />
   <KEY name="child5" value="value5" />
   <KEY name="child7" value="value7" />
</DATA>

If Child 5 is the node to move, the result is...After Transform:

<HEADER>
   <KEY name="child1" value="value1" />
   <KEY name="child2" value="value2" />
   <KEY name="child3" value="value3" />
   <KEY name="child4" value="value4" />
   <KEY name="child5" value="value5" />
</HEADER>
<DATA>
   <KEY name="child7" value="value7" />
</DATA>

current xsl:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output omit-xml-declaration="no" method="xml" indent="yes" />

    <!-- <xsl:variable name="ReplaceLiveDate" select="false()" /> -->
    <xsl:variable name="ReplaceLiveDate" select="true()" />

    <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
    <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

    <!-- base identity transform -->
    <xsl:template match="@*|node()">
       <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
       </xsl:copy>
    </xsl:template>

    <!-- in HEADER: output ROLE sorted by @name -->
    <xsl:template match="HEADER">
        <xsl:copy>
            <xsl:apply-templates select="KEY">
                <xsl:sort select="translate(@name, $smallcase, $uppercase)" order="ascending" />
                <!-- <xsl:sort select="@name" /> -->
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <!-- in DATA: output KEY sorted by @name -->
    <xsl:template match="DATA">
        <xsl:copy>
            <xsl:apply-templates select="KEY">
                <xsl:sort select="translate(@name, $smallcase, $uppercase)" order="ascending" />
                <!-- <xsl:sort select="@name" /> -->
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template> 

I have the following to remove nodes, but unsure how to only do it if the node exist in the header section

<!-- remove processing nodes -->
<xsl:template match="KEY[starts-with(@name, 'child1')]"/>

there are many ways to do that transformation, but in this case i think that the smart one is to use the xslt keys feature see here for a brief description

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">
    <xsl:output omit-xml-declaration="no" method="xml" indent="yes" />

    <!-- <xsl:variable name="ReplaceLiveDate" select="false()" /> -->
    <xsl:variable name="ReplaceLiveDate" select="true()" />

    <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
    <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

    <!-- index all the KEY element of the HEADER -->
    <xsl:key name='head' match='HEADER/KEY' use='@name' />


    <!-- base identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>


    <xsl:template match='HEADER'>
        <xsl:copy>
            <!--  move the child5 'KEY element' under the header  -->
            <xsl:copy-of select="//DATA/KEY[@name = 'child5']"/>
            <!--  apply to childs   -->
            <xsl:apply-templates select="KEY">
                <xsl:sort select="translate(@name, $smallcase, $uppercase)" order="ascending" />
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <!-- delete key nodes duplicated in Header, that is elments not indexed by the 'head' key -->
    <xsl:template match="DATA/KEY[key('head',@name)]">
    </xsl:template>

    <!--  delete the node to be moved -->
    <xsl:template match="DATA/KEY[@name = 'child5']" />

</xsl:stylesheet>

For simplicity the stylesheet above won't sort the header keys in alphabethical order, if you really need it, then a perform-sort is necessary.

I tried to keep much as possible of your original stylesheet, but I didn't find out any other solution following that way.

Below the template if you want key sorted by the @name attribute

<xsl:template match='HEADER'>

    <xsl:variable name='unsorted_keys'>
        <xsl:copy-of select="//DATA/KEY[@name = 'child5']"/>
        <!--  apply to childs   -->
        <xsl:apply-templates select="KEY">
        </xsl:apply-templates>          
    </xsl:variable>

    <xsl:copy>      
        <!--  make sure to copy HEADER attributes  -->
        <xsl:apply-templates select='@*'/>

        <!--  perform sort  -->
        <xsl:perform-sort select="$unsorted_keys/KEY">
            <xsl:sort select="translate(@name, $smallcase, $uppercase)" order="ascending" />
        </xsl:perform-sort>         
    </xsl:copy>
</xsl:template>

Hope this helps

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM