开发者

How can I make my XSLT template cope with missing parent elements?

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&#xa;</xsl:if>
  <xsl:text>@Section @Begin&#xa;</xsl:text>
  <xsl:apply-templates/>
  <xsl:text>@End @Section&#xa;</xsl:text>
  <xsl:if test="not(following-sibling::section)">@EndSections&#xa;</xsl:if>
</xsl:template>

<xsl:template match="chapter">
  <xsl:if test="not(preceding-sibling::chapter)">@BeginChapters&#xa;</xsl:if>
  <xsl:text>@Chapter @Begin&#xa;</xsl:text>
  <xsl:apply-templates/>
  <xsl:text>@End @Chapter&#xa;</xsl:text>
  <xsl:if test="not(following-sibling::chapter)">@EndChapters&#xa;</xsl:if>
</xsl:template>

<xsl:template match="/book">
  <xsl:text>@Book @Begin&#xa;</xsl:text>
  <xsl:apply-templates/>
  <xsl:text>@End @Book&#xa;</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 o开发者_Go百科ne section).


This stylesheet:

<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="chapter|section" mode="chapter" name="makeChapter">
        <xsl:text>  @Chapter @Begin&#xa;</xsl:text>
        <xsl:apply-templates select="self::chapter/node()[1]|
                                     self::section"
                             mode="section"/>
        <xsl:text>  @End @Chapter&#xa;</xsl:text>
        <xsl:apply-templates 
             select="self::chapter/following-sibling::node()[1]|
                     self::section/following-sibling::chapter[1] "
             mode="chapter"/>
    </xsl:template>
    <xsl:template match="section" mode="makeSection" name="makeSection">
        <xsl:text>    @Section @Begin&#xa;</xsl:text>
        <xsl:apply-templates/>
        <xsl:text>    @End @Section&#xa;</xsl:text>
        <xsl:apply-templates select="following-sibling::node()[1]/self::section"
                             mode="makeSection"/>
    </xsl:template>
    <xsl:template match="section" mode="section">
        <xsl:text>    @BeginSections&#xa;</xsl:text>
        <xsl:call-template name="makeSection"/>
        <xsl:text>    @EndSections&#xa;</xsl:text>
    </xsl:template>
    <xsl:template match="book/chapter|book/section">
        <xsl:text>  @BeginChapters&#xa;</xsl:text>
        <xsl:call-template name="makeChapter"/>
        <xsl:text>  @EndChapters&#xa;</xsl:text>
    </xsl:template>
    <xsl:template match="book">
        <xsl:text>@Book @Begin&#xa;</xsl:text>
        <xsl:apply-templates select="node()[1]"/>
        <xsl:text>@End @Book&#xa;</xsl:text>
    </xsl:template>
</xsl:stylesheet>

Output:

@Book @Begin
  @BeginChapters
  @Chapter @Begin
    @BeginSections
    @Section @Begin
    @End @Section
    @EndSections
  @End @Chapter
  @Chapter @Begin
  @End @Chapter
  @EndChapters
@End @Book

Note: Fine grained traversal, grouping adjacents book/sections into one Chapter.

Edit: Corrected following sibling process for Chapters.

With this input:

<book>
    <section/>
    <section/>
    <section/>
    <chapter/>
    <section/>
</book>

Output:

@Book @Begin
  @BeginChapters
  @Chapter @Begin
    @BeginSections
    @Section @Begin
    @End @Section
    @Section @Begin
    @End @Section
    @Section @Begin
    @End @Section
    @EndSections
  @End @Chapter
  @Chapter @Begin
  @End @Chapter
  @Chapter @Begin
    @BeginSections
    @Section @Begin
    @End @Section
    @EndSections
  @End @Chapter
  @EndChapters
@End @Book

Edit: Better named templates to understand.

Note: Five rules: book rule "opens" a book and process first child; book/section|book/chapter rule (always the first because the fine grained transversal) "opens" book chapters, calls makeChapter; makeChapter rule, "opens" a chapter, process first child if context is chapter or self if context is section both in section mode, process next sibling if context is chapter or following chapter if context is section in chapter mode (meaning next chapter); section rule in section mode (because the node by node process, it will always match the first sections for adjacents sections) "opens" chapter sections an calls makeSection rule; makeSection rule "opens" a section an process childs, then process next sibling section in makeSection mode (this rule).


you need to first wrap the orphan sections into a single chapter..

we create a variable for this to hold the wrapped elements

<xsl:variable name="orphan">
  <chapter>
   <xsl:for-each select="/book/section">
      <xsl:copy-of select="." />
   </xsl:for-each>
  </chapter>
</xsl:variable>

then when you apply the templates inside the /book matching template you need to also use this newly created variable

<xsl:apply-templates select="chapter|exslt:node-set($orphan)"/>

and to use the exslt you need to add the namespace

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common">

Final result is

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common">
<xsl:output method="html" encoding="iso-8859-1"/>
<xsl:strip-space elements="*"/>

<xsl:variable name="orphan">
  <chapter>
   <xsl:for-each select="/book/section">
      <xsl:copy-of select="." />
   </xsl:for-each>
  </chapter>
</xsl:variable>

<xsl:template match="section">
  <xsl:if test="not(preceding-sibling::section)">@BeginSections&#xa;</xsl:if>
  <xsl:text>@Section @Begin&#xa;</xsl:text>
  <xsl:apply-templates/>
  <xsl:text>@End @Section&#xa;</xsl:text>
  <xsl:if test="not(following-sibling::section)">@EndSections&#xa;</xsl:if>
</xsl:template>

<xsl:template match="chapter">
  <xsl:if test="not(preceding-sibling::chapter)">@BeginChapters&#xa;</xsl:if>
  <xsl:text>@Chapter @Begin&#xa;</xsl:text>
  <xsl:apply-templates/>
  <xsl:text>@End @Chapter&#xa;</xsl:text>
  <xsl:if test="not(following-sibling::chapter)">@EndChapters&#xa;</xsl:if>
</xsl:template>

<xsl:template match="/book">
  <xsl:text>@Book @Begin&#xa;</xsl:text>
  <xsl:apply-templates select="chapter|exslt:node-set($orphan)"/>
  <xsl:text>@End @Book&#xa;</xsl:text>
</xsl:template>
</xsl:stylesheet>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