Search code examples
xsltxslt-2.0

Wrapping of elements


I have an input xml file that have multiple <b> elements, i have to wrap all <b> that are appearing continuously. i have written an xsl for this. it is better way to do this? also there are extra space generated during transformation after wrapping elements.

Sample Input XML

<chap>
<p><b>The</b> <b>Attorney</b> General <b>(Fees)</b><b>a</b><b>b</b><b>c</b> Determination 2012 No 110 commenced on 1 <b>July</b> <b>2012</b> <b>and</b> was repealed on <b>1</b> <b>July</b> <b>2013</b>. The Determination is yet to be amended by:</p>
</chap>

XSLT

    <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:guru="Self"
    exclude-result-prefixes="xs"
    version="2.0">
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="b[preceding-sibling::node()[1][self::b]]
         |b[preceding-sibling::node()[1][normalize-space(.) = '']][preceding-sibling::node()[2][self::b]]"/>

    <xsl:template match="b">
        <xsl:copy>
            <xsl:apply-templates/>
            <xsl:if test="following-sibling::node()[1][self::b]">
                <xsl:copy-of select="guru:wrap(following-sibling::node()[1], '')"/>
            </xsl:if>
            <xsl:if test="following-sibling::node()[1][normalize-space(.) = '']/following-sibling::node()[1][self::b]">
                <xsl:copy-of select="guru:wrap(following-sibling::node()[2], following-sibling::node()[1])"/>
            </xsl:if> 
        </xsl:copy>
    </xsl:template>

    <xsl:function name="guru:wrap">
        <xsl:param name="b_data"/>
        <xsl:param name="space"/>
        <xsl:value-of select="$space"/>
        <xsl:value-of select="$b_data"/>
        <xsl:if test="$b_data/following-sibling::node()[1][self::b]">
            <xsl:copy-of select="guru:wrap($b_data/following-sibling::node()[1], '')"/>
        </xsl:if>
        <xsl:if test="$b_data/following-sibling::node()[1][normalize-space(.) = '']/following-sibling::node()[1][self::b]">
            <xsl:copy-of select="guru:wrap($b_data/following-sibling::node()[2], $b_data/following-sibling::node()[1])"/>
        </xsl:if> 
    </xsl:function>

</xsl:stylesheet>

Output

<chap>
    <p><b>The Attorney</b>  General <b>(Fees)abc</b> Determination 2012 No 110 commenced on 1 <b>July 2012 and</b>   was repealed on <b>1 July 2013</b>  . The Determination is yet to be amended by:</p>
</chap>

Desired Output

<chap>
    <p><b>The Attorney</b> General <b>(Fees)abc</b> Determination 2012 No 110 commenced on 1 <b>July 2012 and</b> was repealed on <b>1 July 2013</b>. The Determination is yet to be amended by:</p>
</chap>

Thanks in Advance.


Solution

  • I would suggest to use for-each-group group-adjacent="self::b or self::text()[. = ' ']":

      <xsl:template match="p">
          <xsl:copy>
              <xsl:apply-templates select="@*"/>
              <xsl:for-each-group select="node()" group-adjacent="self::b or self::text()[. = ' ']">
                  <xsl:choose>
                      <xsl:when test="current-grouping-key()">
                          <b>
                              <xsl:apply-templates select="current-group()/node() | current-group()[self::text()]"/>
                          </b>
                      </xsl:when>
                      <xsl:otherwise>
                          <xsl:apply-templates select="current-group()"/>
                      </xsl:otherwise>                  
                  </xsl:choose>
              </xsl:for-each-group>
          </xsl:copy>
      </xsl:template>
    

    https://xsltfiddle.liberty-development.net/gWmuiHU/1 shows the result, it uses XSLT 3 but for XSLT 2 you simply have to remove the xsl:mode and keep the identity template

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    

    you have in your code.