<?xml version="1.0" ?>
<!--
	Transforms valid Atom feed into an RDF document using AtomOwl, Dublin
	Core, FOAF, RSS 1.0 and SKOS ontologies.
	Copyright © 2018  Paulina Laura Emilia <vilene -at- posteo -dot- net>

	This XSLT stylesheet is intended to be used as a GRDDL transformation
	referenced inside Atom documents, but it can be used also for any other
	purpose where transformation of Atom feeds to RDF data is needed.

	Only atom:content element does not have an equivalent in the resulting
	RDF document, because I feel that including it is out of scope of this
	transformation.

	AtomOwl ontology is used here sparingly; only for those properties that
	don’t have good equivalents in more widely used ontologies (i.e.
	atomowl:generatorVersion and atomowl:icon).

	For compatibility with the free ‘xsltproc’ XSLT processor, only features
	available in XSLT 1.1 and XPath 1.0 are used by this stylesheet.

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as
	published by the Free Software Foundation, either version 3 of the
	License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Affero General Public License for more details.

	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->
<xsl:stylesheet version="1.0"
	xmlns="http://purl.org/rss/1.0/"
	xmlns:admin="http://webns.net/mvcb/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:atomowl="http://bblfish.net/work/atom-owl/2006-06-06/#"
	xmlns:dcterms="http://purl.org/dc/terms/"
	xmlns:foaf="http://xmlns.com/foaf/0.1/"
	xmlns:ianarel="http://www.iana.org/assignments/relation/"
	xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
	xmlns:skos="http://www.w3.org/2004/02/skos/core#"
	xmlns:xhtml="http://www.w3.org/1999/xhtml"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	exclude-result-prefixes="atom"
