Search

Task

For a project I'm currently working on, I needed to divide the content of an entry into multiple divs.
I'm using Markdown so the text itself was entered like this:

## Headline 1

Some text.

## Headline 2

Some more text.

## Headline 3

Even more text.

The result was the following html:

<h2>Headline 1</h2>
<p>Some text.</p>
<h2>Headline 2</h2>
<p>Some more text.</p>
<h2>Headline 3</h2>
<p>Even more text.</p>

But what I needed was this:

<div id="item1">
    <h2>Headline 1</h2>
    <p>Some text.</p>
</div>
<div id="item2">
    <h2>Headline 2</h2>
    <p>Some more text.</p>
</div>
<div id="item3">
    <h2>Headline 3</h2>
    <p>Even more text.</p>
</div>

Solution

As I think this is common problem to solve, I like to post my solution.
First of all I created a template that applied to all headlines:

<xsl:template match="/data/articles/entry" >
    <xsl:apply-templates select="text/h2" />
</xsl:template>

And a second template that matched these headlines:

<xsl:template match="text/h2">

</xsl:template>

First of all I wanted the template to just show the headline:

<xsl:template match="text/h2">
    <h2><xsl:copy-of select="./*" /></h2>
</xsl:template>

Secondly I wrapped the headlines with the needed divs:

<xsl:template match="text/h2" >
    <xsl:param name="current" select="position()" />
    <div id="item{$current}">
        <h2><xsl:copy-of select="./*" /></h2>
    </div>
</xsl:template>

The only thing missing was the content between two headlines:

<xsl:template match="text/h2">
    <xsl:param name="current" select="position()" />
    <div id="item{$current}">
        <h2><xsl:copy-of select="./*" /></h2>
        <xsl:apply-templates select="/data/articles/entry/text/h2[position() = $current]/following-sibling::*[not(preceding-sibling::h2[position() = $current + 1]) and not(self::h2)]" />  
    </div>
</xsl:template>

To keep all elements between two headlines (instead of just getting their text nodes) I used some advanced XSL manipulations Allen once described in his blog and added:

<xsl:template match="articles//*">
    <xsl:element name="{name()}">
        <xsl:apply-templates select="* | @* | text()" />
    </xsl:element>
</xsl:template>

<xsl:template match="articles//@*">
    <xsl:attribute name="{name(.)}">
        <xsl:value-of select="." />
    </xsl:attribute>
</xsl:template>

That's it! Hope this might be helpful for others.
Nils

Nice, that is exactly what i had on the backburner in this thread. something tells me this would be quite impossible to apply to a horizontal brake tag, because its a single self closing tag...or can it be done?

Nice.

You can easily extend this to create a table of contents by placing named anchors at each headline, and recreating the headlines as links to those anchors in a different mode.

thanks for the post Nils!

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details