Reputation: 1194
I have a document format with <section>
tags that can be infinitely nested. Each section contains things like paragraphs and tables, etc. but it's possible for the first element of a new section to be anything, including another section.
<doc>
<section name="one">
<p> Some text about <i>this</i> section.</p>
<section name="one.one">
<section name="one.one.one">
<p>Other text</p>
</section>
</section>
</section>
</doc>
I'm converting the document format to markdown. It seems the natural way to apply most tags would be with a structure like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="doc|p">
<xsl:apply-templates select="* | text()"/>
</xsl:template>
<xsl:template match="i">
<xsl:text>_</xsl:text>
<xsl:apply-templates select="* | text()"/>
<xsl:text>_</xsl:text>
</xsl:template>
</xsl:stylesheet>
However, with <section>
tags I need to keep track of the nesting depth to construct the section name. It seems like the natural way to do that is with <xsl:call-template>
and <xsl:param>
.
<xsl:template match="section">
<xsl:param name="depth" select="1"/>
<!-- do stuff to write the section name -->
<xsl:apply-templates select="* | text()"/>
</xsl>
The problem I have is that this needs to be triggered with <xsl:call-template>
rather than <xsl:apply-templates>
in order to increment the param.
<xsl:call-template name="section">
<xsl:with-param name="depth" select="$depth+1"/>
</xsl:call-template>
I can't give a parameter to apply-templates
, but neither can I just start the section
(or doc
) template with a recursive call-template
either, because a new section may not be the first tag.
Is there some way to approach this that lets me use call-template
, but maintains the tag order that xslt would normally follow with just the simple apply-templates
tag?
Upvotes: 0
Views: 257
Reputation: 52858
Here's an example of using xsl:number
(with level="multiple"
) to determine the depth. This will give you something like 1.1.1.
(third level) but then you can translate the .
to #
(and the numbers to nothing).
XML Input
<doc>
<section name="one">
<p> Some text about <i>this</i> section.</p>
<section name="one.one">
<section name="one.one.one">
<p>Other text</p>
</section>
</section>
</section>
</doc>
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="chars" select="'.0123456789'"/>
<xsl:template match="i">
<xsl:value-of select="concat('_',.,'_')"/>
</xsl:template>
<xsl:template match="p">
<xsl:apply-templates/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="section">
<xsl:variable name="depth">
<xsl:number level="multiple" format="1."/>
</xsl:variable>
<xsl:value-of select="translate($depth,$chars,'#')"/>
<xsl:value-of select="concat(@name,'
')"/>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Output
#one
Some text about _this_ section.
##one.one
###one.one.one
Other text
EDIT
Here's another way counting ancestor-or-self like Michael Kay suggested (duh!)...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="i">
<xsl:value-of select="concat('_',.,'_')"/>
</xsl:template>
<xsl:template match="p">
<xsl:apply-templates/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="section">
<xsl:for-each select="ancestor-or-self::section">#</xsl:for-each>
<xsl:value-of select="concat(@name,'
')"/>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 2
Reputation: 163342
Firstly, you can pass params with apply-templates just as easily as with call-template: apply-templates allows an xsl:with-param child element.
Secondly, you don't need parameters to determine the depth of nesting because you can use XPath expressions that navigate upwards, e.g. count(ancestor::section)
.
Upvotes: 1