janlindso
janlindso

Reputation: 1253

How to structure XSL to create HTML from XML

I have a XML document for a book. Each XML-document is one chapter, that is structured in the following way:

<doc>
<chapter title="This is the first chapter">
<section>This is a section of information</section>
<paragraph title="This is the first paragraph">This is the text of the first paragraph</paragraph>
<section><paragraph title="This is the second paragraph">This paragraph is inside a secion</paragraph></section>
</chapter>
</doc>

I have a PHP to generate HTML from XSL, but my problem is to structure the XSL in a good way. As you see, paragraphs isn't necessarily the first child of a chapter. And a section can contain a paragraph, but doesn't have to.

How can I easily structure this in XSL? Do I have to nest a bunch of foreach tags? I tried something like this:

<xsl:for-each select="doc/chapter">
                    <h1>
                        <xsl:value-of select="current()/@title"/>
                    </h1>

                <xsl:for-each select="current()/paragraph">
                        <h3>
                            <xsl:value-of select="current()/@title" />
                        </h3>

                            <p><xsl:value-of select="current()" /></p>
        </xsl:for-each>

                <xsl:for-each select="current()/section">
                        <h2>
                            <xsl:value-of select="current()/@title" />
                        </h2>
                    <xsl:for-each select="current()/paragraph">
                            <h3>
                                <xsl:value-of select="current()/@title" />
                            </h3>

                                <p><xsl:value-of select="current()" /></p>
            </xsl:for-each>
        </xsl:for-each>
</xsl:for-each>

The problem is that this will be very advanced and a lot of duplicate foreach'es nested in each other. Is there an easier and better approach for this. If so, how?

Upvotes: 1

Views: 871

Answers (2)

Tim C
Tim C

Reputation: 70648

You need to use template matching here, which is really what XSLT is all about, and excels at. If you want to transform a paragraph element the same way, regardless of where it exists in the document, you first create a template describing how to transform it

<xsl:template match="paragraph">
   <h3>
      <xsl:value-of select="current()/@title"/>
   </h3>
   <p>
     <xsl:value-of select="current()"/>
    </p>
</xsl:template>

Then, instead of doing an xsl:for-each, you can replace it with xsl:apply-templates

<xsl:apply-templates select="paragraph" />

(Note, there is no need to use "current()" here, as it is assumed the xpath expression is relative to the current context node anyway).

But this is still not ideal, as it is assuming you are expecting a paragraph at this point, when maybe there are other elements. Better still is when you do this

<xsl:apply-templates />

This will process all the current child nodes of the current element, and look for templates that match. This will work well if you do have templates for all elements you are wanting to transform.

It is also worth mentioning about XSLT's built-in templates. These will be used to match nodes for which there are no explicit matching templates in your XSLT. These built in templates will output text nodes where they find them, otherwise they will continue processing by looking for templates that match the children of the current node.

You can take advantage of these built-in templates to simplify your XSLT. For example, here is one way of writing it

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="chapter">
         <h1>
            <xsl:value-of select="@title"/>
         </h1>
         <xsl:apply-templates />
   </xsl:template>

   <xsl:template match="paragraph">
      <h3>
         <xsl:value-of select="@title"/>
      </h3>
      <p>
         <xsl:value-of select="."/>
      </p>
   </xsl:template>
</xsl:stylesheet>

Notice how there is no template matching doc or section, but the built-in templates will process, allowing execution to eventually pass to the explicit templates.

EDIT: If you did want to do more changes, just add more changes as required. For example, if you had Section elements with title attributes, just add the following template:

   <xsl:template match="section[@title]">
      <h2>
         <xsl:value-of select="@title"/>
      </h2>
      <xsl:apply-templates />
   </xsl:template>

This template will match section elements, but only ones with title attributes present.

Upvotes: 4

Mark O&#39;Connor
Mark O&#39;Connor

Reputation: 78011

Have you considered switching to the docbook format? The advantage of adopting a standard is that you get to leverage the pre-existing stylesheets for supporting various output formats.

To illustrate the point here is a sample document:

<book>
  <title>An Example Book</title>
  <chapter>
    <title>This is the first chapter</title>
    <section>
      <title>This is the first section</title>
      <para>Paragraph one</para>
      <para>Paragraph two</para>
    </section>
    <section>
      <title>This is the second section</title>
      <para>Paragraph one</para>
      <para>Paragraph two</para>
    </section>
  </chapter>
</book>

Upvotes: 1

Related Questions