2 This is a⸠n extremely simple⸡ progressively‐less‐simple X·S·L·T
 
   3 transform which simply reads in a `template.xml` and :—
 
   5 • Replaces the `<html:shrine-content>` element with the content of the document it is being applied to,
 
   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
 
   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
 
  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.
 
  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`.
 
  16 Feel free to add additional templates and features to suit your needs!
 
  20 © 2022 Lady [@ Lady’s Computer]
 
  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/.
 
  25 <!DOCTYPE xslt:transform [
 
  26         <!ENTITY Atom "http://www.w3.org/2005/Atom">
 
  27         <!ENTITY xhtml "http://www.w3.org/1999/xhtml">
 
  32         xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
 
  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">
 
  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>
 
  45                                         <xslt:when test="contains($noscheme, '/')">
 
  46                                                 <xslt:value-of select="substring-before($noscheme, '/')"/>
 
  49                                                 <xslt:value-of select="$noscheme"/>
 
  54                                 <xslt:value-of select="substring-before($BASEIRI, ':')"/>
 
  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>
 
  63                 <xslt:value-of select="$OUTPUTPATH"/>
 
  65         <xslt:variable name="source" select="current()"/>
 
  66         <xslt:variable name="template" select="document('./template.xml')"/>
 
  69                 Instead of actually processing the root node, process the template in `template` mode.
 
  71         <xslt:template match="/">
 
  73                         <xslt:when test="atom:feed">
 
  74                                 <xslt:apply-templates mode="feed"/>
 
  77                                 <xslt:apply-templates select="$template" mode="template"/>
 
  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.
 
  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-'))]">
 
  92                         <xslt:for-each select="*|text()">
 
  94                                         <xslt:when test="@slot[starts-with(., 'shrine-')]">
 
  96                                                         <xslt:text> placeholder for slotted element </xslt:text>
 
 100                                                 <xslt:apply-templates select="." mode="content"/>
 
 108                 Drop `<meta>` elements which only provide shrine microdata.
 
 110         <xslt:template match="html:meta[not(@name)][starts-with(@itemprop, 'shrine-')]" mode="content">
 
 112                         <xslt:text> placeholder for metadata element </xslt:text>
 
 117                 Process text content; just make a copy.
 
 119         <xslt:template match="text()" mode="content">
 
 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.
 
 128         <xslt:template match="*" mode="template">
 
 129                 <xslt:element name="{local-name()}">
 
 130                         <xslt:for-each select="@*">
 
 133                         <xslt:apply-templates mode="template"/>
 
 138                 Process template text; just make a copy.
 
 140         <xslt:template match="text()" mode="template">
 
 145                 Process the template `<html>` elements.
 
 146                 This copies over `@lang` and non‐shrine `@data-*` attributes from the root node.
 
 148         <xslt:template match="html:html" mode="template">
 
 150                         <xslt:for-each select="@*[not(starts-with(name(), 'xmlns'))]">
 
 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())])">
 
 158                         <xslt:apply-templates mode="template"/>
 
 163                 Process the template `<head>` elements.
 
 164                 This inserts appropriate metadata based on the document.
 
 166         <xslt:template match="html:head" mode="template">
 
 168                         <xslt:for-each select="@*">
 
 171                         <xslt:if test="not(html:title) and not($source//html:title[@slot='shrine-head'])">
 
 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>
 
 179                                         <xslt:when test="$source//html:h1">
 
 181                                                         <xslt:apply-templates select="$source//html:h1[1]" mode="text"/>
 
 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"/>
 
 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.
 
 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-')"/>
 
 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="@*">
 
 210                                                         <xslt:for-each select="$source//*[@slot=concat('shrine-', $kind, '-before')]">
 
 211                                                                 <xslt:apply-templates select="." mode="content"/>
 
 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"/>
 
 224                                 <xslt:element name="{local-name()}">
 
 225                                         <xslt:for-each select="@*">
 
 228                                         <xslt:apply-templates mode="template"/>
 
 237         <xslt:template match="html:shrine-content" mode="template">
 
 238                 <xslt:apply-templates select="$source/*" mode="content"/>
 
 242                 Process miscellaneous template slots.
 
 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"/>
 
 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.
 
 257         <xslt:template match="*|text()" mode="feed">
 
 259                         <xslt:for-each select="@*">
 
 262                         <xslt:apply-templates mode="feed"/>
 
 267                 Process the root feed element.
 
 268                 This adds required metadata when it has not been provided in the source X·M·L.
 
 270         <xslt:template match="atom:feed" mode="feed">
 
 271                 <xslt:text>
</xslt:text> <!-- ensure a newline between the doctype and the feed element -->
 
 272                 <feed xmlns="&Atom;">
 
 273                         <xslt:for-each select="@*">
 
 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>
	</xslt:text>
 
 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>
 
 287                                                         <xslt:value-of select="$baseiri"/>
 
 288                                                         <xslt:text>/</xslt:text>
 
 291                                         <xslt:value-of select="$outputpath"/>
 
 294                         <xslt:if test="not(atom:title)">
 
 295                                 <xslt:text>
	</xslt:text>
 
 296                                 <title xml:lang="en">Untitled</title>
 
 298                         <xslt:if test="not(atom:author)">
 
 299                                 <xslt:text>
	</xslt:text>
 
 301                                         <name xml:lang="en">Anonymous</name>
 
 304                         <xslt:if test="not(atom:link[@rel='alternate'][@type='text/html'])">
 
 305                                 <xslt:text>
	</xslt:text>
 
 306                                 <link rel="alternate" type="text/html" href="{$baseiri}/"/>
 
 308                         <xslt:if test="not(atom:link[@rel='self'])">
 
 309                                 <xslt:text>
	</xslt:text>
 
 310                                 <link rel="self" type="application/atom+xml" href="{$baseiri}{$outputpath}"/>
 
 312                         <xslt:if test="not(atom:updated)">
 
 313                                 <xslt:text>
	</xslt:text>
 
 315                                         <xslt:value-of select="$datetime"/>
 
 318                         <xslt:if test="not(atom:generator)">
 
 319                                 <xslt:text>
	</xslt:text>
 
 320                                 <generator uri="https://github.com/marrus-sh/shrine-xslt">shrine-xslt</generator>
 
 322                         <xslt:apply-templates select="atom:entry" mode="feed"/>
 
 323                         <xslt:text>
</xslt:text> <!-- newline before close tag -->
 
 328                 Process feed entry elements.
 
 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>
 
 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>
 
 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>
 
 344                                         <xslt:value-of select="$entryhref"/>
 
 348                 <xslt:variable name="srcdoc" select="document($srchref)"/>
 
 349                 <xslt:text>
	</xslt:text>
 
 350                 <entry xmlns="&Atom;">
 
 351                         <xslt:for-each select="@*">
 
 354                         <xslt:apply-templates select="text()[following-sibling::*]|*" mode="feed"/>
 
 355                         <xslt:if test="not(atom:id)">
 
 356                                 <xslt:text>
		</xslt:text>
 
 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>
 
 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>
 
 374                                                                         <xslt:value-of select="$baseiri"/>
 
 375                                                                         <xslt:text>/</xslt:text>
 
 378                                                         <xslt:value-of select="$entryhref"/>
 
 383                         <xslt:if test="not(atom:title)">
 
 384                                 <xslt:text>
		</xslt:text>
 
 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>
 
 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>
 
 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>
 
 405                                                 <title xml:lang="en">Untitled</title>
 
 409                         <xslt:if test="not(atom:updated)">
 
 410                                 <xslt:text>
		</xslt:text>
 
 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>
 
 420                                                 <xslt:value-of select="$datetime"/>
 
 424                         <xslt:text>
	</xslt:text> <!-- newline before close tag -->
 
 429                 Process feed link elements.
 
 430                 This simply rewrites relative links to be absolute.
 
 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']">
 
 441                 Provide the complete text content of the provided element.
 
 443         <xslt:template match="*|text()" mode="text">
 
 445                         <xslt:when test="self::*">
 
 446                                 <xslt:apply-templates mode="text"/>
 
 448                         <xslt:when test="self::text()">
 
 455                 Provide the appropriate microdata for the provided element.
 
 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"/>
 
 468                                 <xslt:when test="self::text()|self::html:title|self::html:time[not(@datetime)]">
 
 469                                         <xslt:apply-templates select="." mode="text"/>
 
 471                                 <xslt:when test="self::html:meta[@content]">
 
 472                                         <xslt:value-of select="@content"/>
 
 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"/>
 
 477                                 <xslt:when test="self::html:a|self::html:area|self::html:link">
 
 478                                         <xslt:value-of select="@href"/>
 
 480                                 <xslt:when test="self::html:object">
 
 481                                         <xslt:value-of select="@data"/>
 
 483                                 <xslt:when test="self::html:data|self::html:meter">
 
 484                                         <xslt:value-of select="@value"/>
 
 486                                 <xslt:when test="self::html:time[@datetime]">
 
 487                                         <xslt:value-of select="@datetime"/>
 
 491                                                 <xslt:when test="$plaintext">
 
 492                                                         <xslt:apply-templates mode="text"/>
 
 495                                                         <xslt:if test="$namespace='&Atom;'">
 
 496                                                                 <xslt:attribute name="type">xhtml</xslt:attribute>
 
 498                                                         <div xmlns="&xhtml;">
 
 499                                                                 <xslt:apply-templates mode="content"/>
 
 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.
 
 513         <xslt:output charset="UTF-8" doctype-system="about:legacy-compat" indent="no"/>