Search code examples
xmlxsltxslt-2.0

I need to realign the element tag using XSLT


I'm having multiple topics, I need to include section tag along with the Topic element with numbering ID's

My Input XML is:

<Article>
<Title>Mam</Title>
<Items>
    <Item>
        <Name>cyst1?</Name>
        <Body>
            <h1>aaa</h1><p>knee1.</p>
            <h2>bbb</h2><p>knee2.</p>
        </Body>
    </Item>
    <Item>
        <Name>cyst2?</Name>
        <Body>
            <p>knee3.</p>
        </Body>
    </Item>
</Items>

XSL I Used as:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="#all">
<xsl:template match="Article">
    <xsl:apply-templates/>
</xsl:template>
<xsl:template match="Title"/>
<xsl:template match="h1"/>
<xsl:template match="h2"/>
<xsl:template match="Items">
    <xsl:for-each-group select="*" group-starting-with="Items">
        <topic>
            <xsl:attribute name="id">topic_Head<xsl:number count="Items | Item"/>
            </xsl:attribute>
            <title>
                <xsl:value-of select="//Title"/>
            </title>
            <xsl:for-each-group select="current-group()" group-starting-with="Item">
                <xsl:choose>
                    <xsl:when test="self::Item">
                        <topic>
                            <xsl:attribute name="id">topic_<xsl:number count="Items | Item"/>
                            </xsl:attribute>
                            <xsl:apply-templates select="node()"/>
                        </topic>
                    </xsl:when>
                    <xsl:otherwise>
                        <body>
                            <xsl:apply-templates select="current-group()"/>
                        </body>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each-group>
        </topic>
    </xsl:for-each-group>
</xsl:template>
<xsl:template match="Body">
    <body>
        <xsl:apply-templates/>
    </body>
</xsl:template>
<xsl:template match="p">
    <p>
        <xsl:apply-templates/>
    </p>
</xsl:template>
<xsl:template match="Name">
    <title>
        <xsl:apply-templates/>
    </title>
</xsl:template>

Output I got as:

<topic id="topic_1">
<title>Mam</title>
<topic id="topic_2">
    <title>cyst1?</title>
    <body>
        <p>knee1.</p><p>knee2.</p>
    </body>
</topic>
<topic id="topic_3">
    <title>cyst2?</title>
    <body>
        <p>knee3.</p>
    </body>
</topic>

Expected Output:

<topic id="topic_1">
<title>Mam</title>
<topic id="topic_2">
    <title>cyst1?</title>
    <body>
        <section id="section1">
            <title>aaa</title><p>knee1.</p>
            <section id="section2">
                <title>bbb</title><p>knee2.</p>
            </section>
        </section>
    </body>
</topic>
<topic id="topic_3">
    <title>cyst2?</title>
    <body>
        <p>knee3.</p>
    </body>
</topic>

I need the h1 and h2 come as section with Id attribute and close as per the header order.

Please suggest me coding on this. Thanks in advance.


