JSON Feed support, closes #55

This commit is contained in:
Lucidiot 2021-03-17 20:37:13 +01:00
parent f2154e4468
commit e7398b9b6f
Signed by: lucidiot
GPG Key ID: 3358C1CA6906FB8D
6 changed files with 213 additions and 55 deletions

BIN
img/json.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@ -39,28 +39,28 @@
<region>Canada</region>
<type>Rail, aviation, marine, pipeline</type>
<feed lang="English" format="rss" type="rail" id="tsb-en-rail">
<link>http://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Rail.xml</link>
<link>https://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Rail.xml</link>
</feed>
<feed lang="French" format="rss" type="rail" id="tsb-fr-rail">
<link>http://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Rail.xml</link>
<link>https://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Rail.xml</link>
</feed>
<feed lang="English" format="rss" type="aviation" id="tsb-en-aviation">
<link>http://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Air.xml</link>
<link>https://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Air.xml</link>
</feed>
<feed lang="French" format="rss" type="aviation" id="tsb-fr-aviation">
<link>http://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Aviation.xml</link>
<link>https://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Aviation.xml</link>
</feed>
<feed lang="English" format="rss" type="marine" id="tsb-en-marine">
<link>http://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Marine.xml</link>
<link>https://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Marine.xml</link>
</feed>
<feed lang="French" format="rss" type="marine" id="tsb-fr-marine">
<link>http://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Marine.xml</link>
<link>https://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Marine.xml</link>
</feed>
<feed lang="English" format="rss" type="pipeline" id="tsb-en-pipeline">
<link>http://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Pipeline.xml</link>
<link>https://www.bst-tsb.gc.ca/eng/fils-feeds/TSB%20Pipeline.xml</link>
</feed>
<feed lang="French" format="rss" type="pipeline" id="tsb-fr-pipeline">
<link>http://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Pipeline.xml</link>
<link>https://www.bst-tsb.gc.ca/fra/fils-feeds/BST%20Pipeline.xml</link>
</feed>
</source>

View File

@ -310,6 +310,7 @@
<xs:enumeration value="rss" />
<xs:enumeration value="atom" />
<xs:enumeration value="rdf" />
<xs:enumeration value="json" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>

View File

