From cb4507bb782a219666ce11add09d3b983cc4adce Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Sat, 13 Jul 2024 22:16:25 +0300 Subject: [PATCH] Rename project to Rivista; CSS: Improve position of elements; JS: Improve handling of content in erroneous cases; JS: Render post as HTML when XSLT does not render correctly; JS: Handle posts which were tagged as Text instead of HTML and vice versa; XSLT: Adapt to be viewed on browsers with a lack of support; XSLT: Add Support for link elements of attribute "rel"; Python: Add elements author and link of attribute "rel". --- README.md | 26 ++-- css/stylesheet.css | 148 +++++++++++++++++---- pubsub_to_atom.py | 180 +++++++++++++------------- script/postprocess.js | 97 +++++++++++--- xsl/atom.xsl | 10 +- xsl/atom_as_xhtml.xsl | 292 +++++++++++++++++++++++------------------- xsl/opml.xsl | 10 +- xsl/opml_as_xhtml.xsl | 69 +++++----- 8 files changed, 510 insertions(+), 322 deletions(-) diff --git a/README.md b/README.md index 8ed4956..d953d4c 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# XMPP Journal Publisher +# Rivista -Previously, XMPP PubSub To Atom ("XPTA"). +Previously, XMPP Journal Publisher ("XJP") and XMPP PubSub To Atom ("XPTA"). -XMPP Journal Publisher ("XJP") is a software 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. -XJP 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 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)). -XJP includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform PubSub nodes into static XHTML journal sites. +Rivista includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform PubSub nodes into static XHTML journal sites. -XJP was inspired from Tigase and was motivated by Movim. +Rivista was inspired from Tigase and was motivated by Movim. ## Preview @@ -19,14 +19,14 @@ XJP was inspired from Tigase and was motivated by Movim. ## Motivation -XMPP Journal Publisher is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available +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 XMPP Journal Publisher, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). +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 XMPP Journal Publisher 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 XMPP Journal Publisher would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. +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 @@ -50,8 +50,8 @@ Because XMPP Journal Publisher reads XMPP PubSub nodes, it is possible to view a Extract the source package to a directory that you have permission to run software. ```shell -$ git clone https://git.xmpp-it.net/sch/XJP -$ cd XJP/ +$ git clone https://git.xmpp-it.net/sch/Rivista +$ cd Rivista/ ``` ### Configure @@ -60,7 +60,7 @@ Add account credentials to file `configuration.toml`. ### Start -Execute XMPP Journal Publisher with one of the following commands: +Execute Rivista with one of the following commands: ```shell $ python -m uvicorn main:app --reload @@ -148,7 +148,7 @@ Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) wh 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 XMPP Journal Publisher into production. +And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying Rivista into production. ## Similar Projects diff --git a/css/stylesheet.css b/css/stylesheet.css index 8eda2dc..58bd02d 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -1,8 +1,30 @@ +/* + +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; max-width: 100%; } +/* +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} +*/ + body { background: #000; } @@ -12,26 +34,26 @@ code, pre { max-height: 100%; max-width: 100%; } -img, video { +img, svg, video { + display: block; + max-height: 500px; width: auto; height: auto; } -.notice.no-entry { - margin: auto; - /* margin-left: 2%; */ - /* margin-top: 2%; */ - text-align: center; - padding-top: 100px; - padding-bottom: 150px; -} - h1#title, h2#subtitle, #actions, #references { text-align: center; text-transform: uppercase; line-height: 140%; } +h5.related > a { + margin-right: 5px; + text-decoration: none; + user-select: none; + +} + h3.title > a { display: block; padding-top: 50px; @@ -59,24 +81,17 @@ h3.title > a { padding-left: 2%; } -#menu > ul > li { +#menu > ol > li { padding: 5px; } -#note { - line-height: 30px; - margin: auto; - margin-top: 0.67em; - max-width: 70%; - padding: 10px; - text-align: center; -} - #references { + margin-top: 5em; border-top: 1px solid #eee; } #articles { + min-height: 80vh; display: flex; } @@ -91,10 +106,12 @@ h3.title > a { #journal { margin: auto; + padding-top: 50px; } #articles > ul { margin: auto; + margin-left: 2%; margin-top: 2%; } @@ -103,10 +120,7 @@ h3.title > a { /* height: 500px; */ line-height: 160%; overflow: auto; -} - -#articles #journal > a { - font-weight: bold; + word-wrap: break-word; } #articles div.content { @@ -174,7 +188,91 @@ h3.title > a { text-decoration: underline; } -@media (max-width: 1525px) { +.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 { + line-height: 30px; + margin: auto; + margin-top: 0.67em; + max-width: 70%; + padding: 10px; + text-align: center; + user-select: none; +} + +@media (max-width: 1550px) { #articles { display: unset; } diff --git a/pubsub_to_atom.py b/pubsub_to_atom.py index 0c78193..4bfe043 100644 --- a/pubsub_to_atom.py +++ b/pubsub_to_atom.py @@ -8,6 +8,7 @@ from fastapi.staticfiles import StaticFiles import json from slixmpp import ClientXMPP from slixmpp.exceptions import IqError, IqTimeout +import xml import xml.etree.ElementTree as ET #import importlib.resources @@ -56,17 +57,15 @@ async def view_pubsub_nodes(request: Request): if iq: link = 'xmpp:{pubsub}'.format(pubsub=pubsub) xml_opml = generate_opml(iq) - result = append_stylesheet(xml_opml, 'opml.xsl') + result = append_stylesheet(xml_opml, 'opml') else: text = 'Please check that PubSub Jabber ID is valid and accessible.' xml_atom = error_message(text) - result = append_stylesheet( - xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + 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.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') default = get_configuration('default') if not result: if default['pubsub']: @@ -75,17 +74,16 @@ async def view_pubsub_nodes(request: Request): iq = await get_nodes(pubsub) link = 'xmpp:{pubsub}'.format(pubsub=pubsub) xml_opml = generate_opml(iq) - result = append_stylesheet(xml_opml, 'opml.xsl') + 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.xsl') + 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.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') response = Response(content=result, media_type="application/xml") return response @@ -115,11 +113,12 @@ async def view_node_items(request: Request): else: operator = get_configuration('settings')['operator'] json_data = [{'title' : 'Error retrieving items list.', - 'link' : ('javascript:alert("XJP: XMPP Journal Publisher has experienced an error ' - 'while attempting to retrieve the list of items for Node {} of PubSub {}.")') + '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=XJP: XMPP Journal Publisher;body=Greetings! ' + '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) @@ -128,8 +127,7 @@ async def view_node_items(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, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') # try: # iq = await get_node_items(pubsub, node) @@ -149,26 +147,22 @@ async def view_node_items(request: Request): else: text = 'Please check that PubSub node is valid and accessible.' xml_atom = error_message(text) - result = append_stylesheet( - xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') elif pubsub: text = 'Node parameter is missing.' xml_atom = error_message(text) - result = append_stylesheet( - xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') elif node: text = 'PubSub parameter is missing.' xml_atom = error_message(text) - result = append_stylesheet( - xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, '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, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') default = get_configuration('default') if not result: if default['pubsub'] and default['nodeid']: @@ -178,21 +172,18 @@ async def view_node_items(request: Request): iq = await get_node_items(pubsub, node) link = form_a_node_link(pubsub, node) xml_atom = generate_atom(iq, link) - result = append_stylesheet( - xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') elif not settings['service']: pubsub = default['pubsub'] node = default['nodeid'] iq = await get_node_items(pubsub, node) link = form_a_node_link(pubsub, node) xml_atom = generate_atom(iq, link) - result = append_stylesheet( - xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') + 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.xsl', namespace='http://www.w3.org/2005/Atom') + result = append_stylesheet(xml_atom, 'atom') response = Response(content=result, media_type="application/xml") return response @@ -234,24 +225,25 @@ def form_an_item_link(pubsub, node, item_id): def error_message(text): """Error message in RFC 4287: The Atom Syndication Format.""" - title = 'StreamBurner' + title = 'Rivista' 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.') + 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 = '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, 'author', {'name':'Rivista','email':'rivista@schimon.i2p'}) ET.SubElement(feed, 'generator', { - 'uri': 'https://git.xmpp-it.net/sch/XJP', - 'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)' + 'uri': 'https://git.xmpp-it.net/sch/Rivista', + 'version': '0.1'}).text = 'Rivista' 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, '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 @@ -268,55 +260,77 @@ def generate_atom(iq, link): # link = form_a_node_link(pubsub, node) # subtitle = 'XMPP PubSub Syndication Feed' subtitle = pubsub - 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.') + 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'] - 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/XJP', - 'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)' - ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat() + 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' + ET.SubElement(e_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_payload.find(namespace + 'title') + links = item_payload.find(namespace + 'link') + if (not isinstance(title, xml.etree.ElementTree.Element) and + not isinstance(links, xml.etree.ElementTree.Element)): continue 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: - content_type = content.attrib['type'] if 'type' in content.attrib else 'text' - content_type_text = 'html' if 'html' in content_type else 'text' + e_entry = ET.SubElement(e_feed, 'entry') + ET.SubElement(e_entry, 'title').text = title_text + if isinstance(links, xml.etree.ElementTree.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: - content_type_text = 'html' + 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, xml.etree.ElementTree.Element): + for content in item_payload.findall(namespace + 'content'): + content_text = content.text if content.text else 'No content.' + content_type = content.attrib['type'] if 'type' in content.attrib else 'html' + content_type_text = 'html' if 'html' in content_type else 'html' + ET.SubElement(e_entry, 'content', {'type': content_type_text}).text = content_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 - author = item_payload.find(namespace + 'author') - if author and author.attrib: print(author.attrib) + ET.SubElement(e_entry, 'updated').text = updated_text + e_author = ET.SubElement(e_entry, 'author') + authors = item_payload.find(namespace + 'author') + if isinstance(authors, xml.etree.ElementTree.Element): + for author in item_payload.findall(namespace + 'author'): + if not author.text: continue + author_text = author.text + author_email = link.attrib['href'] if 'href' in link.attrib else '' + author_uri = link.attrib['type'] if 'type' in link.attrib else '' + author_summary = link.attrib['rel'] if 'rel' in link.attrib else '' + ET.SubElement(e_author, 'name').text = author_text + if author and author.attrib: print(author.attrib) + + 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') + + 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.""" @@ -344,25 +358,11 @@ def generate_json(iq): with open(filename, 'w', encoding='utf-8') as f: json.dump(json_data, f, ensure_ascii=False, indent=4) -"""Patch function to append elements which are not provided by feedgenerator""" -def append_element(xml_data, element, text): - root = ET.fromstring(xml_data) - - # Create the generator element - generator_element = ET.Element(element) - generator_element.text = text - - # Append the generator element to the root - root.append(generator_element) - - # Return the modified XML as a string - return ET.tostring(root, encoding='unicode') - """Patch function to append XSLT reference to XML""" """Why is not this a built-in function of ElementTree or LXML""" -def append_stylesheet(xml_data, filename, namespace=None): +def append_stylesheet(xml_data, type): # Register namespace in order to avoide ns0: - if namespace: ET.register_namespace("", namespace) + if type == 'atom': ET.register_namespace('', 'http://www.w3.org/2005/Atom') # Load XML from string tree = ET.fromstring(xml_data) # The following direction removes the XML declaration @@ -370,7 +370,7 @@ def append_stylesheet(xml_data, filename, namespace=None): # Add XML declaration and stylesheet xml_data_declaration = ( '' - ''.format(filename) + + ''.format(type) + xml_data_without_a_declaration) return xml_data_declaration @@ -380,11 +380,11 @@ def generate_opml(iq): opml = ET.Element("opml") opml.set("version", "1.0") head = ET.SubElement(opml, "head") - ET.SubElement(head, "title").text = pubsub + ET.SubElement(head, "title").text = 'An OPML of ' + 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/XJP' + 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 diff --git a/script/postprocess.js b/script/postprocess.js index a9a9678..000ae96 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -2,7 +2,8 @@ window.onload = async function(){ let locationHref = new URL(location.href); let node = locationHref.searchParams.get('node') let pubsub = locationHref.searchParams.get('pubsub') - // Fix button follow + + // Set button follow let follow = document.querySelector('#follow'); if (follow) { //let feedUrl = location.href.replace(/^https?:/, 'feed:'); @@ -14,6 +15,7 @@ window.onload = async function(){ // 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,' + @@ -22,11 +24,62 @@ window.onload = async function(){ let timeStamp = new Date(element.textContent); element.textContent = timeStamp.toUTCString(); } + // Parse Markdown for (let element of document.querySelectorAll('#articles div[type="text"]')) { - let markDown = element.textContent - element.innerHTML = marked.parse(markDown); + element.innerHTML = marked.parse(element.textContent); } + + // 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 (node) { itemsList = await openJson(node) @@ -79,8 +132,11 @@ window.onload = async function(){ document.querySelector('#articles').appendChild(elementDiv); } } + // Convert URI xmpp: to URI http: links. - for (let xmppLink of document.querySelectorAll('#articles > ul a[href^="xmpp:"], ol a[href^="xmpp:"]')) { + for (let xmppLink of document.querySelectorAll( + '#articles > ul > li > div > h3 > a[href^="xmpp:"],' + + '#journal > ol > li > a[href^="xmpp:"]')) { xmppUri = new URL(xmppLink); let parameters = xmppUri.search.split(';'); try { @@ -98,6 +154,7 @@ window.onload = async function(){ console.warn(err) } } + // Display a selection of suggested software. const selection = { 'akregator' : { @@ -144,14 +201,14 @@ window.onload = async function(){ elementH1.textContent = 'Get A News Reader'; elementDiv.appendChild(elementH1); let elementH2 = document.createElement('h2'); - elementH2.textContent = 'Install Feed Reader Apps For Desktop And Mobile'; + elementH2.textContent = 'Install A Feed Reader For Desktop And Mobile'; elementDiv.appendChild(elementH2); const brands = Object.keys(selection); let elementDivSel = document.createElement('div'); elementDivSel.id = 'selection'; for (let i = 0; i < brands.length; i++) { let brand = brands[i]; - elementSpan = document.createElement('span'); + let elementSpan = document.createElement('span'); let elementA = document.createElement('a'); elementA.href = selection[brand].url; elementA.textContent = selection[brand].name; @@ -162,20 +219,24 @@ window.onload = async function(){ elementDivSel.appendChild(elementSpan); elementDiv.appendChild(elementDivSel); } - let elementP = document.createElement('p'); - elementP.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(elementP); - let elementDivReturn = document.createElement('div'); - elementDivReturn.id = 'return'; - elementDivReturn.textContent = 'Return To PubSub...'; - elementDivReturn.addEventListener ('click', function() { + let elementP1 = document.createElement('p'); + elementP1.textContent = '' + + 'This is a selection of desktop, mobile and HTML (sometimes referred to ' + + 'as "online") News Readers for you to choose from.'; + 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 To PubSub'; + elementSpan.addEventListener ('click', function() { document.querySelector('#selection-page').remove(); }); - elementDiv.appendChild(elementDivReturn); + elementDiv.appendChild(elementSpan); document.body.appendChild(elementDiv); }); } diff --git a/xsl/atom.xsl b/xsl/atom.xsl index 72c1dd2..31c6436 100644 --- a/xsl/atom.xsl +++ b/xsl/atom.xsl @@ -1,17 +1,19 @@ - - + media-type = 'application/atom+xml' + method = 'html' + version = '4.01' /> + diff --git a/xsl/atom_as_xhtml.xsl b/xsl/atom_as_xhtml.xsl index 5422a29..8c7fe4d 100644 --- a/xsl/atom_as_xhtml.xsl +++ b/xsl/atom_as_xhtml.xsl @@ -15,9 +15,6 @@ xmlns:georss='http://www.georss.org/georss' xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#' xmlns:atom10='http://www.w3.org/2005/Atom' xmlns:atom='http://www.w3.org/2005/Atom'> - - @@ -38,7 +35,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'> - + @@ -52,7 +49,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'> - StreamBurner + Rivista @@ -90,15 +87,6 @@ xmlns:atom='http://www.w3.org/2005/Atom'> SubToMe - - About - - - Feeds - XMPP @@ -107,6 +95,11 @@ xmlns:atom='http://www.w3.org/2005/Atom'> title='Syndictaion and PubSub.'> Groupchat + + Help +
@@ -145,12 +141,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
@@ -180,19 +176,16 @@ xmlns:atom='http://www.w3.org/2005/Atom'> - - + + - - - - stremburner- + rivista- @@ -230,60 +223,96 @@ xmlns:atom='http://www.w3.org/2005/Atom'> - - - - + + -

- -

+ + updated + +
-

- -

+ + published + +

- +
+ + +

+ + + + + mailto: + + + + Send an Email to + + + + + + + + + + + + + + + + + + By + + + +

+
+ -
@@ -331,75 +360,63 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +

Enclosures

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
-
+
No entry ID
-

No content.

+
    +
  • +
    +

    + + No content + +

    +

    This entry is empty

    +
    Please check that the mother PubSub node is populated with content.
    +
    +
  • +
@@ -417,8 +434,16 @@ xmlns:atom='http://www.w3.org/2005/Atom'> title='The Social Platform Shaped For Your Community.'> Movim + + Rivista + + + Reeder + + title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use XMPP.'> Modern

- This is an XMPP news feed which is conveyed as an XHTML - document, and it can even be viewed by a syndication - feed reader which provides automated notifications on - desktop and mobile. Click - here for a selection of software and pick the - ones that would fit you best! + This is an Atom document which is conveyed as an XHTML + document; + This document 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. + Click here for a + selection of software and pick the ones that would fit + to you best!

diff --git a/xsl/opml.xsl b/xsl/opml.xsl index 9c57a4f..29a63b8 100644 --- a/xsl/opml.xsl +++ b/xsl/opml.xsl @@ -1,17 +1,19 @@ - - + media-type = 'text/x-opml' + method = 'html' + version = '4.01' /> + diff --git a/xsl/opml_as_xhtml.xsl b/xsl/opml_as_xhtml.xsl index 5531e0f..5f5a3a9 100644 --- a/xsl/opml_as_xhtml.xsl +++ b/xsl/opml_as_xhtml.xsl @@ -10,9 +10,6 @@ element inside of html element - - @@ -33,7 +30,7 @@ xmlns:xml='http://www.w3.org/XML/1998/namespace'> - + @@ -41,10 +38,10 @@ xmlns:xml='http://www.w3.org/XML/1998/namespace'> <xsl:choose> - <xsl:when test='title and not(title="")'> - <xsl:value-of select='title'/> + <xsl:when test='//head/title and not(//head/title="")'> + <xsl:value-of select='//head/title'/> </xsl:when> - <xsl:otherwise>StreamBurner</xsl:otherwise> + <xsl:otherwise>Rivista OPML</xsl:otherwise> </xsl:choose> @@ -54,20 +51,9 @@ xmlns:xml='http://www.w3.org/XML/1998/namespace'> -