]> Lady’s Gitweb - Shrine-XSLT/blob - transform.xslt
Add basic microdata support
[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 <!DOCTYPE xslt:transform [
26 <!ENTITY Atom "http://www.w3.org/2005/Atom">
27 <!ENTITY xhtml "http://www.w3.org/1999/xhtml">
28 ]>
29 <xslt:transform
30 xmlns:atom="&Atom;"
31 xmlns:html="&xhtml;"
32 xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
33 version="1.0"
34 >
35 <xslt:param name="BASEIRI" select="'http://example.com'"/>
36 <xslt:param name="DATETIME" select="'1972-12-31T00:00:00Z'"/>
37 <xslt:param name="OUTPUTPATH" select="'/unknown'"/>
38 <xslt:variable name="baseiri">
39 <xslt:choose>
40 <xslt:when test="contains($BASEIRI, '://')"> <!-- there is an authority -->
41 <xslt:variable name="noscheme" select="substring-after($BASEIRI, '://')"/>
42 <xslt:value-of select="substring-before($BASEIRI, '://')"/>
43 <xslt:text>://</xslt:text>
44 <xslt:choose>
45 <xslt:when test="contains($noscheme, '/')">
46 <xslt:value-of select="substring-before($noscheme, '/')"/>
47 </xslt:when>
48 <xslt:otherwise>
49 <xslt:value-of select="$noscheme"/>
50 </xslt:otherwise>
51 </xslt:choose>
52 </xslt:when>
53 <xslt:otherwise>
54 <xslt:value-of select="substring-before($BASEIRI, ':')"/>
55 </xslt:otherwise>
56 </xslt:choose>
57 </xslt:variable>
58 <xslt:variable name="datetime" select="string($DATETIME)"/>
59 <xslt:variable name="outputpath">
60 <xslt:if test="not(starts-with($OUTPUTPATH, '/'))">
61 <xslt:text>/</xslt:text>
62 </xslt:if>
63 <xslt:value-of select="$OUTPUTPATH"/>
64 </xslt:variable>
65 <xslt:variable name="source" select="current()"/>
66 <xslt:variable name="template" select="document('./template.xml')"/>
67
68 <!--
69 Instead of actually processing the root node, process the template in `template` mode.
70 -->
71 <xslt:template match="/">
72 <xslt:choose>
73 <xslt:when test="atom:feed">
74 <xslt:apply-templates mode="feed"/>
75 </xslt:when>
76 <xslt:otherwise>
77 <xslt:apply-templates select="$template" mode="template"/>
78 </xslt:otherwise>
79 </xslt:choose>
80 </xslt:template>
81
82 <!--
83 Process non‐template elements.
84 By default, just copy the element, but remove any `@data-shrine-*` attribuets or `@slot` attributes with a value that begins with `shrine-`.
85 Some elements may have special treatment.
86 -->
87 <xslt:template match="*" mode="content">
88 <xslt:element name="{local-name()}">
89 <xslt:for-each select="@*[not(starts-with(name(), 'data-shrine-')) and not((name()='slot' or name()='itemprop') and starts-with(., 'shrine-'))]">
90 <xslt:copy/>
91 </xslt:for-each>
92 <xslt:for-each select="*|text()">
93 <xslt:choose>
94 <xslt:when test="@slot[starts-with(., 'shrine-')]">
95 <xslt:comment>
96 <xslt:text> placeholder for slotted element </xslt:text>
97 </xslt:comment>
98 </xslt:when>
99 <xslt:otherwise>
100 <xslt:apply-templates select="." mode="content"/>
101 </xslt:otherwise>
102 </xslt:choose>
103 </xslt:for-each>
104 </xslt:element>
105 </xslt:template>
106
107 <!--
108 Drop `<meta>` elements which only provide shrine microdata.
109 -->
110 <xslt:template match="html:meta[not(@name)][starts-with(@itemprop, 'shrine-')]" mode="content">
111 <xslt:comment>
112 <xslt:text> placeholder for metadata element </xslt:text>
113 </xslt:comment>
114 </xslt:template>
115
116 <!--
117 Process text content; just make a copy.
118 -->
119 <xslt:template match="text()" mode="content">
120 <xslt:copy/>
121 </xslt:template>
122
123 <!--
124 Process template elements.
125 By default, just copy the element.
126 This behaviour will be overridden for certain elements to insert the page content.
127 -->
128 <xslt:template match="*" mode="template">
129 <xslt:element name="{local-name()}">
130 <xslt:for-each select="@*">
131 <xslt:copy/>
132 </xslt:for-each>
133 <xslt:apply-templates mode="template"/>
134 </xslt:element>
135 </xslt:template>
136
137 <!--
138 Process template text; just make a copy.
139 -->
140 <xslt:template match="text()" mode="template">
141 <xslt:copy/>
142 </xslt:template>
143
144 <!--
145 Process the template `<html>` elements.
146 This copies over `@lang` and non‐shrine `@data-*` attributes from the root node.
147 -->
148 <xslt:template match="html:html" mode="template">
149 <html>
150 <xslt:for-each select="@*[not(starts-with(name(), 'xmlns'))]">
151 <xslt:copy/>
152 </xslt:for-each>
153 <xslt:for-each select="$source/*/@*[name()='lang' or starts-with(name(), 'data-') and not(starts-with(name(), 'data-shrine-'))]">
154 <xslt:if test="not($template/*/@*[name()=name(current())])">
155 <xslt:copy/>
156 </xslt:if>
157 </xslt:for-each>
158 <xslt:apply-templates mode="template"/>
159 </html>
160 </xslt:template>
161
162 <!--
163 Process the template `<head>` elements.
164 This inserts appropriate metadata based on the document.
165 -->
166 <xslt:template match="html:head" mode="template">
167 <head>
168 <xslt:for-each select="@*">
169 <xslt:copy/>
170 </xslt:for-each>
171 <xslt:if test="not(html:title) and not($source//html:title[@slot='shrine-head'])">
172 <xslt:choose>
173 <xslt:when test="$source//*[@itemprop='shrine-title']">
174 <xslt:apply-templates select="$source//*[@itemprop='shrine-title'][1]" mode="microdata">
175 <xslt:with-param name="tagname" select="'title'"/>
176 <xslt:with-param name="plaintext" select="true()"/>
177 </xslt:apply-templates>
178 </xslt:when>
179 <xslt:when test="$source//html:h1">
180 <title>
181 <xslt:apply-templates select="$source//html:h1[1]" mode="text"/>
182 </title>
183 </xslt:when>
184 </xslt:choose>
185 </xslt:if>
186 <meta name="generator" content="https://github.com/marrus-sh/shrine-xslt"/>
187 <xslt:apply-templates mode="template"/>
188 <xslt:for-each select="$source//*[@slot='shrine-head']">
189 <xslt:apply-templates select="." mode="content"/>
190 </xslt:for-each>
191 </head>
192 </xslt:template>
193
194 <!--
195 Process the template header and footer.
196 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.
197 If no `@data-header` or `@data-footer` attribute is provided, nothing is rendered.
198 -->
199 <xslt:template match="html:shrine-header|html:shrine-footer" mode="template">
200 <xslt:param name="context" select="'shrine-template'"/>
201 <xslt:variable name="kind" select="substring-after(local-name(), 'shrine-')"/>
202 <xslt:choose>
203 <xslt:when test="$context='shrine-template'">
204 <xslt:for-each select="$source/*/@*[name()=concat('data-shrine-', $kind)]">
205 <xslt:for-each select="document(concat('./', ., '-', $kind, '.xml'), $template)/*">
206 <xslt:element name="{local-name()}">
207 <xslt:for-each select="@*">
208 <xslt:copy/>
209 </xslt:for-each>
210 <xslt:for-each select="$source//*[@slot=concat('shrine-', $kind, '-before')]">
211 <xslt:apply-templates select="." mode="content"/>
212 </xslt:for-each>
213 <xslt:apply-templates mode="template">
214 <xslt:with-param name="context" select="concat('shrine-', $kind)"/>
215 </xslt:apply-templates>
216 <xslt:for-each select="$source//*[@slot=concat('shrine-', $kind, '-after')]">
217 <xslt:apply-templates select="." mode="content"/>
218 </xslt:for-each>
219 </xslt:element>
220 </xslt:for-each>
221 </xslt:for-each>
222 </xslt:when>
223 <xslt:otherwise>
224 <xslt:element name="{local-name()}">
225 <xslt:for-each select="@*">
226 <xslt:copy/>
227 </xslt:for-each>
228 <xslt:apply-templates mode="template"/>
229 </xslt:element>
230 </xslt:otherwise>
231 </xslt:choose>
232 </xslt:template>
233
234 <!--
235 Process the content.
236 -->
237 <xslt:template match="html:shrine-content" mode="template">
238 <xslt:apply-templates select="$source/*" mode="content"/>
239 </xslt:template>
240
241 <!--
242 Process miscellaneous template slots.
243 -->
244 <xslt:template match="html:slot[not(ancestor::html:template)]" mode="template">
245 <xslt:param name="context" select="'shrine-template'"/>
246 <xslt:variable name="name" select="string(@name)"/>
247 <xslt:for-each select="$source//*[@slot=concat($context, '-slot-', $name)]">
248 <xslt:apply-templates select="." mode="content"/>
249 </xslt:for-each>
250 </xslt:template>
251
252 <!--
253 Process feed elements and text.
254 By default, just make a copy.
255 This behaviour will be overridden for certain elements to generate feed metadata.
256 -->
257 <xslt:template match="*|text()" mode="feed">
258 <xslt:copy>
259 <xslt:for-each select="@*">
260 <xslt:copy/>
261 </xslt:for-each>
262 <xslt:apply-templates mode="feed"/>
263 </xslt:copy>
264 </xslt:template>
265
266 <!--
267 Process the root feed element.
268 This adds required metadata when it has not been provided in the source X·M·L.
269 -->
270 <xslt:template match="atom:feed" mode="feed">
271 <xslt:text>&#x0A;</xslt:text> <!-- ensure a newline between the doctype and the feed element -->
272 <feed xmlns="&Atom;">
273 <xslt:for-each select="@*">
274 <xslt:copy/>
275 </xslt:for-each>
276 <xslt:apply-templates select="text()[following-sibling::*[not(self::atom:entry)]]|*[not(self::atom:entry)]" mode="feed"/>
277 <xslt:if test="not(atom:id)">
278 <xslt:text>&#x0A;&#x09;</xslt:text>
279 <id>
280 <xslt:choose>
281 <xslt:when test="contains($baseiri, '://')">
282 <xslt:text>oai:</xslt:text>
283 <xslt:value-of select="substring-after($baseiri, '://')"/>
284 <xslt:text>:</xslt:text>
285 </xslt:when>
286 <xslt:otherwise>
287 <xslt:value-of select="$baseiri"/>
288 <xslt:text>/</xslt:text>
289 </xslt:otherwise>
290 </xslt:choose>
291 <xslt:value-of select="$outputpath"/>
292 </id>
293 </xslt:if>
294 <xslt:if test="not(atom:title)">
295 <xslt:text>&#x0A;&#x09;</xslt:text>
296 <title xml:lang="en">Untitled</title>
297 </xslt:if>
298 <xslt:if test="not(atom:author)">
299 <xslt:text>&#x0A;&#x09;</xslt:text>
300 <author>
301 <name xml:lang="en">Anonymous</name>
302 </author>
303 </xslt:if>
304 <xslt:if test="not(atom:link[@rel='alternate'][@type='text/html'])">
305 <xslt:text>&#x0A;&#x09;</xslt:text>
306 <link rel="alternate" type="text/html" href="{$baseiri}/"/>
307 </xslt:if>
308 <xslt:if test="not(atom:link[@rel='self'])">
309 <xslt:text>&#x0A;&#x09;</xslt:text>
310 <link rel="self" type="application/atom+xml" href="{$baseiri}{$outputpath}"/>
311 </xslt:if>
312 <xslt:if test="not(atom:updated)">
313 <xslt:text>&#x0A;&#x09;</xslt:text>
314 <updated>
315 <xslt:value-of select="$datetime"/>
316 </updated>
317 </xslt:if>
318 <xslt:if test="not(atom:generator)">
319 <xslt:text>&#x0A;&#x09;</xslt:text>
320 <generator uri="https://github.com/marrus-sh/shrine-xslt">shrine-xslt</generator>
321 </xslt:if>
322 <xslt:apply-templates select="atom:entry" mode="feed"/>
323 <xslt:text>&#x0A;</xslt:text> <!-- newline before close tag -->
324 </feed>
325 </xslt:template>
326
327 <!--
328 Process feed entry elements.
329 -->
330 <xslt:template match="atom:entry[atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html']]" mode="feed">
331 <xslt:variable name="entryhref" select="atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html'][1]/@href"/>
332 <xslt:variable name="srchref">
333 <xslt:text>./sources</xslt:text>
334 <xslt:choose>
335 <xslt:when test="substring($entryhref, string-length($entryhref))='/'">
336 <xslt:value-of select="substring($entryhref, 1, string-length($entryhref) - 1)"/>
337 <xslt:text>.xml</xslt:text>
338 </xslt:when>
339 <xslt:when test="substring($entryhref, string-length($entryhref) - 4)='.html'">
340 <xslt:value-of select="substring($entryhref, 1, string-length($entryhref) - 4)"/>
341 <xslt:text>xml</xslt:text>
342 </xslt:when>
343 <xslt:otherwise>
344 <xslt:value-of select="$entryhref"/>
345 </xslt:otherwise>
346 </xslt:choose>
347 </xslt:variable>
348 <xslt:variable name="srcdoc" select="document($srchref)"/>
349 <xslt:text>&#x0A;&#x09;</xslt:text>
350 <entry xmlns="&Atom;">
351 <xslt:for-each select="@*">
352 <xslt:copy/>
353 </xslt:for-each>
354 <xslt:apply-templates select="text()[following-sibling::*]|*" mode="feed"/>
355 <xslt:if test="not(atom:id)">
356 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
357 <xslt:choose>
358 <xslt:when test="$srcdoc//*[@itemprop='shrine-id']">
359 <xslt:apply-templates select="$srcdoc//*[@itemprop='shrine-id'][1]" mode="microdata">
360 <xslt:with-param name="tagname" select="'id'"/>
361 <xslt:with-param name="namespace" select="'&Atom;'"/>
362 <xslt:with-param name="plaintext" select="true()"/>
363 </xslt:apply-templates>
364 </xslt:when>
365 <xslt:otherwise>
366 <id>
367 <xslt:choose>
368 <xslt:when test="contains($baseiri, '://')">
369 <xslt:text>oai:</xslt:text>
370 <xslt:value-of select="substring-after($baseiri, '://')"/>
371 <xslt:text>:</xslt:text>
372 </xslt:when>
373 <xslt:otherwise>
374 <xslt:value-of select="$baseiri"/>
375 <xslt:text>/</xslt:text>
376 </xslt:otherwise>
377 </xslt:choose>
378 <xslt:value-of select="$entryhref"/>
379 </id>
380 </xslt:otherwise>
381 </xslt:choose>
382 </xslt:if>
383 <xslt:if test="not(atom:title)">
384 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
385 <xslt:choose>
386 <xslt:when test="$srcdoc//*[@itemprop='shrine-title']">
387 <xslt:apply-templates select="$srcdoc//*[@itemprop='shrine-title'][1]" mode="microdata">
388 <xslt:with-param name="tagname" select="'title'"/>
389 <xslt:with-param name="namespace" select="'&Atom;'"/>
390 </xslt:apply-templates>
391 </xslt:when>
392 <xslt:when test="$srcdoc//html:h1">
393 <xslt:apply-templates select="$srcdoc//html:h1[1]" mode="microdata">
394 <xslt:with-param name="tagname" select="'title'"/>
395 <xslt:with-param name="namespace" select="'&Atom;'"/>
396 </xslt:apply-templates>
397 </xslt:when>
398 <xslt:when test="$srcdoc//html:title">
399 <xslt:apply-templates select="$srcdoc//html:title[1]" mode="microdata">
400 <xslt:with-param name="tagname" select="'title'"/>
401 <xslt:with-param name="namespace" select="'&Atom;'"/>
402 </xslt:apply-templates>
403 </xslt:when>
404 <xslt:otherwise>
405 <title xml:lang="en">Untitled</title>
406 </xslt:otherwise>
407 </xslt:choose>
408 </xslt:if>
409 <xslt:if test="not(atom:updated)">
410 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
411 <xslt:choose>
412 <xslt:when test="$srcdoc//*[@itemprop='shrine-updated']">
413 <xslt:apply-templates select="$srcdoc//*[@itemprop='shrine-updated'][1]" mode="microdata">
414 <xslt:with-param name="tagname" select="'updated'"/>
415 <xslt:with-param name="namespace" select="'&Atom;'"/>
416 <xslt:with-param name="plaintext" select="true()"/>
417 </xslt:apply-templates>
418 </xslt:when>
419 <xslt:otherwise>
420 <xslt:value-of select="$datetime"/>
421 </xslt:otherwise>
422 </xslt:choose>
423 </xslt:if>
424 <xslt:text>&#x0A;&#x09;</xslt:text> <!-- newline before close tag -->
425 </entry>
426 </xslt:template>
427
428 <!--
429 Process feed link elements.
430 This simply rewrites relative links to be absolute.
431 -->
432 <xslt:template match="atom:link[starts-with(@href, '/')]" mode="feed">
433 <link xmlns="&Atom;" rel="{@rel}" href="{$baseiri}{@href}">
434 <xslt:for-each select="@*[local-name()!='rel' and local-name()!='href']">
435 <xslt:copy/>
436 </xslt:for-each>
437 </link>
438 </xslt:template>
439
440 <!--
441 Provide the complete text content of the provided element.
442 -->
443 <xslt:template match="*|text()" mode="text">
444 <xslt:choose>
445 <xslt:when test="self::*">
446 <xslt:apply-templates mode="text"/>
447 </xslt:when>
448 <xslt:when test="self::text()">
449 <xslt:copy/>
450 </xslt:when>
451 </xslt:choose>
452 </xslt:template>
453
454 <!--
455 Provide the appropriate microdata for the provided element.
456 -->
457 <xslt:template match="*|text()" mode="microdata">
458 <xslt:param name="tagname"/>
459 <xslt:param name="namespace"/>
460 <xslt:param name="plaintext" select="false()"/>
461 <xslt:element name="{$tagname}" namespace="{$namespace}">
462 <xslt:if test="@lang">
463 <xslt:attribute name="xml:lang">
464 <xslt:value-of select="@lang"/>
465 </xslt:attribute>
466 </xslt:if>
467 <xslt:choose>
468 <xslt:when test="self::text()|self::html:title|self::html:time[not(@datetime)]">
469 <xslt:apply-templates select="." mode="text"/>
470 </xslt:when>
471 <xslt:when test="self::html:meta[@content]">
472 <xslt:value-of select="@content"/>
473 </xslt:when>
474 <xslt:when test="self::html:audio|self::html:embed|self::html:iframe|self::html:img|self::html:source|self::html:track|self::html:video">
475 <xslt:value-of select="@src"/>
476 </xslt:when>
477 <xslt:when test="self::html:a|self::html:area|self::html:link">
478 <xslt:value-of select="@href"/>
479 </xslt:when>
480 <xslt:when test="self::html:object">
481 <xslt:value-of select="@data"/>
482 </xslt:when>
483 <xslt:when test="self::html:data|self::html:meter">
484 <xslt:value-of select="@value"/>
485 </xslt:when>
486 <xslt:when test="self::html:time[@datetime]">
487 <xslt:value-of select="@datetime"/>
488 </xslt:when>
489 <xslt:otherwise>
490 <xslt:choose>
491 <xslt:when test="$plaintext">
492 <xslt:apply-templates mode="text"/>
493 </xslt:when>
494 <xslt:otherwise>
495 <xslt:if test="$namespace='&Atom;'">
496 <xslt:attribute name="type">xhtml</xslt:attribute>
497 </xslt:if>
498 <div xmlns="&xhtml;">
499 <xslt:apply-templates mode="content"/>
500 </div>
501 </xslt:otherwise>
502 </xslt:choose>
503 </xslt:otherwise>
504 </xslt:choose>
505 </xslt:element>
506 </xslt:template>
507
508 <!--
509 Set up output.
510 Note that this relies on “default” output method detection specified in X·S·L·T in order to work.
511 The `about:legacy-compat` system doctype is for H·T·M·L compatibility but is harmless in X·M·L.
512 -->
513 <xslt:output charset="UTF-8" doctype-system="about:legacy-compat" indent="no"/>
514 </xslt:transform>
This page took 0.07325 seconds and 5 git commands to generate.