Namrata
Namrata

Reputation: 13

Need sequence number based on the nesting of the element

Need sequence number based on the nesting of the element. In below example all the reference elements which is not child of any reference element will have sequence number, any nested reference element should have parent sequence number + decimal + position of current reference element.

Input.xml

    <root>
        <front>
            <reference1 type="ref" href="a.xml">
                <reference1 type="ref" href="x.xml"/>
                <reference1 type="ref" href="z.xml"/>
            </reference>
        </front>
        <reference2 type="ref" href="b.xml"/>
        <reference2 type="ref" href="c.xml">
            <reference2 type="ref" href="d.xml">
                <reference2 type="ref" href="y.xml"/>
            </reference>
        </reference>
        <back>
            <reference3 type="ref" href="e.xml"/>
        </back>
    </root>

Output.xml

    <root>
        <reference href="a.xml" sequence="1"/>
        <reference href="x.xml" sequence="1.1"/>
        <reference href="z.xml" sequence="1.2"/>
        <reference href="b.xml" sequence="2"/>
        <reference href="c.xml" sequence="3"/>
        <reference href="d.xml" sequence="3.1"/>
        <reference href="y.xml" sequence="3.1.1"/>
        <reference href="e.xml" sequence="4"/>
    </root>

I am trying this but not able to get the logic:

          <xsl:template match="/">
             <root>
                 <xsl:for-each select="//reference">
                     <reference>
                                 <xsl:attribute name="href">
                                     <xsl:value-of select="@href"/>
                                 </xsl:attribute>
                         <xsl:if test="not(child::reference)">
                                 <xsl:attribute name="sequence">
                                     <xsl:value-of select="position()"/>
                                 </xsl:attribute>
                         </xsl:if>
                         <xsl:if test="child::reference">
                             <!-- something to be done here -->
                         </xsl:if>
                     </reference>    
                 </xsl:for-each>
             </root>

Thanks

Upvotes: 1

Views: 494

Answers (1)

Daniel Haley
Daniel Haley

Reputation: 52868

My suggestion would be to use <xsl:number level="multiple"/> to do the counting for you. I think that in order for the counts to be correct, you'd have to narrow it down to just reference elements. I might be wrong and there could be an easier way, but I only had a few minutes to spend on this.

In my example, I use a moded template and assigned the cleaned results to a variable. I then apply-templates to that variable to do the actual transform.

This will only work in XSLT 2.0, but since you tagged the question 2.0 you should be ok.

XML Input (added a couple of <foo/> elements for testing)

<root>
    <front>
        <reference href="a.xml">
            <reference href="x.xml"/>
            <reference href="z.xml"/>
        </reference>
    </front>
    <reference href="b.xml"/>
    <foo/>
    <reference href="c.xml">
        <reference href="d.xml">
            <foo>
                <reference href="y.xml"/>                
            </foo>
        </reference>
    </reference>
    <back>
        <reference href="e.xml"/>
    </back>
</root>

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:variable name="refs">
        <refs>
            <xsl:apply-templates select="/*//reference[not(ancestor::reference)]" mode="clean"/>                
        </refs>
    </xsl:variable>

    <xsl:template match="/root">
        <xsl:copy>
            <xsl:apply-templates select="$refs"/>            
        </xsl:copy>
    </xsl:template>

    <xsl:template match="reference">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:attribute name="x">
                <xsl:number level="multiple"/>
            </xsl:attribute>
        </xsl:copy>
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="reference" mode="clean">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select=".//reference[ancestor::reference[1] is current()]" mode="clean"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

XML Output

<root>
   <reference href="a.xml" x="1"/>
   <reference href="x.xml" x="1.1"/>
   <reference href="z.xml" x="1.2"/>
   <reference href="b.xml" x="2"/>
   <reference href="c.xml" x="3"/>
   <reference href="d.xml" x="3.1"/>
   <reference href="y.xml" x="3.1.1"/>
   <reference href="e.xml" x="4"/>
</root>

EDIT

Here's an updated stylesheet for the updated input in the question. I only needed to change reference to *[@type='ref'] and add a count attribute to xsl:number.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:variable name="refs">
        <refs>
            <xsl:apply-templates select="/*//*[@type='ref'][not(ancestor::*[@type='ref'])]" mode="clean"/>                
        </refs>
    </xsl:variable>

    <xsl:template match="/root">
        <xsl:copy>
            <xsl:apply-templates select="$refs"/>            
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[@type='ref']">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:attribute name="x">
                <xsl:number count="*[@type='ref']" level="multiple"/>
            </xsl:attribute>
        </xsl:copy>
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="*[@type='ref']" mode="clean">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select=".//*[@type='ref'][ancestor::*[@type='ref'][1] is current()]" mode="clean"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions