EBamba
EBamba

Reputation: 145

How to group adjacent xml elements with xslt 2.0 or 3.0?

I need to adjust below xslt code to wrap the elements that are adjacent to any p whose name attributes include "level_" as a child to the preceding p, which gets transformed to li tags in the output xml.

Here is the source xml:

    <root>
    <header>Header text</header>
    <p name="paragraph">Normal Paragraph</p>
    <p name="level_1">Value 1</p>
    <p name="level_1">Value 2 table</p>
    <table frame="all" rowsep="1" colsep="1"   
    id="table_whs_jmk_hzb">
    <title>table1</title>
    <tgroup cols="2" align="center">
    <colspec colname="c1" colnum="1" colwidth="1*"/>
    <colspec colname="c2" colnum="2" colwidth="1*"/>
    <thead>
    <row>
    <entry>Header 1</entry>
    <entry>Header 2</entry>
    </row>
    </thead>
    <tbody>
    <row>
    <entry>Testing cell 1 text</entry>
    <entry>Testing cell 2</entry>
    </row>
    </tbody>
    </tgroup>
    </table>
    <p name="level_2">Value 3</p>
    <p name="level_2">Value 4</p>
    <p name="level_1">Value 5 note</p>
    <note>Another element</note>
    <p name="level_1">Another Value 1</p>
    <p name="level_2">Another Value 2</p>
    <p name="level_2">Another Value 3 reference</p>
    <reference>Some reference text</reference>
    <p name="level_3">Another Value 4</p>
    <p name="level_3">Another Value 5 example</p>
    <example>Example text goes here</example>
    <p name="level_4">Another Value 3 image</p>
    <image src="./output/media/img(1).jpg"/>
    <p name="level_4">Another Value 4</p>
    <p name="level_1">Another Value 5</p>
    <p name="level_1">Another Value 6</p>
</root>

And the desired output should look like:

   <root>
    <header>Header text</header>
    <p name="paragraph">Normal Paragraph</p>
    <ol outputclass="ol-level_1 ">
    <li outputclass="li-level_1 ">Value 1</li>
    <li outputclass="li-level_1 ">Value 2 table <table frame="all" 
    rowsep="1" colsep="1"
    id="table_whs_jmk_hzb">
    <title>table1</title>
    <tgroup cols="2" align="center">
    <colspec colname="c1" colnum="1" colwidth="1*"/>
    <colspec colname="c2" colnum="2" colwidth="1*"/>
    <thead>
    <row>
    <entry>Header 1</entry>
    <entry>Header 2</entry>
    </row>
    </thead>
    <tbody>
    <row>
    <entry>Testing cell 1 text</entry>
    <entry>Testing cell 2</entry>
    </row>
    </tbody>
    </tgroup>
    </table>
    <ol outputclass="ol-level-2 ">
    <li outputclass="li-level_2 ">Value 3</li>
    <li outputclass="li-level_2 ">Value 4</li>
    </ol>
    </li>
    <li outputclass="li-level_1 ">Value 5 note <note>Another   
    element</note>
    </li>
   <li outputclass="li-level_1 ">Another Value 1 
   <ol outputclass="ol-level-2 ">
   <li outputclass="li-level_2 ">Another Value 2</li>
   <li outputclass="li-level_2 ">Another Value 3 reference 
   <reference>Some reference text</reference>
   <ol outputclass="ol-level-3 ">
   <li outputclass="li-level_3 ">Another Value 4</li>
   <li outputclass="li-level_3 ">Another Value 5 example  
   <example>Example text goes here</example>
   <ol outputclass="ol-level-4 ">
   <li outputclass="li-level_4 ">Another Value 3 image 
   <image src="./output/media/img(1).jpg"/></li>
   <li outputclass="li-level_4 ">Another Value 4</li>
   </ol></li></ol></li></ol></li>
    <li outputclass="li-level_1 ">Another Value 5</li>
    <li outputclass="li-level_1 ">Another Value 6</li>
    </ol></root>

And here is the xslt provided by @martin-honnen that I would like modified to get the desired output:

<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"
 exclude-result-prefixes="#all" version="3.0">
 <xsl:output method="xml" indent="yes"/>
 <xsl:function name="mf:group" as="node()*">
 <xsl:param name="items" as="element()*"/>
 <xsl:param name="level" as="xs:integer"/>
 <xsl:for-each-group select="$items" group-starting-with="p[@name = 'level_' || $level]">
 <xsl:choose>
 <xsl:when test="self::p[@name = 'level_' || $level]">
 <li outputclass="li-{@name}">
 <xsl:apply-templates/>
 <xsl:where-populated>
 <ol outputclass="ol-level-{$level + 1}">
 <xsl:sequence select="mf:group(tail(current-group()), $level + 1)"/>
 </ol>
 </xsl:where-populated>
 </li>
 </xsl:when>
 <xsl:otherwise>
 <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:for-each-group>
 </xsl:function>
 <xsl:template match="root">
 <root>
 <xsl:copy-of select="header"/>
 <xsl:copy-of select="p[@name = 'paragraph']"/>
 <ol outputclass="ol-level_1 ">
 <xsl:sequence select="mf:group(p[@name = (1 to 6) ! ('level_' || .)], 1)"/>
 </ol>
 </root>
 </xsl:template>
 <!-- Templates for note, example, table, and image -->
 <xsl:template match="note | example | table | image">
 <xsl:copy-of select="."/>
 </xsl:template>
 </xsl:stylesheet>

Upvotes: 0

Views: 58

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167716

Try this adaption of the solution to your previous question:

<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"
    exclude-result-prefixes="#all"
    version="3.0">
  
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="items" as="element()*"/>
    <xsl:param name="level" as="xs:integer"/>
    <xsl:variable name="first-level-item" select="$items[self::p[@name = 'level_' || $level]][1]"/>
    <xsl:variable name="first-level-item-pos" select="index-of($items/generate-id(), generate-id($first-level-item))"/>
    <xsl:apply-templates select="$items[empty($first-level-item-pos) or position() lt $first-level-item-pos]"/>
    <xsl:if test="$first-level-item">
      <ol outputclass="ol-{$first-level-item/@name}">
        <xsl:for-each-group select="$items[position() ge $first-level-item-pos]" group-starting-with="p[@name = 'level_' || $level]">
          <xsl:choose>
            <xsl:when test="self::p[@name = 'level_' || $level]">
              <li outputclass="li-{@name}">
                <xsl:apply-templates/>
                <xsl:sequence select="mf:group(tail(current-group()), $level + 1)"/>
              </li>
            </xsl:when>
            <xsl:otherwise>
              <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each-group>        
      </ol>
    </xsl:if>
  </xsl:function>
  
  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="root">
    <xsl:copy>
       <xsl:sequence select="mf:group(*, 1)"/>
    </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>

Online fiddle example.

Upvotes: 1

Related Questions