Flatten an XML structure by extracting elements and splitting rest into individual elements using XSLT

Sebastian Zartner

I have an XML structure like the following which is similar to HTML:

<paragraph>
  text
  <unordered-list>
    <list-item>list item</list-item>
  </unordered-list>
  more text <link url="https://stackoverflow.com">link text</link>
</paragraph>

I want to transform it into HTML using XSLT. The issue is that HTML doesn't allow list elements (<ol>/<ul>) within paragraphs (<p>). Therefore I need to split everything that is outside the lists into a paragraph. I.e. the structure I want to produce should look like this:

<p>
  text
</p>
<ul>
  <li>list item</li>
</ul>
<p>
  more text <a href="https://stackoverflow.com">link text</a>
</p>

My first idea was to loop over all nodes via <xsl:for-each select="./node()"> and put the lists in <ol>/<ul> and the rest in <p>, though that obviously doesn't work because it wraps every node separately. Searching for answers here I saw XSLT Grouping Siblings, which goes into the same direction, though is meant to split a flat structure into groups. My use case, though, is to "extract" an element and create multiple individual elements from the rest, and by that flatten the structure.

How can that be achieved?

Sebastian Zartner

michael.hor257k gave the right hint. The solution to extract the list from my example is to group the siblings.

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="utf-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="paragraph">
        <!-- start sibling recursion -->
        <xsl:apply-templates select="node()[1]"/>
    </xsl:template>

    <!-- first node in a group -->
    <xsl:template match="paragraph/node()[not(self::unordered-list)]">
        <p>
            <xsl:call-template name="identity"/>
            <!-- collect the following node in this group -->
            <xsl:apply-templates select="following-sibling::node()[1][not(self::unordered-list)]" mode="collect"/>
        </p>
        <!-- continue recursion with the following divider -->
        <xsl:apply-templates select="following-sibling::unordered-list[1]"/>
    </xsl:template>

    <!-- other nodes in a group -->
    <xsl:template match="node()" mode="collect">
        <xsl:call-template name="identity"/>
        <!-- collect the following node in this group -->
        <xsl:apply-templates select="following-sibling::node()[1][not(self::unordered-list)]" mode="collect"/>
    </xsl:template>

    <xsl:template match="unordered-list">
        <ul>
            <xsl:apply-templates/>
        </ul>
        <!-- restart sibling recursion -->
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>

    <xsl:template match="list-item">
        <li>
            <xsl:for-each select="./node()">
                <xsl:apply-templates select="."/>
            </xsl:for-each>
        </li>
    </xsl:template>

    <xsl:template match="link" mode="collect">
        <a>
            <xsl:attribute name="href">
                <xsl:value-of select="@url"/>
            </xsl:attribute>
            <xsl:value-of select="."/>
        </a>
        <!-- collect the following node in this group -->
        <xsl:apply-templates select="following-sibling::node()[1][not(self::unordered-list)]" mode="collect"/>
    </xsl:template>
</xsl:stylesheet>

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related