Solution

  • Following styleshhet will work.

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:ahf="http://www.antennahouse.com/names/XSLT/Functions/Document"
        exclude-result-prefixes="#all">
    
        <xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="Article">
            <topic>
                <xsl:attribute name="id" select="ahf:genTopicId(.)"/>
                <xsl:apply-templates/>
            </topic>
        </xsl:template>
    
        <xsl:template match="Title">
            <title>
                <xsl:apply-templates/>
            </title>
        </xsl:template>
    
        <xsl:template match="Items">
            <xsl:apply-templates/>
        </xsl:template>
    
        <xsl:template match="Item">
            <topic>
                <xsl:attribute name="id" select="ahf:genTopicId(.)"/>
                <xsl:apply-templates/>            
            </topic>
        </xsl:template>
    
        <xsl:template match="Name">
            <title>
                <xsl:apply-templates/>
            </title>
        </xsl:template>
    
        <xsl:template match="Body">
            <body>
                <xsl:call-template name="groupBody">
                    <xsl:with-param name="prmBodyElem" select="*"/>
                </xsl:call-template>
            </body>
        </xsl:template>
    
        <xsl:template name="groupBody">
            <xsl:param name="prmBodyElem" as="element()*"/>
            <xsl:variable name="maxHn" as="xs:integer" select="ahf:getMaxHn($prmBodyElem)"/>
            <xsl:choose>
                <xsl:when test="$maxHn eq $notHn">
                    <xsl:apply-templates select="$prmBodyElem"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:for-each-group select="$prmBodyElem" group-adjacent="ahf:genGroupNo(.,$maxHn,$prmBodyElem)">
                        <xsl:choose>
                            <xsl:when test="current-grouping-key() eq 0">
                                <xsl:apply-templates select="current-group()"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <section>
                                    <xsl:attribute name="id" select="ahf:genSectionId(current-group()[1])"/>
                                    <title>
                                        <xsl:apply-templates select="./node()"/>
                                    </title>
                                    <xsl:call-template name="groupBody">
                                        <xsl:with-param name="prmBodyElem" select="subsequence(current-group(),2)"/>
                                    </xsl:call-template>
                                </section>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>            
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    
        <xsl:variable name="notHn" as="xs:integer" select="9999"/>
    
        <xsl:function name="ahf:getMaxHn" as="xs:integer">
            <xsl:param name="prmElem" as="element()+"/>
            <xsl:variable name="hn" as="xs:integer*">
                <xsl:for-each select="$prmElem">
                    <xsl:choose>
                        <xsl:when test="matches(name(),'h[1-9]')">
                            <xsl:sequence select="xs:integer(substring(name(),2))"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:sequence select="$notHn"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each>
            </xsl:variable>
            <xsl:sequence select="min($hn)"/>
        </xsl:function>
    
        <xsl:template match="p">
            <p>
                <xsl:apply-templates/>
            </p>
        </xsl:template>
    
        <!-- Generate id number for topic -->
        <xsl:function name="ahf:genTopicId" as="xs:string">
            <xsl:param name="prmElem" as="element()"/>
            <xsl:sequence select="concat('topic_',count($prmElem|root($prmElem)//*[self::Article or self::Item][. &lt;&lt; $prmElem]))"/>
        </xsl:function>
    
        <!-- Grouping by hN (N = $prmMaxHn)
             0: not h1, h2, h3... 
             > 1: hN group
          -->
        <xsl:function name="ahf:genGroupNo" as="xs:integer">
            <xsl:param name="prmElem" as="element()"/>
            <xsl:param name="prmMaxHn" as="xs:integer"/>
            <xsl:param name="prmRange" as="element()*"/>
            <xsl:variable name="GroupHn" as="xs:string" select="concat('h',$prmMaxHn)"/>
            <xsl:sequence select="count($prmElem[self::*[name() eq $GroupHn]]) + count($prmElem/preceding-sibling::*[name() eq $GroupHn][(. is $prmRange[1]) or (. &gt;&gt; $prmRange[1])])"/>
        </xsl:function>
    
        <!--Generate id number for section -->
        <xsl:function name="ahf:genSectionId" as="xs:string">
            <xsl:param name="prmElem" as="element()"/>
            <xsl:sequence select="concat('section_',count($prmElem|$prmElem/preceding::*[matches(name(),'h[1-9]')]))"/>
        </xsl:function>
    
    </xsl:stylesheet>
    

    [The result]

    <?xml version="1.0" encoding="UTF-8"?>
    <topic id="topic_1">
       <title>Mam</title>
       <topic id="topic_2">
          <title>cyst1?</title>
          <body>
             <section id="section_1">
                <title>aaa</title>
                <p>knee1.</p>
                <section id="section_2">
                   <title>bbb</title>
                   <p>knee2.</p>
                </section>
             </section>
          </body>
       </topic>
       <topic id="topic_3">
          <title>cyst2?</title>
          <body>
             <p>knee3.</p>
          </body>
       </topic>
    </topic>
    

    I'm still afraid if the expected result is DITA topic instance, nesting section element is not allowed.