]> git.draconx.ca Git - homepage.git/blob - layouts/default.xsl
Adjust whitespace preservation rules.
[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-2019 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
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' />
39
40 <func:function name='f:ends-with'>
41   <xsl:param name='a' />
42   <xsl:param name='b' />
43   <func:result
44     select='substring($a, string-length($a)-string-length($b)+1)=$b' />
45 </func:function>
46
47 <xsl:template match='node()|@*'>
48   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
49 </xsl:template>
50
51 <!--
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.
56
57   If an element has any text nodes at all, then it is not pretty-printed and
58   neither are any of its descendents.
59
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.
63 -->
64 <xsl:template match='xhtml:pre'>
65   <xsl:copy>
66     <xsl:apply-templates select='node()|@*' />
67     <xsl:text>&#x2060;</xsl:text>
68   </xsl:copy>
69 </xsl:template>
70
71 <!--
72   Likewise, adding spaces between consecutive span-level elements where
73   none existed before won't go over well.
74 -->
75 <xsl:template name='glue-preceding-span'>
76   <xsl:if test='f:element-is-span(preceding-sibling::node()[1])'>
77     <xsl:text>&#x2060;</xsl:text>
78   </xsl:if>
79 </xsl:template>
80
81 <xsl:template match='*[f:element-is-span()]'>
82   <xsl:call-template name='glue-preceding-span' />
83   <xsl:copy>
84     <xsl:apply-templates select='node()|@*' />
85     <xsl:if test='*'>
86       <!-- avoid breaking within a span element -->
87       <xsl:text>&#x2060;</xsl:text>
88     </xsl:if>
89   </xsl:copy>
90 </xsl:template>
91
92 <!--
93   Manually strip whitespace-only text nodes so the pretty printer can do its
94   thing on remaining elements.
95 -->
96 <xsl:template match='text()[normalize-space(.) = ""]'>
97   <xsl:choose>
98     <!-- preserve anything according to xml:space -->
99     <xsl:when test='ancestor::*[@xml:space][1][@xml:space="preserve"]'>
100       <xsl:copy />
101     </xsl:when>
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(.) != ""]'>
111       <xsl:copy />
112     </xsl:when>
113   </xsl:choose>
114 </xsl:template>
115
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()' />
119 </xsl:template>
120 <xsl:template match='xhtml:p/node()[position()=last()][self::text()]'>
121   <xsl:value-of select='f:strip-trailing()' />
122 </xsl:template>
123
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, "//"), ":", "/"), "/"),
131               "/")' />
132
133   <xsl:copy>
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'>
138         <xsl:if test='@rel'>
139           <xsl:value-of select='@rel' />
140           <xsl:text> </xsl:text>
141         </xsl:if>
142         <xsl:text>external noopener noreferrer</xsl:text>
143       </xsl:attribute>
144     </xsl:if>
145     <xsl:apply-templates select='node()' />
146   </xsl:copy>
147 </xsl:template>
148
149 <xsl:template match='xhtml:h2[@id]'>
150   <xsl:variable name='fragment' select='concat("#", @id)' />
151   <xsl:copy>
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>)
157       </small>
158     </xsl:if>
159   </xsl:copy>
160 </xsl:template>
161
162 <xsl:template match='copyright'>
163   <p>
164     <xsl:text>Copyright © </xsl:text>
165     <xsl:value-of select='text()' />
166     <xsl:text>.</xsl:text>
167   </p>
168 </xsl:template>
169
170 <xsl:template match='license'>
171   <p>
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>
175     </xsl:if>
176     <xsl:text> is permitted under the terms of the </xsl:text>
177     <a rel='license'>
178       <xsl:attribute name='href'>
179         <xsl:value-of select='normalize-space(uri)' />
180       </xsl:attribute>
181       <xsl:value-of select='name' />
182     </a>
183     <xsl:text>.</xsl:text>
184   </p>
185 </xsl:template>
186
187 <xsl:template match='source'>
188   <p>
189     <xsl:text>This document was compiled from </xsl:text>
190     <a href='{concat($source-uri, revision, ":", file)}'>
191       <xsl:value-of select='file' />
192     </a>
193     <xsl:text> on </xsl:text>
194     <xsl:value-of select='compiletime' />
195     <xsl:text>.</xsl:text>
196   </p>
197 </xsl:template>
198
199 <xsl:template match='/'>
200   <html>
201     <head>
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:," />
205       <title>
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, " – ")' />
209         </xsl:if>
210         <xsl:value-of select='$site-title' />
211       </title>
212     </head>
213     <body>
214       <xsl:apply-templates select='/document/xhtml:html/@*' />
215
216       <xsl:if test='/document/hierarchy/parent'>
217         <p id='sitetitle'>
218           <small><xsl:value-of select='$site-title' /></small>
219         </p>
220         <div id='breadcrumbs'>
221           <strong>Return to: </strong>
222           <ul>
223             <xsl:for-each select='/document/hierarchy/parent'>
224               <li><a href='{uri}'><xsl:value-of select='name'/></a></li>
225             </xsl:for-each>
226           </ul>
227         </div>
228         <hr />
229       </xsl:if>
230
231       <xsl:apply-templates select='/document/xhtml:html/node()' />
232
233       <hr />
234       <div id='footer'>
235         <xsl:apply-templates select='/document/copyright' />
236         <xsl:apply-templates select='/document/license' />
237         <xsl:apply-templates select='/document/source' />
238       </div>
239     </body>
240   </html>
241 </xsl:template>
242
243 </xsl:stylesheet>