1 <?xml version='1.0' encoding='UTF-8' ?>
3 Nick's web site: XHTML output stage
5 Copyright © 2018-2019 Nick Bowler
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <https://www.gnu.org/licenses/>
20 <xsl:stylesheet version='1.0'
21 xmlns='http://www.w3.org/1999/xhtml'
22 xmlns:xhtml='http://www.w3.org/1999/xhtml'
23 xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
24 xmlns:func='http://exslt.org/functions'
25 xmlns:f='http://draconx.ca/my-functions'
26 extension-element-prefixes='func f'
27 exclude-result-prefixes='xhtml'>
29 <xsl:import href='layouts/functions.xsl' />
31 <xsl:output method='xml' encoding='UTF-8' indent='yes'
32 doctype-public='-//W3C//DTD XHTML 1.1//EN'
33 doctype-system='http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd' />
35 <xsl:param name='source-uri'
36 select='"//git.draconx.ca/gitweb/homepage.git/blob/"' />
37 <xsl:param name='site-title' select='"The Citrine Citadel"' />
38 <xsl:param name='section-links' select='//document/section-links' />
40 <func:function name='f:ends-with'>
41 <xsl:param name='a' />
42 <xsl:param name='b' />
44 select='substring($a, string-length($a)-string-length($b)+1)=$b' />
47 <xsl:template match='node()|@*'>
48 <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
52 Nokogiri's pretty-printer is a bit weird. Regardless of the indentation
53 setting, if an element has no child text nodes then it will be pretty-
54 printed. This works by adding arbitrary whitespace to that element, and
55 then all of its children are eligible to be pretty-printed.
57 If an element has any text nodes at all, then it is not pretty-printed and
58 neither are any of its descendents.
60 Adding arbitrary whitespace to <pre> is bad, so we inject zero-width non-
61 breaking spaces to prevent this. This will render fine but the spaces
62 should be removed before final output to avoid problems with copy+paste.
64 <xsl:template match='xhtml:pre'>
66 <xsl:apply-templates select='node()|@*' />
67 <xsl:text>⁠</xsl:text>
72 Likewise, adding spaces between consecutive span-level elements where
73 none existed before won't go over well.
75 <xsl:template name='glue-preceding-span'>
76 <xsl:if test='f:element-is-span(preceding-sibling::node()[1])'>
77 <xsl:text>⁠</xsl:text>
81 <xsl:template match='*[f:element-is-span()]'>
82 <xsl:call-template name='glue-preceding-span' />
84 <xsl:apply-templates select='node()|@*' />
86 <!-- avoid breaking within a span element -->
87 <xsl:text>⁠</xsl:text>
93 Manually strip whitespace-only text nodes so the pretty printer can do its
94 thing on remaining elements.
96 <xsl:template match='text()[normalize-space(.) = ""]'>
98 <!-- preserve anything according to xml:space -->
99 <xsl:when test='ancestor::*[@xml:space][1][@xml:space="preserve"]'>
102 <!-- preserve anything under <pre> -->
103 <xsl:when test='ancestor::xhtml:pre'><xsl:copy /></xsl:when>
104 <!-- preserve whitespace which is the only child node of an element -->
105 <xsl:when test='count(../node()) = 1'><xsl:copy /></xsl:when>
106 <!-- preserve whitespace between consecutive span-level elements
107 which have at least one non-whitespace sibling text element -->
108 <xsl:when test='f:element-is-span(preceding-sibling::node()[1])
109 and f:element-is-span(following-sibling::node()[1])
110 and ../text()[normalize-space(.) != ""]'>
116 <!-- Clean up whitespace where harmless to do so -->
117 <xsl:template match='xhtml:p/node()[1][self::text()]'>
118 <xsl:value-of select='f:strip-leading()' />
120 <xsl:template match='xhtml:p/node()[position()=last()][self::text()]'>
121 <xsl:value-of select='f:strip-trailing()' />
124 <!-- Add rel attributes to external links -->
125 <xsl:template match='xhtml:a[starts-with(@href,"http://")
126 or starts-with(@href,"https://")
127 or starts-with(@href,"//")]'>
128 <xsl:variable name='domain'
129 select='substring-before(
130 concat(translate(substring-after(@href, "//"), ":", "/"), "/"),
134 <xsl:apply-templates select='@*' />
135 <xsl:if test='not($domain="draconx.ca"
136 or f:ends-with($domain, ".draconx.ca"))'>
137 <xsl:attribute name='rel'>
139 <xsl:value-of select='@rel' />
140 <xsl:text> </xsl:text>
142 <xsl:text>external noopener noreferrer</xsl:text>
145 <xsl:apply-templates select='node()' />
149 <xsl:template match='xhtml:h2[@id]'>
150 <xsl:variable name='fragment' select='concat("#", @id)' />
152 <xsl:apply-templates select='node()|@*' />
153 <xsl:if test='$section-links = "yes"'>
154 <xsl:text> </xsl:text>
155 <small class='permalink'>
156 (<a href='{$fragment}'><xsl:value-of select='$fragment' /></a>)
162 <xsl:template match='copyright'>
164 <xsl:text>Copyright © </xsl:text>
165 <xsl:value-of select='text()' />
166 <xsl:text>.</xsl:text>
170 <xsl:template match='license'>
172 <xsl:text>Copying and distribution of this material</xsl:text>
173 <xsl:if test='normalize-space(modification-allowed)="yes"'>
174 <xsl:text>, with or without modification,</xsl:text>
176 <xsl:text> is permitted under the terms of the </xsl:text>
178 <xsl:attribute name='href'>
179 <xsl:value-of select='normalize-space(uri)' />
181 <xsl:value-of select='name' />
183 <xsl:text>.</xsl:text>
187 <xsl:template match='source'>
189 <xsl:text>This document was compiled from </xsl:text>
190 <a href='{concat($source-uri, revision, ":", file)}'>
191 <xsl:value-of select='file' />
193 <xsl:text> on </xsl:text>
194 <xsl:value-of select='compiletime' />
195 <xsl:text>.</xsl:text>
199 <xsl:template match='/'>
202 <meta name='viewport' content='width=device-width, initial-scale=1' />
203 <link rel='stylesheet' type='text/css' href='/style.css' />
204 <link rel="icon" href="data:," />
206 <xsl:variable name='page-title' select='string(/document/title)' />
207 <xsl:if test='$page-title and $site-title != $page-title'>
208 <xsl:value-of select='concat($page-title, " – ")' />
210 <xsl:value-of select='$site-title' />
214 <xsl:apply-templates select='/document/xhtml:html/@*' />
216 <xsl:if test='/document/hierarchy/parent'>
218 <small><xsl:value-of select='$site-title' /></small>
220 <div id='breadcrumbs'>
221 <strong>Return to: </strong>
223 <xsl:for-each select='/document/hierarchy/parent'>
224 <li><a href='{uri}'><xsl:value-of select='name'/></a></li>
231 <xsl:apply-templates select='/document/xhtml:html/node()' />
235 <xsl:apply-templates select='/document/copyright' />
236 <xsl:apply-templates select='/document/license' />
237 <xsl:apply-templates select='/document/source' />