(remember to declare the X·H·T·M·L namespace!), and you should give it
a `@lang` attribute as well. An example is provided.
-The `@data-shrine-header` and `@data-shrine-footer` attributes on the
-root elements of your pages specify the names of the header and footer
-to use on the page. You can use headers and footers to supply page
-navigation, branding, and so forth. For each header and footer you
-specify, you will need to create a corresponding `$-header.xml` or
-`$-footer.xml` (where `$` is the header/footer name) which provides
-the contents. These files should be placed in *this* (repository root)
-directory, not in `sources/`.
+The (entirely optional) `@data-shrine-header` and `@data-shrine-footer`
+attributes on the root elements of your pages specify the names of the
+header and footer to use on the page. You can use headers and footers
+to supply page navigation, branding, and so forth. For each header and
+footer you specify, you will need to create a corresponding
+`$-header.xml` or `$-footer.xml` (where `$` is the header/footer name)
+which provides the contents. These files should be placed in *this*
+(repository root) directory, not in `sources/`.
The `template.xml` file in this directory contains the main page
template, and you should edit it to add styling and so forth to your
corresponding to your source files will be created in the `public/`
directory (which you can then serve statically from your server).
-## Atom Feeds
+## Advanced Features
+
+### Slots
+
+You can use the `@slot` attribute with a few special values to insert
+content in various places :—
+
+- `@slot="shrine-head"` will place the content into the `<head>` of
+ the resulting document. This is especially useful for `<title>`,
+ `<meta>`, and `<style>` elements.
+
+- For `shrine-header` and `shrine-footer`, there are `-before` and
+ `-after` slot names which will place content into the beginning or
+ ending of the shrine header or footer, respectively.
+
+- You can define your own slots with `<slot>` in templates, headers,
+ and footers; this will enable a corresponding `@slot` name beginning
+ with `shrine-template-slot-`, `shrine-header-slot-` or
+ `shrine-footer-slot`, respectively.
+
+### Microdata
+
+You can use the H·T·M·L `@itemprop` attribute to define metadata for
+your site pages. The following values are supported :—
+
+- `shrine-title`: The title of the page.
+
+- `shrine-id`: A persistent, globally‐unique identifier for the page.
+
+- `shrine-updated`: The datetime that the page was last updated.
+
+Multiple values for a single `@itemprop` are not currently supported.
+Where possible, X·M·L markup will be preserved when accessing metadata
+values.
+
+### Atom Feeds
Any `.atom` files you provide will automatically be filled out with
`<id>`s, `<updated>` values, and other necessary information before
`make` to allow the X·S·L·T to transform relative links into absolute
ones.
-It is recommended that :—
-
-- You do *not* provide your own `<id>`s and rely on the generated ones
- (which will be an `oai:` URI derived from the file path).
-
-- You *do* manually provide `<updated>` times for individual entries
- (although not the feed as a whole); otherwise every entry will be
- marked as updated every time the feed is generated.
-
-- All `<link rel="alternate">` elements in `<entry>` elements use a
- relative `@href` with a leading slash. This should point to the
- *final* location of the entry (within, but not including, `public/`).
-
There’s an example `feed.atom` provided so you can see what this
- feature might look like in practice.
+feature might look like in practice.
-## Notes
+## Additional Notes
- The created files have a `.html` extension and need to be served
with a `text/html` media type.
(`<html>`) element of the template, as will `@lang` and `@xml:lang`.
You can use this to help configure page‐specific styling.
-- You can use the `@slot` attribute with a few special values to insert
- content in various places :—
-
- - `@slot="shrine-head"` will place the content into the `<head>` of
- the resulting document. This is especially useful for `<title>`,
- `<meta>`, and `<style>` elements.
-
- - For `shrine-header` and `shrine-footer`, there are `-before` and
- `-after` slot names which will place content into the beginning or
- ending of the shrine header or footer, respectively.
-
- - You can define your own slots with `<slot>` in templates, headers,
- and footers; this will enable a corresponding `@slot` name
- beginning with `shrine-template-slot-`, `shrine-header-slot-` or
- `shrine-footer-slot`, respectively.
-
- If you delete files from `sources/`, the corresponding files in
`public/` will **not** be deleted and will need to be manually
removed. An easy way to ensure that there are no outdated files in
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
+<!DOCTYPE xslt:transform [
+ <!ENTITY Atom "http://www.w3.org/2005/Atom">
+ <!ENTITY xhtml "http://www.w3.org/1999/xhtml">
+]>
<xslt:transform
- xmlns:atom="http://www.w3.org/2005/Atom"
- xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:atom="&Atom;"
+ xmlns:html="&xhtml;"
xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
version="1.0"
>
-->
<xslt:template match="*" mode="content">
<xslt:element name="{local-name()}">
- <xslt:for-each select="@*[not(starts-with(name(), 'data-shrine-')) and not(name()='slot' and starts-with(., 'shrine-'))]">
+ <xslt:for-each select="@*[not(starts-with(name(), 'data-shrine-')) and not((name()='slot' or name()='itemprop') and starts-with(., 'shrine-'))]">
<xslt:copy/>
</xslt:for-each>
<xslt:for-each select="*|text()">
<xslt:choose>
<xslt:when test="@slot[starts-with(., 'shrine-')]">
<xslt:comment>
- <text> placeholder for slotted element </text>
+ <xslt:text> placeholder for slotted element </xslt:text>
</xslt:comment>
</xslt:when>
<xslt:otherwise>
</xslt:element>
</xslt:template>
+ <!--
+ Drop `<meta>` elements which only provide shrine microdata.
+ -->
+ <xslt:template match="html:meta[not(@name)][starts-with(@itemprop, 'shrine-')]" mode="content">
+ <xslt:comment>
+ <xslt:text> placeholder for metadata element </xslt:text>
+ </xslt:comment>
+ </xslt:template>
+
<!--
Process text content; just make a copy.
-->
<xslt:for-each select="@*">
<xslt:copy/>
</xslt:for-each>
- <xslt:if test="not($source//html:title[@slot='shrine-head'])">
- <title>
- <xslt:apply-templates select="$source//html:h1" mode="text"/>
- </title>
+ <xslt:if test="not(html:title) and not($source//html:title[@slot='shrine-head'])">
+ <xslt:choose>
+ <xslt:when test="$source//*[@itemprop='shrine-title']">
+ <xslt:apply-templates select="$source//*[@itemprop='shrine-title'][1]" mode="microdata">
+ <xslt:with-param name="tagname" select="'title'"/>
+ <xslt:with-param name="plaintext" select="true()"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:when test="$source//html:h1">
+ <title>
+ <xslt:apply-templates select="$source//html:h1[1]" mode="text"/>
+ </title>
+ </xslt:when>
+ </xslt:choose>
</xslt:if>
<meta name="generator" content="https://github.com/marrus-sh/shrine-xslt"/>
<xslt:apply-templates mode="template"/>
-->
<xslt:template match="atom:feed" mode="feed">
<xslt:text>
</xslt:text> <!-- ensure a newline between the doctype and the feed element -->
- <feed xmlns="http://www.w3.org/2005/Atom">
+ <feed xmlns="&Atom;">
<xslt:for-each select="@*">
<xslt:copy/>
</xslt:for-each>
-->
<xslt:template match="atom:entry[atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html']]" mode="feed">
<xslt:variable name="entryhref" select="atom:link[@rel='alternate'][starts-with(@href, '/')][@type='text/html'][1]/@href"/>
+ <xslt:variable name="srchref">
+ <xslt:text>./sources</xslt:text>
+ <xslt:choose>
+ <xslt:when test="substring($entryhref, string-length($entryhref))='/'">
+ <xslt:value-of select="substring($entryhref, 1, string-length($entryhref) - 1)"/>
+ <xslt:text>.xml</xslt:text>
+ </xslt:when>
+ <xslt:when test="substring($entryhref, string-length($entryhref) - 4)='.html'">
+ <xslt:value-of select="substring($entryhref, 1, string-length($entryhref) - 4)"/>
+ <xslt:text>xml</xslt:text>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:value-of select="$entryhref"/>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:variable>
+ <xslt:variable name="srcdoc" select="document($srchref)"/>
<xslt:text>
	</xslt:text>
