]> Lady’s Gitweb - Shrine-XSLT/blob - transform.xslt
e8ecb3d55fe1ec7047b7c20eb7319673d2a49aca
[Shrine-XSLT] / transform.xslt
1 <!--
2 This is a⸠n extremely simple⸡ progressively‐less‐simple X·S·L·T
3 transform which simply reads in a `template.xml` and :—
4
5 • Replaces the `<html:shrine-content>` element with the content of the document it is being applied to,
6
7 • If the root element of the document it is being applied to has a `@data-shrine-header` attribute, replaces the `<html:shrine-header>` with the contents of the corresponding header file (`⸺-header.xml`), and
8
9 • If the root element of the document it is being applied to has a `@data-shrine-footer` attribute, replaces the `<html:shrine-footer>` with the contents of the corresponding footer file (`⸺-footer.xml`), and
10
11 • Copies any remaining `@lang` or `@data-*` attributes from the root element of the document it is being applied to over to the root element of the template.
12
13 The intent is that this file is used in conjunction with a Makefile to quickly automate inserting header and footer content into documents.
14 The exact feature·set might be somewhat more expansive than the description above; for a lengthier overview of what this file does, see `README.markdown`.
15
16 Feel free to add additional templates and features to suit your needs!
17
18 ___
19
20 © 2022 Lady [@ Lady’s Computer]
21
22 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
23 If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
24 -->
25 <xslt:transform
26 xmlns:atom="http://www.w3.org/2005/Atom"
27 xmlns:html="http://www.w3.org/1999/xhtml"
28 xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
29 version="1.0"
30 >
31 <xslt:param name="BASEIRI" select="'http://example.com'"/>
32 <xslt:param name="DATETIME" select="'1972-12-31T00:00:00Z'"/>
33 <xslt:param name="OUTPUTPATH" select="'/unknown'"/>
34 <xslt:variable name="baseiri">
35 <xslt:choose>
36 <xslt:when test="contains($BASEIRI, '://')"> <!-- there is an authority -->
37 <xslt:variable name="noscheme" select="substring-after($BASEIRI, '://')"/>
38 <xslt:value-of select="substring-before($BASEIRI, '://')"/>
39 <xslt:text>://</xslt:text>
40 <xslt:choose>
41 <xslt:when test="contains($noscheme, '/')">
42 <xslt:value-of select="substring-before($noscheme, '/')"/>
43 </xslt:when>
44 <xslt:otherwise>
45 <xslt:value-of select="$noscheme"/>
46 </xslt:otherwise>
47 </xslt:choose>
48 </xslt:when>
49 <xslt:otherwise>
50 <xslt:value-of select="substring-before($BASEIRI, ':')"/>
51 </xslt:otherwise>
52 </xslt:choose>
53 </xslt:variable>
54 <xslt:variable name="datetime" select="string($DATETIME)"/>
55 <xslt:variable name="outputpath">
56 <xslt:if test="not(starts-with($OUTPUTPATH, '/'))">
57 <xslt:text>/</xslt:text>
58 </xslt:if>
59 <xslt:value-of select="$OUTPUTPATH"/>
60 </xslt:variable>
61 <xslt:variable name="source" select="current()"/>
62 <xslt:variable name="template" select="document('./template.xml')"/>
63
64 <!--
65 Instead of actually processing the root node, process the template in `template` mode.
66 -->
67 <xslt:template match="/">
68 <xslt:choose>
69 <xslt:when test="atom:feed">
70 <xslt:apply-templates mode="feed"/>
71 </xslt:when>
72 <xslt:otherwise>
73 <xslt:apply-templates select="$template" mode="template"/>
74 </xslt:otherwise>
75 </xslt:choose>
76 </xslt:template>
77
78 <!--
79 Process non‐template elements.
80 By default, just copy the element, but remove any `@data-shrine-*` attribuets or `@slot` attributes with a value that begins with `shrine-`.
81 Some elements may have special treatment.
82 -->
83 <xslt:template match="*" mode="content">
84 <xslt:element name="{local-name()}">
85 <xslt:for-each select="@*[not(starts-with(name(), 'data-shrine-')) and not(name()='slot' and starts-with(., 'shrine-'))]">
86 <xslt:copy/>
87 </xslt:for-each>
88 <xslt:for-each select="*|text()">
89 <xslt:choose>
90 <xslt:when test="@slot[starts-with(., 'shrine-')]">
91 <xslt:comment>
92 <text> placeholder for slotted element </text>
93 </xslt:comment>
94 </xslt:when>
95 <xslt:otherwise>
96 <xslt:apply-templates select="." mode="content"/>
97 </xslt:otherwise>
98 </xslt:choose>
99 </xslt:for-each>
100 </xslt:element>
101 </xslt:template>
102
103 <!--
104 Process text content; just make a copy.
105 -->
106 <xslt:template match="text()" mode="content">
107 <xslt:copy/>
108 </xslt:template>
109
110 <!--
111 Process template elements.
112 By default, just copy the element.
113 This behaviour will be overridden for certain elements to insert the page content.
114 -->
115 <xslt:template match="*" mode="template">
116 <xslt:element name="{local-name()}">
117 <xslt:for-each select="@*">
118 <xslt:copy/>
119 </xslt:for-each>
120 <xslt:apply-templates mode="template"/>
121 </xslt:element>
122 </xslt:template>
123
124 <!--
125 Process template text; just make a copy.
126 -->
127 <xslt:template match="text()" mode="template">
128 <xslt:copy/>
129 </xslt:template>
130
131 <!--
132 Process the template `<html>` elements.
133 This copies over `@lang` and non‐shrine `@data-*` attributes from the root node.
134 -->
135 <xslt:template match="html:html" mode="template">
136 <html>
137 <xslt:for-each select="@*[not(starts-with(name(), 'xmlns'))]">
138 <xslt:copy/>
139 </xslt:for-each>
140 <xslt:for-each select="$source/*/@*[name()='lang' or starts-with(name(), 'data-') and not(starts-with(name(), 'data-shrine-'))]">
141 <xslt:if test="not($template/*/@*[name()=name(current())])">
142 <xslt:copy/>
143 </xslt:if>
144 </xslt:for-each>
145 <xslt:apply-templates mode="template"/>
146 </html>
147 </xslt:template>
148
149 <!--
150 Process the template `<head>` elements.
151 This inserts appropriate metadata based on the document.
152 -->
153 <xslt:template match="html:head" mode="template">
154 <head>
155 <xslt:for-each select="@*">
156 <xslt:copy/>
157 </xslt:for-each>
158 <xslt:if test="not($source//html:title[@slot='shrine-head'])">
159 <title>
160 <xslt:apply-templates select="$source//html:h1" mode="text"/>
161 </title>
162 </xslt:if>
163 <meta name="generator" content="https://github.com/marrus-sh/shrine-xslt"/>
164 <xslt:apply-templates mode="template"/>
165 <xslt:for-each select="$source//*[@slot='shrine-head']">
166 <xslt:apply-templates select="." mode="content"/>
167 </xslt:for-each>
168 </head>
169 </xslt:template>
170
171 <!--
172 Process the template header and footer.
173 Read the corresponding `@data-header` or `@data-footer` attribute of the root element, append `"-header.xml"` or `"-footer.xml"` to the end of it, and process the resulting document.
174 If no `@data-header` or `@data-footer` attribute is provided, nothing is rendered.
175 -->
176 <xslt:template match="html:shrine-header|html:shrine-footer" mode="template">
177 <xslt:param name="context" select="'shrine-template'"/>
178 <xslt:variable name="kind" select="substring-after(local-name(), 'shrine-')"/>
179 <xslt:choose>
180 <xslt:when test="$context='shrine-template'">
181 <xslt:for-each select="$source/*/@*[name()=concat('data-shrine-', $kind)]">
182 <xslt:for-each select="document(concat('./', ., '-', $kind, '.xml'), $template)/*">
183 <xslt:element name="{local-name()}">
184 <xslt:for-each select="@*">
185 <xslt:copy/>
186 </xslt:for-each>
187 <xslt:for-each select="$source//*[@slot=concat('shrine-', $kind, '-before')]">
188 <xslt:apply-templates select="." mode="content"/>
189 </xslt:for-each>
190 <xslt:apply-templates mode="template">
191 <xslt:with-param name="context" select="concat('shrine-', $kind)"/>
192 </xslt:apply-templates>
193 <xslt:for-each select="$source//*[@slot=concat('shrine-', $kind, '-after')]">
194 <xslt:apply-templates select="." mode="content"/>
195 </xslt:for-each>
196 </xslt:element>
197 </xslt:for-each>
198 </xslt:for-each>
199 </xslt:when>
200 <xslt:otherwise>
201 <xslt:element name="{local-name()}">
202 <xslt:for-each select="@*">
203 <xslt:copy/>
204 </xslt:for-each>
205 <xslt:apply-templates mode="template"/>
206 </xslt:element>
207 </xslt:otherwise>
208 </xslt:choose>
209 </xslt:template>
210
211 <!--
212 Process the content.
213 -->
214 <xslt:template match="html:shrine-content" mode="template">
215 <xslt:apply-templates select="$source/*" mode="content"/>
216 </xslt:template>
217
218 <!--
219 Process miscellaneous template slots.
220 -->
221 <xslt:template match="html:slot[not(ancestor::html:template)]" mode="template">
222 <xslt:param name="context" select="'shrine-template'"/>
223 <xslt:variable name="name" select="string(@name)"/>
224 <xslt:for-each select="$source//*[@slot=concat($context, '-slot-', $name)]">
225 <xslt:apply-templates select="." mode="content"/>
226 </xslt:for-each>
227 </xslt:template>
228
229 <!--
230 Process feed elements and text.
231 By default, just make a copy.
232 This behaviour will be overridden for certain elements to generate feed metadata.
233 -->
234 <xslt:template match="*|text()" mode="feed">
235 <xslt:copy>
236 <xslt:for-each select="@*">
237 <xslt:copy/>
238 </xslt:for-each>
239 <xslt:apply-templates mode="feed"/>
240 </xslt:copy>
241 </xslt:template>
242
243 <!--
244 Process the root feed element.
245 This adds required metadata when it has not been provided in the source X·M·L.
246 -->
247 <xslt:template match="atom:feed" mode="feed">
248 <xslt:text>&#x0A;</xslt:text> <!-- ensure a newline between the doctype and the feed element -->
249 <feed xmlns="http://www.w3.org/2005/Atom">
250 <xslt:for-each select="@*">
251 <xslt:copy/>
252 </xslt:for-each>
253 <xslt:apply-templates select="text()[following-sibling::*[not(self::atom:entry)]]|*[not(self::atom:entry)]" mode="feed"/>
254 <xslt:if test="not(atom:id)">
255 <xslt:text>&#x0A;&#x09;</xslt:text>
256 <id>
257 <xslt:choose>
258 <xslt:when test="contains($baseiri, '://')">
259 <xslt:text>oai:</xslt:text>
260 <xslt:value-of select="substring-after($baseiri, '://')"/>
261 <xslt:text>:</xslt:text>
262 </xslt:when>
263 <xslt:otherwise>
264 <xslt:value-of select="$baseiri"/>
265 <xslt:text>/</xslt:text>
266 </xslt:otherwise>
267 </xslt:choose>
268 <xslt:value-of select="$outputpath"/>
269 </id>
270 </xslt:if>
271 <xslt:if test="not(atom:title)">
272 <xslt:text>&#x0A;&#x09;</xslt:text>
273 <title xml:lang="en">Untitled</title>
274 </xslt:if>
275 <xslt:if test="not(atom:author)">
276 <xslt:text>&#x0A;&#x09;</xslt:text>
277 <author>
278 <name xml:lang="en">Anonymous</name>
279 </author>
280 </xslt:if>
281 <xslt:if test="not(atom:link[@rel='alternate'][@type='text/html'])">
282 <xslt:text>&#x0A;&#x09;</xslt:text>
283 <link rel="alternate" type="text/html" href="{$baseiri}/"/>
284 </xslt:if>
285 <xslt:if test="not(atom:link[@rel='self'])">
286 <xslt:text>&#x0A;&#x09;</xslt:text>
287 <link rel="self" type="application/atom+xml" href="{$baseiri}{$outputpath}"/>
288 </xslt:if>
289 <xslt:if test="not(atom:updated)">
290 <xslt:text>&#x0A;&#x09;</xslt:text>
291 <updated>
292 <xslt:value-of select="$datetime"/>
293 </updated>
294 </xslt:if>
295 <xslt:if test="not(atom:generator)">
296 <xslt:text>&#x0A;&#x09;</xslt:text>
297 <generator uri="https://github.com/marrus-sh/shrine-xslt">shrine-xslt</generator>
298 </xslt:if>
299 <xslt:apply-templates select="atom:entry" mode="feed"/>
300 <xslt:text>&#x0A;</xslt:text> <!-- newline before close tag -->
301 </feed>
302 </xslt:template>
303
304 <!--
305 Process feed entry elements.
306 -->
307 <xslt:template match="atom:entry[atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html']]" mode="feed">
308 <xslt:variable name="entryhref" select="atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html'][1]/@href"/>
309 <xslt:text>&#x0A;&#x09;</xslt:text>
310 <entry xmlns="http://www.w3.org/2005/Atom">
311 <xslt:for-each select="@*">
312 <xslt:copy/>
313 </xslt:for-each>
314 <xslt:apply-templates select="text()[following-sibling::*]|*" mode="feed"/>
315 <xslt:if test="not(atom:id)">
316 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
317 <id>
318 <xslt:choose>
319 <xslt:when test="contains($baseiri, '://')">
320 <xslt:text>oai:</xslt:text>
321 <xslt:value-of select="substring-after($baseiri, '://')"/>
322 <xslt:text>:</xslt:text>
323 </xslt:when>
324 <xslt:otherwise>
325 <xslt:value-of select="$baseiri"/>
326 <xslt:text>/</xslt:text>
327 </xslt:otherwise>
328 </xslt:choose>
329 <xslt:value-of select="$entryhref"/>
330 </id>
331 </xslt:if>
332 <xslt:if test="not(atom:title)">
333 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
334 <title xml:lang="en">Untitled</title>
335 </xslt:if>
336 <xslt:if test="not(atom:updated)">
337 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
338 <updated>
339 <xslt:value-of select="$datetime"/>
340 </updated>
341 </xslt:if>
342 <xslt:text>&#x0A;&#x09;</xslt:text> <!-- newline before close tag -->
343 </entry>
344 </xslt:template>
345
346 <!--
347 Process feed link elements.
348 This simply rewrites absolute links to be relative.
349 -->
350 <xslt:template match="atom:link[starts-with(@href, '/')]" mode="feed">
351 <link xmlns="http://www.w3.org/2005/Atom" rel="{@rel}" href="{$baseiri}{@href}">
352 <xslt:for-each select="@*[local-name()!='rel' and local-name()!='href']">
353 <xslt:copy/>
354 </xslt:for-each>
355 </link>
356 </xslt:template>
357
358 <!--
359 Provide the complete text content of the provided element.
360 -->
361 <xslt:template match="*|text()" mode="text">
362 <xslt:choose>
363 <xslt:when test="self::*">
364 <xslt:apply-templates mode="text"/>
365 </xslt:when>
366 <xslt:when test="self::text()">
367 <xslt:copy/>
368 </xslt:when>
369 </xslt:choose>
370 </xslt:template>
371
372 <!--
373 Set up output.
374 Note that this relies on “default” output method detection specified in X·S·L·T in order to work.
375 The `about:legacy-compat` system doctype is for H·T·M·L compatibility but is harmless in X·M·L.
376 -->
377 <xslt:output charset="UTF-8" doctype-system="about:legacy-compat" indent="no"/>
378 </xslt:transform>
This page took 0.069126 seconds and 3 git commands to generate.