I’d like to apologize for the poor title – I really didn’t know how to phrase it any better. I’m currently working on an XSLT 1.0 script (using xsltproc) which transforms a simple XML format into a text representation suitable for consumption by an PDF generator.
In my XML format, there are just three elements: <book>, <chapter> and <section>. However, due to some nasty feature of the DTD, I have a hard time writing a proper XSLT script to transform the document. Here’s the DTD which describes their relation:
<!ELEMENT book ((chapter|section)*)>
<!ELEMENT chapter (section*)>
<!ELEMENT section (#PCDATA)>
Here’s the my XSLT stylesheet which performs the translation:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:strip-space elements="*"/>
<xsl:template match="section">
<xsl:if test="not(preceding-sibling::section)">@BeginSections
</xsl:if>
<xsl:text>@Section @Begin
</xsl:text>
<xsl:apply-templates/>
<xsl:text>@End @Section
</xsl:text>
<xsl:if test="not(following-sibling::section)">@EndSections
</xsl:if>
</xsl:template>
<xsl:template match="chapter">
<xsl:if test="not(preceding-sibling::chapter)">@BeginChapters
</xsl:if>
<xsl:text>@Chapter @Begin
</xsl:text>
<xsl:apply-templates/>
<xsl:text>@End @Chapter
</xsl:text>
<xsl:if test="not(following-sibling::chapter)">@EndChapters
</xsl:if>
</xsl:template>
<xsl:template match="/book">
<xsl:text>@Book @Begin
</xsl:text>
<xsl:apply-templates/>
<xsl:text>@End @Book
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Now, here comes the tricky part and my question: the DTD makes it possible to have <section> elements as the direct children of <book>. However, I still have to yield the same output as if that /book/section element was actually /book/chapter/section.
So e.g.: <book><section/><chapter/></book> becomes (I indented the output for better readability)
@Book @Begin
@BeginChapters
@Chapter @Begin
@BeginSections
@Section @Begin
@End @Section
@EndSections
@End @Chapter
@Chapter @Begin
@End @Chapter
@EndChapters
@End @Book
So what I’d like to do is to adjust my XSLT script so that the ‘section’ template somehow also calls the ‘chapter’ template in case the given <section> element is not within a <chapter>. How could I do this?
For what it’s worth, here’s another example. Multiple <section> elements which are not already in a <chapter> should get merged into one. Hence,<book><section/><section/><section/><chapter/><section/></book> yields output for three chapters (the first of which has three sections, the second has none, the third has one section).
This stylesheet:
Output:
Note: Fine grained traversal, grouping adjacents book/sections into one Chapter.
Edit: Corrected following sibling process for Chapters.
With this input:
Output:
Edit: Better named templates to understand.
Note: Five rules:
bookrule “opens” a book and process first child;book/section|book/chapterrule (always the first because the fine grained transversal) “opens” book chapters, callsmakeChapter;makeChapterrule, “opens” a chapter, process first child if context ischapteror self if context issectionboth insectionmode, process next sibling if context ischapteror followingchapterif context issectioninchaptermode (meaning next chapter);sectionrule insectionmode (because the node by node process, it will always match the firstsectionsfor adjacentssections) “opens” chapter sections an callsmakeSectionrule;makeSectionrule “opens” a section an process childs, then process next siblingsectioninmakeSectionmode (this rule).