An initial prototype.
This commit is contained in:
parent
874d10ac5e
commit
af9d5ce688
11 changed files with 841 additions and 2 deletions
73
README.md
73
README.md
|
@ -1,3 +1,74 @@
|
||||||
# PubSubToAtom
|
# XMPP PubSub To Atom
|
||||||
|
|
||||||
A little client that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP.
|
A little client that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP.
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
XMPP PubSub To Atom ("XPTA") is a simple Python script that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP.
|
||||||
|
|
||||||
|
XPTA generates Atom syndication feeds ([RFC 4287](https://www.rfc-editor.org/rfc/rfc4287)) from XMPP PubSub nodes ([XEP-0060](http://xmpp.org/extensions/xep-0060.html)).
|
||||||
|
|
||||||
|
This software was inspired from Tigase and was motivated by Movim.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Python >= 3.5
|
||||||
|
* fastapi
|
||||||
|
* feedgenerator
|
||||||
|
* lxml
|
||||||
|
* slixmpp
|
||||||
|
* tomllib
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Extract the source package to a directory that you have permission to run
|
||||||
|
software.
|
||||||
|
|
||||||
|
Execute with one of the followings:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ uvicorn pubsub_to_atom:app --host 127.0.0.1 --port 8000
|
||||||
|
$ python -m uvicorn pubsub_to_atom:app --reload
|
||||||
|
$ fastapi dev pubsub_to_atom.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
It ois possible to view a complete node and even a single item, which means, that it is possible to save bandwidth and it further means that a considered and carefully earnest use of this software would saves system overhead, which includes CPU, I/O and RAM usage.
|
||||||
|
|
||||||
|
### Viewing node items
|
||||||
|
|
||||||
|
|PubSub |Node |
|
||||||
|
|--- |--- |
|
||||||
|
|xmpp:pubsub.movim.eu|berlin-xmpp-meetup|
|
||||||
|
|
||||||
|
URL: http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup
|
||||||
|
|
||||||
|
|
||||||
|
### Viewing a node item
|
||||||
|
|
||||||
|
|PubSub |Node |Item |
|
||||||
|
|--- |--- |--- |
|
||||||
|
|xmpp:pubsub.movim.eu|berlin-xmpp-meetup|let-s-talk-about-the-xsf-and-possibly-other-things-6A8eV4|
|
||||||
|
|
||||||
|
URL: http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup&item=let-s-talk-about-the-xsf-and-possibly-other-things-6A8eV4
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Schimon Jehudah Zackary
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
CSS and XSLT stylesheets are licensed under the license MIT.
|
||||||
|
|
||||||
|
Python code is licensed under the license AGPL-3.0 only.
|
||||||
|
|
||||||
|
## Acknowledgement
|
||||||
|
|
||||||
|
Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat
|
||||||
|
|
||||||
|
## Similar Projects
|
||||||
|
|
||||||
|
* [AtomEntry](https://github.com/tigase/sureim/blob/master/site/src/main/java/tigase/sure/web/site/client/pubsub/AtomEntry.java) - Convert XMPP Pubsub Nodes to Atom Syndication Format.
|
||||||
|
|
||||||
|
* [AtomToPubsub](https://github.com/edhelas/atomtopubsub) - A little client that parses Atom + RSS feeds and send them on XMPP Pubsub Nodes.
|
||||||
|
|
3
configuration.toml
Normal file
3
configuration.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[account]
|
||||||
|
xmpp = ""
|
||||||
|
pass = ""
|
67
css/stylesheet.css
Normal file
67
css/stylesheet.css
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
* {
|
||||||
|
color: #f5f5f5;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1#title, h2#subtitle, #actions, #references {
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#actions, #references {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding: 10px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#actions > *, #references * {
|
||||||
|
letter-spacing: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc > ul > li {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#note {
|
||||||
|
line-height: 30px;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 70%;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#references {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#articles > ul > li > div > p.content {
|
||||||
|
font-size: 120%;
|
||||||
|
line-height: 30px;
|
||||||
|
margin: auto;
|
||||||
|
padding-left: 2%;
|
||||||
|
padding-right: 10%;
|
||||||
|
/* text-align: justify; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#articles > ul > li > div.entry {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#articles > ul > li > div.entry h1 {
|
||||||
|
font-size: 2vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#articles > ul > li > div.entry h2 {
|
||||||
|
font-size: 1.5vw;
|
||||||
|
}
|
138
pubsub_to_atom.py
Normal file
138
pubsub_to_atom.py
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#import datetime
|
||||||
|
from fastapi import FastAPI, Request, Response
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
import feedgenerator
|
||||||
|
from slixmpp import ClientXMPP
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
#import importlib.resources
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except:
|
||||||
|
import tomli as tomllib
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
class XmppInstance(ClientXMPP):
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
super().__init__(jid, password)
|
||||||
|
self.register_plugin('xep_0060')
|
||||||
|
self.connect()
|
||||||
|
# self.process(forever=False)
|
||||||
|
|
||||||
|
xmpp = None
|
||||||
|
|
||||||
|
# Mount static scripts and stylesheet directories
|
||||||
|
app.mount("/css", StaticFiles(directory="css"), name="css")
|
||||||
|
app.mount("/script", StaticFiles(directory="script"), name="script")
|
||||||
|
app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/atom')
|
||||||
|
async def view_pubsub(request: Request):
|
||||||
|
global xmpp
|
||||||
|
if not xmpp:
|
||||||
|
with open('configuration.toml', mode="rb") as configuration:
|
||||||
|
credentials = tomllib.load(configuration)['account']
|
||||||
|
xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
|
||||||
|
# xmpp.connect()
|
||||||
|
|
||||||
|
pubsub = request.query_params.get('pubsub', '')
|
||||||
|
node = request.query_params.get('node', '')
|
||||||
|
item_id = request.query_params.get('item', '')
|
||||||
|
if pubsub and node and item_id:
|
||||||
|
iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id)
|
||||||
|
link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
|
||||||
|
pubsub=pubsub, node=node, item=item_id)
|
||||||
|
xml_atom = pubsub_to_atom(iq, link)
|
||||||
|
result = append_stylesheet(xml_atom)
|
||||||
|
elif pubsub and node:
|
||||||
|
iq = await xmpp.plugin['xep_0060'].get_items(pubsub, node)
|
||||||
|
link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node)
|
||||||
|
xml_atom = pubsub_to_atom(iq, link)
|
||||||
|
result = append_stylesheet(xml_atom)
|
||||||
|
elif pubsub:
|
||||||
|
iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub)
|
||||||
|
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
|
||||||
|
result = pubsub_to_opml(iq)
|
||||||
|
elif node:
|
||||||
|
result = 'PubSub parameter is missing.'
|
||||||
|
else:
|
||||||
|
result = ('Mandatory parameter PubSub and '
|
||||||
|
'optional parameter Node are missing.')
|
||||||
|
return Response(content=result, media_type="application/xml")
|
||||||
|
|
||||||
|
def pubsub_to_atom(iq,link):
|
||||||
|
"""Convert XEP-0060: Publish-Subscribe to RFC 4287: The Atom Syndication Format."""
|
||||||
|
feed = feedgenerator.Atom1Feed(
|
||||||
|
description = ('This is a syndication feed generated with PubSub to '
|
||||||
|
'Atom, which conveys XEP-0060: Publish-Subscribe nodes '
|
||||||
|
'to standard RFC 4287: The Atom Syndication Format.'),
|
||||||
|
language = iq['pubsub']['items']['lang'],
|
||||||
|
link = link,
|
||||||
|
subtitle = 'XMPP PubSub Syndication Feed',
|
||||||
|
title = iq['pubsub']['items']['node'])
|
||||||
|
# See also iq['pubsub']['items']['substanzas']
|
||||||
|
entries = iq['pubsub']['items']
|
||||||
|
for entry in entries:
|
||||||
|
item = entry['payload']
|
||||||
|
namespace = '{http://www.w3.org/2005/Atom}'
|
||||||
|
title = item.find(namespace + 'title')
|
||||||
|
title = None if title == None else title.text
|
||||||
|
feed_url = 'sch'
|
||||||
|
updated = item.find(namespace + 'updated')
|
||||||
|
updated = None if updated == None else updated.text
|
||||||
|
# if updated: updated = datetime.datetime.fromisoformat(updated)
|
||||||
|
content = item.find(namespace + 'content')
|
||||||
|
content = 'No content' if content == None else content.text
|
||||||
|
link = item.find(namespace + 'link')
|
||||||
|
link = '' if link == None else link.attrib['href']
|
||||||
|
author = item.find(namespace + 'author')
|
||||||
|
if author and author.attrib: print(author.attrib)
|
||||||
|
author = 'None' if author == None else author.text
|
||||||
|
# create entry
|
||||||
|
feed.add_item(
|
||||||
|
description = content,
|
||||||
|
# enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None,
|
||||||
|
link = link,
|
||||||
|
# pubdate = updated,
|
||||||
|
title = title,
|
||||||
|
unique_id = link)
|
||||||
|
xml_atom = feed.writeString('utf-8')
|
||||||
|
xml_atom_extended = append_element(
|
||||||
|
xml_atom,
|
||||||
|
'generator',
|
||||||
|
'XPTA: XMPP PubSub To Atom')
|
||||||
|
return xml_atom_extended
|
||||||
|
|
||||||
|
"""Patch function to append elements which are not provided by feedgenerator"""
|
||||||
|
def append_element(xml_data, element, text):
|
||||||
|
root = ET.fromstring(xml_data)
|
||||||
|
|
||||||
|
# Create the generator element
|
||||||
|
generator_element = ET.Element(element)
|
||||||
|
generator_element.text = text
|
||||||
|
|
||||||
|
# Append the generator element to the root
|
||||||
|
root.append(generator_element)
|
||||||
|
|
||||||
|
# Return the modified XML as a string
|
||||||
|
return ET.tostring(root, encoding='unicode')
|
||||||
|
|
||||||
|
"""Patch function to append XSLT reference to XML"""
|
||||||
|
"""Why is not this a built-in function of ElementTree or LXML"""
|
||||||
|
def append_stylesheet(xml_data):
|
||||||
|
# Register namespace in order to avoide ns0:
|
||||||
|
ET.register_namespace("", "http://www.w3.org/2005/Atom")
|
||||||
|
# Load XML from string
|
||||||
|
tree = ET.fromstring(xml_data)
|
||||||
|
# The following direction removes the XML declaration
|
||||||
|
xml_data_no_declaration = ET.tostring(tree, encoding='unicode')
|
||||||
|
# Add XML declaration and stylesheet
|
||||||
|
xml_data_declaration = ('<?xml version="1.0" encoding="utf-8"?>'
|
||||||
|
'<?xml-stylesheet type="text/xsl" href="xsl/stylesheet.xsl"?>' +
|
||||||
|
xml_data_no_declaration)
|
||||||
|
return xml_data_declaration
|
6
script/marked.min.js
vendored
Normal file
6
script/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
script/postprocess.js
Normal file
12
script/postprocess.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
window.onload = function(){
|
||||||
|
// Convert ISO8601 To UTC
|
||||||
|
for (element of document.querySelectorAll('#articles > ul > li > div > h4')) {
|
||||||
|
timeStamp = new Date(element.textContent);
|
||||||
|
element.textContent = timeStamp.toUTCString();
|
||||||
|
}
|
||||||
|
// Parse Markdown
|
||||||
|
for (element of document.querySelectorAll('#articles > ul > li > div > p')) {
|
||||||
|
markDown = element.textContent
|
||||||
|
element.innerHTML = marked.parse(markDown);
|
||||||
|
}
|
||||||
|
}
|
422
xsl/atom_as_xhtml.xsl
Normal file
422
xsl/atom_as_xhtml.xsl
Normal file
|
@ -0,0 +1,422 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
|
||||||
|
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
|
||||||
|
include https://schimon.i2p/ in attribute name='generator' of
|
||||||
|
element <meta/> inside of html element </head>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<xsl:stylesheet version='1.0'
|
||||||
|
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
|
||||||
|
xmlns:xml='http://www.w3.org/XML/1998/xmlbase/'
|
||||||
|
xmlns:media='http://search.yahoo.com/mrss/'
|
||||||
|
xmlns:georss='http://www.georss.org/georss'
|
||||||
|
xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#'
|
||||||
|
xmlns:atom10='http://www.w3.org/2005/Atom'
|
||||||
|
xmlns:atom='http://www.w3.org/2005/Atom'>
|
||||||
|
<!-- Atom 1.0 Syndication Format -->
|
||||||
|
<xsl:output
|
||||||
|
media-type='application/atom+xml' />
|
||||||
|
<xsl:template match='/atom:feed'>
|
||||||
|
<!-- index right-to-left language codes -->
|
||||||
|
<!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
|
||||||
|
<xsl:variable name='rtl'
|
||||||
|
select='@xml:lang[
|
||||||
|
contains(self::node(),"ar") or
|
||||||
|
contains(self::node(),"fa") or
|
||||||
|
contains(self::node(),"he") or
|
||||||
|
contains(self::node(),"ji") or
|
||||||
|
contains(self::node(),"ku") or
|
||||||
|
contains(self::node(),"ur") or
|
||||||
|
contains(self::node(),"yi")]'/>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<xsl:call-template name='metadata'>
|
||||||
|
<xsl:with-param name='name' select='"description"' />
|
||||||
|
<xsl:with-param name='content' select='atom:subtitle' />
|
||||||
|
</xsl:call-template>
|
||||||
|
<xsl:call-template name='metadata'>
|
||||||
|
<xsl:with-param name='name' select='"generator"' />
|
||||||
|
<xsl:with-param name='content' select='StreamBurner' />
|
||||||
|
</xsl:call-template>
|
||||||
|
<xsl:call-template name='metadata'>
|
||||||
|
<xsl:with-param name='name' select='"mimetype"' />
|
||||||
|
<xsl:with-param name='content' select='"application/atom+xml"' />
|
||||||
|
</xsl:call-template>
|
||||||
|
<title>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:title and not(atom:title="")'>
|
||||||
|
<xsl:value-of select='atom:title'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>StreamBurner</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</title>
|
||||||
|
<!-- TODO media='print' -->
|
||||||
|
<link href='/css/stylesheet.css' rel='stylesheet' type='text/css' media='screen'/>
|
||||||
|
<!-- whether language code is of direction right-to-left -->
|
||||||
|
<xsl:if test='$rtl'>
|
||||||
|
<link id='semitic' href='/css/stylesheet-rtl.css' rel='stylesheet' type='text/css' />
|
||||||
|
</xsl:if>
|
||||||
|
<script type='text/javascript' src='/script/marked.min.js'/>
|
||||||
|
<script type='text/javascript' src='/script/postprocess.js'/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='actions'>
|
||||||
|
<a title='Subscribe the latest updates and news.'
|
||||||
|
onclick='window.open("feed:" + location.href, "_self")'>
|
||||||
|
<!-- xsl:attribute name="href">
|
||||||
|
feed:<xsl:value-of select="atom:link[@rel='self']/@href" />
|
||||||
|
</xsl:attribute -->
|
||||||
|
Follow
|
||||||
|
</a>
|
||||||
|
<a title='Subscribe via SubToMe.'>
|
||||||
|
<xsl:attribute name="href">
|
||||||
|
https://www.subtome.com/#/subscribe?feeds=<xsl:value-of select="atom:link[@rel='self']/@href" />
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name="onclick">
|
||||||
|
(
|
||||||
|
function(btn){
|
||||||
|
var z=document.createElement('script');
|
||||||
|
document.subtomeBtn=btn;
|
||||||
|
z.src='https://www.subtome.com/load.js';document.body.appendChild(z);
|
||||||
|
}
|
||||||
|
)(this);
|
||||||
|
return false;
|
||||||
|
</xsl:attribute>
|
||||||
|
SubToMe
|
||||||
|
</a>
|
||||||
|
<a href='https://git.xmpp-it.net/sch/PubSubToAtom'
|
||||||
|
title='About PubSub To Atom.'>
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
<a href='https://aboutfeeds.com/'
|
||||||
|
title='Of the benefits of syndication feed.'
|
||||||
|
id='aboutfeeds'>
|
||||||
|
Feeds
|
||||||
|
</a>
|
||||||
|
<a href='https://xmpp.org/about/technology-overview/'
|
||||||
|
title='Of the benefits of XMPP.'>
|
||||||
|
XMPP
|
||||||
|
</a>
|
||||||
|
<a href='https://join.jabber.network/#syndication@conference.movim.eu?join'
|
||||||
|
title='Syndictaion and PubSub.'>
|
||||||
|
Groupchat
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id='feed'>
|
||||||
|
<div id='header'>
|
||||||
|
<!-- feed title -->
|
||||||
|
<h1 id='title'>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:title and not(atom:title="")'>
|
||||||
|
<xsl:attribute name='title'>
|
||||||
|
<xsl:value-of select='atom:title'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select='atom:title'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<div class='empty'></div>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</h1>
|
||||||
|
<!-- feed subtitle -->
|
||||||
|
<h2 id='subtitle'>
|
||||||
|
<xsl:attribute name='title'>
|
||||||
|
<xsl:value-of select='atom:subtitle'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select='atom:subtitle'/>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:entry'>
|
||||||
|
<div id='toc'>
|
||||||
|
<!-- xsl:for-each select='atom:entry[position() <21]' -->
|
||||||
|
<ul>
|
||||||
|
<xsl:for-each select='atom:entry[not(position() >20)]'>
|
||||||
|
<li>
|
||||||
|
<xsl:element name='a'>
|
||||||
|
<xsl:attribute name='href'>
|
||||||
|
<xsl:text>#stremburner-</xsl:text>
|
||||||
|
<xsl:value-of select='position()'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='string-length(atom:title) > 0'>
|
||||||
|
<xsl:value-of select='atom:title'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
*** No Title ***
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:element>
|
||||||
|
</li>
|
||||||
|
</xsl:for-each>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<div id='articles'>
|
||||||
|
<!-- feed entry -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:entry'>
|
||||||
|
<ul>
|
||||||
|
<xsl:for-each select='atom:entry[not(position() >20)]'>
|
||||||
|
<li>
|
||||||
|
<div class='entry'>
|
||||||
|
<!-- entry title -->
|
||||||
|
<h3 class='title'>
|
||||||
|
<xsl:element name='a'>
|
||||||
|
<xsl:attribute name='href'>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:link[contains(@rel,"alternate")]'>
|
||||||
|
<xsl:value-of select='atom:link[contains(@rel,"alternate")]/@href'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select='atom:link/@href'/>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='title'>
|
||||||
|
<xsl:value-of select='atom:title'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='id'>
|
||||||
|
<xsl:text>stremburner-</xsl:text>
|
||||||
|
<xsl:value-of select='position()'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='string-length(atom:title) > 0'>
|
||||||
|
<xsl:value-of select='atom:title'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
*** No Title ***
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:element>
|
||||||
|
</h3>
|
||||||
|
<!-- geographic location -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='geo:lat and geo:long'>
|
||||||
|
<xsl:variable name='lat' select='geo:lat'/>
|
||||||
|
<xsl:variable name='lng' select='geo:long'/>
|
||||||
|
<span class='geolocation'>
|
||||||
|
<a href='geo:{$lat},{$lng}'>📍</a>
|
||||||
|
</span>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='geo:Point'>
|
||||||
|
<xsl:variable name='lat' select='geo:Point/geo:lat'/>
|
||||||
|
<xsl:variable name='lng' select='geo:Point/geo:long'/>
|
||||||
|
<span class='geolocation'>
|
||||||
|
<a href='geo:{$lat},{$lng}'>📍</a>
|
||||||
|
</span>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='georss:point'>
|
||||||
|
<xsl:variable name='lat' select='substring-before(georss:point, " ")'/>
|
||||||
|
<xsl:variable name='lng' select='substring-after(georss:point, " ")'/>
|
||||||
|
<xsl:variable name='name' select='georss:featurename'/>
|
||||||
|
<span class='geolocation'>
|
||||||
|
<a href='geo:{$lat},{$lng}' title='{$name}'>📍</a>
|
||||||
|
</span>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<!-- div class='posted' -->
|
||||||
|
<!-- entry author -->
|
||||||
|
<!-- xsl:if test='atom:author'>
|
||||||
|
<span class='author'>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:author/atom:email'>
|
||||||
|
<xsl:element name='a'>
|
||||||
|
<xsl:attribute name='href'>
|
||||||
|
<xsl:text>mailto:</xsl:text>
|
||||||
|
<xsl:value-of select='atom:author/atom:email'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='title'>
|
||||||
|
<xsl:text>Send an Email to </xsl:text>
|
||||||
|
<xsl:value-of select='atom:author/atom:email'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select='atom:author/atom:name'/>
|
||||||
|
</xsl:element>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='atom:author/atom:uri'>
|
||||||
|
<xsl:element name='a'>
|
||||||
|
<xsl:attribute name='href'>
|
||||||
|
<xsl:value-of select='atom:author/atom:uri'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='title'>
|
||||||
|
<xsl:value-of select='atom:author/atom:summary'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select='atom:author/atom:name'/>
|
||||||
|
</xsl:element>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select='atom:name'/>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</span>
|
||||||
|
</xsl:if -->
|
||||||
|
<!-- entry date -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:updated'>
|
||||||
|
<h4 class='updated'>
|
||||||
|
<xsl:value-of select='atom:updated'/>
|
||||||
|
</h4>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='atom:published'>
|
||||||
|
<h4 class='published'>
|
||||||
|
<xsl:value-of select='atom:published'/>
|
||||||
|
</h4>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<h4 class='warning atom1 published'></h4>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
<!-- /div -->
|
||||||
|
<!-- entry content -->
|
||||||
|
<!-- entry summary of GitLab Atom Syndication Feeds -->
|
||||||
|
<xsl:if test='atom:content or atom:summary'>
|
||||||
|
<p class='content'>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:summary[contains(@type,"text")]'>
|
||||||
|
<xsl:attribute name='type'>
|
||||||
|
<xsl:value-of select='atom:summary/@type'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select='atom:summary'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='atom:summary[contains(@type,"base64")]'>
|
||||||
|
<!-- TODO add xsl:template to handle inline media -->
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='atom:content[contains(@type,"text")]'>
|
||||||
|
<xsl:attribute name='type'>
|
||||||
|
<xsl:value-of select='atom:content/@type'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select='atom:content'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='atom:content[contains(@type,"base64")]'>
|
||||||
|
<!-- TODO add xsl:template to handle inline media -->
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='atom:summary and not(atom:summary="")'>
|
||||||
|
<xsl:value-of select='atom:summary' disable-output-escaping='yes'/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select='atom:content' disable-output-escaping='yes'/>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</p>
|
||||||
|
</xsl:if>
|
||||||
|
<!-- entry enclosure -->
|
||||||
|
<xsl:if test='atom:link[contains(@rel,"enclosure")]'>
|
||||||
|
<div class='enclosure' title='Right-click and Save link as…'>
|
||||||
|
<xsl:for-each select='atom:link[contains(@rel,"enclosure")]'>
|
||||||
|
<xsl:element name='span'>
|
||||||
|
<xsl:attribute name='icon'>
|
||||||
|
<xsl:value-of select='substring-before(@type,"/")'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:element>
|
||||||
|
<xsl:element name='a'>
|
||||||
|
<xsl:attribute name='href'>
|
||||||
|
<xsl:value-of select='@href'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='download'/>
|
||||||
|
<xsl:call-template name='extract-filename'>
|
||||||
|
<xsl:with-param name='url' select='@href' />
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:element>
|
||||||
|
<xsl:element name='span'>
|
||||||
|
<xsl:attribute name='class'>
|
||||||
|
<xsl:value-of select='substring-before(@type,"/")'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:element>
|
||||||
|
<xsl:if test='@length > 0'>
|
||||||
|
<xsl:call-template name='transform-filesize'>
|
||||||
|
<xsl:with-param name='length' select='@length' />
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:element name='br'/>
|
||||||
|
</xsl:for-each>
|
||||||
|
<xsl:for-each select='media:content'>
|
||||||
|
<xsl:element name='span'>
|
||||||
|
<xsl:attribute name='icon'>
|
||||||
|
<xsl:value-of select='@medium'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:element>
|
||||||
|
<xsl:element name='a'>
|
||||||
|
<xsl:attribute name='href'>
|
||||||
|
<xsl:value-of select='@url'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='download'/>
|
||||||
|
<xsl:call-template name='extract-filename'>
|
||||||
|
<xsl:with-param name='url' select='@url' />
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:element>
|
||||||
|
<xsl:element name='span'>
|
||||||
|
<xsl:attribute name='class'>
|
||||||
|
<xsl:value-of select='@medium'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:element>
|
||||||
|
<xsl:if test='@fileSize > 0'>
|
||||||
|
<xsl:call-template name='transform-filesize'>
|
||||||
|
<xsl:with-param name='length' select='@fileSize' />
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:element name='br'/>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</xsl:if>
|
||||||
|
</div>
|
||||||
|
<!-- entry id -->
|
||||||
|
<xsl:if test='not(atom:id)'>
|
||||||
|
<div class='warning atom1 id'></div>
|
||||||
|
</xsl:if>
|
||||||
|
</li>
|
||||||
|
</xsl:for-each>
|
||||||
|
</ul>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<div class='notice no-entry'></div>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id='references'>
|
||||||
|
<a href='https://joinjabber.org/'
|
||||||
|
title='An inclusive space on the Jabber network.'>
|
||||||
|
JoinJabber
|
||||||
|
</a>
|
||||||
|
<a href='https://libervia.org/'
|
||||||
|
title='The Universal Communication Ecosystem.'>
|
||||||
|
Libervia
|
||||||
|
</a>
|
||||||
|
<a href='https://join.movim.eu/'
|
||||||
|
title='The social platform shaped for your community.'>
|
||||||
|
Movim
|
||||||
|
</a>
|
||||||
|
<a href='https://modernxmpp.org/'
|
||||||
|
title='A project to improve the quality of user-to-user messaging applications that use XMPP.'>
|
||||||
|
Modern
|
||||||
|
</a>
|
||||||
|
<a href='https://xmpp.org/'
|
||||||
|
title='The universal messaging standard.'>
|
||||||
|
XMPP
|
||||||
|
</a>
|
||||||
|
<a href='https://xmpp.org/extensions/xep-0060.html'
|
||||||
|
title='XEP-0060: Publish-Subscribe.'>
|
||||||
|
PubSub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- note -->
|
||||||
|
<p id='note'>
|
||||||
|
<i>
|
||||||
|
This is an XMPP news feed which is conveyed as HTML,
|
||||||
|
and it can even be viewed by a syndication feed reader
|
||||||
|
which provides automated notifications on desktop and
|
||||||
|
mobile. <a href="">Click here</a> for a selection of
|
||||||
|
software that would fit you best!
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
25
xsl/extract-filename.xsl
Normal file
25
xsl/extract-filename.xsl
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
|
||||||
|
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
|
||||||
|
include https://schimon.i2p/ in attribute name='generator' of
|
||||||
|
element <meta/> inside of html element </head>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||||
|
<!-- extract filename from given url string -->
|
||||||
|
<xsl:template name='extract-filename'>
|
||||||
|
<xsl:param name='url'/>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test='contains($url,"/")'>
|
||||||
|
<xsl:call-template name='extract-filename'>
|
||||||
|
<xsl:with-param name='url' select='substring-after($url,"/")'/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select='$url'/>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
26
xsl/metadata.xsl
Normal file
26
xsl/metadata.xsl
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
|
||||||
|
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
|
||||||
|
include https://schimon.i2p/ in attribute name='generator' of
|
||||||
|
element <meta/> inside of html element </head>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||||
|
<!-- set page metadata -->
|
||||||
|
<xsl:template name='metadata'>
|
||||||
|
<xsl:param name='name'/>
|
||||||
|
<xsl:param name='content'/>
|
||||||
|
<xsl:if test='$content and not($content="")'>
|
||||||
|
<xsl:element name='meta'>
|
||||||
|
<xsl:attribute name='name'>
|
||||||
|
<xsl:value-of select='$name'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:attribute name='content'>
|
||||||
|
<xsl:value-of select='$content'/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:element>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
29
xsl/stylesheet.xsl
Normal file
29
xsl/stylesheet.xsl
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
|
||||||
|
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
|
||||||
|
include https://schimon.i2p/ in attribute name='generator' of
|
||||||
|
element <meta/> inside of html element </head>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||||
|
|
||||||
|
<xsl:output
|
||||||
|
method = 'html'
|
||||||
|
indent = 'yes'
|
||||||
|
omit-xml-decleration='no' />
|
||||||
|
|
||||||
|
<!-- Atom 1.0 Syndication Format -->
|
||||||
|
<xsl:include href='atom_as_xhtml.xsl'/>
|
||||||
|
|
||||||
|
<!-- extract filename from given url string -->
|
||||||
|
<xsl:include href='extract-filename.xsl'/>
|
||||||
|
|
||||||
|
<!-- set page metadata -->
|
||||||
|
<xsl:include href='metadata.xsl'/>
|
||||||
|
|
||||||
|
<!-- transform filesize from given length string -->
|
||||||
|
<xsl:include href='transform-filesize.xsl'/>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
40
xsl/transform-filesize.xsl
Normal file
40
xsl/transform-filesize.xsl
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
|
||||||
|
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
|
||||||
|
include https://schimon.i2p/ in attribute name='generator' of
|
||||||
|
element <meta/> inside of html element </head>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||||
|
<!-- transform filesize from given length string -->
|
||||||
|
<xsl:template name='transform-filesize'>
|
||||||
|
<xsl:param name='length'/>
|
||||||
|
<!-- TODO consider xsl:decimal-format and xsl:number -->
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- TODO consider removal of Byte -->
|
||||||
|
<xsl:when test='$length < 2'>
|
||||||
|
<xsl:value-of select='$length'/>
|
||||||
|
Byte
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='floor($length div 1024) < 1'>
|
||||||
|
<xsl:value-of select='$length'/>
|
||||||
|
Bytes
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='floor($length div (1024 * 1024)) < 1'>
|
||||||
|
<xsl:value-of select='floor($length div 1024)'/>.<xsl:value-of select='substring($length mod 1024,0,2)'/>
|
||||||
|
KiB
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='floor($length div (1024 * 1024 * 1024)) < 1'>
|
||||||
|
<xsl:value-of select='floor($length div (1024 * 1024))'/>.<xsl:value-of select='substring($length mod (1024 * 1024),0,2)'/>
|
||||||
|
MiB
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<!-- P2P links -->
|
||||||
|
<xsl:value-of select='floor($length div (1024 * 1024 * 1024))'/>.<xsl:value-of select='substring($length mod (1024 * 1024 * 1024),0,2)'/>
|
||||||
|
GiB
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
Loading…
Reference in a new issue