>
	<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"
		cdata-section-elements="title description dcterms:rights" />

	<!-- Key defined to avoid duplicate link descriptions. -->
	<xsl:key name="link" match="atom:link" use="@href" />

	<!-- Function returning the part of a given IRI before and including the last slash. -->
	<xsl:template name="baseIRI">
		<xsl:param name="iri" />
		<xsl:value-of select="concat(substring-before($iri, '/'), '/')" />
		<xsl:if test="contains(substring-after($iri, '/'), '/')">
			<xsl:call-template name="baseIRI">
				<xsl:with-param name="iri" select="substring-after($iri, '/')" />
			</xsl:call-template>
		</xsl:if>
	</xsl:template>

	<!-- Common Atom constructs. -->
	<xsl:template name="atomCommonAttributes">
		<!-- There is no need to call this template for every Atom element,
		     only for those that don’t call any other template with common constructs. -->
		<xsl:copy-of select="@xml:base | @xml:lang" />
	</xsl:template>

	<xsl:template name="atomContainerElement">
		<xsl:call-template name="atomCommonAttributes" />

		<!-- RSS 1.0 properties. -->
		<xsl:apply-templates select="atom:title" />
		<xsl:apply-templates select="atom:link[@rel = 'self' or @rel = 'http://www.iana.org/assignments/relation/self']" />
		<xsl:apply-templates select="atom:subtitle | atom:summary" />

		<!-- Rest of properties, selected at once to preserve their ordering in the source Atom feed. -->
		<xsl:apply-templates select="atom:author | atom:category | atom:contributor | atom:generator | atom:id | atom:icon
			| atom:logo | atom:link[not(@rel = 'self' or @rel = 'http://www.iana.org/assignments/relation/self')]
			| atom:published | atom:rights | atom:source | atom:updated" />
	</xsl:template>

	<xsl:template name="atomDateConstruct">
		<xsl:call-template name="atomCommonAttributes" />
		<xsl:attribute name="rdf:datatype">http://www.w3.org/2001/XMLSchema#dateTime</xsl:attribute>
		<xsl:value-of select="." />
	</xsl:template>

	<xsl:template name="atomPersonConstruct">
		<xsl:call-template name="atomCommonAttributes" />
		<foaf:Person>
			<xsl:apply-templates select="atom:name | atom:email | atom:uri" />
		</foaf:Person>
	</xsl:template>

	<xsl:template name="atomTextConstruct">
		<xsl:call-template name="atomCommonAttributes" />
		<xsl:choose>
			<xsl:when test="@type = 'xhtml'">
				<xsl:attribute name="rdf:parseType">Literal</xsl:attribute>
				<xsl:copy-of select="xhtml:div" />
			</xsl:when>
			<xsl:otherwise>
				<xsl:value-of select="." />
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<!-- Root-level template. -->
	<xsl:template match="/">
		<rdf:RDF>
			<!-- Because scope of the xml:base and xml:lang attributes of an atom:feed is the whole document,
			     they need to be duplicated also in the root element of the RDF document that is the result
			     of transformation. -->
			<xsl:copy-of select="atom:feed/@xml:base | atom:feed/@xml:lang" />

			<xsl:apply-templates select="atom:feed" />
			<xsl:apply-templates select="atom:feed/atom:entry" />
			<xsl:apply-templates select="atom:feed//atom:link[generate-id() = generate-id(key('link', @href))]"
				mode="description" />
		</rdf:RDF>
	</xsl:template>

	<!-- Atom elements. -->
	<xsl:template match="atom:author">
		<dcterms:creator>
			<xsl:call-template name="atomPersonConstruct" />
		</dcterms:creator>
	</xsl:template>

	<xsl:template match="atom:category">
		<dcterms:subject>
			<skos:Concept>
				<xsl:call-template name="atomCommonAttributes" />

				<!-- We need to use Dublin Core here, because SKOS allows to identify concepts only by IRI,
				     but Atom category identifiers are literals. -->
				<dcterms:identifier>
					<xsl:value-of select="@term" />
				</dcterms:identifier>

				<xsl:if test="@label">
					<skos:prefLabel>
						<xsl:value-of select="@label" />
					</skos:prefLabel>
				</xsl:if>

				<xsl:if test="@scheme">
					<skos:inScheme rdf:resource="{@scheme}" />
				</xsl:if>
			</skos:Concept>
		</dcterms:subject>
	</xsl:template>

	<xsl:template match="atom:contributor">
		<dcterms:contributor>
			<xsl:call-template name="atomPersonConstruct" />
		</dcterms:contributor>
	</xsl:template>

	<xsl:template match="atom:email">
		<foaf:mbox rdf:resource="mailto:{.}">
			<xsl:call-template name="atomCommonAttributes" />
		</foaf:mbox>
	</xsl:template>

	<xsl:template match="atom:entry">
		<item rdf:about="{atom:id}">
			<xsl:call-template name="atomContainerElement" />
		</item>
	</xsl:template>

	<xsl:template match="atom:feed">
		<channel rdf:about="{atom:id}">
			<xsl:call-template name="atomContainerElement" />
			<xsl:if test="atom:entry">
				<items>
					<rdf:Seq>
						<xsl:for-each select="atom:entry">
							<rdf:li rdf:resource="{atom:id}" />
						</xsl:for-each>
					</rdf:Seq>
				</items>
			</xsl:if>
		</channel>
	</xsl:template>

	<xsl:template match="atom:generator">
		<admin:generatorAgent>
			<foaf:Project>
				<xsl:call-template name="atomCommonAttributes" />

				<xsl:if test="@uri">
					<xsl:attribute name="rdf:about">
						<xsl:value-of select="@uri" />
					</xsl:attribute>
				</xsl:if>

				<foaf:name>
					<xsl:value-of select="." />
				</foaf:name>

				<xsl:if test="@version">
					<atomowl:generatorVersion>
						<xsl:value-of select="@version" />
					</atomowl:generatorVersion>
				</xsl:if>
			</foaf:Project>
		</admin:generatorAgent>
	</xsl:template>

	<xsl:template match="atom:icon">
		<atomowl:icon rdf:resource="{.}">
			<xsl:call-template name="atomCommonAttributes" />
		</atomowl:icon>
	</xsl:template>

	<xsl:template match="atom:id">
		<dcterms:identifier rdf:datatype="http://purl.org/dc/terms/URI">
			<xsl:call-template name="atomCommonAttributes" />
			<xsl:value-of select="." />
		</dcterms:identifier>
	</xsl:template>

	<!-- The following two templates for processing Atom links do not check if we can construct valid XML element names
	     from link relations, so they depend on Atom feed authors and generators doing the sane thing, otherwise they
	     throw errors. -->
	<xsl:template match="atom:link[(not(contains(@rel, '/') or contains(@rel, ':'))
			or starts-with(@rel, 'http://www.iana.org/assignments/relation/'))
			and not(@rel = 'self' or @rel = 'http://www.iana.org/assignments/relation/self')]">
		<!-- Links whose relation type belongs to the IANA Registry of Link Relations and isn’t ‘self’. -->
		<xsl:variable name="relation">
			<xsl:choose>
				<xsl:when test="not(@rel)">alternate</xsl:when>
				<xsl:when test="starts-with(@rel, 'http://www.iana.org/assignments/relation/')">
					<xsl:value-of select="substring-after(@rel, 'http://www.iana.org/assignments/relation/')" />
				</xsl:when>
				<xsl:otherwise>
					<xsl:value-of select="@rel" />
				</xsl:otherwise>
			</xsl:choose>
		</xsl:variable>

		<xsl:variable name="element">
			<xsl:choose>
				<!-- For relations defined in the Atom specification,
				     we’re using the closest Dublin Core term as a property. -->
				<xsl:when test="$relation = 'alternate'">dcterms:hasFormat</xsl:when>
				<xsl:when test="$relation = 'enclosure' or $relation = 'related'">dcterms:relation</xsl:when>
				<xsl:when test="$relation = 'via'">dcterms:source</xsl:when>

				<!-- For any other relation, we’re using properties in the IANA Registry of Link Relations
				     namespace. While they’re not formal RDF properties, they’re commonly used as such e.g.
				     in the output of RDFa distillers. -->
				<xsl:otherwise>ianarel:<xsl:value-of select="$relation" /></xsl:otherwise>
			</xsl:choose>
		</xsl:variable>

		<xsl:element name="{$element}">
			<xsl:call-template name="atomCommonAttributes" />
			<xsl:attribute name="rdf:resource">
				<xsl:value-of select="@href" />
			</xsl:attribute>
		</xsl:element>
	</xsl:template>

	<xsl:template match="atom:link[(contains(@rel, '/') or contains(@rel, ':'))
			and not(starts-with(@rel, 'http://www.iana.org/assignments/relation/'))]">
		<!-- Links with relation outside of the IANA Registry of Link Relations. -->
		<xsl:variable name="namespace">
			<xsl:choose>
				<xsl:when test="contains(@rel, '#')">
					<xsl:value-of select="concat(substring-before(@rel, '#'), '#')" />
				</xsl:when>
				<xsl:otherwise>
					<xsl:call-template name="baseIRI">
						<xsl:with-param name="iri" select="@rel" />
					</xsl:call-template>
				</xsl:otherwise>
			</xsl:choose>
		</xsl:variable>

		<xsl:element namespace="{$namespace}" name="{substring-after(@rel, $namespace)}">
			<xsl:call-template name="atomCommonAttributes" />
			<xsl:attribute name="rdf:resource">
				<xsl:value-of select="@href" />
			</xsl:attribute>
		</xsl:element>
	</xsl:template>

	<xsl:template match="atom:link[@rel = 'self' or @rel = 'http://www.iana.org/assignments/relation/self']">
		<!-- Links whose relation type is ‘self’. -->
		<link rdf:datatype="http://purl.org/dc/terms/URI">
			<xsl:call-template name="atomCommonAttributes" />
			<xsl:value-of select="@href" />
		</link>
	</xsl:template>

	<xsl:template match="atom:link[@hreflang | @length | @title | @type]" mode="description">
		<!-- Link target descriptions. -->
		<rdf:Description rdf:about="{@href}">
			<xsl:call-template name="atomCommonAttributes" />

			<xsl:if test="@length">
				<dcterms:extent>
					<xsl:value-of select="@length" />
				</dcterms:extent>
			</xsl:if>

			<xsl:if test="@type">
				<dcterms:format rdf:datatype="http://purl.org/dc/terms/IMT">
					<xsl:value-of select="@type" />
				</dcterms:format>
			</xsl:if>

			<xsl:if test="@hreflang">
				<dcterms:language rdf:datatype="http://purl.org/dc/terms/RFC3066">
					<xsl:value-of select="@hreflang" />
				</dcterms:language>
			</xsl:if>

			<xsl:if test="@title">
				<dcterms:title>
					<xsl:value-of select="@title" />
				</dcterms:title>
			</xsl:if>
		</rdf:Description>
	</xsl:template>

	<xsl:template match="atom:logo">
		<foaf:logo rdf:resource="{.}">
			<xsl:call-template name="atomCommonAttributes" />
		</foaf:logo>
	</xsl:template>

	<xsl:template match="atom:name">
		<foaf:name>
			<xsl:call-template name="atomCommonAttributes" />
			<xsl:value-of select="." />
		</foaf:name>
	</xsl:template>

	<xsl:template match="atom:published">
		<dcterms:issued>
			<xsl:call-template name="atomDateConstruct" />
		</dcterms:issued>
	</xsl:template>

	<xsl:template match="atom:rights">
		<dcterms:rights>
			<xsl:call-template name="atomTextConstruct" />
		</dcterms:rights>
	</xsl:template>

	<xsl:template match="atom:source">
		<!-- Because atom:id attribute is optional in atom:source, and RSS 1.0 document
		     cannot have more than one top-level channel element, we’re providing description
		     of the source feed inside description of an entry, for simplicity. -->
		<dcterms:source>
			<channel>
				<xsl:if test="atom:id">
					<xsl:attribute name="rdf:about">
						<xsl:value-of select="atom:id" />
					</xsl:attribute>
				</xsl:if>

				<!-- Technically atom:source isn’t a container element,
				     but we’re treating it as one, again for simplicity. -->
				<xsl:call-template name="atomContainerElement" />
			</channel>
		</dcterms:source>
	</xsl:template>

	<xsl:template match="atom:subtitle | atom:summary">
		<description>
			<xsl:call-template name="atomTextConstruct" />
		</description>
	</xsl:template>

	<xsl:template match="atom:title">
		<title>
			<xsl:call-template name="atomTextConstruct" />
		</title>
	</xsl:template>

	<xsl:template match="atom:updated">
		<dcterms:modified>
			<xsl:call-template name="atomDateConstruct" />
		</dcterms:modified>
	</xsl:template>

	<xsl:template match="atom:uri">
		<foaf:page rdf:resource="{.}">
			<xsl:call-template name="atomCommonAttributes" />
		</foaf:page>
	</xsl:template>
</xsl:stylesheet>
