Search code examples
xsltxslt-1.0xqueryxslt-2.0xslt-3.0

How to handle Nested 'LIST' with 'label' of each 'list-item'


In the below example we are trying to handle Nested 'LIST' with 'label' of each 'list-item':

Label Example:

style="ListNum1" then a.,b.,c., ... etc.
style="ListNum2" then (1),(2),(3), ... etc.
style="ListNum3" then (a),(b),(c), ... etc.

Can anyone help.

INPUT XML:

<?xml version="1.0" encoding="UTF-8"?>
<body>
<list-item style="ListNum1"><p content-type="new">Your client may (<styled-content style="stat" style-type="Stat-Cal">Prob C &#x00a7;13659</styled-content>) because:</p></list-item>
<list-item style="ListNum2"><p content-type="new">For later income tax purposes</p></list-item>
<list-item style="ListNum3"><p content-type="new">Documents a &#x201c;stepped-up basis&#x201d; (<italic>i.e.,</italic> fair.</p></list-item>
<list-item style="ListNum3"><p content-type="new">Provides evidence for your client.</p></list-item>
<list-item style="ListNum2"><p content-type="new">If you or your <bold>client</bold>.</p></list-item>
<list-item style="ListNum1"><p content-type="new">If transfer <italic>unincorporated business</italic> appraisal. <styled-content style="stat" style-type="Stat-Cal">Prob C &#x00a7;13658</styled-content>.</p></list-item>
</body>

EXPECTED OUTPUT:

<?xml version="1.0" encoding="UTF-8"?>
<body>
<list type="ListNum1">
<list-item style="ListNum1"><label>a.</label><p content-type="new">Your client may (<styled-content style="stat" style-type="Stat-Cal">Prob C &#x00a7;13659</styled-content>) because:</p></list-item>
<list type="ListNum2">
<list-item style="ListNum2"><label>(1)</label><p content-type="new">For later income tax purposes</p>
<list type="ListNum3">
<list-item style="ListNum3"><label>(a)</label><p content-type="new">Documents a &#x201c;stepped-up basis&#x201d; (<italic>i.e.,</italic> fair.</p></list-item>
<list-item style="ListNum3"><label>(b)</label><p content-type="new">Provides evidence for your client.</p></list-item>
</list></list-item>
<list-item style="ListNum2"><label>(2)</label><p content-type="new">If you or your <bold>client</bold>.</p></list-item>
</list></list-item>
<list-item style="ListNum1"><label>b.</label><p content-type="new">If transfer <italic>unincorporated business</italic> appraisal. <styled-content style="stat" style-type="Stat-Cal">Prob C &#x00a7;13658</styled-content>.</p></list-item>
</list>
</body>

XSLT CODE:

<?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"
exclude-result-prefixes="xs"
version="2.0">

<xsl:output indent="yes"/>

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

<xsl:template match="list-item[@style='ListNum1']">
    <xsl:if test="not(preceding-sibling::*[1][self::list-item[@style='ListNum1']])">
        <xsl:text disable-output-escaping="yes"><![CDATA[<list list-type="ListNum1">]]></xsl:text>
    </xsl:if>
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates/>
    </xsl:copy>
    <xsl:if test="not(following-sibling::*[1][self::list-item[@style='ListNum1']])">
        <xsl:text disable-output-escaping="yes"><![CDATA[</list>]]></xsl:text>
    </xsl:if>
</xsl:template>

<xsl:template match="list-item[@style='ListNum2']">
    <xsl:if test="not(preceding-sibling::*[1][self::list-item[@style='ListNum2']])">
        <xsl:text disable-output-escaping="yes"><![CDATA[<list list-type="ListNum2">]]></xsl:text>
    </xsl:if>
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates/>
    </xsl:copy>
    <xsl:if test="not(following-sibling::*[1][self::list-item[@style='ListNum2']])">
        <xsl:text disable-output-escaping="yes"><![CDATA[</list>]]></xsl:text>
    </xsl:if>
</xsl:template>

<xsl:template match="list-item[@style='ListNum3']">
    <xsl:if test="not(preceding-sibling::*[1][self::list-item[@style='ListNum3']])">
        <xsl:text disable-output-escaping="yes"><![CDATA[<list list-type="ListNum3">]]></xsl:text>
    </xsl:if>
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates/>
    </xsl:copy>
    <xsl:if test="not(following-sibling::*[1][self::list-item[@style='ListNum3']])">
        <xsl:text disable-output-escaping="yes"><![CDATA[</list>]]></xsl:text>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

Reference URL # https://xsltfiddle.liberty-development.net/93nwMoZ/1


Solution

  • The recursive grouping is like this:

    <?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:mf="http://example.com/mf"
        expand-text="yes"
        exclude-result-prefixes="#all"
        version="3.0">
        
      <xsl:param name="format-map" as="map(xs:integer, xs:string)"
        select="map { 1 : 'a', 2 : '1', 3 : 'A' }"/>
        
      <xsl:function name="mf:group" as="node()*">
          <xsl:param name="items" as="element(list-item)*"/>
          <xsl:param name="level" as="xs:integer"/>
          <xsl:where-populated>
              <list type="ListNum{$level}">
                <xsl:for-each-group select="$items" group-starting-with="list-item[@style = 'ListNum' || $level]">
                    <xsl:copy>
                        <label>{format-integer(position(), $format-map($level))}</label>
                        <xsl:apply-templates select="node(), mf:group(tail(current-group()), $level + 1)"/>
                    </xsl:copy>  
                </xsl:for-each-group>  
              </list>
          </xsl:where-populated>
      </xsl:function>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="body">
          <xsl:copy>
              <xsl:sequence select="mf:group(list-item, 1)"/>
          </xsl:copy>
      </xsl:template>
      
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/ei5R4v4

    For the label formatting details, perhaps extend it as

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        expand-text="yes"
        exclude-result-prefixes="#all"
        version="3.0">
        
      <xsl:param name="format-map" as="map(xs:integer, xs:string)"
        select="map { 1 : 'a', 2 : '1', 3 : 'a' }"/>
        
      <xsl:function name="mf:format" as="xs:string">
          <xsl:param name="number" as="xs:integer"/>
          <xsl:param name="level" as="xs:integer"/>
          <xsl:variable name="formatted-number"
            select="format-integer($number, $format-map($level))"/>
          <xsl:sequence
            select="if ($level = 1)
                    then  $formatted-number || '.'
                    else  '(' || $formatted-number || ')'"/>
      </xsl:function>
        
      <xsl:function name="mf:group" as="node()*">
          <xsl:param name="items" as="element(list-item)*"/>
          <xsl:param name="level" as="xs:integer"/>
          <xsl:where-populated>
              <list type="ListNum{$level}">
                <xsl:for-each-group select="$items" group-starting-with="list-item[@style = 'ListNum' || $level]">
                    <xsl:copy>
                        <label>{mf:format(position(), $level)}</label>
                        <xsl:apply-templates select="node(), mf:group(tail(current-group()), $level + 1)"/>
                    </xsl:copy>  
                </xsl:for-each-group>  
              </list>
          </xsl:where-populated>
      </xsl:function>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="body">
          <xsl:copy>
              <xsl:sequence select="mf:group(list-item, 1)"/>
          </xsl:copy>
      </xsl:template>
      
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/ei5R4v4/1