@ -0,0 +1,52 @@
# Atom->JSON Feed converter
# Expects xmltodict JSON output as input, outputs a JSON Feed 1.1 feed backwards-compatible with 1.0
# Does not support entries without links.
# Required variables:
# $feed_url: Absolute URL to the resulting JSON Feed
def ensure_string: if type == "array" then .[0] else . end | if type == "object" then .["#text"] else . end;
def ensure_array: (. // []) | if type == "array" then . else [.] end;
def find_links(rel): [(.link // []) | if type == "array" then .[] else . end | select(.["@rel"] == rel)];
def parse_authors: [
((.author | ensure_array) + (.contributor | ensure_array))[]
| . as $author
| {"name": .name}
| if $author.uri then .url = $author.uri else . end
];
.feed | . as $feed | {
"version": "https://jsonfeed.org/version/1.1",
"title": (.title | ensure_string),
"home_page_url": find_links("alternate")[0]["@href"],
"feed_url": $feed_url,
"description": (.description | ensure_string // ""),
"expired": false,
"authors": parse_authors,
"items": [
.entry | ensure_array[] | select(.link) | . as $entry | {
"id": .id,
"title": (.title | ensure_string),
"url": find_links("alternate")[0]["@href"],
"date_published": (.published // .updated),
"date_modified": .updated,
"authors": parse_authors,
"tags": [
.category | ensure_array[] | .["@term"]
],
"attachments": [
find_links("enclosure")[] | {
"url": .["@href"],
"size_in_bytes": .["@length"],
"mime_type": .["@type"]
}
],
"content_html": (.content | ensure_array[0] | ensure_string // "")
}
# Optional summary
| if $entry.summary then .summary = ($entry.summary | ensure_string // "") else . end
# Optional external URL
| if ($entry|find_links("related")[0]) then .external_url = ($entry|find_links("related")[0]["@href"]) else . end
]
}
# Optional icon
| if $feed.icon then .icon = $feed.icon else . end

View File

@ -0,0 +1,43 @@
# RSS->JSON Feed converter
# Expects xmltodict JSON output as input, outputs a JSON Feed 1.1 feed backwards-compatible with 1.0
# Does not support RSS items without links.
# Required variables:
# $feed_url: Absolute URL to the resulting JSON Feed
def ensure_string: if type == "array" then .[0] else . end | if type == "object" then .["#text"] else . end;
def ensure_array: (. // []) | if type == "array" then . else [.] end;
.rss.channel | . as $channel | {
"version": "https://jsonfeed.org/version/1.1",
"title": (.title | ensure_string),
"home_page_url": (.link | ensure_string),
"feed_url": $feed_url,
"description": (.description | ensure_string),
"expired": false,
"items": [
.item | ensure_array[] | select(.link) | . as $item | {
"id": (.guid // .link | ensure_string),
"title": .title,
"url": .link,
"content_html": (.description // "" | ensure_string),
"tags": [
.category | ensure_array[] | ensure_string
],
"attachments": [
.enclosure | ensure_array[] | {
"url": .["@url"],
"size_in_bytes": .["@length"],
"mime_type": .["@type"]
}
]
}
# Optional publication date
| if $item.pubDate then .date_published = ($item.pubDate | strptime("%a, %d %b %Y %T %z") | mktime | todateiso8601) else . end
# Optional author
| if $item.author then .author = {"name": $item.author} | .authors = [.author] else . end
]
}
# Optional language
| if $channel.language then .language = $channel.language else . end
# Optional icon
| if $channel.image then .icon = $channel.image.url else . end

View File

@ -6,9 +6,10 @@
xmlns:itsb="http://tilde.town/~lucidiot/itsb/">
<xsl:output method="xml" />
<!--
Preprocesses the itsb.xml file to autocomplete with converted feeds.
-->
<!-- Preprocesses the itsb.xml file to autocomplete with converted feeds. -->
<!-- Caution: Final slash is required. -->
<xsl:variable name="itsbRoot" select="'https://tilde.town/~lucidiot/itsb/'" />
<xsl:template match="node()|@*">
<xsl:copy>
@ -24,58 +25,119 @@
<xsl:variable name="type" select="@type" />
<!-- This is an Atom feed and there is no RSS feed: add a conversion -->
<xsl:if test="@format = 'atom' and not(../itsb:feed[((not(@lang) and not($lang)) or @lang = $lang) and ((not(@type) and not($type)) or @type = $type) and (@format = 'rss' or @format = 'rdf')])">
<feed>
<xsl:if test="@lang">
<xsl:attribute name="lang">
<xsl:value-of select="@lang" />
<xsl:choose>
<xsl:when test="@format = 'atom' and not(../itsb:feed[((not(@lang) and not($lang)) or @lang = $lang) and ((not(@type) and not($type)) or @type = $type) and (@format = 'rss' or @format = 'rdf')])">
<feed format="rss">
<xsl:if test="@lang">
<xsl:attribute name="lang">
<xsl:value-of select="@lang" />
</xsl:attribute>
</xsl:if>
<xsl:if test="@type">
<xsl:attribute name="type">
<xsl:value-of select="@type" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="id">
<xsl:value-of select="@id" />
<xsl:text>-rss</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:if test="@type">
<xsl:attribute name="type">
<xsl:value-of select="@type" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="id">
<xsl:value-of select="@id" />
<xsl:text>-2rss</xsl:text>
</xsl:attribute>
<xsl:attribute name="format">rss</xsl:attribute>
<xsl:choose>
<xsl:when test="itsb:output">
<shell>
<xsl:text>cat $DIR/feeds/</xsl:text>
<xsl:value-of select="itsb:output/text()" />
</shell>
</xsl:when>
<xsl:otherwise>
<curl>
<url>
<xsl:value-of select="itsb:link/text()" />
</url>
</curl>
</xsl:otherwise>
</xsl:choose>
<xml2json />
<jq path="convert/atom2rss.jq" />
<json2xml />
<output>
<xsl:text>_rss/</xsl:text>
<xsl:choose>
<xsl:when test="itsb:output">
<xsl:value-of select="itsb:output/text()" />
<shell>
<xsl:text>cat $DIR/feeds/</xsl:text>
<xsl:value-of select="itsb:output/text()" />
</shell>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@id" />
<xsl:text>.xml</xsl:text>
<curl>
<url>
<xsl:value-of select="itsb:link/text()" />
</url>
</curl>
</xsl:otherwise>
</xsl:choose>
</output>
</feed>
</xsl:if>
<xml2json />
<jq path="convert/atom2rss.jq" />
<json2xml />
<output>
<xsl:text>_rss/</xsl:text>
<xsl:choose>
<xsl:when test="itsb:output">
<xsl:value-of select="itsb:output/text()" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@id" />
<xsl:text>.xml</xsl:text>
</xsl:otherwise>
</xsl:choose>
</output>
</feed>
<xsl:call-template name="jsonfeed" />
</xsl:when>
<xsl:when test="@format = 'rss'">
<xsl:call-template name="jsonfeed" />
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="jsonfeed">
<feed format="json">
<xsl:if test="@lang">
<xsl:attribute name="lang">
<xsl:value-of select="@lang" />
</xsl:attribute>
</xsl:if>
<xsl:if test="@type">
<xsl:attribute name="type">
<xsl:value-of select="@type" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="id">
<xsl:value-of select="@id" />
<xsl:text>-json</xsl:text>
</xsl:attribute>
<xsl:choose>
<xsl:when test="itsb:output">
<shell>
<xsl:text>cat $DIR/feeds/</xsl:text>
<xsl:value-of select="itsb:output/text()" />
</shell>
</xsl:when>
<xsl:otherwise>
<curl>
<url>
<xsl:value-of select="itsb:link/text()" />
</url>
</curl>
</xsl:otherwise>
</xsl:choose>
<xml2json />
<jq>
<xsl:attribute name="path">
<xsl:text>convert/</xsl:text>
<xsl:value-of select="@format" />
<xsl:text>2jsonfeed.jq</xsl:text>
</xsl:attribute>
<arg name="feed_url">
<xsl:value-of select="$itsbRoot" />
<xsl:text>feeds/_json/</xsl:text>
<xsl:value-of select="@id" />
<xsl:text>.json</xsl:text>
</arg>
</jq>
<output>
<xsl:text>_json/</xsl:text>
<xsl:value-of select="@id" />
<xsl:text>.json</xsl:text>
</output>
</feed>
</xsl:template>
</xsl:stylesheet>