1
votes

I'm doing xml to html transformation using XSLT,

my input xml file looks like this,

<doc>
    <h1>main header 1-1</h1>
    <p>para 1</p>
    <p>para 2</p>

    <h1>main header 1-2</h1>
    <p>para 3</p>
    <p>para 4</p>
    <h2>sub header 2-1</h2>
    <p>para 5</p>
    <p>para 6</p>
    <p>para 7</p>
    <h2>sub header 2-3</h2>
    <p>para 8</p>
    <p>para 9</p>

    <h1>main header 1-3</h1>
    <p>para 10</p>
    <h2>sub header 2-3</h2>
    <p>para 11</p>
    <p>para 12</p>
</doc>

my expected output from above xml file is looks like this,

   <doc>
        <div id="h1-1">
            <h1>main header 1-1</h1>
            <p>para 1</p>
            <p>para 2</p>
        </div>
        <div id="h1-2">
            <h1>main header 1-2</h1>
            <p>para 3</p>
            <p>para 4</p>
        </div>
        <div id="h2-1">
            <h2>sub header 2-1</h2>
            <p>para 5</p>
            <p>para 6</p>
            <p>para 7</p>
        </div>
        <div id="h2-2">
            <h2>sub header 2-3</h2>
            <p>para 8</p>
            <p>para 9</p>
        </div>
        <div id="h1-3">
            <h1>main header 1-3</h1>
            <p>para 10</p>
        </div>
        <div id="h2-3">
            <h2>sub header 2-3</h2>
            <p>para 11</p>
            <p>para 12</p>
        </div> 
   </doc>

I have following xsl to do that task,

<xsl:function name="san:group-div" as="element()*">
        <xsl:param name="elements" as="element()*"/>
        <xsl:param name="level" as="xs:integer"/>
        <xsl:for-each-group select="$elements" group-starting-with="*[local-name() eq concat('h', $level)]">
            <xsl:choose>
                <xsl:when test="self::*[local-name() eq concat('h', $level)]">
                    <div id="{local-name()}-{count(preceding-sibling::*[local-name() eq local-name(current())]) + 1}"> 
                        <xsl:apply-templates select="current-group()"/>
                    </div>
                    <xsl:sequence select="san:group-div(current-group() except ., $level + 1)"/>
                </xsl:when>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:function>

    <xsl:template match="doc">
        <xsl:copy>
           <xsl:sequence select="san:group-div(*, 1)"/>
        </xsl:copy>
    </xsl:template>

however it gives me following result.

<doc>
        <div id="h1-1">
            <h1>main header 1-1</h1>
            <p>para 1</p>
            <p>para 2</p>
        </div>
        <div id="h1-2">
            <h1>main header 1-2</h1>
            <p>para 3</p>
            <p>para 4</p>
            <h2>sub header 2-1</h2>
            <p>para 5</p>
            <p>para 6</p>
            <p>para 7</p>
            <h2>sub header 2-3</h2>
            <p>para 8</p>
            <p>para 9</p>
        </div>
        <div id="h2-1">
            <h2>sub header 2-1</h2>
            <p>para 5</p>
            <p>para 6</p>
            <p>para 7</p>
        </div>
        <div id="h2-2">
            <h2>sub header 2-3</h2>
            <p>para 8</p>
            <p>para 9</p>
        </div>
        <div id="h1-3">
            <h1>main header 1-3</h1>
            <p>para 10</p>
            <h2>sub header 2-3</h2>
            <p>para 11</p>
            <p>para 12</p>
        </div>
        <div id="h2-3">
            <h2>sub header 2-3</h2>
            <p>para 11</p>
            <p>para 12</p>
        </div>
  </doc>

As it shows in the result, <h2> nodes are also copied in to the <h1> div s. (see <div id="h1-2"> and <div id="h1-3">).

can anyone suggest How can I arrange my code to get my expected output?

1

1 Answers

2
votes

How about ...

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

<xsl:output method="html" version="5.0" indent="yes" encoding="utf-8" />
<xsl:strip-space elements="*" />

<xsl:template match="/">
  <html>
    <head>
      <title>Doc</title>
    </head>
    <body>
      <xsl:apply-templates />
    </body>
  </html>
</xsl:template>

<xsl:template match="doc">
  <doc>
    <xsl:for-each-group select="h1|h2|h3|p" group-adjacent="
      if (self::p)
        then
          (preceding-sibling::h1 | preceding-sibling::h2 | preceding-sibling::h3)[last()]/local-name()
        else
          local-name()">
      <xsl:for-each-group select="current-group()" group-starting-with="h1|h2|h3">
        <div id="{local-name()}-{position()}">   
          <xsl:copy-of select="current-group()" />      
        </div>    
      </xsl:for-each-group>   
    </xsl:for-each-group>
  </doc>
</xsl:template>

</xsl:stylesheet>

Assumptions

This assumes that your heading levels are limited to h1, h2 and h3. If not, it is a trivial exercise to adjust as required.