- <entry xmlns="http://www.w3.org/2005/Atom">
+ <entry xmlns="&Atom;">
<xslt:for-each select="@*">
<xslt:copy/>
</xslt:for-each>
<xslt:apply-templates select="text()[following-sibling::*]|*" mode="feed"/>
<xslt:if test="not(atom:id)">
<xslt:text>
		</xslt:text>
- <id>
- <xslt:choose>
- <xslt:when test="contains($baseiri, '://')">
- <xslt:text>oai:</xslt:text>
- <xslt:value-of select="substring-after($baseiri, '://')"/>
- <xslt:text>:</xslt:text>
- </xslt:when>
- <xslt:otherwise>
- <xslt:value-of select="$baseiri"/>
- <xslt:text>/</xslt:text>
- </xslt:otherwise>
- </xslt:choose>
- <xslt:value-of select="$entryhref"/>
- </id>
+ <xslt:choose>
+ <xslt:when test="$srcdoc//*[@itemprop='shrine-id']">
+ <xslt:apply-templates select="$srcdoc//*[@itemprop='shrine-id'][1]" mode="microdata">
+ <xslt:with-param name="tagname" select="'id'"/>
+ <xslt:with-param name="namespace" select="'&Atom;'"/>
+ <xslt:with-param name="plaintext" select="true()"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:otherwise>
+ <id>
+ <xslt:choose>
+ <xslt:when test="contains($baseiri, '://')">
+ <xslt:text>oai:</xslt:text>
+ <xslt:value-of select="substring-after($baseiri, '://')"/>
+ <xslt:text>:</xslt:text>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:value-of select="$baseiri"/>
+ <xslt:text>/</xslt:text>
+ </xslt:otherwise>
+ </xslt:choose>
+ <xslt:value-of select="$entryhref"/>
+ </id>
+ </xslt:otherwise>
+ </xslt:choose>
</xslt:if>
<xslt:if test="not(atom:title)">
<xslt:text>
		</xslt:text>
