Compare commits

..

No commits in common. "main" and "apt-instructions-for-readme" have entirely different histories.

10 changed files with 311 additions and 1511 deletions

View file

@ -1,18 +1,14 @@
# Rivista # XMPP PubSub To Atom
Previously, XMPP Journal Publisher ("XJP") and XMPP PubSub To Atom ("XPTA"). A little client that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP.
Rivista is a software that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP. ## About
Rivista 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)). 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.
Rivista includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform PubSub nodes into static XHTML journal sites. 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)).
Rivista was inspired from Tigase and was motivated by Movim. This software was inspired from Tigase and was motivated by Movim.
## Instances
* https://rivista.woodpeckersnest.eu/
## Preview ## Preview
@ -21,24 +17,15 @@ Rivista was inspired from Tigase and was motivated by Movim.
[<img alt="59d860ab-d7c8-477c-bb4b-86924485cbbb" src="screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png" width="200px"/>](screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png) [<img alt="59d860ab-d7c8-477c-bb4b-86924485cbbb" src="screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png" width="200px"/>](screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png)
[<img alt="selection" src="screenshot/selection.png" width="200px"/>](screenshot/selection.png) [<img alt="selection" src="screenshot/selection.png" width="200px"/>](screenshot/selection.png)
## Motivation
Rivista is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available
from HTTP to both, XML news readers and even HTML browsers.
This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate Rivista, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes).
The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by HTML browsers (client-side) from XSLT stylesheets.
Because Rivista reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of Rivista would save bandwidth and system overhead, which includes CPU, I/O and RAM usage.
## Requirements ## Requirements
* Python >= 3.5 * Python >= 3.5
* fastapi * fastapi
* feedgenerator
* lxml * lxml
* slixmpp * slixmpp
* tomllib (Python <= 3.10) * tomllib
* uvicorn * uvicorn
## Installation ## Installation
@ -51,11 +38,12 @@ Because Rivista reads XMPP PubSub nodes, it is possible to view a complete set o
### Download ### Download
Extract the source package to a directory that you have permission to run software. Extract the source package to a directory that you have permission to run
software.
```shell ```shell
$ git clone https://git.xmpp-it.net/sch/Rivista $ git clone https://git.xmpp-it.net/sch/PubSubToAtom
$ cd Rivista/ $ cd PubSubToAtom/
``` ```
### Configure ### Configure
@ -64,10 +52,9 @@ Add account credentials to file `configuration.toml`.
### Start ### Start
Execute Rivista with one of the following commands: Execute PubSubToAtom with one of the following commands:
```shell ```shell
$ python -m uvicorn main:app --reload
$ python -m uvicorn pubsub_to_atom:app --reload $ python -m uvicorn pubsub_to_atom:app --reload
$ python -m uvicorn pubsub_to_atom:app --reload --host 127.0.0.1 --port 8000 $ python -m uvicorn pubsub_to_atom:app --reload --host 127.0.0.1 --port 8000
$ uvicorn pubsub_to_atom:app --host 127.0.0.1 --port 8000 $ uvicorn pubsub_to_atom:app --host 127.0.0.1 --port 8000
@ -76,13 +63,13 @@ $ fastapi dev pubsub_to_atom.py
## Usage ## Usage
It is 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 save system overhead, which includes CPU, I/O and RAM usage. It is 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 PubSub ### Viewing PubSub
Suppose you have the following PubSub nodes and items. Suppose you have the following nodes and items.
|Jabber ID |Node |Item | |PubSub |Node |Item |
|--- |--- |--- | |--- |--- |--- |
|blog.jmp.chat |urn:xmpp:microblog:0|launch-2023 | |blog.jmp.chat |urn:xmpp:microblog:0|launch-2023 |
|edhelas%40movim.eu|urn:xmpp:microblog:0|working-on-launching-the-movim-network-qPBzwc | |edhelas%40movim.eu|urn:xmpp:microblog:0|working-on-launching-the-movim-network-qPBzwc |
@ -90,17 +77,6 @@ Suppose you have the following PubSub nodes and items.
|news.movim.eu |Phoronix | | |news.movim.eu |Phoronix | |
|pubsub.movim.eu |berlin-xmpp-meetup |7363a41d-1146-40b3-ac0f-8ee2559591a3 | |pubsub.movim.eu |berlin-xmpp-meetup |7363a41d-1146-40b3-ac0f-8ee2559591a3 |
|pubsub.movim.eu |berlin-xmpp-meetup |let-s-talk-about-the-xsf-and-possibly-other-things-6A8eV4| |pubsub.movim.eu |berlin-xmpp-meetup |let-s-talk-about-the-xsf-and-possibly-other-things-6A8eV4|
|pubsub.movim.eu |jesus-christ-son-of-god|the-passion-of-christ-redemption-and-salvation-for-all-who-believe-moSqXO|
|pubsub.woodpeckersnest.space|PlanetJabber | |
|pubsub.woodpeckersnest.space|xmpp-it | |
|pubsub%40sure.im |news | |
#### To view pubsub nodes
```
http://127.0.0.1:8000/opml?pubsub=news.movim.eu
http://127.0.0.1:8000/opml?pubsub=pubsub.woodpeckersnest.space
```
#### To view node items #### To view node items
@ -108,10 +84,6 @@ http://127.0.0.1:8000/opml?pubsub=pubsub.woodpeckersnest.space
http://127.0.0.1:8000/atom?pubsub=edhelas%40movim.eu&node=urn%3Axmpp%3Amicroblog%3A0 http://127.0.0.1:8000/atom?pubsub=edhelas%40movim.eu&node=urn%3Axmpp%3Amicroblog%3A0
http://127.0.0.1:8000/atom?pubsub=news.movim.eu&node=Phoronix http://127.0.0.1:8000/atom?pubsub=news.movim.eu&node=Phoronix
http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup
http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=jesus-christ-son-of-god
http://127.0.0.1:8000/atom?pubsub=pubsub.woodpeckersnest.space&node=PlanetJabber
http://127.0.0.1:8000/atom?pubsub=pubsub.woodpeckersnest.space&node=xmpp-it
http://127.0.0.1:8000/atom?pubsub=pubsub%40sure.im&node=news
``` ```
#### To view a node item #### To view a node item
@ -122,15 +94,8 @@ http://127.0.0.1:8000/atom?pubsub=edhelas%40movim.eu&node=urn%3Axmpp%3Amicroblog
http://127.0.0.1:8000/atom?pubsub=goffi%40goffi.org&node=urn%3Axmpp%3Amicroblog%3A0&item=libervia-v0-8-la-cecilia-BdQ4 http://127.0.0.1:8000/atom?pubsub=goffi%40goffi.org&node=urn%3Axmpp%3Amicroblog%3A0&item=libervia-v0-8-la-cecilia-BdQ4
http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup&item=7363a41d-1146-40b3-ac0f-8ee2559591a3 http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup&item=7363a41d-1146-40b3-ac0f-8ee2559591a3
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 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
http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=jesus-christ-son-of-god&item=the-passion-of-christ-redemption-and-salvation-for-all-who-believe-moSqXO
``` ```
## Supported XEPs
- [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html)
- [XEP-0277: Microblogging over XMPP](https://xmpp.org/extensions/xep-0277.html)
- [XEP-0472: Pubsub Social Feed](https://xmpp.org/extensions/xep-0472.html)
## Author ## Author
Schimon Jehudah Zackary Schimon Jehudah Zackary
@ -145,15 +110,7 @@ Python code is licensed under the license AGPL-3.0 only.
## Acknowledgement ## Acknowledgement
Thank you to Mr. Peter Saint-Andre ([stpeter](https://stpeter.im/journal/731.html)) for manifesting [Atom Over XMPP](https://www.ietf.org/archive/id/draft-saintandre-atompub-notify-07.html). Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat
Thank you to Mr. Wojtek and [Tigase](https://tigase.org/) for publicly exposing an implementation of PubSub as Syndication at [Sure.IM](https://sure.im/) as [feeds.tigase.im](http://feeds.tigase.im/atom?server=pubsub@sure.im&node=news).
Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) who consistently and earnestly showcases the potential of PubSub as a publication platform with project [Movim](https://movim.eu/).
A special thank you to the gentlemen "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI.
And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying Rivista into production.
## Similar Projects ## Similar Projects

View file

@ -1,15 +1,3 @@
# An account to connect XMPP Journal Publisher to the XMPP network.
[account] [account]
xmpp = "" # Jabber ID. xmpp = ""
pass = "" # Password. pass = ""
# A default node, when no arguments are set.
[default]
pubsub = "pubsub.woodpeckersnest.space" # Jabber ID.
nodeid = "PlanetJabber" # Node ID.
# Settings
[settings]
service = 1 # Enable server as a service.
include = "" # Limit service to a given domain.
operator = "" # A Jabber ID to contact with, in case of an error.

View file

@ -1,64 +1,18 @@
/*
TODO
pubsub: news.movim.eu
node: fake-news
item: fdef84f5-e3e1-41ea-9b68-af4bb9130f77
title: #14October2023EpochEclipse
date: Fri, 15 Jan 2021 20:24:46
*/
* { * {
color: #eee; color: #eee;
max-width: 100%; max-width: 100%;
} }
/*
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
*/
body { body {
background: #000; background: #000;
} }
code, pre {
overflow: auto;
max-height: 100%;
max-width: 100%; }
img, svg, video {
display: block;
max-height: 500px;
width: auto;
height: auto;
}
h1#title, h2#subtitle, #actions, #references { h1#title, h2#subtitle, #actions, #references {
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
line-height: 140%; line-height: 140%;
} }
h5.related > a {
margin-right: 5px;
text-decoration: none;
user-select: none;
}
h3.title > a {
display: block;
padding-top: 50px;
}
#actions, #references { #actions, #references {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
line-height: 150%; line-height: 150%;
@ -81,67 +35,41 @@ h3.title > a {
padding-left: 2%; padding-left: 2%;
} }
#menu > ol > li { #menu > ul > li {
padding: 5px; padding: 5px;
} }
#note {
line-height: 30px;
margin: auto;
max-width: 70%;
padding: 10px;
text-align: center;
}
#references { #references {
margin-top: 5em;
border-top: 1px solid #eee; border-top: 1px solid #eee;
} }
#articles {
min-height: 80vh;
display: flex;
}
#articles #journal { #articles > ul > li > div > p.content {
margin-left: 2%;
margin-right: 2%;
margin-top: 2%;
min-width: 350px;
padding-bottom: 0.67em;
width: 20%;
}
#journal {
margin: auto;
padding-top: 50px;
}
#articles > ul {
margin: auto;
margin-left: 2%;
margin-top: 2%;
}
#articles #journal ol,
#articles #journal ul {
/* height: 500px; */
line-height: 160%;
overflow: auto;
word-wrap: break-word;
}
#articles div.content {
font-size: 120%; font-size: 120%;
line-height: 200%; line-height: 30px;
margin: auto; margin: auto;
padding-left: 2%; padding-left: 2%;
padding-right: 10%; padding-right: 10%;
/* text-align: justify; */ /* text-align: justify; */
word-wrap: break-word;
} }
#articles div.entry { #articles > ul > li > div.entry {
padding-bottom: 0.67em; padding-bottom: 50px;
} }
#articles div.entry h1 { #articles > ul > li > div.entry h1 {
font-size: 115%; /* 2vw */ font-size: 2vw;
} }
#articles div.entry h2 { #articles > ul > li > div.entry h2 {
font-size: 1.5vw; font-size: 1.5vw;
} }
@ -157,7 +85,7 @@ h3.title > a {
top: 0; top: 0;
} }
#selection-page p { #selection-page h3 {
margin-left: 10%; margin-left: 10%;
margin-right: 10%; margin-right: 10%;
} }
@ -175,11 +103,11 @@ h3.title > a {
} }
#selection-page #selection { #selection-page #selection {
margin-bottom: 2%; margin-bottom: 5%;
} }
#selection-page #return { #selection-page #return {
/* font-style: italic; */ font-style: italic;
margin: auto; margin: auto;
} }
@ -187,117 +115,3 @@ h3.title > a {
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }
.content[type='text'] {
white-space: pre-wrap;
}
#articles div.entry span.tags {
display: inline-flex;
/* display: ruby; */
flex-wrap: wrap;
}
#articles div.entry span.tags > div {
margin: 5px;
}
.enclosures {
cursor: help;
direction: ltr;
margin: 5px auto 15px 1%;
padding: 15px;
padding: 1em;
/* background: #222;
border: 1px solid GrayText;
border-radius: 4px;
border-radius: .5em;
color: #525c66;
border-left: double;
max-width: 40%; */
}
.enclosure a {
margin: 3px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.enclosure > span[icon='atom']:after,
.enclosure > span[icon='html5']:after,
.enclosure > span[icon='rss']:after {
content: '📰';
margin:3px;
}
.enclosure > span.audio:after {
content: ' (Audio file) ';
}
.enclosure > span[icon='audio']:after{
content: '🎼️';
margin: 3px;
}
.enclosure > span:after {
content: ' (Document file) ';
}
.enclosure > span[icon]:after {
content: '📄️';
margin: 3px;
}
.enclosure > span.executable:after{
content: ' (Executable file) ';
}
.enclosure > span[icon='executable']:after {
content: '📦️';
margin: 3px;
}
.enclosure > span.image:after {
content: ' (Image file) ';
}
.enclosure > span[icon='image']:after {
content: '🖼️';
margin: 3px;
}
.enclosure > span.video:after {
content: ' (Video file) ';
}
.enclosure > span[icon='video']:after {
content: '📽️';
margin:3px;
}
#note, #small {
line-height: 30px;
margin: auto;
margin-top: 0.67em;
max-width: 80%;
padding: 10px;
text-align: center;
user-select: none;
}
#small {
font-size: 80%;
}
@media (max-width: 1550px) {
#articles {
display: unset;
}
#articles #journal {
margin-right: unset;
min-width: unset;
width: unset;
}
}

