]> Lady’s Gitweb - Shrine-XSLT/blob - transform.xslt
Basic support for Atom feeds
[Shrine-XSLT] / transform.xslt
1 <!--
2 This is an extremely simple XSLT transform which simply reads in a
3 `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="content">
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.
173 Read the `@data-header` attribute of the root element, append `"-header.xml"` to the end of it, and process the resulting document.
174 If no `@data-header` attribute is provided, no header is rendered.
175 -->
176 <xslt:template match="html:shrine-header" mode="template">
177 <xslt:for-each select="$source/*/@data-shrine-header">
178 <xslt:apply-templates select="document(concat('./', ., '-header.xml'), $template)/*" mode="content"/>
179 </xslt:for-each>
180 </xslt:template>
181
182 <!--
183 Process the template footer.
184 Read the `@data-footer` attribute of the root element, append `"-footer.xml"` to the end of it, and process the resulting document.
185 If no `@data-footer` attribute is provided, no footer is rendered.
186 -->
187 <xslt:template match="html:shrine-footer" mode="template">
188 <xslt:for-each select="$source/*/@data-shrine-footer">
189 <xslt:apply-templates select="document(concat('./', ., '-footer.xml'), $template)/*" mode="content"/>
190 </xslt:for-each>
191 </xslt:template>
192
193 <!--
194 Process the content.
195 -->
196 <xslt:template match="html:shrine-content" mode="template">
197 <xslt:apply-templates select="$source/*" mode="content"/>
198 </xslt:template>
199
200 <!--
201 Process feed elements and text.
202 By default, just make a copy.
203 This behaviour will be overridden for certain elements to generate feed metadata.
204 -->
205 <xslt:template match="*|text()" mode="feed">
206 <xslt:copy>
207 <xslt:for-each select="@*">
208 <xslt:copy/>
209 </xslt:for-each>
210 <xslt:apply-templates mode="feed"/>
211 </xslt:copy>
212 </xslt:template>
213
214 <!--
215 Process the root feed element.
216 This adds required metadata when it has not been provided in the source X·M·L.
217 -->
218 <xslt:template match="atom:feed" mode="feed">
219 <xslt:text>&#x0A;</xslt:text> <!-- ensure a newline between the doctype and the feed element -->
220 <feed xmlns="http://www.w3.org/2005/Atom">
221 <xslt:for-each select="@*">
222 <xslt:copy/>
223 </xslt:for-each>
224 <xslt:apply-templates select="text()[following-sibling::*[not(self::atom:entry)]]|*[not(self::atom:entry)]" mode="feed"/>
225 <xslt:if test="not(atom:id)">
226 <xslt:text>&#x0A;&#x09;</xslt:text>
227 <id>
228 <xslt:choose>
229 <xslt:when test="contains($baseiri, '://')">
230 <xslt:text>oai:</xslt:text>
231 <xslt:value-of select="substring-after($baseiri, '://')"/>
232 <xslt:text>:</xslt:text>
233 </xslt:when>
234 <xslt:otherwise>
235 <xslt:value-of select="$baseiri"/>
236 <xslt:text>/</xslt:text>
237 </xslt:otherwise>
238 </xslt:choose>
239 <xslt:value-of select="$outputpath"/>
240 </id>
241 </xslt:if>
242 <xslt:if test="not(atom:title)">
243 <xslt:text>&#x0A;&#x09;</xslt:text>
244 <title xml:lang="en">Untitled</title>
245 </xslt:if>
246 <xslt:if test="not(atom:author)">
247 <xslt:text>&#x0A;&#x09;</xslt:text>
248 <author>
249 <name xml:lang="en">Anonymous</name>
250 </author>
251 </xslt:if>
252 <xslt:if test="not(atom:link[@rel='alternate'][@type='text/html'])">
253 <xslt:text>&#x0A;&#x09;</xslt:text>
254 <link rel="alternate" type="text/html" href="{$baseiri}/"/>
255 </xslt:if>
256 <xslt:if test="not(atom:link[@rel='self'])">
257 <xslt:text>&#x0A;&#x09;</xslt:text>
258 <link rel="self" type="application/atom+xml" href="{$baseiri}{$outputpath}"/>
259 </xslt:if>
260 <xslt:if test="not(atom:updated)">
261 <xslt:text>&#x0A;&#x09;</xslt:text>
262 <updated>
263 <xslt:value-of select="$datetime"/>
264 </updated>
265 </xslt:if>
266 <xslt:if test="not(atom:generator)">
267 <xslt:text>&#x0A;&#x09;</xslt:text>
268 <generator uri="https://github.com/marrus-sh/shrine-xslt">shrine-xslt</generator>
269 </xslt:if>
270 <xslt:apply-templates select="atom:entry" mode="feed"/>
271 <xslt:text>&#x0A;</xslt:text> <!-- newline before close tag -->
272 </feed>
273 </xslt:template>
274
275 <!--
276 Process feed entry elements.
277 -->
278 <xslt:template match="atom:entry[atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html']]" mode="feed">
279 <xslt:variable name="entryhref" select="atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html'][1]/@href"/>
280 <xslt:text>&#x0A;&#x09;</xslt:text>
281 <entry xmlns="http://www.w3.org/2005/Atom">
282 <xslt:for-each select="@*">
283 <xslt:copy/>
284 </xslt:for-each>
285 <xslt:apply-templates select="text()[following-sibling::*]|*" mode="feed"/>
286 <xslt:if test="not(atom:id)">
287 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
288 <id>
289 <xslt:choose>
290 <xslt:when test="contains($baseiri, '://')">
291 <xslt:text>oai:</xslt:text>
292 <xslt:value-of select="substring-after($baseiri, '://')"/>
293 <xslt:text>:</xslt:text>
294 </xslt:when>
295 <xslt:otherwise>
296 <xslt:value-of select="$baseiri"/>
297 <xslt:text>/</xslt:text>
298 </xslt:otherwise>
299 </xslt:choose>
300 <xslt:value-of select="$entryhref"/>
301 </id>
302 </xslt:if>
303 <xslt:if test="not(atom:title)">
304 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
305 <title xml:lang="en">Untitled</title>
306 </xslt:if>
307 <xslt:if test="not(atom:updated)">
308 <xslt:text>&#x0A;&#x09;&#x09;</xslt:text>
309 <updated>
310 <xslt:value-of select="$datetime"/>
311 </updated>
312 </xslt:if>
313 <xslt:text>&#x0A;&#x09;</xslt:text> <!-- newline before close tag -->
314 </entry>
315 </xslt:template>
316
317 <!--
318 Process feed link elements.
319 This simply rewrites absolute links to be relative.
320 -->
321 <xslt:template match="atom:link[starts-with(@href, '/')]" mode="feed">
322 <link xmlns="http://www.w3.org/2005/Atom" rel="{@rel}" href="{$baseiri}{@href}">
323 <xslt:for-each select="@*[local-name()!='rel' and local-name()!='href']">
324 <xslt:copy/>
325 </xslt:for-each>
326 </link>
327 </xslt:template>
328
329 <!--
330 Provide the complete text content of the provided element.
331 -->
332 <xslt:template match="*|text()" mode="text">
333 <xslt:choose>
334 <xslt:when test="self::*">
335 <xslt:apply-templates mode="text"/>
336 </xslt:when>
337 <xslt:when test="self::text()">
338 <xslt:copy/>
339 </xslt:when>
340 </xslt:choose>
341 </xslt:template>
342
343 <!--
344 Set up output.
345 Note that this relies on “default” output method detection specified in X·S·L·T in order to work.
346 The `about:legacy-compat` system doctype is for H·T·M·L compatibility but is harmless in X·M·L.
347 -->
348 <xslt:output charset="UTF-8" doctype-system="about:legacy-compat" indent="no"/>
349 </xslt:transform>
This page took 0.084993 seconds and 5 git commands to generate.