Listing. An XSLT 2.0 transformer to convert Bike outlines to HTML [0087]
We can write convert Bike outlines to reasonable HTML using an XSLT 2.0 stylesheet, bike-to-html.xsl detailed below.
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:html="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="xhtml">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
We will allow several tags to be copied verbatim into the output, as Bike uses these in the same way that idiomatic HTML does.
<xsl:template match="html:html | html:body | html:code | html:strong | html:em | html:mark">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>Bike leaves behind a lot of empty span elements; we drop these.
<xsl:template match="html:span">
<xsl:apply-templates />
</xsl:template>Bike uses ul for all lists; the list type is determined not at this level, but rather by each individual item’s @data-type attribute. To get this data into the HTML list model, we must group items that have the same @data-type and wrap them in an appropriate list-forming element.
To do this, we use XSLT 2.0’s xsl:for-each-group instruction to group adjacent li elements by their @data-type attribute. (It is extremely difficult and error-prone to write equivalent code in the more widely available XSLT 1.0.) We must convert @data-type to a string: otherwise, the transformer will crash when it encounters an item without a @data-type attribute.
<xsl:template match="html:ul">
<xsl:for-each-group select="html:li" group-adjacent="string(@data-type)">
<xsl:choose>
<xsl:when test="@data-type='ordered' or @data-type='task'">
<ol>
<xsl:apply-templates select="current-group()" />
</ol>
</xsl:when>
<xsl:when test="@data-type='unordered'">
<ul>
<xsl:apply-templates select="current-group()" />
</ul>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>Next, we match each individual li element; the content of a list item is stored in a p element directly under li, so we let the transformer fall thorugh the parent and then format the content underneath according to the @data-type of the item.
<xsl:template match="html:li">
<xsl:apply-templates />
</xsl:template>
<xsl:template
match="html:li[@data-type='ordered' or @data-type='unordered' or @data-type='task']/html:p">
<li>
<xsl:apply-templates />
</li>
</xsl:template>Bike has correctly adopted the optimal explicit and relative model of hierarchy, in contrast to HTML; this means that the depth of a heading is not reflected in the element that introduces it, but is instead inferred from its actual position in the outline hierarchy. To convert Bike outlines to idiomatic HTML, we must flatten the hierarchy and introduce explicit heading levels; luckily, this is easy to accomplish in XSLT by counting the ancestors of heading type.
<xsl:template match="html:li[@data-type='heading']/html:p">
<xsl:element
name="h{count(ancestor::html:li[@data-type='heading'])}">
<xsl:apply-templates />
</xsl:element>
</xsl:template>The remainder of the row types are not difficult to render; you may prefer alternative formatting depending on your goals.
<xsl:template match="html:li[@data-type='quote']/html:p">
<blockquote>
<xsl:apply-templates />
</blockquote>
</xsl:template>
<xsl:template match="html:li[@data-type='note']/html:p">
<p>
<em>
<xsl:apply-templates />
</em>
</p>
</xsl:template>
<xsl:template match="html:li[not(@data-type)]/html:p">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
</xsl:stylesheet>