View file

@ -1 +0,0 @@
This directory caches lists of PubSub nodes.

View file

@ -1,15 +1,12 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime #import datetime
from fastapi import FastAPI, Request, Response from fastapi import FastAPI, Request, Response
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import json import feedgenerator
from os import mkdir
from os.path import exists
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
#import importlib.resources #import importlib.resources
@ -18,6 +15,8 @@ try:
except: except:
import tomli as tomllib import tomli as tomllib
app = FastAPI()
class XmppInstance(ClientXMPP): class XmppInstance(ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super().__init__(jid, password)
@ -27,11 +26,8 @@ class XmppInstance(ClientXMPP):
xmpp = None xmpp = None
app = FastAPI()
# Mount static graphic, script and stylesheet directories # Mount static graphic, script and stylesheet directories
app.mount("/css", StaticFiles(directory="css"), name="css") app.mount("/css", StaticFiles(directory="css"), name="css")
app.mount("/data", StaticFiles(directory="data"), name="data")
app.mount("/graphic", StaticFiles(directory="graphic"), name="graphic") app.mount("/graphic", StaticFiles(directory="graphic"), name="graphic")
app.mount("/script", StaticFiles(directory="script"), name="script") app.mount("/script", StaticFiles(directory="script"), name="script")
app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl") app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl")
@ -40,540 +36,108 @@ app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl")
async def favicon(): async def favicon():
return FileResponse('favicon.ico') return FileResponse('favicon.ico')
@app.route('/')
@app.get('/opml')
async def view_pubsub_nodes(request: Request):
global xmpp
if not xmpp:
credentials = get_configuration('account')
xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect()
pubsub = request.query_params.get('pubsub', '')
settings = get_configuration('settings')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub:
iq = await get_nodes(pubsub)
if iq:
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml')
else:
text = 'Please ensure that PubSub "{}" (Jabber ID) is valid and accessible.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
default = get_configuration('default')
if not result:
if default['pubsub']:
if not pubsub:
pubsub = default['pubsub']
iq = await get_nodes(pubsub)
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml')
elif not settings['service']:
pubsub = default['pubsub']
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml')
else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
response = Response(content=result, media_type="application/xml")
return response
@app.get('/atom') @app.get('/atom')
async def view_node_items(request: Request): async def view_pubsub(request: Request):
global xmpp global xmpp
if not xmpp: if not xmpp:
credentials = get_configuration('account') with open('configuration.toml', mode="rb") as configuration:
credentials = tomllib.load(configuration)['account']
xmpp = XmppInstance(credentials['xmpp'], credentials['pass']) xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect() # xmpp.connect()
pubsub = request.query_params.get('pubsub', '') pubsub = request.query_params.get('pubsub', '')
node = request.query_params.get('node', '') node = request.query_params.get('node', '')
item_id = request.query_params.get('item', '') item_id = request.query_params.get('item', '')
settings = get_configuration('settings')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub and node and item_id: if pubsub and node and item_id:
iq = await get_node_item(pubsub, node, item_id) iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id)
if iq:
link = form_an_item_link(pubsub, node, item_id)
xml_atom = generate_atom_comment(iq, link) if 'urn:xmpp:microblog:0:comments/' in node else generate_atom_post(iq, link)
iq = await get_node_items(pubsub, node)
if not '/' in node:
if iq:
generate_json(iq)
else:
operator = get_configuration('settings')['operator']
json_data = [{'title' : 'Error retrieving node items.',
'link' : ('javascript:alert("Rivista has experienced an error '
'while attempting to retrieve the list of items for '
'Node {} of PubSub {}.")')
.format(node, pubsub)},
{'title' : 'Contact the operator.',
'link' : ('xmpp:{}?message;subject=Rivista;body=Greetings! '
'I am contacting you to inform you that there is an error listing '
'node items for Node {} on PubSub {}.').format(operator, node, pubsub)}]
filename = 'data/{}.json'.format(node)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(json_data, f, ensure_ascii=False, indent=4)
else:
text = 'Please ensure that PubSub node "{}" and item "{}" are valid and accessible.'.format(node, item_id)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
# try:
# iq = await get_node_items(pubsub, node)
# generate_json(iq, node)
# except:
# operator = get_configuration('settings')['operator']
# json_data = [{'title' : 'Timeout retrieving node items from {}'.format(node),
# 'link' : 'xmpp:{}?message'.format(operator)}]
# filename = 'data/{}.json'.format(node)
# with open(filename, 'w', encoding='utf-8') as f:
# json.dump(json_data, f, ensure_ascii=False, indent=4)
elif pubsub and node:
iq = await get_node_items(pubsub, node)
if iq:
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom_comment(iq, link) if 'urn:xmpp:microblog:0:comments/' in node else generate_atom_post(iq, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
elif pubsub:
text = 'Node parameter is missing.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
elif node:
text = 'PubSub parameter is missing.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
# else:
# text = ('Mandatory parameter PubSub and '
# 'optional parameter Node are missing.')
# xml_atom = error_message(text)
# result = append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
if not result:
default = get_configuration('default')
if default['pubsub'] and default['nodeid']:
if not pubsub and not node:
pubsub = default['pubsub']
node = default['nodeid']
iq = await get_node_items(pubsub, node)
if iq:
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom_post(iq, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = error_message(text)
elif not settings['service']:
pubsub = default['pubsub']
node = default['nodeid']
iq = await get_node_items(pubsub, node)
if iq:
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom_post(iq, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
response = Response(content=result, media_type="application/xml")
return response
def get_configuration(section):
with open('configuration.toml', mode="rb") as configuration:
result = tomllib.load(configuration)[section]
return result
#@timeout(5)
async def get_node_item(pubsub, node, item_id):
try:
iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
async def get_node_items(pubsub, node):
try:
iq = await xmpp.plugin['xep_0060'].get_items(pubsub, node, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
async def get_nodes(pubsub):
try:
iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
def form_a_node_link(pubsub, node):
link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node)
return link
def form_an_item_link(pubsub, node, item_id):
link = 'xmpp:{pubsub}?;node={node};item={item}'.format( link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
pubsub=pubsub, node=node, item=item_id) pubsub=pubsub, node=node, item=item_id)
return link 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 error_message(text): def pubsub_to_atom(iq, link):
"""Error message in RFC 4287: The Atom Syndication Format.""" """Convert XEP-0060: Publish-Subscribe to RFC 4287: The Atom Syndication Format."""
title = 'Rivista' feed = feedgenerator.Atom1Feed(
subtitle = 'XMPP Journal Publisher' description = ('This is a syndication feed generated with PubSub to '
description = ('This is a syndication feed generated with Rivista, an XMPP ' 'Atom, which conveys XEP-0060: Publish-Subscribe nodes '
'Journal Publisher, which conveys XEP-0060: Publish-' 'to standard RFC 4287: The Atom Syndication Format.'),
'Subscribe nodes to standard RFC 4287: The Atom Syndication ' language = iq['pubsub']['items']['lang'],
'Format.') link = link,
language = 'en' subtitle = 'XMPP PubSub Syndication Feed',
feed = ET.Element("feed") title = iq['pubsub']['items']['node'])
feed.set('xmlns', 'http://www.w3.org/2005/Atom') # See also iq['pubsub']['items']['substanzas']
ET.SubElement(feed, 'title', {'type': 'text'}).text = title
ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(feed, 'author', {'name':'Rivista','email':'rivista@schimon.i2p'})
ET.SubElement(feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/Rivista',
'version': '0.1'}).text = 'Rivista XJP'
ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
entry = ET.SubElement(feed, 'entry')
ET.SubElement(entry, 'title').text = 'Error'
ET.SubElement(entry, 'id').text = 'rivista-error'
ET.SubElement(entry, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
ET.SubElement(entry, 'published').text = datetime.datetime.now(datetime.UTC).isoformat()
# ET.SubElement(entry, 'summary', {'type': summary_type_text}).text = summary_text
ET.SubElement(entry, 'content', {'type': 'text'}).text = text
return ET.tostring(feed, encoding='unicode')
# generate_rfc_4287
def generate_atom_post(iq, link):
"""Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
pubsub = iq['from'].bare
node = iq['pubsub']['items']['node']
title = node
link = link
# link = form_a_node_link(pubsub, node)
# subtitle = 'XMPP PubSub Syndication Feed'
subtitle = pubsub
description = ('This is a syndication feed generated with Rivista, an XMPP '
'Journal Publisher, which conveys XEP-0060: Publish-'
'Subscribe nodes to standard RFC 4287: The Atom Syndication '
'Format.')
language = iq['pubsub']['items']['lang']
items = iq['pubsub']['items']
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = title
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(e_feed, 'link', {'rel': 'self', 'href': link})
ET.SubElement(e_feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/Rivista',
'version': '0.1'}).text = 'Rivista XJP'
ET.SubElement(e_feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
for item in list(items)[::-1]:
item_id = item['id']
item_payload = item['payload']
namespace = '{http://www.w3.org/2005/Atom}'
title = item_payload.find(namespace + 'title')
links = item_payload.find(namespace + 'link')
if (not isinstance(title, ET.Element) and
not isinstance(links, ET.Element)): continue
e_entry = ET.SubElement(e_feed, 'entry')
title_text = None if title == None else title.text
ET.SubElement(e_entry, 'title').text = title_text
if isinstance(links, ET.Element):
for link in item_payload.findall(namespace + 'link'):
link_href = link.attrib['href'] if 'href' in link.attrib else ''
link_type = link.attrib['type'] if 'type' in link.attrib else ''
link_rel = link.attrib['rel'] if 'rel' in link.attrib else ''
ET.SubElement(e_entry, 'link', {'href': link_href, 'rel': link_rel, 'type': link_type})
else:
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = form_an_item_link(pubsub, node, item_id)
ET.SubElement(e_entry, 'link', {'href': link_xmpp, 'rel': 'alternate', 'type': 'x-scheme-handler/xmpp'})
contents = item_payload.find(namespace + 'content')
if isinstance(contents, ET.Element):
for content in item_payload.findall(namespace + 'content'):
if not content.text: continue
content_text = content.text
content_type = content.attrib['type'] if 'type' in content.attrib else 'html'
content_type_text = 'html' if 'html' in content_type else 'text'
ET.SubElement(e_entry, 'content', {'type': content_type_text}).text = content_text
else:
summary = item_payload.find(namespace + 'summary')
summary_text = summary.text if summary else None
if summary_text:
summary_type = summary.attrib['type'] if 'type' in summary.attrib else 'html'
summary_type_text = 'html' if 'html' in summary_type else 'text'
ET.SubElement(e_entry, 'content', {'type': summary_type_text}).text = summary_text
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
published = item_payload.find(namespace + 'published')
published_text = None if published == None else published.text
ET.SubElement(e_entry, 'published').text = published_text
updated = item_payload.find(namespace + 'updated')
updated_text = None if updated == None else updated.text
ET.SubElement(e_entry, 'updated').text = updated_text
authors = item_payload.find(namespace + 'author')
if isinstance(authors, ET.Element):
for author in item_payload.findall(namespace + 'author'):
e_author = ET.SubElement(e_entry, 'author')
author_email = author.find(namespace + 'email')
if author_email is not None:
author_email_text = author_email.text
if author_email_text: ET.SubElement(e_author, 'email').text = author_email.text
else:
author_email_text = None
author_uri = author.find(namespace + 'uri')
if author_uri is not None:
author_uri_text = author_uri.text
if author_uri_text: ET.SubElement(e_author, 'uri').text = author_uri.text
else:
author_uri_text = None
author_name = author.find(namespace + 'name')
if author_name is not None and author_name.text:
author_name_text = author_name.text
else:
author_name_text = author_uri_text or author_email_text
ET.SubElement(e_author, 'name').text = author_name_text
for uri in item_payload.iter(namespace + 'author'):
uri_text = uri.text
if uri_text:
ET.SubElement(e_entry, 'uri').text = uri_text
# if not e_author:
# ET.SubElement(e_author, 'name').text = uri_text
# ET.SubElement(e_author, 'uri').text = uri_text
categories = item_payload.find(namespace + 'category')
if isinstance(categories, ET.Element):
for category in item_payload.findall(namespace + 'category'):
if 'term' in category.attrib and category.attrib['term']:
category_term = category.attrib['term']
ET.SubElement(e_entry, 'category', {'term': category_term})
identifier = item_payload.find(namespace + 'id')
if identifier and identifier.attrib: print(identifier.attrib)
identifier_text = None if identifier == None else identifier.text
ET.SubElement(e_entry, 'id').text = identifier_text
# ET.SubElement(e_entry, 'summary', {'type': summary_type_text}).text = summary_text
return ET.tostring(e_feed, encoding='unicode')
# generate_rfc_4287
def generate_atom_comment(iq, link):
"""Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
pubsub = iq['from'].bare
node = iq['pubsub']['items']['node']
title = node
link = link
# link = form_a_node_link(pubsub, node)
# subtitle = 'XMPP PubSub Syndication Feed'
subtitle = pubsub
description = ('This is a syndication feed generated with Rivista, an XMPP '
'Journal Publisher, which conveys XEP-0060: Publish-'
'Subscribe nodes to standard RFC 4287: The Atom Syndication '
'Format.')
language = iq['pubsub']['items']['lang']
items = iq['pubsub']['items']
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = title
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(e_feed, 'link', {'rel': 'self', 'href': link})
ET.SubElement(e_feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/Rivista',
'version': '0.1'}).text = 'Rivista XJP'
ET.SubElement(e_feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
for item in list(items)[::-1]:
item_id = item['id']
item_payload = item['payload']
namespace = '{http://www.w3.org/2005/Atom}'
authors = item_payload.find(namespace + 'author')
links = item_payload.find(namespace + 'link')
if (not isinstance(authors, ET.Element) and
not isinstance(links, ET.Element)): continue
e_entry = ET.SubElement(e_feed, 'entry')
author_text = None
for author in item_payload.findall(namespace + 'author'):
author_email = author.find(namespace + 'email')
if author_email is not None:
author_text = author_email.text
if not author_text:
author_uri = author.find(namespace + 'uri')
if author_uri is not None:
author_text = author_uri.text
if not author_text:
author_name = author.find(namespace + 'name')
if author_name is not None and author_name.text:
author_text = author_name.text
if not author_text:
for uri in item_payload.iter(namespace + 'author'):
author_text = uri.text
if author_text:
ET.SubElement(e_entry, 'title').text = author_text
break
if isinstance(links, ET.Element):
for link in item_payload.findall(namespace + 'link'):
link_href = link.attrib['href'] if 'href' in link.attrib else ''
link_type = link.attrib['type'] if 'type' in link.attrib else ''
link_rel = link.attrib['rel'] if 'rel' in link.attrib else ''
ET.SubElement(e_entry, 'link', {'href': link_href, 'rel': link_rel, 'type': link_type})
else:
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = form_an_item_link(pubsub, node, item_id)
ET.SubElement(e_entry, 'link', {'href': link_xmpp, 'rel': 'alternate', 'type': 'x-scheme-handler/xmpp'})
contents = item_payload.find(namespace + 'content')
if isinstance(contents, ET.Element):
for content in item_payload.findall(namespace + 'content'):
if not content.text: continue
content_text = content.text
content_type = content.attrib['type'] if 'type' in content.attrib else 'html'
content_type_text = 'html' if 'html' in content_type else 'text'
ET.SubElement(e_entry, 'content', {'type': content_type_text}).text = content_text
else:
summary = item_payload.find(namespace + 'summary')
summary_text = summary.text if summary else None
if summary_text:
summary_type = summary.attrib['type'] if 'type' in summary.attrib else 'html'
summary_type_text = 'html' if 'html' in summary_type else 'text'
ET.SubElement(e_entry, 'content', {'type': summary_type_text}).text = summary_text
else:
title = item_payload.find(namespace + 'title')
title_text = None if title == None else title.text
if title_text:
ET.SubElement(e_entry, 'content').text = title_text
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
published = item_payload.find(namespace + 'published')
published_text = None if published == None else published.text
ET.SubElement(e_entry, 'published').text = published_text
updated = item_payload.find(namespace + 'updated')
updated_text = None if updated == None else updated.text
ET.SubElement(e_entry, 'updated').text = updated_text
authors = item_payload.find(namespace + 'author')
if isinstance(authors, ET.Element):
contact_text = None
for author in item_payload.findall(namespace + 'author'):
author_email = author.find(namespace + 'email')
if author_email is not None:
contact_text = author_email.text
if not contact_text:
author_uri = author.find(namespace + 'uri')
if author_uri is not None:
contact_text = author_uri.text
if not contact_text:
for uri in item_payload.iter(namespace + 'author'):
contact_text = uri.text
if contact_text:
if contact_text.startswith('xmpp:'):
contact_type = 'x-scheme-handler/xmpp'
elif contact_text.startswith('mailto:'):
contact_type = 'x-scheme-handler/mailto'
else:
contact_type = None
if contact_type:
ET.SubElement(e_entry, 'link', {'href': contact_text, 'rel': 'contact', 'type': contact_type})
else:
ET.SubElement(e_entry, 'link', {'href': contact_text, 'rel': 'contact'})
break
categories = item_payload.find(namespace + 'category')
if isinstance(categories, ET.Element):
for category in item_payload.findall(namespace + 'category'):
if 'term' in category.attrib and category.attrib['term']:
category_term = category.attrib['term']
ET.SubElement(e_entry, 'category', {'term': category_term})
identifier = item_payload.find(namespace + 'id')
if identifier and identifier.attrib: print(identifier.attrib)
identifier_text = None if identifier == None else identifier.text
ET.SubElement(e_entry, 'id').text = identifier_text
# ET.SubElement(e_entry, 'summary', {'type': summary_type_text}).text = summary_text
return ET.tostring(e_feed, encoding='unicode')
def generate_json(iq):
"""Create a JSON file from node items."""
json_data = []
pubsub = iq['from'].bare
node = iq['pubsub']['items']['node']
entries = iq['pubsub']['items'] entries = iq['pubsub']['items']
for entry in entries: for entry in entries:
item_id = entry['id'] item = entry['payload']
item_payload = entry['payload']
namespace = '{http://www.w3.org/2005/Atom}' namespace = '{http://www.w3.org/2005/Atom}'
title = item_payload.find(namespace + 'title') title = item.find(namespace + 'title')
title_text = '*** No Title ***' if title == None else title.text title = None if title == None else title.text
# updated = item.find(namespace + 'updated') feed_url = 'gemini://schimon.i2p/'
# updated = None if updated == None else updated.text updated = item.find(namespace + 'updated')
updated = None if updated == None else updated.text
# if updated: updated = datetime.datetime.fromisoformat(updated) # if updated: updated = datetime.datetime.fromisoformat(updated)
link_href = form_an_item_link(pubsub, node, item_id) content = item.find(namespace + 'content')
# link = item.find(namespace + 'link') content = 'No content' if content == None else content.text
# link_href = '' if link == None else link.attrib['href'] link = item.find(namespace + 'link')
json_data_entry = {'title' : title_text, link = '' if link == None else link.attrib['href']
'link' : link_href} author = item.find(namespace + 'author')
json_data.append(json_data_entry) if author and author.attrib: print(author.attrib)
#if len(json_data) > 6: break author = 'None' if author == None else author.text
directory = 'data/{}/'.format(pubsub) # create entry
if not exists(directory): feed.add_item(
mkdir(directory) description = content,
filename = 'data/{}/{}.json'.format(pubsub, node) # 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
with open(filename, 'w', encoding='utf-8') as f: """Patch function to append elements which are not provided by feedgenerator"""
json.dump(json_data, f, ensure_ascii=False, indent=4) 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""" """Patch function to append XSLT reference to XML"""
"""Why is not this a built-in function of ElementTree or LXML""" """Why is not this a built-in function of ElementTree or LXML"""
def append_stylesheet(xml_data, type): def append_stylesheet(xml_data):
# Register namespace in order to avoide ns0: # Register namespace in order to avoide ns0:
if type == 'atom': ET.register_namespace('', 'http://www.w3.org/2005/Atom') ET.register_namespace("", "http://www.w3.org/2005/Atom")
# Load XML from string # Load XML from string
tree = ET.fromstring(xml_data) tree = ET.fromstring(xml_data)
# The following direction removes the XML declaration # The following direction removes the XML declaration
xml_data_without_a_declaration = ET.tostring(tree, encoding='unicode') xml_data_no_declaration = ET.tostring(tree, encoding='unicode')
# Add XML declaration and stylesheet # Add XML declaration and stylesheet
xml_data_declaration = ( xml_data_declaration = ('<?xml version="1.0" encoding="utf-8"?>'
'<?xml version="1.0" encoding="utf-8"?>' '<?xml-stylesheet type="text/xsl" href="xsl/stylesheet.xsl"?>' +
'<?xml-stylesheet type="text/xsl" href="xsl/{}.xsl"?>'.format(type) + xml_data_no_declaration)
xml_data_without_a_declaration)
return xml_data_declaration return xml_data_declaration
def generate_opml(iq):
pubsub = iq['from'].bare
items = iq['disco_items']['items']
opml = ET.Element("opml")
opml.set("version", "1.0")
head = ET.SubElement(opml, "head")
ET.SubElement(head, "title").text = 'An OPML of ' + pubsub
ET.SubElement(head, "description").text = (
"PubSub Nodes of {}").format(pubsub)
ET.SubElement(head, "generator").text = 'Rivista'
ET.SubElement(head, "urlPublic").text = 'https://git.xmpp-it.net/sch/Rivista'
time_stamp = datetime.datetime.now(datetime.UTC).isoformat()
ET.SubElement(head, "dateCreated").text = time_stamp
ET.SubElement(head, "dateModified").text = time_stamp
body = ET.SubElement(opml, "body")
for item in items:
pubsub, node, title = item
uri = form_a_node_link(pubsub, node)
outline = ET.SubElement(body, "outline")
outline.set("text", title or node)
outline.set("xmlUrl", uri)
return ET.tostring(opml, encoding='unicode')

View file

@ -1,167 +1,14 @@
window.onload = async function(){ window.onload = function(){
let locationHref = new URL(location.href); // Convert ISO8601 To UTC/
let node = locationHref.searchParams.get('node') for (let element of document.querySelectorAll('#articles > ul > li > div > h4, #feed > #header > h2#subtitle.date')) {
let pubsub = locationHref.searchParams.get('pubsub')
// Set button follow
let follow = document.querySelector('#follow');
if (follow) {
//let feedUrl = location.href.replace(/^https?:/, 'feed:');
let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`;
follow.href = feedUrl;
follow.type = `application/atom+xml`;
follow.addEventListener ('click', function() {
window.open(feedUrl, "_self");
});
// Fix button subtome
document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl;
}
// Convert ISO8601 To UTC
for (let element of document.querySelectorAll(
'#articles > ul > li > div > h4.published,' +
'#articles > ul > li > div > h4.updated, ' +
'#feed > #header > h2#subtitle.date')) {
let timeStamp = new Date(element.textContent); let timeStamp = new Date(element.textContent);
element.textContent = timeStamp.toUTCString(); element.textContent = timeStamp.toUTCString();
} }
// Parse Markdown/
// Parse Markdown for (let element of document.querySelectorAll('#articles > ul > li > div > p')) {
for (let element of document.querySelectorAll('#articles div[type="text"]')) { let markDown = element.textContent
element.innerHTML = marked.parse(element.textContent); element.innerHTML = marked.parse(markDown);
} }
// NOTE Report this issue to Movim. See node "deltachat" of pubsub "news.movim.eu".
for (let element of document.querySelectorAll('#articles div[type="html"]')) {
if (!element.children.length) {
element.innerHTML = marked.parse(element.textContent);
}
}
/*
NOTE
The reason for the following code to parse HTML inside an software which
already parses HTML, is that some people who influence the so called Gecko
HTML Engine product (mostly people from the advertisement industry with whom
I shamefully used to work with) have intentions to eliminate standards and
technologies such as Syndication (Atom/RDF/RSS), XSLT and many other
technologies that actually empower people and their privacy.
Recently, some changes were made to the XSLT parser of Gecko which result in
noncompliance with the XSLT standard.
The XSLT feature that was removed is "disable-output-escaping" which upon the
value "yes" the XSLT engine should transform and treat a subject string into
HTML, yet the Gecko HTML Engine ignores this XSLT direction.
This change was probably made in order to:
* Frustrate new XSLT developers; or
* Influence new XSLT developers to think that XSLT has a limited set of
features and believing of some sort of inability to parse HTML from a
retrieved HTML string; and
* Consequently cause developers to abstain from using the XSLT technology.
Do not use HTML browsers or use Ladybird, Pale Moon or browsers that are
powered by KHTML (WebKit) instead of anti-privacy software such as Chromium
and Gecko.
*/
// Parse HTML
//if (navigator.userAgent.includes(') Gecko/')) {
// for (let element of document.querySelectorAll('#articles div[type="html"]')) {
// element.innerHTML = element.textContent;
// }
//}
for (let element of document.querySelectorAll('#articles div[type="html"]')) {
if (!element.children.length) {
element.innerHTML = element.textContent;
}
}
// Build a journal list
if (locationHref.pathname.startsWith('/atom') && pubsub && node && !node.includes('/')) {
itemsList = await openJson(pubsub, node)
if (itemsList && locationHref.searchParams.get('item')) {
let elementDiv = document.createElement('div');
elementDiv.id = 'journal';
let elementH3 = document.createElement('h3');
elementH3.textContent = 'Journal';
elementDiv.appendChild(elementH3);
let elementH4 = document.createElement('h4');
elementH4.textContent = node;
elementDiv.appendChild(elementH4);
let elementUl = document.createElement('ol');
elementDiv.appendChild(elementUl);
for (let item of itemsList.reverse()) {
let elementLi = document.createElement('li');
let elementA = document.createElement('a');
elementA.textContent = item.title;
elementA.href = item.link;
elementLi.appendChild(elementA);
elementUl.appendChild(elementLi);
console.log(elementLi.length)
if (elementUl.children.length > 9) {break};
}
let elementB = document.createElement('b');
elementB.textContent = 'Actions';
elementDiv.appendChild(elementB);
let elementUl2 = document.createElement('ul');
elementDiv.appendChild(elementUl2);
links = [
{'text' : 'Subscribe from an XMPP client.',
'href' : `xmpp:${pubsub}?pubsub;action=subscribe;node=${node}`,
'type' : 'x-scheme-handler/xmpp'},
{'text' : 'Subscribe with a News Reader.',
'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`,
'type' : 'application/atom+xml'},
{'text' : 'Browse the journal.',
'href' : `atom?pubsub=${pubsub}&node=${node}`,
'type' : 'application/atom+xml'},
{'text' : 'Browse the portal.',
'href' : `opml?pubsub=${pubsub}`,
'type' : 'text/x-opml'}
]
for (let link of links) {
let elementLi = document.createElement('li');
let elementA = document.createElement('a');
elementA.textContent = link.text;
elementA.href = link.href;
elementA.type = link.type;
elementLi.appendChild(elementA);
elementUl2.appendChild(elementLi);
}
elementDiv.appendChild(elementUl2);
// document.querySelector('#feed').appendChild(elementDiv); // This would result in a combination of Title, Article, and Journal
document.querySelector('#articles').appendChild(elementDiv);
}
}
// Convert URI xmpp: to URI http: links.
for (let xmppLink of document.querySelectorAll(
'#articles h3 > a[href^="xmpp:"][id^="rivista-"],' +
'#articles h5.related > a[class^="rivista-"],' +
'#journal > ol > li > a[href^="xmpp:"]')) {
xmppUri = new URL(xmppLink);
let parameters = xmppUri.search.split(';');
try {
try {
let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1];
let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1];
let pubsub = xmppUri.pathname;
xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}`
} catch {
let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1];
let pubsub = xmppUri.pathname;
xmppLink.href = `atom?pubsub=${pubsub}&node=${node}`
}
} catch (err) {
console.warn(err)
}
}
// Display a selection of suggested software. // Display a selection of suggested software.
const selection = { const selection = {
'akregator' : { 'akregator' : {
@ -205,17 +52,24 @@ window.onload = async function(){
let elementDiv = document.createElement('div'); let elementDiv = document.createElement('div');
elementDiv.id = 'selection-page'; elementDiv.id = 'selection-page';
let elementH1 = document.createElement('h1'); let elementH1 = document.createElement('h1');
elementH1.textContent = 'Select A News Reader'; elementH1.textContent = 'Get A News Reader';
elementDiv.appendChild(elementH1); elementDiv.appendChild(elementH1);
let elementH2 = document.createElement('h2'); let elementH2 = document.createElement('h2');
elementH2.textContent = 'Install A Feed Reader For Desktop And Mobile'; elementH2.textContent = 'Install Feed Reader Apps For Desktop And Mobile';
elementDiv.appendChild(elementH2); elementDiv.appendChild(elementH2);
let elementH3 = document.createElement('h3');
elementH3.textContent = '' +
'This is a selection of desktop applications, mobile apps and online ' +
'services for you to choose from. This selection includes news ' +
'readers, podcast managers, torrent clients, chat bots, HTML browsers ' +
'and plugins which support syndication feeds.';
elementDiv.appendChild(elementH3);
const brands = Object.keys(selection); const brands = Object.keys(selection);
let elementDivSel = document.createElement('div'); let elementDivSel = document.createElement('div');
elementDivSel.id = 'selection'; elementDivSel.id = 'selection';
for (let i = 0; i < brands.length; i++) { for (let i = 0; i < brands.length; i++) {
let brand = brands[i]; let brand = brands[i];
let elementSpan = document.createElement('span'); elementSpan = document.createElement('span');
let elementA = document.createElement('a'); let elementA = document.createElement('a');
elementA.href = selection[brand].url; elementA.href = selection[brand].url;
elementA.textContent = selection[brand].name; elementA.textContent = selection[brand].name;
@ -226,40 +80,13 @@ window.onload = async function(){
elementDivSel.appendChild(elementSpan); elementDivSel.appendChild(elementSpan);
elementDiv.appendChild(elementDivSel); elementDiv.appendChild(elementDivSel);
} }
let elementP1 = document.createElement('p'); let elementDivReturn = document.createElement('div');
elementP1.textContent = '' + elementDivReturn.id = 'return';
'This is a selection of desktop, mobile and HTML (sometimes referred to ' + elementDivReturn.textContent = 'Return To PubSub...';
'as "online") News Readers for you to choose from.'; elementDivReturn.addEventListener ('click', function() {
elementDiv.appendChild(elementP1);
let elementP2 = document.createElement('p');
elementP2.textContent = '' +
'This selection includes: Podcast Managers, Torrent ' +
'Clients, Chat Bots, HTML Browsers and Plugins which support ' +
'syndication feeds.';
elementDiv.appendChild(elementP2);
let elementSpan = document.createElement('span');
elementSpan.id = 'return';
elementSpan.textContent = 'Return';
elementSpan.addEventListener ('click', function() {
document.querySelector('#selection-page').remove(); document.querySelector('#selection-page').remove();
}); });
elementDiv.appendChild(elementSpan); elementDiv.appendChild(elementDivReturn);
document.body.appendChild(elementDiv); document.body.appendChild(elementDiv);
}); });
} }
async function openJson(pubsubJid, nodeId) {
return fetch(`/data/${pubsubJid}/${nodeId}.json`)
.then(response => {
if (!response.ok) {
throw new Error('HTTP Error: ' + response.status);
}
return response.json();
})
.then(json => {
return json;
})
.catch(err => {
console.warn(err);
})
}

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- <!--
Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head> element <meta/> inside of html element </head>
@ -15,6 +15,9 @@ xmlns:georss='http://www.georss.org/georss'
xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#' xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#'
xmlns:atom10='http://www.w3.org/2005/Atom' xmlns:atom10='http://www.w3.org/2005/Atom'
xmlns:atom='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'> <xsl:template match='/atom:feed'>
<!-- index right-to-left language codes --> <!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang --> <!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
@ -35,7 +38,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:call-template> </xsl:call-template>
<xsl:call-template name='metadata'> <xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' /> <xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='Rivista' /> <xsl:with-param name='content' select='StreamBurner' />
</xsl:call-template> </xsl:call-template>
<xsl:call-template name='metadata'> <xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' /> <xsl:with-param name='name' select='"mimetype"' />
@ -43,17 +46,14 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:call-template> </xsl:call-template>
<title> <title>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:title and not(atom:title="") and count(atom:entry) &gt; 1'> <xsl:when test='atom:title and not(atom:title="")'>
<xsl:value-of select='atom:title'/> <xsl:value-of select='atom:title'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:entry'> <xsl:otherwise>StreamBurner</xsl:otherwise>
<xsl:value-of select='atom:entry/atom:title'/>
</xsl:when>
<xsl:otherwise>Rivista</xsl:otherwise>
</xsl:choose> </xsl:choose>
</title> </title>
<!-- TODO media='print' --> <!-- TODO media='print' -->
<link rel='stylesheet' type='text/css' media='screen' href='/css/stylesheet.css'/> <link href='/css/stylesheet.css' rel='stylesheet' type='text/css' media='screen'/>
<link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/> <link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/>
<!-- whether language code is of direction right-to-left --> <!-- whether language code is of direction right-to-left -->
<xsl:if test='$rtl'> <xsl:if test='$rtl'>
@ -64,18 +64,18 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</head> </head>
<body> <body>
<div id='actions'> <div id='actions'>
<a id='follow' title='Subscribe the latest updates and news.' <a title='Subscribe the latest updates and news.'
onclick='window.open(location.href.replace(/^https?:/, "feed:"), "_self")'> onclick='window.open("feed:" + location.href, "_self")'>
<!-- xsl:attribute name="href"> <!-- xsl:attribute name="href">
feed:<xsl:value-of select="atom:link[@rel='self']/@href" /> feed:<xsl:value-of select="atom:link[@rel='self']/@href" />
</xsl:attribute --> </xsl:attribute -->
Follow Follow
</a> </a>
<a id='subtome' title='Subscribe via SubToMe.'> <a title='Subscribe via SubToMe.'>
<xsl:attribute name='href'> <xsl:attribute name="href">
javascript:location.href='https://www.subtome.com/#/subscribe?feeds='+location.href; https://www.subtome.com/#/subscribe?feeds=<xsl:value-of select="atom:link[@rel='self']/@href" />
</xsl:attribute> </xsl:attribute>
<xsl:attribute name='onclick'> <xsl:attribute name="onclick">
( (
function(btn){ function(btn){
var z=document.createElement('script'); var z=document.createElement('script');
@ -87,6 +87,15 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
SubToMe SubToMe
</a> </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/' <a href='https://xmpp.org/about/technology-overview/'
title='Of the benefits of XMPP.'> title='Of the benefits of XMPP.'>
XMPP XMPP
@ -95,11 +104,6 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
title='Syndictaion and PubSub.'> title='Syndictaion and PubSub.'>
Groupchat Groupchat
</a> </a>
<a href='https://aboutfeeds.com/'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Help
</a>
</div> </div>
<div id='feed'> <div id='feed'>
<div id='header'> <div id='header'>
@ -113,7 +117,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:value-of select='atom:entry/atom:title'/> <xsl:value-of select='atom:entry/atom:title'/>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
No title StreamBurner
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</h1> </h1>
@ -131,9 +135,6 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:attribute name='class'><xsl:text>date</xsl:text></xsl:attribute> <xsl:attribute name='class'><xsl:text>date</xsl:text></xsl:attribute>
<xsl:value-of select='atom:entry/atom:published'/> <xsl:value-of select='atom:entry/atom:published'/>
</xsl:when> </xsl:when>
<xsl:otherwise>
Rivista XMPP Journal Publisher
</xsl:otherwise>
</xsl:choose> </xsl:choose>
</h2> </h2>
</div> </div>
@ -141,12 +142,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<div id='menu'> <div id='menu'>
<h3>Latest Posts</h3> <h3>Latest Posts</h3>
<!-- xsl:for-each select='atom:entry[position() &lt;21]' --> <!-- xsl:for-each select='atom:entry[position() &lt;21]' -->
<ol> <ul>
<xsl:for-each select='atom:entry[not(position() &gt; 20)]'> <xsl:for-each select='atom:entry[not(position() &gt; 20)]'>
<li> <li>
<xsl:element name='a'> <xsl:element name='a'>
<xsl:attribute name='href'> <xsl:attribute name='href'>
<xsl:text>#rivista-</xsl:text> <xsl:text>#stremburner-</xsl:text>
<xsl:value-of select='position()'/> <xsl:value-of select='position()'/>
</xsl:attribute> </xsl:attribute>
<xsl:choose> <xsl:choose>
@ -160,7 +161,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:element> </xsl:element>
</li> </li>
</xsl:for-each> </xsl:for-each>
</ol> </ul>
</div> </div>
</xsl:if> </xsl:if>
<div id='articles'> <div id='articles'>
@ -176,16 +177,19 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:element name='a'> <xsl:element name='a'>
<xsl:attribute name='href'> <xsl:attribute name='href'>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:link[@rel="self"]'> <xsl:when test='atom:link[contains(@rel,"alternate")]'>
<xsl:value-of select='atom:link[@rel="self"]/@href'/> <xsl:value-of select='atom:link[contains(@rel,"alternate")]/@href'/>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
<xsl:value-of select='atom:link/@href'/> <xsl:value-of select='atom:link/@href'/>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</xsl:attribute> </xsl:attribute>
<xsl:attribute name='title'>
<xsl:value-of select='atom:title'/>
</xsl:attribute>
<xsl:attribute name='id'> <xsl:attribute name='id'>
<xsl:text>rivista-</xsl:text> <xsl:text>stremburner-</xsl:text>
<xsl:value-of select='position()'/> <xsl:value-of select='position()'/>
</xsl:attribute> </xsl:attribute>
<xsl:choose> <xsl:choose>
@ -223,30 +227,10 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</span> </span>
</xsl:when> </xsl:when>
</xsl:choose> </xsl:choose>
<!-- entry date --> <!-- div class='posted' -->
<xsl:element name='h4'>
<xsl:choose>
<xsl:when test='atom:updated'>
<xsl:attribute name='class'>
<xsl:text>updated</xsl:text>
</xsl:attribute>
<xsl:value-of select='atom:updated'/>
</xsl:when>
<xsl:when test='atom:published'>
<xsl:attribute name='class'>
<xsl:text>published</xsl:text>
</xsl:attribute>
<xsl:value-of select='atom:published'/>
</xsl:when>
<xsl:otherwise>
<h4 class='warning atom1 published'></h4>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
<!-- entry author --> <!-- entry author -->
<xsl:if test='atom:author'> <!-- xsl:if test='atom:author'>
<h4 class='author'> <span class='author'>
<xsl:text>By </xsl:text>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:author/atom:email'> <xsl:when test='atom:author/atom:email'>
<xsl:element name='a'> <xsl:element name='a'>
@ -272,81 +256,33 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:value-of select='atom:author/atom:name'/> <xsl:value-of select='atom:author/atom:name'/>
</xsl:element> </xsl:element>
</xsl:when> </xsl:when>
<xsl:when test='atom:author/atom:name'> <xsl:otherwise>
<xsl:value-of select='atom:author/atom:name'/> <xsl:value-of select='atom:name'/>
</xsl:when> </xsl:otherwise>
<xsl:when test='atom:uri'>
<xsl:value-of select='atom:uri'/>
</xsl:when>
</xsl:choose> </xsl:choose>
</span>
</xsl:if -->
<!-- entry date -->
<xsl:choose>
<xsl:when test='atom:updated'>
<h4 class='updated'>
<xsl:value-of select='atom:updated'/>
</h4> </h4>
</xsl:if> </xsl:when>
<h5 class='related'> <xsl:when test='atom:published'>
<xsl:if test='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]'> <h4 class='published'>
<xsl:element name='a'> <xsl:value-of select='atom:published'/>
<xsl:attribute name='href'> </h4>
<xsl:value-of select='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]/@href'/> </xsl:when>
</xsl:attribute> <xsl:otherwise>
<xsl:attribute name='class'> <h4 class='warning atom1 published'></h4>
<xsl:text>rivista-jabber</xsl:text> </xsl:otherwise>
</xsl:attribute> </xsl:choose>
💡️ Source <!-- /div -->
</xsl:element>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]/@href'/>
</xsl:attribute>
(XMPP)
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="contact"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="contact"]/@href'/>
</xsl:attribute>
<xsl:attribute name='class'>
<xsl:text>contact-uri</xsl:text>
</xsl:attribute>
🪪️ Contact
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="replies"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="replies"]/@href'/>
</xsl:attribute>
<xsl:attribute name='class'>
<xsl:text>rivista-replies</xsl:text>
</xsl:attribute>
💬 Discussion
</xsl:element>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="replies"]/@href'/>
</xsl:attribute>
(XMPP)
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="alternate" and @type="text/html"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="alternate" and @type="text/html"]/@href'/>
</xsl:attribute>
📜 HTML
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="related" and @type="text/html"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="related" and @type="text/html"]/@href'/>
</xsl:attribute>
📜 HTML (Related)
</xsl:element>
</xsl:if>
</h5>
<!-- entry content --> <!-- entry content -->
<!-- entry summary of GitLab Atom Syndication Feeds -->
<xsl:if test='atom:content or atom:summary'> <xsl:if test='atom:content or atom:summary'>
<div class='content'> <p class='content'>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:summary[contains(@type,"text")]'> <xsl:when test='atom:summary[contains(@type,"text")]'>
<xsl:attribute name='type'> <xsl:attribute name='type'>
@ -354,12 +290,6 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select='atom:summary'/> <xsl:value-of select='atom:summary'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:summary[contains(@type,"html")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:summary/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:summary' disable-output-escaping='yes'/>
</xsl:when>
<xsl:when test='atom:summary[contains(@type,"base64")]'> <xsl:when test='atom:summary[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media --> <!-- TODO add xsl:template to handle inline media -->
</xsl:when> </xsl:when>
@ -369,12 +299,6 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select='atom:content'/> <xsl:value-of select='atom:content'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:content[contains(@type,"html")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:content/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:content' disable-output-escaping='yes'/>
</xsl:when>
<xsl:when test='atom:content[contains(@type,"base64")]'> <xsl:when test='atom:content[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media --> <!-- TODO add xsl:template to handle inline media -->
</xsl:when> </xsl:when>
@ -389,25 +313,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:choose> </xsl:choose>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</div> </p>
</xsl:if>
<!-- entry tags -->
<xsl:if test='atom:category/@term'>
<h4>Tags</h4>
<span class='tags'>
<xsl:for-each select='atom:category'>
<xsl:element name='div'>
<xsl:value-of select='@term'/>
</xsl:element>
</xsl:for-each>
</span>
</xsl:if> </xsl:if>
<!-- entry enclosure --> <!-- entry enclosure -->
<xsl:if test='atom:link[@rel="enclosure"]'> <xsl:if test='atom:link[contains(@rel,"enclosure")]'>
<h4>Media</h4>
<span class='enclosures' title='Right-click and Save link as…'>
<xsl:for-each select='atom:link[@rel="enclosure"]'>
<div class='enclosure' title='Right-click and Save link as…'> <div class='enclosure' title='Right-click and Save link as…'>
<xsl:for-each select='atom:link[contains(@rel,"enclosure")]'>
<xsl:element name='span'> <xsl:element name='span'>
<xsl:attribute name='icon'> <xsl:attribute name='icon'>
<xsl:value-of select='substring-before(@type,"/")'/> <xsl:value-of select='substring-before(@type,"/")'/>
@ -432,41 +343,55 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:with-param name='length' select='@length' /> <xsl:with-param name='length' select='@length' />
</xsl:call-template> </xsl:call-template>
</xsl:if> </xsl:if>
<br/> <xsl:element name='br'/>
</div>
</xsl:for-each> </xsl:for-each>
</span> <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 &gt; 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> </xsl:if>
</div> </div>
<!-- entry id --> <!-- entry id -->
<xsl:if test='not(atom:id)'> <xsl:if test='not(atom:id)'>
<div class='warning atom1 id'>No entry ID</div> <div class='warning atom1 id'></div>
</xsl:if> </xsl:if>
</li> </li>
</xsl:for-each> </xsl:for-each>
</ul> </ul>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
<ul> <div class='notice no-entry'></div>
<li>
<div class='entry'>
<h3 class='title'>
<a href='javascript:alert("Please check that the mother PubSub node is populated with content.")'>
No content
</a>
</h3>
<h4>This entry is empty</h4>
<div class='content'>Please check that the mother PubSub node is populated with content.</div>
</div>
</li>
</ul>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</div> </div>
</div> </div>
<div id='references'> <div id='references'>
<a href='https://joinjabber.org/' <a href='https://joinjabber.org/'
title='An Inclusive Space On The Jabber Network.'> title='An inclusive space on the Jabber network.'>
JoinJabber JoinJabber
</a> </a>
<a href='https://libervia.org/' <a href='https://libervia.org/'
@ -474,27 +399,15 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
Libervia Libervia
</a> </a>
<a href='https://join.movim.eu/' <a href='https://join.movim.eu/'
title='The Social Platform Shaped For Your Community.'> title='The social platform shaped for your community.'>
Movim Movim
</a> </a>
<a href='https://git.xmpp-it.net/sch/Rivista'
title='A Journal Publisher And Browser For XMPP.'>
Rivista
</a>
<a href='https://git.xmpp-it.net/sch/Blasta'
title='A Social Bookmark Manager For XMPP.'>
Blasta
</a>
<a href='https://github.com/SeveFP/Reeder'
title='An XMPP-Based Feed Reader.'>
Reeder
</a>
<a href='https://modernxmpp.org/' <a href='https://modernxmpp.org/'
title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use XMPP.'> title='A project to improve the quality of user-to-user messaging applications that use XMPP.'>
Modern Modern
</a> </a>
<a href='https://xmpp.org/' <a href='https://xmpp.org/'
title='The Universal Messaging Standard.'> title='The universal messaging standard.'>
XMPP XMPP
</a> </a>
<a href='https://xmpp.org/extensions/xep-0060.html' <a href='https://xmpp.org/extensions/xep-0060.html'
@ -504,25 +417,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</div> </div>
<!-- note --> <!-- note -->
<p id='note'> <p id='note'>
This an Atom document which can also be viewed with
a Syndication Feed Reader (also referred to as News Reader
or RSS Reader) which provides automated news updates and
notifications on desktop and mobile.
<span id="selection-link">Click here</span> for a
selection of software and pick the ones that would fit
to you best!
</p>
<p id='small'>
<i> <i>
This ASF (Atom Syndication Format) This is an XMPP news feed which is conveyed as HTML,
document is conveyed as an XHTML document. and it can even be viewed by a syndication feed reader
This document was produced by an which provides automated notifications on desktop and
XSLT <a href="xsl/atom.xsl">stylesheet</a>. mobile. <span id="selection-link">Click here</span> for
XSLT is a powerful technology which transforms XML a selection of software that would fit you best!
documents into HTML, JSON, PDF, Text XHTML, and
(modified) XML documents;
<a href="https://www.w3.org/Style/XSL/">Learn more</a>
about The Extensible Stylesheet Language Family (XSL).
</i> </i>
</p> </p>
</body> </body>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2024 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 encoding = 'UTF-8'
indent = 'yes'
media-type = 'text/x-opml'
method = 'html'
version = '4.01' />
<!-- Outline Processor Markup Language 1.0 -->
<xsl:include href='opml_as_xhtml.xsl'/>
<!-- set page metadata -->
<xsl:include href='metadata.xsl'/>
</xsl:stylesheet>

View file

@ -1,234 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2024 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/namespace'>
<xsl:template match='/opml'>
<!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
<xsl:variable name='rtl'
select='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='subtitle' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='Rivista' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' />
<xsl:with-param name='content' select='"text/x-opml"' />
</xsl:call-template>
<title>
<xsl:choose>
<xsl:when test='//head/title and not(//head/title="")'>
<xsl:value-of select='//head/title'/>
</xsl:when>
<xsl:otherwise>Rivista OPML</xsl:otherwise>
</xsl:choose>
</title>
<!-- TODO media='print' -->
<link rel='stylesheet' type='text/css' media='screen' href='/css/stylesheet.css'/>
<link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/>
<!-- 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/postprocess.js'/>
</head>
<body>
<div id='actions'>
<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>
<a href='https://aboutfeeds.com/'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Help
</a>
</div>
<div id='feed'>
<div id='header'>
<!-- feed title -->
<h1 id='title'>
<xsl:choose>
<xsl:when test='//head/title and not(//head/title="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/title'/>
</xsl:when>
<xsl:otherwise>
Rivista OPML Collection
</xsl:otherwise>
</xsl:choose>
</h1>
<!-- feed subtitle -->
<h2 id='subtitle'>
<xsl:choose>
<xsl:when test='//head/description and not(//head/description="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/description'/>
</xsl:when>
<xsl:otherwise>
Outline Processor Markup Language
</xsl:otherwise>
</xsl:choose>
</h2>
</div>
<xsl:if test='count(//outline) &gt; 1'>
<div id='menu'>
<h3>Subscriptions</h3>
<!-- xsl:for-each select='outline[position() &lt;21]' -->
<ol>
<xsl:for-each select='//outline[not(position() &gt; 20)]'>
<li>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>#rivista-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</li>
</xsl:for-each>
</ol>
</div>
</xsl:if>
<div id='articles'>
<!-- opml outline -->
<xsl:choose>
<xsl:when test='//outline'>
<ul>
<xsl:for-each select='//outline[not(position() &gt; 20)]'>
<li>
<div class='entry'>
<!-- outline title -->
<h3 class='title'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='@xmlUrl'/>
</xsl:attribute>
<xsl:attribute name='id'>
<xsl:text>rivista-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</h3>
<!-- entry content -->
<h4>
<xsl:value-of select='@text'/>
</h4>
<p class='content'>
<xsl:value-of select='@xmlUrl'/>
</p>
</div>
</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://git.xmpp-it.net/sch/Rivista'
title='A Journal Publisher And Browser For XMPP.'>
Rivista
</a>
<a href='https://git.xmpp-it.net/sch/Blasta'
title='A Social Bookmark Manager For XMPP.'>
Blasta
</a>
<a href='https://github.com/SeveFP/Reeder'
title='An XMPP-Based Feed Reader.'>
Reeder
</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'>
This is an OPML document which includes a collection of
subscriptions, and it can be imported to
a Syndication Feed Reader (also referred to as News Reader
or RSS Reader) which provides automated news updates and
notifications on desktop and mobile.
<span id="selection-link">Click here</span> for a
selection of software and pick the ones that would fit
to you best!
</p>
<p id='small'>
<i>
This OPML (Outline Processor Markup Language)
document is conveyed as an XHTML document.
This document was produced by an
XSLT <a href="xsl/opml.xsl">stylesheet</a>.
XSLT is a powerful technology which transforms XML
documents into HTML, JSON, PDF, Text XHTML, and
(modified) XML documents;
<a href="https://www.w3.org/Style/XSL/">Learn more</a>
about The Extensible Stylesheet Language Family (XSL).
</i>
</p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View file

@ -1,24 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- <!--
Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head> element <meta/> inside of html element </head>
--> -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding = 'UTF-8'
indent = 'yes' <xsl:output
media-type = 'application/atom+xml'
method = 'html' method = 'html'
version = '4.01' /> indent = 'yes'
<!-- Atom Syndication Format 1.0 --> omit-xml-decleration='no' />
<!-- Atom 1.0 Syndication Format -->
<xsl:include href='atom_as_xhtml.xsl'/> <xsl:include href='atom_as_xhtml.xsl'/>
<!-- extract filename from given url string --> <!-- extract filename from given url string -->
<xsl:include href='extract-filename.xsl'/> <xsl:include href='extract-filename.xsl'/>
<!-- set page metadata --> <!-- set page metadata -->
<xsl:include href='metadata.xsl'/> <xsl:include href='metadata.xsl'/>
<!-- transform filesize from given length string --> <!-- transform filesize from given length string -->
<xsl:include href='transform-filesize.xsl'/> <xsl:include href='transform-filesize.xsl'/>
</xsl:stylesheet> </xsl:stylesheet>