]> git.draconx.ca Git - homepage.git/blob - layouts/default.xsl
Improve styling of command examples.
[homepage.git] / layouts / default.xsl
1 <?xml version='1.0' encoding='UTF-8' ?>
2 <!--
3   Nick's web site: XHTML output stage
4
5   Copyright © 2018-2021 Nick Bowler
6
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.
11
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.
16
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/>
19 -->
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'>
28
29 <xsl:import href='layouts/functions.xsl' />
30
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'
34   cdata-section-elements='style script' />
35
36 <xsl:param name='source-uri'
37   select='"//git.draconx.ca/gitweb/homepage.git/"' />
38 <xsl:param name='site-title' select='"The Citrine Citadel"' />
39 <xsl:param name='section-links' select='//document/section-links' />
40
41 <func:function name='f:ends-with'>
42   <xsl:param name='a' />
43   <xsl:param name='b' />
44   <func:result
45     select='substring($a, string-length($a)-string-length($b)+1)=$b' />
46 </func:function>
47
48 <xsl:template match='node()|@*'>
49   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
50 </xsl:template>
51
52 <!--
53   Nokogiri's pretty-printer is a bit weird.  Regardless of the indentation
54   setting, if an element has no child text nodes then it will be pretty-
55   printed.  This works by adding arbitrary whitespace to that element, and
56   then all of its children are eligible to be pretty-printed.
57
58   If an element has any text nodes at all, then it is not pretty-printed and
59   neither are any of its descendents.
60
61   Adding arbitrary whitespace to <pre> is bad, so we inject zero-width non-
62   breaking spaces to prevent this.  This will render fine but the spaces
63   should be removed before final output to avoid problems with copy+paste.
64 -->
65 <xsl:template match='xhtml:pre'>
66   <xsl:copy>
67     <xsl:apply-templates select='node()|@*' />
68     <xsl:text>&#x2060;</xsl:text>
69   </xsl:copy>
70 </xsl:template>
71
72 <!--
73   Likewise, adding spaces between consecutive span-level elements where
74   none existed before won't go over well.
75 -->
76 <xsl:template name='glue-preceding-span'>
77   <xsl:if test='f:element-is-span(preceding-sibling::node()[1])'>
78     <xsl:text>&#x2060;</xsl:text>
79   </xsl:if>
80 </xsl:template>
81
82 <xsl:template match='*[f:element-is-span()]'>
83   <xsl:call-template name='glue-preceding-span' />
84   <xsl:copy>
85     <xsl:apply-templates select='node()|@*' />
86     <xsl:if test='*'>
87       <!-- avoid breaking within a span element -->
88       <xsl:text>&#x2060;</xsl:text>
89     </xsl:if>
90   </xsl:copy>
91 </xsl:template>
92
93 <!--
94   Manually strip whitespace-only text nodes so the pretty printer can do its
95   thing on remaining elements.
96 -->
97 <xsl:template match='text()[normalize-space(.) = ""]'>
98   <xsl:choose>
99     <!-- preserve anything according to xml:space -->
100     <xsl:when test='ancestor::*[@xml:space][1][@xml:space="preserve"]'>
101       <xsl:copy />
102     </xsl:when>
103     <!-- preserve anything under <pre> -->
104     <xsl:when test='ancestor::xhtml:pre'><xsl:copy /></xsl:when>
105     <!-- preserve whitespace which is the only child node of an element -->
106     <xsl:when test='count(../node()) = 1'><xsl:copy /></xsl:when>
107     <!-- preserve whitespace between consecutive span-level elements
108          which have at least one non-whitespace sibling text element -->
109     <xsl:when test='f:element-is-span(preceding-sibling::node()[1])
110                     and f:element-is-span(following-sibling::node()[1])
111                     and ../text()[normalize-space(.) != ""]'>
112       <xsl:copy />
113     </xsl:when>
114   </xsl:choose>
115 </xsl:template>
116
117 <!-- Clean up whitespace where harmless to do so -->
118 <xsl:template match='xhtml:p/node()[1][self::text()]'>
119   <xsl:value-of select='f:strip-leading()' />
120 </xsl:template>
121 <xsl:template match='xhtml:p/node()[position()=last()][self::text()]'>
122   <xsl:value-of select='f:strip-trailing()' />
123 </xsl:template>
124
125 <!-- Add rel attributes to external links -->
126 <xsl:template match='xhtml:a[starts-with(@href,"http://")
127                           or starts-with(@href,"https://")
128                           or starts-with(@href,"//")]'>
129   <xsl:variable name='domain'
130     select='substring-before(
131               concat(translate(substring-after(@href, "//"), ":", "/"), "/"),
132               "/")' />
133
134   <xsl:copy>
135     <xsl:apply-templates select='@*' />
136     <xsl:if test='not($domain="draconx.ca"
137                       or f:ends-with($domain, ".draconx.ca"))'>
138       <xsl:attribute name='rel'>
139         <xsl:if test='@rel'>
140           <xsl:value-of select='@rel' />
141           <xsl:text> </xsl:text>
142         </xsl:if>
143         <xsl:text>external noopener noreferrer</xsl:text>
144       </xsl:attribute>
145     </xsl:if>
146     <xsl:apply-templates select='node()' />
147   </xsl:copy>
148 </xsl:template>
149
150 <xsl:template match='xhtml:h2[@id]'>
151   <xsl:variable name='fragment' select='concat("#", @id)' />
152   <xsl:copy>
153     <xsl:apply-templates select='node()|@*' />
154     <xsl:if test='$section-links = "yes"'>
155       <xsl:text> </xsl:text>
156       <small class='permalink'>
157         (<a href='{$fragment}'><xsl:value-of select='$fragment' /></a>)
158       </small>
159     </xsl:if>
160   </xsl:copy>
161 </xsl:template>
162
163 <!--
164   Convert caption attribute on tables into proper caption elements, to allow
165   a simple way to add captions to kramdown tables.
166 -->
167 <xsl:template match='@caption[parent::xhtml:table]' />
168 <xsl:template match='xhtml:table[@caption]'>
169   <xsl:copy>
170     <xsl:apply-templates select='@*' />
171     <caption><xsl:value-of select='normalize-space(@caption)' /></caption>
172     <xsl:apply-templates select='node()' />
173   </xsl:copy>
174 </xsl:template>
175
176 <!--
177   Delete style elements, as they will get hoisted occur under <head> below.
178   If the generate-listing attribute was specified, produce a code listing
179   where the style attribute was found.
180 -->
181 <xsl:template match='xhtml:style|@generate-listing[parent::xhtml:style]' />
182 <xsl:template match='xhtml:style[@generate-listing]'>
183   <pre>&#x2060;<code><xsl:value-of select='f:strip-leading(.)' /></code></pre>
184 </xsl:template>
185
186 <!--
187   Add a simple way to reference a document node by ID and include the XHTML
188   code listing directly in the document.
189 -->
190 <xsl:template match='xhtml:generate-xhtml-listing'>
191   <xsl:variable name='target' select='@target' />
192   <pre>&#x2060;<code>
193     <xsl:value-of select='f:xhtml-listing(//xhtml:*[@id=$target])' />
194   </code></pre>
195 </xsl:template>
196
197 <!-- For paragraphs containing only kbd elements, wrap in blockquote. -->
198 <xsl:template match='xhtml:p[*[last()=count(../xhtml:kbd)]]'>
199   <blockquote>
200     <xsl:copy>
201       <xsl:apply-templates select='node()|@*' />
202     </xsl:copy>
203   </blockquote>
204 </xsl:template>
205
206 <!--
207   Wrap each word of text in kbd elements in spans, so they can be styled
208   to avoid linebreaks in the middle of option names and other bad places.
209 -->
210 <xsl:template name='spanify-text' match='xhtml:kbd/text()'>
211   <xsl:param name='text' select='normalize-space(.)' />
212   <xsl:variable name='firstword' select='substring-before($text, " ")' />
213   <xsl:choose>
214     <xsl:when test='$firstword'>
215       <span><xsl:value-of select='$firstword' /></span>
216       <xsl:text> </xsl:text>
217     </xsl:when>
218     <xsl:when test='$text'>
219       <span><xsl:value-of select='$text' /></span>
220     </xsl:when>
221   </xsl:choose>
222   <xsl:if test='$firstword'>
223     <xsl:call-template name='spanify-text'>
224       <xsl:with-param name='text' select='substring-after($text, " ")' />
225     </xsl:call-template>
226   </xsl:if>
227 </xsl:template>
228
229 <xsl:template match='copyright'>
230   <p>
231     <xsl:text>Copyright © </xsl:text>
232     <xsl:value-of select='text()' />
233     <xsl:text>.</xsl:text>
234   </p>
235 </xsl:template>
236
237 <xsl:template match='license'>
238   <p>
239     <xsl:text>Copying and distribution of this material</xsl:text>
240     <xsl:if test='normalize-space(modification-allowed)="yes"'>
241       <xsl:text>, with or without modification,</xsl:text>
242     </xsl:if>
243     <xsl:text> is permitted under the terms of the </xsl:text>
244     <a rel='license'>
245       <xsl:attribute name='href'>
246         <xsl:value-of select='normalize-space(uri)' />
247       </xsl:attribute>
248       <xsl:value-of select='name' />
249     </a>
250     <xsl:text>.</xsl:text>
251   </p>
252 </xsl:template>
253
254 <func:function name='f:matching-child'>
255   <xsl:param name='child' select='./copyright-holder' />
256   <xsl:param name='node' select='.' />
257   <xsl:param name='nodeset' select='$node/../*[name()=name($node)]' />
258
259   <func:result select='$nodeset[*[name()=name($child)]=$child]' />
260 </func:function>
261
262 <func:function name='f:attribution-order'>
263   <xsl:param name='a' />
264   <xsl:param name='b' />
265
266   <xsl:variable name='docmatch'
267     select='number($a/copyright-holder = /document/copyright-holder)
268             - number($b/copyright-holder = /document/copyright-holder)' />
269
270   <xsl:variable name='authmatch'
271     select='count(f:matching-child($a/copyright-holder, $a))
272             - count(f:matching-child($b/copyright-holder, $b))' />
273
274   <xsl:variable name='licmatch'
275     select='count(f:matching-child($a/license, $a))
276             - count(f:matching-child($b/license, $b))' />
277
278   <xsl:choose>
279     <xsl:when test='$docmatch'><func:result select='$docmatch' /></xsl:when>
280     <xsl:when test='$authmatch'><func:result select='$authmatch' /></xsl:when>
281     <xsl:when test='$licmatch'><func:result select='$licmatch' /></xsl:when>
282     <xsl:otherwise><func:result select='"nope"' /></xsl:otherwise>
283   </xsl:choose>
284 </func:function>
285
286 <xsl:template match='image/license'>
287   <xsl:text>, </xsl:text>
288   <a href='{uri}' rel='license'><xsl:value-of select='shortname' /></a>
289 </xsl:template>
290
291 <xsl:template match='image'>
292   <xsl:choose>
293     <xsl:when test='position() = 1'>, except </xsl:when>
294     <xsl:when test='position() = last()'> and </xsl:when>
295     <xsl:otherwise>, </xsl:otherwise>
296   </xsl:choose>
297   <a href='{uri}'><xsl:value-of select='title' /></a>
298   <xsl:text> © </xsl:text>
299   <xsl:value-of select='copyright' />
300   <xsl:apply-templates select='license' />
301 </xsl:template>
302
303 <xsl:template name='image-attribution'>
304 <!--
305   <xsl:variable name='x' select='/document/image[copyright-holder="Nick Bowler"][1]' />
306   <xsl:variable name='y' select='/document/image[copyright-holder="Nick Bowler"][4]' />
307 -->
308   <xsl:variable name='images-fragment'>
309     <xsl:for-each select='/document/image'>
310       <xsl:sort select='number(copyright-holder = /document/copyright-holder)'
311                 data-type='number' order='descending' />
312       <xsl:sort select='count(f:matching-child(copyright-holder))'
313                 data-type='number' order='descending' />
314       <xsl:sort select='copyright-holder' order='descending' />
315       <xsl:sort select='count(f:matching-child(license))'
316                 data-type='number' order='descending' />
317       <xsl:sort select='license/identifier' order='descending' />
318
319       <xsl:call-template name='notransform' />
320     </xsl:for-each>
321   </xsl:variable>
322   <xsl:variable name='images' select='exslt:node-set($images-fragment)/*' />
323
324   <xsl:variable name='abbrev-split'
325       select='count($images[copyright-holder = $images[1]/copyright-holder
326                  and license/identifier = $images[1]/license/identifier])' />
327
328   <xsl:variable name='abbrev-years-fragment'>
329     <xsl:for-each select='$images[$abbrev-split >= position()]/copyright-year'>
330       <xsl:sort data-type='number' />
331       <copyright-year><xsl:value-of select='.' /></copyright-year>
332     </xsl:for-each>
333   </xsl:variable>
334   <xsl:variable name='abbrev-years'
335     select='exslt:node-set($abbrev-years-fragment)/*' />
336
337   <p>
338     <xsl:text>Images © </xsl:text>
339     <xsl:value-of select='$abbrev-years[1]' />
340     <xsl:if test='$abbrev-years[last()] != $abbrev-years[1]'>
341       <xsl:value-of select='concat("–", $abbrev-years[last()])' />
342     </xsl:if>
343     <xsl:value-of select='concat(" ", $images[1]/copyright-holder)' />
344     <xsl:apply-templates select='$images[1]/license' />
345     <xsl:apply-templates select='$images[position() > $abbrev-split]' />
346     <xsl:text>.</xsl:text>
347   </p>
348 </xsl:template>
349
350 <xsl:template match='source'>
351   <p>
352     <xsl:text>This document was compiled</xsl:text>
353     <xsl:choose>
354       <xsl:when test='file'>
355         <xsl:text> from </xsl:text>
356         <a href='{concat($source-uri, "blob/", revision, ":", file)}'>
357           <xsl:value-of select='file' />
358         </a>
359       </xsl:when>
360       <xsl:when test='dir'>
361         <xsl:text> from </xsl:text>
362         <a href='{concat($source-uri, "tree/", revision, ":", dir)}'>
363           <xsl:value-of select='dir' />
364         </a>
365       </xsl:when>
366     </xsl:choose>
367     <xsl:text> on </xsl:text>
368     <xsl:value-of select='compiletime' />
369     <xsl:text>.</xsl:text>
370   </p>
371 </xsl:template>
372
373 <xsl:template match='xhtml:h1[not(preceding::xhtml:h1)]'>
374   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
375   <xsl:if test='/document/article/published'>
376     <div id='article-info'>
377       <p>
378         <xsl:text>Posted </xsl:text>
379         <xsl:value-of select='/document/article/published' />
380       </p>
381     </div>
382   </xsl:if>
383 </xsl:template>
384
385 <xsl:template match='/'>
386   <html>
387     <head>
388       <meta name='viewport' content='width=device-width, initial-scale=1' />
389       <link rel='stylesheet' type='text/css' href='/style.css' />
390       <link rel="icon" href="data:," />
391       <title>
392         <xsl:variable name='page-title' select='string(/document/title)' />
393         <xsl:if test='$page-title and $site-title != $page-title'>
394           <xsl:value-of select='concat($page-title, " – ")' />
395         </xsl:if>
396         <xsl:value-of select='$site-title' />
397       </title>
398       <!-- Hoist all style elements to <head> as required by the doctype. -->
399       <xsl:for-each select='//xhtml:style'>
400         <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
401       </xsl:for-each>
402     </head>
403     <body>
404       <xsl:apply-templates select='/document/xhtml:html/@*' />
405
406       <xsl:if test='/document/hierarchy/parent'>
407         <p id='sitetitle'>
408           <small><xsl:value-of select='$site-title' /></small>
409         </p>
410         <div id='breadcrumbs'>
411           <strong>Return to: </strong>
412           <ul>
413             <xsl:for-each select='/document/hierarchy/parent'>
414               <li><a href='{uri}'><xsl:value-of select='name'/></a></li>
415             </xsl:for-each>
416           </ul>
417         </div>
418         <hr />
419       </xsl:if>
420
421       <xsl:apply-templates select='/document/xhtml:html/node()' />
422
423       <hr />
424       <div id='footer'>
425         <xsl:apply-templates select='/document/copyright' />
426         <xsl:apply-templates select='/document/license' />
427         <xsl:apply-templates select='/document/source' />
428       </div>
429     </body>
430   </html>
431 </xsl:template>
432
433 <xsl:include href='layouts/clickytable.xsl' />
434
435 </xsl:stylesheet>