Add OPML support;

Set a new default node (Thank you roughnecks);
Improve CSS, JS, XSLT;
Neglect external libraries to produce syndications.
This commit is contained in:
Schimon Jehudah, Adv. 2024-07-12 15:39:17 +03:00
parent e07ff6e838
commit d1f1edbaca
8 changed files with 519 additions and 185 deletions

View file

@ -5,8 +5,8 @@ pass = "" # Password.
# A default node, when no arguments are set.
[default]
pubsub = "blog.jmp.chat" # Jabber ID.
nodeid = "urn:xmpp:microblog:0" # Node ID.
pubsub = "pubsub.woodpeckersnest.space" # Jabber ID.
nodeid = "PlanetJabber" # Node ID.
# Settings
[settings]

View file

@ -7,6 +7,17 @@ body {
background: #000;
}
code, pre {
overflow: auto;
display: inline-block;
max-height: 500px;
max-width: 100%; }
img, video {
width: auto;
height: auto;
}
h1#title, h2#subtitle, #actions, #references {
text-align: center;
text-transform: uppercase;
@ -64,6 +75,7 @@ h1#title, h2#subtitle, #actions, #references {
width: 20%;
}
#articles #journal ol,
#articles #journal ul {
/* height: 500px; */
line-height: 160%;
@ -74,24 +86,25 @@ h1#title, h2#subtitle, #actions, #references {
font-weight: bold;
}
#articles > ul > li > div > p.content {
#articles div.content {
font-size: 120%;
line-height: 30px;
line-height: 200%;
margin: auto;
padding-left: 2%;
padding-right: 10%;
/* text-align: justify; */
word-wrap: break-word;
}
#articles > ul > li > div.entry {
#articles div.entry {
padding-bottom: 0.67em;
}
#articles > ul > li > div.entry h1 {
#articles div.entry h1 {
font-size: 2vw;
}
#articles > ul > li > div.entry h2 {
#articles div.entry h2 {
font-size: 1.5vw;
}
@ -138,7 +151,7 @@ h1#title, h2#subtitle, #actions, #references {
text-decoration: underline;
}
@media (max-width: 950px) {
@media (max-width: 1525px) {
#articles {
display: unset;
}

View file

@ -2,10 +2,10 @@
# -*- coding: utf-8 -*-
import datetime
from dateutil import parser
from fastapi import FastAPI, Request, Response
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import feedgenerator
import json
from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout
@ -57,12 +57,11 @@ async def view_pubsub(request: Request):
if pubsub and node and item_id:
iq = await get_node_item(pubsub, node, item_id)
if iq:
link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
pubsub=pubsub, node=node, item=item_id)
xml_atom = generate_rfc_4287(iq, link)
link = form_an_item_link(pubsub, node, item_id)
xml_atom = generate_atom(iq, link)
iq = await get_node_items(pubsub, node)
if iq:
generate_json(iq, node)
generate_json(iq)
else:
operator = get_configuration('settings')['operator']
json_data = [{'title' : 'Error retrieving items list.',
@ -79,7 +78,8 @@ async def view_pubsub(request: Request):
else:
text = 'Please check that PubSub node and item are valid and accessible.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
# try:
# iq = await get_node_items(pubsub, node)
@ -94,32 +94,37 @@ async def view_pubsub(request: Request):
elif pubsub and node:
iq = await get_node_items(pubsub, node)
if iq:
link = form_a_link(pubsub, node)
xml_atom = generate_rfc_4287(iq, link)
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom(iq, link)
else:
text = 'Please check that PubSub node is valid and accessible.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
elif pubsub:
iq = await get_nodes(pubsub)
if iq:
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
result = pubsub_to_opml(iq)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml.xsl')
else:
text = 'Please check that PubSub Jabber ID is valid and accessible.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
elif node:
text = 'PubSub parameter is missing.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
# else:
# result = ('Mandatory parameter PubSub and '
# 'optional parameter Node are missing.')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
default = get_configuration('default')
if not result:
if default['pubsub'] and default['nodeid']:
@ -127,20 +132,23 @@ async def view_pubsub(request: Request):
pubsub = default['pubsub']
node = default['nodeid']
iq = await get_node_items(pubsub, node)
link = form_a_link(pubsub, node)
link = form_a_node_link(pubsub, node)
xml_atom = generate_rfc_4287(iq, link)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
elif not settings['service']:
pubsub = default['pubsub']
node = default['nodeid']
iq = await get_node_items(pubsub, node)
link = form_a_link(pubsub, node)
link = form_a_node_link(pubsub, node)
xml_atom = generate_rfc_4287(iq, link)
result = append_stylesheet(xml_atom)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/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)
result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
response = Response(content=result, media_type="application/xml")
return response
@ -166,104 +174,125 @@ async def get_node_items(pubsub, node):
async def get_nodes(pubsub):
try:
await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
def form_a_link(pubsub, node):
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(
pubsub=pubsub, node=node, item=item_id)
return link
def error_message(text):
"""Error message in RFC 4287: The Atom Syndication Format."""
feed = feedgenerator.Atom1Feed(
description = ('This is a syndication feed generated with XMPP Journal '
'Publisher (XJP), which conveys XEP-0060: Publish-'
'Subscribe nodes to standard RFC 4287: The Atom '
'Syndication Format.'),
language = 'en',
link = '',
subtitle = 'XMPP Journal Publisher',
title = 'StreamBurner')
namespace = '{http://www.w3.org/2005/Atom}'
feed_url = 'gemini://schimon.i2p/'
# create entry
feed.add_item(
description = text,
# enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None,
link = '',
# pubdate = updated,
title = 'Error',
# unique_id = ''
)
xml_atom = feed.writeString('utf-8')
xml_atom_extended = append_element(
xml_atom,
'generator',
'XMPP Journal Publisher (XJP)')
return xml_atom_extended
title = 'StreamBurner'
subtitle = 'XMPP Journal Publisher'
description = ('This is a syndication feed generated with XMPP Journal '
'Publisher, which conveys XEP-0060: Publish-Subscribe '
'nodes to standard RFC 4287: The Atom Syndication Format.')
language = 'en'
feed = ET.Element("feed")
feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(feed, 'title', {'type': 'text'}).text = title
ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(feed, 'author', {'name':'XMPP Journal Publisher','email':'xjp@schimon.i2p'})
ET.SubElement(feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/XMPPJournalPublisher',
'version': '0.1'}).text = 'XMPP Journal Publisher (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 = 'xjp-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')
def generate_rfc_4287(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']
# generate_rfc_4287
def generate_atom(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'
description = ('This is a syndication feed generated with 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']
feed = ET.Element("feed")
feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(feed, 'title', {'type': 'text'}).text = title
ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(feed, 'link', {'rel': 'self', 'href': link})
ET.SubElement(feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/XMPPJournalPublisher',
'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)'
ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
for item in items:
item_id = item['id']
item_payload = item['payload']
namespace = '{http://www.w3.org/2005/Atom}'
title = item.find(namespace + 'title')
title = None if title == None else title.text
updated = item.find(namespace + 'updated')
updated = None if updated == None else updated.text
published = item.find(namespace + 'published')
published = None if published == None else published.text
if not updated and not published: updated = datetime.datetime.utcnow().isoformat()
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')
title = item_payload.find(namespace + 'title')
title_text = None if title == None else title.text
# link = item_payload.find(namespace + 'link')
# link_href = '' if link == None else link.attrib['href']
link_href = form_an_item_link(pubsub, node, item_id)
if not title_text or not link_href: continue
content = item_payload.find(namespace + 'content')
content_text = 'No content' if content == None else content.text
if content.attrib:
content_type = content.attrib['type'] if 'type' in content.attrib else 'text'
content_type_text = 'html' if 'html' in content_type else 'text'
published = item_payload.find(namespace + 'published')
published_text = None if published == None else published.text
if published: published_dt = parser.parse(published_text)
updated = item_payload.find(namespace + 'updated')
updated_text = None if updated == None else updated.text
author = item_payload.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 = published or updated,
title = title,
unique_id = link)
xml_atom = feed.writeString('utf-8')
xml_atom_extended = append_element(
xml_atom,
'generator',
'XMPP Journal Publisher (XJP)')
return xml_atom_extended
author_text = 'None' if author == None else author.text
identifier = item_payload.find(namespace + 'id')
if identifier and identifier.attrib: print(identifier.attrib)
identifier_text = 'None' if identifier == None else identifier.text
entry = ET.SubElement(feed, 'entry')
ET.SubElement(entry, 'title').text = title_text
ET.SubElement(entry, 'link', {'href': link_href})
ET.SubElement(entry, 'id').text = identifier_text
ET.SubElement(entry, 'updated').text = updated_text
ET.SubElement(entry, 'published').text = published_text
# ET.SubElement(entry, 'summary', {'type': summary_type_text}).text = summary_text
ET.SubElement(entry, 'content', {'type': content_type_text}).text = content_text
return ET.tostring(feed, encoding='unicode')
def generate_json(iq, node):
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']
for entry in entries:
item = entry['payload']
item_id = entry['id']
item_payload = entry['payload']
namespace = '{http://www.w3.org/2005/Atom}'
title = item.find(namespace + 'title')
title = None if title == None else title.text
title = item_payload.find(namespace + 'title')
title_text = None if title == None else title.text
# updated = item.find(namespace + 'updated')
# updated = None if updated == None else updated.text
# if updated: updated = datetime.datetime.fromisoformat(updated)
link = item.find(namespace + 'link')
link = '' if link == None else link.attrib['href']
json_data_entry = {'title' : title,
'link' : link}
link_href = form_an_item_link(pubsub, node, item_id)
# link = item.find(namespace + 'link')
# link_href = '' if link == None else link.attrib['href']
json_data_entry = {'title' : title_text,
'link' : link_href}
json_data.append(json_data_entry)
if len(json_data) > 6: break
filename = 'data/{}.json'.format(node)
@ -286,15 +315,40 @@ def append_element(xml_data, element, text):
"""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):
def append_stylesheet(xml_data, filename, namespace=None):
# Register namespace in order to avoide ns0:
ET.register_namespace("", "http://www.w3.org/2005/Atom")
if namespace: ET.register_namespace("", namespace)
# 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')
xml_data_without_a_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)
xml_data_declaration = (
'<?xml version="1.0" encoding="utf-8"?>'
'<?xml-stylesheet type="text/xsl" href="xsl/{}"?>'.format(filename) +
xml_data_without_a_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 = pubsub
ET.SubElement(head, "description").text = (
"PubSub Nodes of {}").format(pubsub)
ET.SubElement(head, "generator").text = "XMPP Journal Publisher (XJP)"
ET.SubElement(head, "urlPublic").text = (
"https://git.xmpp-it.net/sch/XMPPJournalPublisher")
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,84 +1,99 @@
window.onload = async function(){
// Fix button follow
let follow = document.querySelector('#follow');
//let feedUrl = location.href.replace(/^https?:/, 'feed:');
let locationHref = new URL(location.href);
let node = locationHref.searchParams.get('node')
let pubsub = locationHref.searchParams.get('pubsub')
let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`;
follow.href = feedUrl;
follow.addEventListener ('click', function() {
window.open(feedUrl, "_self");
});
// Fix button subtome
document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl;
// Fix 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.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, #feed > #header > h2#subtitle.date')) {
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);
element.textContent = timeStamp.toUTCString();
}
// Parse Markdown
for (let element of document.querySelectorAll('#articles > ul > li > div > p')) {
for (let element of document.querySelectorAll('#articles > ul > li > div > p[type="text"]')) {
let markDown = element.textContent
element.innerHTML = marked.parse(markDown);
}
// Build a journal list
itemsList = await openJson(node)
if (itemsList && locationHref.searchParams.get('item')) {
node = locationHref.searchParams.get('node')
pubsub = locationHref.searchParams.get('pubsub')
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('ul');
elementDiv.appendChild(elementUl);
for (let item of itemsList) {
let elementLi = document.createElement('li');
let elementA = document.createElement('a');
elementA.textContent = item.title;
elementA.href = item.link;
elementLi.appendChild(elementA);
elementUl.appendChild(elementLi);
if (node) {
itemsList = await openJson(node)
if (itemsList && locationHref.searchParams.get('item')) {
node = locationHref.searchParams.get('node')
pubsub = locationHref.searchParams.get('pubsub')
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) {
let elementLi = document.createElement('li');
let elementA = document.createElement('a');
elementA.textContent = item.title;
elementA.href = item.link;
elementLi.appendChild(elementA);
elementUl.appendChild(elementLi);
}
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}`},
{'text' : 'Subscribe with a News Reader.',
'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`},
{'text' : 'Browse the journal.',
'href' : `atom?pubsub=${pubsub}&node=${node}`},
{'text' : 'Browse the portal.',
'href' : `atom?pubsub=${pubsub}`}
]
for (let link of links) {
let elementLi = document.createElement('li');
let elementA = document.createElement('a');
elementA.textContent = link.text;
elementA.href = link.href;
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);
}
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}`},
{'text' : 'Subscribe with a News Reader.',
'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`},
{'text' : 'Browse the journal.',
'href' : `atom?pubsub=${pubsub}&node=${node}`}
]
for (let link of links) {
let elementLi = document.createElement('li');
let elementA = document.createElement('a');
elementA.textContent = link.text;
elementA.href = link.href;
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('a[href^="xmpp:"]')) {
for (let xmppLink of document.querySelectorAll('#articles > ul a[href^="xmpp:"], ol a[href^="xmpp:"]')) {
xmppUri = new URL(xmppLink);
let parameters = xmppUri.search.split(';');
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}`
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)
}

View file

@ -8,22 +8,15 @@ 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' />
<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>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
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>
@ -46,9 +46,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:call-template>
<title>
<xsl:choose>
<xsl:when test='atom:title and not(atom:title="")'>
<xsl:when test='atom:title and not(atom:title="") and count(atom:entry) &gt; 1'>
<xsl:value-of select='atom:title'/>
</xsl:when>
<xsl:when test='atom:entry'>
<xsl:value-of select='atom:entry/atom:title'/>
</xsl:when>
<xsl:otherwise>StreamBurner</xsl:otherwise>
</xsl:choose>
</title>
@ -282,7 +285,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<!-- entry content -->
<!-- entry summary of GitLab Atom Syndication Feeds -->
<xsl:if test='atom:content or atom:summary'>
<p class='content'>
<div class='content'>
<xsl:choose>
<xsl:when test='atom:summary[contains(@type,"text")]'>
<xsl:attribute name='type'>
@ -290,6 +293,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute>
<xsl:value-of select='atom:summary'/>
</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")]'>
<!-- TODO add xsl:template to handle inline media -->
</xsl:when>
@ -299,6 +308,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute>
<xsl:value-of select='atom:content'/>
</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")]'>
<!-- TODO add xsl:template to handle inline media -->
</xsl:when>
@ -313,7 +328,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</p>
</div>
</xsl:if>
<!-- entry enclosure -->
<xsl:if test='atom:link[contains(@rel,"enclosure")]'>

18
xsl/opml.xsl Normal file
View file

@ -0,0 +1,18 @@
<?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='opml_as_xhtml.xsl'/>
<!-- set page metadata -->
<xsl:include href='metadata.xsl'/>
</xsl:stylesheet>

226
xsl/opml_as_xhtml.xsl Normal file
View file

@ -0,0 +1,226 @@
<?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'>
<!-- Outline Processor Markup Language 1.0 -->
<xsl:output
media-type='text/x-opml' />
<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='StreamBurner' />
</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='title and not(title="")'>
<xsl:value-of select='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'/>
<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/marked.min.js'/>
<script type='text/javascript' src='/script/postprocess.js'/>
</head>
<body>
<div id='actions'>
<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='//head/title and not(//head/title="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/title'/>
</xsl:when>
<xsl:when test='//outline'>
<xsl:value-of select='//outline/@text'/>
</xsl:when>
<xsl:otherwise>
StreamBurner OPML Feed
</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]' -->
<ul>
<xsl:for-each select='//outline[not(position() &gt; 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(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</li>
</xsl:for-each>
</ul>
</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='title'>
<xsl:value-of select='@text'/>
</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(@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://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
<b title ='Outline Processor Markup Language'>OPML</b>
document which is conveyed as an HTML document; This
document includes a list of subscriptionsis and is
intended to be imported to a syndication feed reader
which provides automated notifications on desktop and
mobile. <span id="selection-link">Click here</span> for
a selection of software and pick the ones that would fit
you best!
</i>
</p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>