- <title xml:lang="en">Untitled</title>
+ <xslt:choose>
+ <xslt:when test="$srcdoc//*[@itemprop='shrine-title']">
+ <xslt:apply-templates select="$srcdoc//*[@itemprop='shrine-title'][1]" mode="microdata">
+ <xslt:with-param name="tagname" select="'title'"/>
+ <xslt:with-param name="namespace" select="'&Atom;'"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:when test="$srcdoc//html:h1">
+ <xslt:apply-templates select="$srcdoc//html:h1[1]" mode="microdata">
+ <xslt:with-param name="tagname" select="'title'"/>
+ <xslt:with-param name="namespace" select="'&Atom;'"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:when test="$srcdoc//html:title">
+ <xslt:apply-templates select="$srcdoc//html:title[1]" mode="microdata">
+ <xslt:with-param name="tagname" select="'title'"/>
+ <xslt:with-param name="namespace" select="'&Atom;'"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:otherwise>
+ <title xml:lang="en">Untitled</title>
+ </xslt:otherwise>
+ </xslt:choose>
</xslt:if>
<xslt:if test="not(atom:updated)">
<xslt:text>
		</xslt:text>
- <updated>
- <xslt:value-of select="$datetime"/>
- </updated>
+ <xslt:choose>
+ <xslt:when test="$srcdoc//*[@itemprop='shrine-updated']">
+ <xslt:apply-templates select="$srcdoc//*[@itemprop='shrine-updated'][1]" mode="microdata">
+ <xslt:with-param name="tagname" select="'updated'"/>
+ <xslt:with-param name="namespace" select="'&Atom;'"/>
+ <xslt:with-param name="plaintext" select="true()"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:value-of select="$datetime"/>
+ </xslt:otherwise>
+ </xslt:choose>
</xslt:if>
<xslt:text>
	</xslt:text> <!-- newline before close tag -->
</entry>
<!--
Process feed link elements.
- This simply rewrites absolute links to be relative.
+ This simply rewrites relative links to be absolute.
-->
<xslt:template match="atom:link[starts-with(@href, '/')]" mode="feed">
- <link xmlns="http://www.w3.org/2005/Atom" rel="{@rel}" href="{$baseiri}{@href}">
+ <link xmlns="&Atom;" rel="{@rel}" href="{$baseiri}{@href}">
<xslt:for-each select="@*[local-name()!='rel' and local-name()!='href']">
<xslt:copy/>
</xslt:for-each>
</xslt:choose>
</xslt:template>
+ <!--
+ Provide the appropriate microdata for the provided element.
+ -->
+ <xslt:template match="*|text()" mode="microdata">
+ <xslt:param name="tagname"/>
+ <xslt:param name="namespace"/>
+ <xslt:param name="plaintext" select="false()"/>
+ <xslt:element name="{$tagname}" namespace="{$namespace}">
+ <xslt:if test="@lang">
+ <xslt:attribute name="xml:lang">
+ <xslt:value-of select="@lang"/>
+ </xslt:attribute>
+ </xslt:if>
+ <xslt:choose>
+ <xslt:when test="self::text()|self::html:title|self::html:time[not(@datetime)]">
+ <xslt:apply-templates select="." mode="text"/>
+ </xslt:when>
+ <xslt:when test="self::html:meta[@content]">
+ <xslt:value-of select="@content"/>
+ </xslt:when>
+ <xslt:when test="self::html:audio|self::html:embed|self::html:iframe|self::html:img|self::html:source|self::html:track|self::html:video">
+ <xslt:value-of select="@src"/>
+ </xslt:when>
+ <xslt:when test="self::html:a|self::html:area|self::html:link">
+ <xslt:value-of select="@href"/>
+ </xslt:when>
+ <xslt:when test="self::html:object">
+ <xslt:value-of select="@data"/>
+ </xslt:when>
+ <xslt:when test="self::html:data|self::html:meter">
+ <xslt:value-of select="@value"/>
+ </xslt:when>
+ <xslt:when test="self::html:time[@datetime]">
+ <xslt:value-of select="@datetime"/>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:choose>
+ <xslt:when test="$plaintext">
+ <xslt:apply-templates mode="text"/>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:if test="$namespace='&Atom;'">
+ <xslt:attribute name="type">xhtml</xslt:attribute>
+ </xslt:if>
+ <div xmlns="&xhtml;">
+ <xslt:apply-templates mode="content"/>
+ </div>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:element>
+ </xslt:template>
+
<!--
Set up output.
Note that this relies on “default” output method detection specified in X·S·L·T in order to work.