aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortdro <tdro@noreply.example.com>2022-12-03 20:09:08 -0500
committertdro <tdro@noreply.example.com>2022-12-03 20:09:08 -0500
commit3be44b5e0e224ad2d19f5af479de58680ee905fa (patch)
tree7647b99bf960318ff13e42383a5962052f9187c6
parentd27acd8bb3bdbfa1c8a70b76592f9c39eed18810 (diff)
downloadcanory-3be44b5e0e224ad2d19f5af479de58680ee905fa.tar.gz
canory-3be44b5e0e224ad2d19f5af479de58680ee905fa.tar.bz2
canory-3be44b5e0e224ad2d19f5af479de58680ee905fa.zip
assets: Add schemas
House keeping guardrails
-rw-r--r--Makefile22
-rw-r--r--assets/schemas/atom-v1.0.rng605
-rw-r--r--assets/schemas/jsonfeed-v1.1.json297
-rw-r--r--assets/schemas/rss-v2.0.rng86
-rw-r--r--assets/schemas/rss-v2.0.xsd383
-rw-r--r--shell.nix8
-rw-r--r--themes/default/layouts/_default/rss.xml4
7 files changed, 1399 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index f438eb9..b952bf2 100644
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,9 @@ server:
test:
make test-html
make test-xsl
+ make test-xml
+ make test-rss
+ make test-jsonfeed
test-html:
validatornu \
@@ -31,12 +34,23 @@ test-html:
public/default/index.html
test-css:
- validatornu --css \
- assets/css/default.css
+ validatornu --css \
+ assets/css/default.css
test-xsl:
- xsltproc \
- public/rss.xsl
+ xsltproc \
+ public/rss.xsl
+
+test-xml:
+ xmllint --noout \
+ public/rss.xml
+
+test-rss:
+ xmllint --noout --relaxng assets/schemas/rss-v2.0.rng public/rss.xml
+# xmllint --noout --schema assets/schemas/rss-v2.0.xsd public/rss.xml
+
+test-jsonfeed:
+ check-jsonschema --schemafile assets/schemas/jsonfeed-v1.1.json public/index.json
icons:
rm -rf static/icons
diff --git a/assets/schemas/atom-v1.0.rng b/assets/schemas/atom-v1.0.rng
new file mode 100644
index 0000000..da47078
--- /dev/null
+++ b/assets/schemas/atom-v1.0.rng
@@ -0,0 +1,605 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ -*- rnc -*-
+ RELAX NG Compact Syntax Grammar for the
+ Atom Format Specification Version 11
+-->
+<grammar
+ xmlns:atom="http://www.w3.org/2005/Atom"
+ xmlns:s="http://www.ascc.net/xml/schematron"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ xmlns="http://relaxng.org/ns/structure/1.0"
+ ns="http://www.w3.org/1999/xhtml"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ >
+ <start>
+ <choice>
+ <ref name="atomFeed"/>
+ <ref name="atomEntry"/>
+ </choice>
+ </start>
+ <!-- Common attributes -->
+ <define name="atomCommonAttributes">
+ <optional>
+ <attribute name="xml:base">
+ <ref name="atomUri"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="xml:lang">
+ <ref name="atomLanguageTag"/>
+ </attribute>
+ </optional>
+ <zeroOrMore>
+ <ref name="undefinedAttribute"/>
+ </zeroOrMore>
+ </define>
+ <!-- Text Constructs -->
+ <define name="atomPlainTextConstruct">
+ <ref name="atomCommonAttributes"/>
+ <optional>
+ <attribute name="type">
+ <choice>
+ <value>text</value>
+ <value>html</value>
+ </choice>
+ </attribute>
+ </optional>
+ <text/>
+ </define>
+ <define name="atomXHTMLTextConstruct">
+ <ref name="atomCommonAttributes"/>
+ <attribute name="type">
+ <value>xhtml</value>
+ </attribute>
+ <ref name="xhtmlDiv"/>
+ </define>
+ <define name="atomTextConstruct">
+ <choice>
+ <ref name="atomPlainTextConstruct"/>
+ <ref name="atomXHTMLTextConstruct"/>
+ </choice>
+ </define>
+ <!-- Person Construct -->
+ <define name="atomPersonConstruct">
+ <ref name="atomCommonAttributes"/>
+ <interleave>
+ <element name="atom:name">
+ <text/>
+ </element>
+ <optional>
+ <element name="atom:uri">
+ <ref name="atomUri"/>
+ </element>
+ </optional>
+ <optional>
+ <element name="atom:email">
+ <ref name="atomEmailAddress"/>
+ </element>
+ </optional>
+ <zeroOrMore>
+ <ref name="extensionElement"/>
+ </zeroOrMore>
+ </interleave>
+ </define>
+ <!-- Date Construct -->
+ <define name="atomDateConstruct">
+ <ref name="atomCommonAttributes"/>
+ <data type="dateTime"/>
+ </define>
+ <!-- atom:feed -->
+ <define name="atomFeed">
+ <element name="atom:feed">
+ <s:rule context="atom:feed">
+ <s:assert test="atom:author or not(atom:entry[not(atom:author)])">An atom:feed must have an atom:author unless all of its atom:entry children have an atom:author.</s:assert>
+ </s:rule>
+ <ref name="atomCommonAttributes"/>
+ <interleave>
+ <zeroOrMore>
+ <ref name="atomAuthor"/>
+ </zeroOrMore>
+ <zeroOrMore>
+ <ref name="atomCategory"/>
+ </zeroOrMore>
+ <zeroOrMore>
+ <ref name="atomContributor"/>
+ </zeroOrMore>
+ <optional>
+ <ref name="atomGenerator"/>
+ </optional>
+ <optional>
+ <ref name="atomIcon"/>
+ </optional>
+ <ref name="atomId"/>
+ <zeroOrMore>
+ <ref name="atomLink"/>
+ </zeroOrMore>
+ <optional>
+ <ref name="atomLogo"/>
+ </optional>
+ <optional>
+ <ref name="atomRights"/>
+ </optional>
+ <optional>
+ <ref name="atomSubtitle"/>
+ </optional>
+ <ref name="atomTitle"/>
+ <ref name="atomUpdated"/>
+ <zeroOrMore>
+ <ref name="extensionElement"/>
+ </zeroOrMore>
+ </interleave>
+ <zeroOrMore>
+ <ref name="atomEntry"/>
+ </zeroOrMore>
+ </element>
+ </define>
+ <!-- atom:entry -->
+ <define name="atomEntry">
+ <element name="atom:entry">
+ <s:rule context="atom:entry">
+ <s:assert test="atom:link[@rel='alternate'] or atom:link[not(@rel)] or atom:content">An atom:entry must have at least one atom:link element with a rel attribute of 'alternate' or an atom:content.</s:assert>
+ </s:rule>
+ <s:rule context="atom:entry">
+ <s:assert test="atom:author or ../atom:author or atom:source/atom:author">An atom:entry must have an atom:author if its feed does not.</s:assert>
+ </s:rule>
+ <ref name="atomCommonAttributes"/>
+ <interleave>
+ <zeroOrMore>
+ <ref name="atomAuthor"/>
+ </zeroOrMore>
+ <zeroOrMore>
+ <ref name="atomCategory"/>
+ </zeroOrMore>
+ <optional>
+ <ref name="atomContent"/>
+ </optional>
+ <zeroOrMore>
+ <ref name="atomContributor"/>
+ </zeroOrMore>
+ <ref name="atomId"/>
+ <zeroOrMore>
+ <ref name="atomLink"/>
+ </zeroOrMore>
+ <optional>
+ <ref name="atomPublished"/>
+ </optional>
+ <optional>
+ <ref name="atomRights"/>
+ </optional>
+ <optional>
+ <ref name="atomSource"/>
+ </optional>
+ <optional>
+ <ref name="atomSummary"/>
+ </optional>
+ <ref name="atomTitle"/>
+ <ref name="atomUpdated"/>
+ <zeroOrMore>
+ <ref name="extensionElement"/>
+ </zeroOrMore>
+ </interleave>
+ </element>
+ </define>
+ <!-- atom:content -->
+ <define name="atomInlineTextContent">
+ <element name="atom:content">
+ <ref name="atomCommonAttributes"/>
+ <optional>
+ <attribute name="type">
+ <choice>
+ <value>text</value>
+ <value>html</value>
+ </choice>
+ </attribute>
+ </optional>
+ <zeroOrMore>
+ <text/>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="atomInlineXHTMLContent">
+ <element name="atom:content">
+ <ref name="atomCommonAttributes"/>
+ <attribute name="type">
+ <value>xhtml</value>
+ </attribute>
+ <ref name="xhtmlDiv"/>
+ </element>
+ </define>
+ <define name="atomInlineOtherContent">
+ <element name="atom:content">
+ <ref name="atomCommonAttributes"/>
+ <optional>
+ <attribute name="type">
+ <ref name="atomMediaType"/>
+ </attribute>
+ </optional>
+ <zeroOrMore>
+ <choice>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="atomOutOfLineContent">
+ <element name="atom:content">
+ <ref name="atomCommonAttributes"/>
+ <optional>
+ <attribute name="type">
+ <ref name="atomMediaType"/>
+ </attribute>
+ </optional>
+ <attribute name="src">
+ <ref name="atomUri"/>
+ </attribute>
+ <empty/>
+ </element>
+ </define>
+ <define name="atomContent">
+ <choice>
+ <ref name="atomInlineTextContent"/>
+ <ref name="atomInlineXHTMLContent"/>
+ <ref name="atomInlineOtherContent"/>
+ <ref name="atomOutOfLineContent"/>
+ </choice>
+ </define>
+ <!-- atom:author -->
+ <define name="atomAuthor">
+ <element name="atom:author">
+ <ref name="atomPersonConstruct"/>
+ </element>
+ </define>
+ <!-- atom:category -->
+ <define name="atomCategory">
+ <element name="atom:category">
+ <ref name="atomCommonAttributes"/>
+ <attribute name="term"/>
+ <optional>
+ <attribute name="scheme">
+ <ref name="atomUri"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="label"/>
+ </optional>
+ <ref name="undefinedContent"/>
+ </element>
+ </define>
+ <!-- atom:contributor -->
+ <define name="atomContributor">
+ <element name="atom:contributor">
+ <ref name="atomPersonConstruct"/>
+ </element>
+ </define>
+ <!-- atom:generator -->
+ <define name="atomGenerator">
+ <element name="atom:generator">
+ <ref name="atomCommonAttributes"/>
+ <optional>
+ <attribute name="uri">
+ <ref name="atomUri"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="version"/>
+ </optional>
+ <text/>
+ </element>
+ </define>
+ <!-- atom:icon -->
+ <define name="atomIcon">
+ <element name="atom:icon">
+ <ref name="atomCommonAttributes"/>
+ <ref name="atomUri"/>
+ </element>
+ </define>
+ <!-- atom:id -->
+ <define name="atomId">
+ <element name="atom:id">
+ <ref name="atomCommonAttributes"/>
+ <ref name="atomUri"/>
+ </element>
+ </define>
+ <!-- atom:logo -->
+ <define name="atomLogo">
+ <element name="atom:logo">
+ <ref name="atomCommonAttributes"/>
+ <ref name="atomUri"/>
+ </element>
+ </define>
+ <!-- atom:link -->
+ <define name="atomLink">
+ <element name="atom:link">
+ <ref name="atomCommonAttributes"/>
+ <attribute name="href">
+ <ref name="atomUri"/>
+ </attribute>
+ <optional>
+ <attribute name="rel">
+ <choice>
+ <ref name="atomNCName"/>
+ <ref name="atomUri"/>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="type">
+ <ref name="atomMediaType"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="hreflang">
+ <ref name="atomLanguageTag"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="title"/>
+ </optional>
+ <optional>
+ <attribute name="length"/>
+ </optional>
+ <ref name="undefinedContent"/>
+ </element>
+ </define>
+ <!-- atom:published -->
+ <define name="atomPublished">
+ <element name="atom:published">
+ <ref name="atomDateConstruct"/>
+ </element>
+ </define>
+ <!-- atom:rights -->
+ <define name="atomRights">
+ <element name="atom:rights">
+ <ref name="atomTextConstruct"/>
+ </element>
+ </define>
+ <!-- atom:source -->
+ <define name="atomSource">
+ <element name="atom:source">
+ <ref name="atomCommonAttributes"/>
+ <interleave>
+ <zeroOrMore>
+ <ref name="atomAuthor"/>
+ </zeroOrMore>
+ <zeroOrMore>
+ <ref name="atomCategory"/>
+ </zeroOrMore>
+ <zeroOrMore>
+ <ref name="atomContributor"/>
+ </zeroOrMore>
+ <optional>
+ <ref name="atomGenerator"/>
+ </optional>
+ <optional>
+ <ref name="atomIcon"/>
+ </optional>
+ <optional>
+ <ref name="atomId"/>
+ </optional>
+ <zeroOrMore>
+ <ref name="atomLink"/>
+ </zeroOrMore>
+ <optional>
+ <ref name="atomLogo"/>
+ </optional>
+ <optional>
+ <ref name="atomRights"/>
+ </optional>
+ <optional>
+ <ref name="atomSubtitle"/>
+ </optional>
+ <optional>
+ <ref name="atomTitle"/>
+ </optional>
+ <optional>
+ <ref name="atomUpdated"/>
+ </optional>
+ <zeroOrMore>
+ <ref name="extensionElement"/>
+ </zeroOrMore>
+ </interleave>
+ </element>
+ </define>
+ <!-- atom:subtitle -->
+ <define name="atomSubtitle">
+ <element name="atom:subtitle">
+ <ref name="atomTextConstruct"/>
+ </element>
+ </define>
+ <!-- atom:summary -->
+ <define name="atomSummary">
+ <element name="atom:summary">
+ <ref name="atomTextConstruct"/>
+ </element>
+ </define>
+ <!-- atom:title -->
+ <define name="atomTitle">
+ <element name="atom:title">
+ <ref name="atomTextConstruct"/>
+ </element>
+ </define>
+ <!-- atom:updated -->
+ <define name="atomUpdated">
+ <element name="atom:updated">
+ <ref name="atomDateConstruct"/>
+ </element>
+ </define>
+ <!-- Low-level simple types -->
+ <define name="atomNCName">
+ <data type="string">
+ <param name="minLength">1</param>
+ <param name="pattern">[^:]*</param>
+ </data>
+ </define>
+ <!-- Whatever a media type is, it contains at least one slash -->
+ <define name="atomMediaType">
+ <data type="string">
+ <param name="pattern">.+/.+</param>
+ </data>
+ </define>
+ <!-- As defined in RFC 3066 -->
+ <define name="atomLanguageTag">
+ <data type="string">
+ <param name="pattern">[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*</param>
+ </data>
+ </define>
+ <!--
+ Unconstrained; it's not entirely clear how IRI fit into
+ xsd:anyURI so let's not try to constrain it here
+ -->
+ <define name="atomUri">
+ <text/>
+ </define>
+ <!-- Whatever an email address is, it contains at least one @ -->
+ <define name="atomEmailAddress">
+ <data type="string">
+ <param name="pattern">.+@.+</param>
+ </data>
+ </define>
+ <!-- Simple Extension -->
+ <define name="simpleExtensionElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName ns="http://www.w3.org/2005/Atom"/>
+ </except>
+ </anyName>
+ <text/>
+ </element>
+ </define>
+ <!-- Structured Extension -->
+ <define name="structuredExtensionElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName ns="http://www.w3.org/2005/Atom"/>
+ </except>
+ </anyName>
+ <choice>
+ <group>
+ <oneOrMore>
+ <attribute>
+ <anyName/>
+ </attribute>
+ </oneOrMore>
+ <zeroOrMore>
+ <choice>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </group>
+ <group>
+ <zeroOrMore>
+ <attribute>
+ <anyName/>
+ </attribute>
+ </zeroOrMore>
+ <group>
+ <optional>
+ <text/>
+ </optional>
+ <oneOrMore>
+ <ref name="anyElement"/>
+ </oneOrMore>
+ <zeroOrMore>
+ <choice>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </group>
+ </group>
+ </choice>
+ </element>
+ </define>
+ <!-- Other Extensibility -->
+ <define name="extensionElement">
+ <choice>
+ <ref name="simpleExtensionElement"/>
+ <ref name="structuredExtensionElement"/>
+ </choice>
+ </define>
+ <define name="undefinedAttribute">
+ <attribute>
+ <anyName>
+ <except>
+ <name>xml:base</name>
+ <name>xml:lang</name>
+ <nsName ns=""/>
+ </except>
+ </anyName>
+ </attribute>
+ </define>
+ <define name="undefinedContent">
+ <zeroOrMore>
+ <choice>
+ <text/>
+ <ref name="anyForeignElement"/>
+ </choice>
+ </zeroOrMore>
+ </define>
+ <define name="anyElement">
+ <element>
+ <anyName/>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="anyForeignElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName ns="http://www.w3.org/2005/Atom"/>
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+ <!-- XHTML -->
+ <define name="anyXHTML">
+ <element>
+ <nsName/>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyXHTML"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="xhtmlDiv">
+ <element name="xhtml:div">
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyXHTML"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+</grammar>
+<!-- EOF -->
diff --git a/assets/schemas/jsonfeed-v1.1.json b/assets/schemas/jsonfeed-v1.1.json
new file mode 100644
index 0000000..0d6653d
--- /dev/null
+++ b/assets/schemas/jsonfeed-v1.1.json
@@ -0,0 +1,297 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "JSON Feed",
+ "description": "JSON Feed Version 1.1",
+ "type": "object",
+ "properties": {
+ "version": {
+ "description": "The URL of the version of the format the feed uses. This should appear at the very top.",
+ "type": "string",
+ "format": "uri",
+ "enum": [
+ "https://jsonfeed.org/version/1",
+ "https://jsonfeed.org/version/1.1"
+ ]
+ },
+ "title": {
+ "description": "The name of the feed, which will often correspond to the name of the website (blog, for instance), though not necessarily.",
+ "type": "string"
+ },
+ "home_page_url": {
+ "description": "The URL of the resource that the feed describes. This resource may or may not actually be a “home” page, but it should be an HTML page. If a feed is published on the public web, this should be considered as required.",
+ "type": "string",
+ "format": "uri"
+ },
+ "feed_url": {
+ "description": "The URL of the feed, serves as the unique identifier for the feed. This should be considered required for feeds on the public web.",
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "description": "Provides more detail, beyond the title, on what the feed is about. A feed reader may display this text.",
+ "type": "string"
+ },
+ "user_comment": {
+ "description": "A description of the purpose of the feed. This is for the use of people looking at the raw JSON, and should be ignored by feed readers.",
+ "type": "string"
+ },
+ "next_url": {
+ "description": "The URL of a feed that provides the next n items, where n is determined by the publisher. This allows for pagination, but with the expectation that reader software is not required to use it and probably won’t use it very often.",
+ "type": "string",
+ "format": "uri"
+ },
+ "icon": {
+ "description": "The URL of an image for the feed suitable to be used in a timeline, much the way an avatar might be used. It should be square and relatively large — such as 512 x 512 pixels — so that it can be scaled-down and so that it can look good on retina displays. It should use transparency where appropriate, since it may be rendered on a non-white background.",
+ "type": "string",
+ "format": "uri"
+ },
+ "favicon": {
+ "description": "The URL of an image for the feed suitable to be used in a source list. It should be square and relatively small, but not smaller than 64 x 64 pixels (so that it can look good on retina displays). This image should use transparency where appropriate, since it may be rendered on a non-white background.",
+ "type": "string",
+ "format": "uri"
+ },
+ "author": {
+ "description": "The feed author.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/author"
+ }
+ ]
+ },
+ "authors": {
+ "description": "One or more feed authors.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/author"
+ }
+ },
+ "language": {
+ "description": "The primary language for the feed in the format specified in RFC 5646. The value is usually a 2-letter language tag from ISO 639-1, optionally followed by a region tag.",
+ "type": "string"
+ },
+ "expired": {
+ "description": "Whether or not the feed is finished — that is, whether or not it will ever update again. A feed for a temporary event, such as an instance of the Olympics, could expire. If the value is true, then it’s expired. Any other value, or the absence of expired, means the feed may continue to update.",
+ "type": "boolean"
+ },
+ "hubs": {
+ "description": "Describes endpoints that can be used to subscribe to real-time notifications from the publisher of this feed.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "description": "Describes the protocol used to talk with the hub, such as “rssCloud” or “WebSub.”",
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "patternProperties": {
+ "^_[a-zA-Z][^.]*$": {
+ "$ref": "#/definitions/extension"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "url"
+ ]
+ }
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "Unique for that item for that feed over time. If an item is ever updated, the id should be unchanged. New items should never use a previously-used id. Ideally, the id is the full URL of the resource described by the item, since URLs make great unique identifiers.",
+ "type": "string"
+ },
+ "url": {
+ "description": "The URL of the resource described by the item. It’s the permalink. This may be the same as the id.",
+ "type": "string",
+ "format": "uri"
+ },
+ "external_url": {
+ "description": "The URL of a page elsewhere. This is especially useful for linkblogs. If url links to where you’re talking about a thing, then external_url links to the thing you’re talking about.",
+ "type": "string",
+ "format": "uri"
+ },
+ "title": {
+ "description": "Plain text. Microblog items in particular may omit titles.",
+ "type": "string"
+ },
+ "content_html": {
+ "description": "The HTML of the item.",
+ "type": "string"
+ },
+ "content_text": {
+ "description": "The plain text of the item.",
+ "type": "string"
+ },
+ "summary": {
+ "description": "A plain text sentence or two describing the item. This might be presented in a timeline, for instance, where a detail view would display all of content_html or content_text.",
+ "type": "string"
+ },
+ "image": {
+ "description": "The URL of the main image for the item. This image may also appear in the content_html — if so, it’s a hint to the feed reader that this is the main, featured image. Feed readers may use the image as a preview (probably resized as a thumbnail and placed in a timeline).",
+ "type": "string",
+ "format": "uri"
+ },
+ "banner_image": {
+ "description": "The URL of an image to use as a banner. Some blogging systems (such as Medium) display a different banner image chosen to go with each post, but that image wouldn’t otherwise appear in the content_html. A feed reader with a detail view may choose to show this banner image at the top of the detail view, possibly with the title overlaid.",
+ "type": "string",
+ "format": "uri"
+ },
+ "date_published": {
+ "description": "The date in RFC 3339 format.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "date_modified": {
+ "description": "The modification date in RFC 3339 format.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "author": {
+ "description": "The author of the item.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/author"
+ }
+ ]
+ },
+ "authors": {
+ "description": "The authors of the item.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/author"
+ }
+ },
+ "tags": {
+ "description": "Can have any plain text values you want. Tags tend to be just one word, but they may be anything. Note: they are not the equivalent of Twitter hashtags. Some blogging systems and other feed formats call these categories.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "language": {
+ "description": "The language for this item, using the format specified in RFC 5646. The value can be different than the primary language for the feed when a specific item is written in a different language than other items in the feed.",
+ "type": "string"
+ },
+ "attachments": {
+ "description": "Lists related resources. Podcasts, for instance, would include an attachment that’s an audio or video file.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "description": "The location of the attachment.",
+ "type": "string",
+ "format": "uri"
+ },
+ "mime_type": {
+ "description": "The type of the attachment, such as “audio/mpeg.”",
+ "type": "string"
+ },
+ "title": {
+ "description": "A name for the attachment. Important: if there are multiple attachments, and two or more have the exact same title (when title is present), then they are considered as alternate representations of the same thing. In this way a podcaster, for instance, might provide an audio recording in different formats.",
+ "type": "string"
+ },
+ "size_in_bytes": {
+ "description": "How large the file is.",
+ "type": "number"
+ },
+ "duration_in_seconds": {
+ "description": "How long it takes to listen to or watch, when played at normal speed.",
+ "type": "number"
+ }
+ },
+ "patternProperties": {
+ "^_[a-zA-Z][^.]*$": {
+ "$ref": "#/definitions/extension"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "url",
+ "mime_type"
+ ]
+ }
+ }
+ },
+ "patternProperties": {
+ "^_[a-zA-Z][^.]*$": {
+ "$ref": "#/definitions/extension"
+ }
+ },
+ "additionalProperties": false,
+ "anyOf": [
+ {
+ "required": [
+ "content_html"
+ ]
+ },
+ {
+ "required": [
+ "content_text"
+ ]
+ }
+ ],
+ "required": [
+ "id"
+ ]
+ }
+ }
+ },
+ "patternProperties": {
+ "^_[a-zA-Z][^.]*$": {
+ "$ref": "#/definitions/extension"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "version",
+ "title",
+ "items"
+ ],
+ "definitions": {
+ "author": {
+ "title": "Author",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The author’s name.",
+ "type": "string"
+ },
+ "url": {
+ "description": "The URL of a site owned by the author. It could be a blog, micro-blog, Twitter account, and so on. Ideally the linked-to page provides a way to contact the author, but that’s not required. The URL could be a mailto: link.",
+ "type": "string",
+ "format": "uri"
+ },
+ "avatar": {
+ "description": "The URL for an image for the author. It should be square and relatively large — such as 512 x 512 pixels — and should use transparency where appropriate, since it may be rendered on a non-white background.",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "minProperties": 1,
+ "patternProperties": {
+ "^_[a-zA-Z][^.]*$": {
+ "$ref": "#/definitions/extension"
+ }
+ },
+ "additionalProperties": false
+ },
+ "extension": {
+ "title": "Extension",
+ "type": "object",
+ "patternProperties": {
+ "^[^.]*$": {}
+ },
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/assets/schemas/rss-v2.0.rng b/assets/schemas/rss-v2.0.rng
new file mode 100644
index 0000000..31708ca
--- /dev/null
+++ b/assets/schemas/rss-v2.0.rng
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar
+ xmlns:atom="http://www.w3.org/2005/Atom"
+ xmlns="http://relaxng.org/ns/structure/1.0"
+ ns=""
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ >
+ <start>
+ <element name="rss">
+ <attribute name="version">
+ <data type="decimal"/>
+ </attribute>
+ <element name="channel">
+ <ref name="title"/>
+ <ref name="link"/>
+ <ref name="description"/>
+ <element name="language">
+ <data type="NCName"/>
+ </element>
+ <element name="category">
+ <data type="NCName"/>
+ </element>
+ <element name="generator">
+ <text/>
+ </element>
+ <element name="lastBuildDate">
+ <text/>
+ </element>
+ <element name="image">
+ <ref name="title"/>
+ <element name="url">
+ <text/>
+ </element>
+ <ref name="link"/>
+ </element>
+ <oneOrMore>
+ <element name="atom:link">
+ <attribute name="href"/>
+ <attribute name="rel">
+ <data type="NCName"/>
+ </attribute>
+ <optional>
+ <attribute name="type"/>
+ </optional>
+ </element>
+ </oneOrMore>
+ <oneOrMore>
+ <element name="item">
+ <ref name="title"/>
+ <ref name="link"/>
+ <element name="pubDate">
+ <text/>
+ </element>
+ <element name="guid">
+ <text/>
+ </element>
+ <ref name="description"/>
+ <element name="atom:author">
+ <element name="atom:name">
+ <text/>
+ </element>
+ <element name="atom:uri">
+ <text/>
+ </element>
+ </element>
+ </element>
+ </oneOrMore>
+ </element>
+ </element>
+ </start>
+ <define name="title">
+ <element name="title">
+ <text/>
+ </element>
+ </define>
+ <define name="link">
+ <element name="link">
+ <text/>
+ </element>
+ </define>
+ <define name="description">
+ <element name="description">
+ <text/>
+ </element>
+ </define>
+</grammar>
diff --git a/assets/schemas/rss-v2.0.xsd b/assets/schemas/rss-v2.0.xsd
new file mode 100644
index 0000000..5b1ce1a
--- /dev/null
+++ b/assets/schemas/rss-v2.0.xsd
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="rss" type="rss"/>
+ <xsd:complexType name="rss">
+ <xsd:sequence>
+ <xsd:element name="channel" type="channel" maxOccurs="1" minOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="version" type="version"/>
+ </xsd:complexType>
+ <xsd:simpleType name="version">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="2.0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="channel">
+ <xsd:sequence>
+ <xsd:element name="title" type="xsd:string" maxOccurs="1" minOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation>
+ The name of the channel. It's how people refer
+ to your service. If you have an HTML website
+ that contains the same information as your RSS
+ file, the title of your channel should be the
+ same as the title of your website.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="link" type="xsd:string" maxOccurs="1" minOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation>
+ The URL to the HTML website corresponding to the
+ channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="description" type="xsd:string" maxOccurs="1" minOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation>
+ Phrase or sentence describing the channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="language" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ The language the channel is written in. This
+ allows aggregators to group all Italian language
+ sites, for example, on a single page. A list of
+ allowable values for this element, as provided
+ by Netscape, is here
+ [http://www.rssboard.org/rss-language-codes].
+ You may also use values defined by the W3C
+ [http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes].
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="copyright" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Copyright notice for content in the channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="managingEditor" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Email address for person responsible for
+ editorial content.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="webMaster" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Email address for person responsible for
+ technical issues relating to channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="pubDate" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ The publication date for the content in the
+ channel. For example, the New York Times
+ publishes on a daily basis, the publication date
+ flips once every 24 hours. That's when the
+ pubDate of the channel changes. All date-times
+ in RSS conform to the Date and Time
+ Specification of RFC 822, with the exception
+ that the year may be expressed with two
+ characters or four characters (four preferred).
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="category" type="category" maxOccurs="unbounded" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Specify one or more categories that the channel
+ belongs to. Follows the same rules as the
+ &lt;item&gt;-level category element.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="generator" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ A string indicating the program used to generate
+ the channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="lastBuildDate" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ The last time the content of the channel
+ changed.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="docs" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ A URL that points to the documentation for the
+ format used in the RSS file. It's probably a
+ pointer to this page. It's for people who might
+ stumble across an RSS file on a Web server 25
+ years from now and wonder what it is.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="cloud" type="cloud" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Allows processes to register with a cloud to be
+ notified of updates to the channel, implementing
+ a lightweight publish-subscribe protocol for RSS
+ feeds. More info here.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="ttl" type="xsd:int" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ ttl stands for time to live. It's a number of
+ minutes that indicates how long a channel can be
+ cached before refreshing from the source.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="image" type="image" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Specifies a GIF, JPEG or PNG image that can be
+ displayed with the channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="rating" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ The PICS [http://www.w3.org/PICS/] rating for
+ the channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="textInput" type="textInput" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Specifies a text input box that can be displayed
+ with the channel.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="skipHours" type="skipHours" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ A hint for aggregators telling them which hours
+ they can skip. This element contains up to 24
+ &lt;hour&gt; sub-elements whose value is a
+ number between 0 and 23, representing a time in
+ GMT, when aggregators, if they support the
+ feature, may not read the channel on hours
+ listed in the &lt;skipHours&gt; element. The
+ hour beginning at midnight is hour zero.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="skipDays" type="skipDays" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ A hint for aggregators telling them which days
+ they can skip. This element contains up to seven
+ &lt;day&gt;
+ sub-elements whose value is Monday, Tuesday,
+ Wednesday, Thursday, Friday, Saturday or
+ Sunday. Aggregators may not read the channel
+ during days listed in the
+ &lt;skipDays&gt;element.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="item" type="item" maxOccurs="unbounded" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="cloud">
+ <xsd:attribute name="domain" type="xsd:string"/>
+ <xsd:attribute name="port">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:int">
+ <xsd:maxInclusive value="65536"/>
+ <xsd:minInclusive value="0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="path" type="xsd:string"/>
+ <xsd:attribute name="registerProcedure" type="xsd:string"/>
+ <xsd:attribute name="protocol" type="xsd:string"/>
+ </xsd:complexType>
+ <xsd:complexType name="image">
+ <xsd:sequence>
+ <xsd:element name="title" type="xsd:string" maxOccurs="1" minOccurs="1"/>
+ <xsd:element name="url" type="xsd:string" maxOccurs="1" minOccurs="1"/>
+ <xsd:element name="link" type="xsd:string" maxOccurs="1" minOccurs="0"/>
+ <xsd:element name="width" default="88" maxOccurs="1" minOccurs="0">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:int">
+ <xsd:minExclusive value="0"/>
+ <xsd:maxInclusive value="144"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="height" default="31" maxOccurs="1" minOccurs="0">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:int">
+ <xsd:minExclusive value="0"/>
+ <xsd:maxInclusive value="400"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="description" type="xsd:string" maxOccurs="1" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="textInput">
+ <xsd:sequence>
+ <xsd:element name="title" type="xsd:string" maxOccurs="1" minOccurs="1"/>
+ <xsd:element name="description" type="xsd:string" maxOccurs="1" minOccurs="1"/>
+ <xsd:element name="name" type="xsd:string" maxOccurs="1" minOccurs="1"/>
+ <xsd:element name="link" type="xsd:string" maxOccurs="1" minOccurs="1"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="skipHours">
+ <xsd:sequence>
+ <xsd:element name="hour" minOccurs="1" maxOccurs="24">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:int">
+ <xsd:maxInclusive value="23"/>
+ <xsd:minInclusive value="0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="skipDays">
+ <xsd:sequence>
+ <xsd:element name="day" maxOccurs="7">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Monday"/>
+ <xsd:enumeration value="Tuesday"/>
+ <xsd:enumeration value="Wednesday"/>
+ <xsd:enumeration value="Thursday"/>
+ <xsd:enumeration value="Friday"/>
+ <xsd:enumeration value="Saturday"/>
+ <xsd:enumeration value="Sunday"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="item">
+ <xsd:sequence>
+ <xsd:element name="title" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ The title of the item.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="link" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The URL of the item.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="description" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The item synopsis.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="author" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Email address of the author of the item.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="category" type="category" maxOccurs="unbounded" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Includes the item in one or more categories.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="comments" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ URL of a page for comments relating to the item.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="enclosure" type="enclosure" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes a media object that is attached to the
+ item.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="guid" type="guid" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ A string that uniquely identifies the item.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="pubDate" type="xsd:string" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Indicates when the item was published.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="source" type="source" maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ The RSS channel that the item came from.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="category">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="domain" type="xsd:string"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ <xsd:complexType name="enclosure">
+ <xsd:attribute name="url" type="xsd:string" use="required"/>
+ <xsd:attribute name="length" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:int">
+ <xsd:minExclusive value="0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="guid">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="isPermaLink" type="xsd:boolean"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ <xsd:complexType name="source">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="url" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+</xsd:schema>
diff --git a/shell.nix b/shell.nix
index 76d3906..151c411 100644
--- a/shell.nix
+++ b/shell.nix
@@ -17,6 +17,11 @@ let
sha256 = "1ciwifsx2hrp0ymm077zfb5q8ravrk545bda1q249y2spw9np4ms";
}) { };
+ check-jsonschema = pkgs.callPackage (pkgs.fetchurl {
+ url = "https://raw.githubusercontent.com/NixOS/nixpkgs/82e9e32436d5886102f1e5f7c17aed8475504991/pkgs/development/tools/check-jsonschema/default.nix";
+ sha256 = "sha256-PImZIMGa6+DE2f4tXiPS8ck7enThJvi9uiyxHvXm9WQ";
+ }) { };
+
hugo = pkgs.callPackage ({ lib, buildGo118Module, fetchgit, installShellFiles }:
buildGo118Module rec {
@@ -55,6 +60,7 @@ in mkShellPure {
inherit hugo;
packages = [
+ check-jsonschema
hugo
validatornu
pkgs.busybox
@@ -62,6 +68,8 @@ in mkShellPure {
pkgs.entr
pkgs.git
pkgs.gnumake
+ pkgs.jing
+ pkgs.libxml2
pkgs.libxslt
pkgs.php
pkgs.subversion
diff --git a/themes/default/layouts/_default/rss.xml b/themes/default/layouts/_default/rss.xml
index 562da9e..8248cc1 100644
--- a/themes/default/layouts/_default/rss.xml
+++ b/themes/default/layouts/_default/rss.xml
@@ -65,13 +65,13 @@
<description>{{ $description }}</description>
<language>{{ .Site.LanguageCode }}</language>
<category>{{ partial "author-user.html" . }}</category>
+ <generator>Hugo {{ hugo.Version }}</generator>
+ <lastBuildDate>{{ $lastBuildDate }}</lastBuildDate>
<image>
<title>{{ $title }}</title>
<url>{{ $image.Permalink }}</url>
<link>{{ .Permalink }}</link>
</image>
- <generator>Hugo {{ hugo.Version }}</generator>
- <lastBuildDate>{{ $lastBuildDate }}</lastBuildDate>
{{ $atomSelf }}
{{ $atomPrevious }}