Created
December 13, 2012 20:29
-
-
Save nicowilliams/4279509 to your computer and use it in GitHub Desktop.
XSL idiom for converting from <hN>Section Title</hN> to nested <section> style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE xsl:stylesheet [ ]> | |
<xsl:stylesheet version="2.0" | |
xpath-default-namespace="http://www.w3.org/1999/xhtml" | |
xmlns="foobar" | |
xmlns:xhtml="http://www.w3.org/1999/xhtml" | |
xmlns:foo="foobar" | |
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" | |
xmlns:xs="http://www.w3.org/2001/XMLSchema" | |
exclude-result-prefixes="foo"> | |
<!-- The following is a snippet of XSLT for XML document structure deepening. --> | |
<xsl:template match="*[matches(name(), '^h[2-9]') and ends-with(@class, 'section')]"> | |
<!-- N is the integer in hN --> | |
<xsl:variable name="N" select="substring-after(name(), 'h') cast as xs:integer"/> | |
<xsl:variable name="thisHN" select="name()"/> | |
<!-- We refer to this <hN> in various XPath contexts below where | |
current() will no longer be this <h2>, so we need to save it --> | |
<xsl:variable name="cur_sect" select="current()"/> | |
<xsl:element name="section"> | |
<xsl:attribute name="title" select="text()"/> | |
<!-- Handle the contents of just this section. Ask for all | |
siblings of this <hN> where the nodes we're looking for are | |
NOT hN, and their preceding <hN> is this one. --> | |
<xsl:apply-templates | |
select="(following-sibling::*[not(matches(name(), '^h[2-9]')) and | |
(preceding-sibling::*[matches(name(), '^h[2-9]')])[last()] is $cur_sect])"/> | |
<!-- Handle sub-sections of this section. Ask for all sibling | |
hNs of this hN where their preceding parent hN is | |
this one. --> | |
<xsl:apply-templates | |
select="following-sibling::*[matches(name(), '^h[2-9]') and | |
(preceding-sibling::*[name() = $thisHN])[last()] is $cur_sect and | |
(substring-after(name(), 'h') cast as xs:integer = ($N + 1)) | |
]"/> | |
</xsl:element> | |
</xsl:template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It took me a while to figure out a generic solution to this problem of | |
deepening XML document structure using XSLT. I could not find general | |
solutions online (or in the two books on XSLT that I bought), and though | |
I never worked out an XSLT 1.0 solution that worked well enough, I did | |
find a solution that works perfectly with XSLT 2.0. | |
The idiom consists of a template matching <hN>s that applies templates | |
to all following-sibling nodes that are not <hN> nodes and whose | |
preceding <hN> sibling is the current <hN> (the content of this | |
section) and then applies templates to all following-sibling nodes that | |
are <hN> nodes with N+1 and whose preceding-sibling <hN> is this one. | |
These are somewhat convoluted XPath expressions, so let's lay them out | |
in the attached .xsl file. | |
The XPath expression for selecting "the contents of this <hN>" is | |
roughly this: | |
(following-sibling::*[not(matches(name(), '^h[2-9]')) and | |
(preceding-sibling::*[matches(name(), '^h[2-9]')])[last()] is $cur_sect]) | |
where $cur_sect is a variable bound to "current()" prior to this | |
expression (i.e., the <hN> in question). | |
The XPath expression for selecting "sub-sections of this <hN>" is | |
roughly this: | |
following-sibling::*[matches(name(), '^h[2-9]') and | |
(preceding-sibling::*[name() = $thisHN])[last()] is $cur_sect and | |
(substring-after(name(), 'h') cast as xs:integer = ($N + 1))] | |
where $cur_sect is as described above, $thisHN is a variable bound to | |
$cur_sect's "name()", and $N is an xs:integer: the N in $thisHN | |
(<xsl:variable name="N" select="substring-after(name(), 'h') cast as xs:integer"/>). | |
That's it! | |
This generalizes really well, I think, at least as long as you can | |
figure out N or otherwise select following-sibling nodes that are | |
clearly sub-sections of the current one. | |
(I accidentally created this as an anonymous gist, | |
https://gist.github.com/4279476, even though I was signed in. This is | |
the same gist, re-created as me.) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment