Search code examples
xmlxpathxslt

Rearranging hierarchy - move the following-sibling of an element before the end of that element


I need to rearrange hierarchy (move lists that appear after the para, inside of the para):

From: <para updated="20240205"><text> ...  </text> ... </para><list type="num"> ... </list>
To: <para updated="20240205"><text> ... </text> ... <list type="num"> ... </list></para>

Notes:

  • The DTD allows para's to contain lists and lists can also contain para's
  • There could be multiple lists following the para that need to be moved to the end of the preceding para

I am relatively new to XSL and am unsure of how to handle this being that these are currently siblings

I did not try anything yet because I was not sure how to approach this.

Thanks


Solution

  • Use e.g.

      <xsl:template match="*[para]">
        <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para">
            <xsl:choose>
              <xsl:when test="self::para">
                <xsl:copy>
                  <xsl:apply-templates select="@*, node(), tail(current-group())"/>
                </xsl:copy>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:copy>
      </xsl:template>
    

    I think tail is XPath 3/XSLT 3 so if you are really using an XSLT 2 processor use subsequence(current-group(), 2) instead.

    Of course pushing everything through apply-templates assumes you have the identity transformation (template) set up for the nodes you don't want to change and add templates for any transformation on nodes you want to change.