Raw Source
sjehuda / Newspaper (XMLHttpRequest)

// ==UserScript== 
// @name        Newspaper (XMLHttpRequest)
// @namespace   io.github.sjehuda.syndication4human
// @description Native Feed Viewer. Render syndication web feeds (supports Atom, RDF and RSS)
// @homepageURL https://sjehuda.github.io/newspaper.html
// @supportURL  https://openuserjs.org/scripts/sjehuda/Newspaper/issues
// @updateURL   https://openuserjs.org/meta/sjehuda/Newspaper.meta.js
// @copyright   2023, Schimon Jehudah (http://schimon.i2p)
// @license     MIT; https://opensource.org/licenses/MIT
// @icon        data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=
// @version     23.02
// @run-at      document-start
// ==/UserScript==

// TODO
// 
// 1) Migrate to SaxonJS
//    Because XSLTProcessor is limited and inferior by-design
//
//    It's a shame to witness a multi-billion worth organization
//    and also a pioneer of raising awareness to Web Feeds since
//    the early 2000's, is now silencing this precious technology,
//    also by providing a too simple XSLT processor as if it was
//    made as a project inside a junior high school
//
// 2) "Lazy" Content media loader
//    https://www.merixstudio.com/blog/lazy-loading-pure-javascript/
//    https://css-tricks.com/the-complete-guide-to-lazy-loading-images/
//
// 3) Color schemes
//    https://alligator.io/feed.xml
//
// 4) Respect existing stylesheets
// 
// 5) Support JSON
//
// NOTE
//
// 1) If XSLTProcessor is be able to store multiple
//    stylesheets, including CSS, then we can save
//    over 20 lines of code
// 
// 2) <p class="explanation">This is a podcast RSS feed generated by <a href="https://castos.com/seriously-simple-podcasting/">Seriously Simple Podcasting</a>. It is meant for consumption by podcast feed readers using the URL in the address bar.</p>
// 

var
  xmlHTTPRequest, xmlFile,
  mimeType, isFeed;

const mimeArray = [
  'application/atom+xml',
  'application/stream+json',
  'application/rss+xml',
  'application/rdf+xml'
];

httpGetAsync(
  document.documentURI,
  checkMediaType
);

// TODO Promises with then()
// https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call

/*
xmlFile = httpGetAsync(document.documentURI, checkMediaType)

if (xmlFile) {
  renderFeed(
    xmlFile
  );
}
*/

function httpGetAsync(uri, callback) {
  var xmlHTTPRequest = new XMLHttpRequest();
  xmlHTTPRequest.onreadystatechange = function() { 
    if (xmlHTTPRequest.readyState === 4) {
      xmlFile = xmlHTTPRequest.responseText;
      //console.log('xmlHTTPRequest.responseText')
      //console.log(xmlHTTPRequest.responseText)
      if (document.URL.startsWith('file:')) {
        mimeType = document.contentType;
      } else {
          mimeType =
            xmlHTTPRequest
            .getResponseHeader('Content-Type');
        }
      // FIXME
      // Not a good practice
      // Passing a file via a function
      // that returns true or false.
      // NOTE
      // Strangely enough, xmlFile
      // passes via the function even
      // without an explicit mention.
      callback(mimeType);
    }
  };
  xmlHTTPRequest.open("GET", uri, true); // true for asynchronous 
  xmlHTTPRequest.send(null);
}

// Check if is of type syndication feed
function checkMediaType(mimeType) {
  //console.log(mimeType);
  
  // TODO Reorder if statements below
  if (!mimeType) {
    mimeType = 'text/plain';
  }

  for (let i = 0; i < mimeArray.length; i++) {
    if (mimeType.includes(mimeArray[i])
       ) {
      isFeed = true;
      break;
    }
    // Check if is application/xml or text/xml
    else
    if (mimeType.includes('/xml') || mimeType.includes('/html')) {
      // We do not use forEach, because we need to break out once at least on tag is found
      // List array by most commonly used formats and tags
      const tagArray = ['feed', 'atom:link', 'rss', 'rdf:RDF'];
      for (let i = 0; i < tagArray.length; i++) {
        var query = document.getElementsByTagName(tagArray[i]);
        if (query.length) {
          isFeed = true;
          break;
        }
      }
    }
    // Check if is plain text (XPath only is usable)
    else
    if (mimeType.match('text/plain')) {
      const tagArray = ['<feed', '<atom:link', '<rss', '<rdf:RDF'];
      for (let i = 0; i < tagArray.length; i++) {
        query = ['//*[contains(text(), "' + tagArray[i] + '")]'];
        query = queryByXPath(query);
        if (query.length) {
          isFeed = true;
          break;
        }
      }
    }
  }

  // TODO
  // replace "isFeed = true; break;" by
  // "return true or return xmlFile"
  // FIXME
  // Not a good practice
  // This should be outside of
  // this true/false function
  if (!isFeed) { return; }

  renderFeed(xmlFile);

}

// Begin processing
function renderFeed(xmlFile) {

  var
    domParser,
    xmlDocument, xsltProcessor,
    newDocument, head, style, isRTL,
    stylesheet_ltr, stylesheet_rtl,
    insertDocument, removeDocument;
    

  // FIXME
  // Make this function to work
  // function domParser(xmlDoc, xmlType) {
  //   domParser = new DOMParser();
  //   domParser.parseFromString(xmlDoc, xmlType);
  // }

  xmlFile =
    xmlFile
    .trim();

  domParser = new DOMParser();

  xmlDocument =
    domParser
    .parseFromString(
      xmlFile,
      'text/xml'
    );

  // NOTE
  // Examine multiple use of xslt processor for CSS, RTL and less pressure on memory
  // RESOLVED
  // Not possible, unless you output to XML as many times as the stylesheets minus one time.
  // Expensive and will confuse newcomers.
  // TODO
  // Consider migration to SaxonJS XSLT 3.0 Processor
  xsltProcessor = new XSLTProcessor();

  const xslFile = '<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet xmlns="http://purl.org/rss/1.0/" xmlns:admin="http://webns.net/mvcb/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:atom10="http://www.w3.org/2005/Atom" xmlns:c="http://s.opencalais.com/1/pred/" xmlns:cc="http://web.resource.org/cc/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dct="http://purl.org/dc/terms/" xmlns:dbo="http://dbpedia.org/ontology/" xmlns:dbp="http://dbpedia.org/property/" xmlns:enc="http://purl.oclc.org/net/rss_2.0/enc#" xmlns:fh="http://purl.org/syndication/history/1.0" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:foo="http://purl.org/rss/1.0/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:georss="http://www.georss.org/georss" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:media="http://search.yahoo.com/mrss/" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns:prov="http://www.w3.org/ns/prov#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:sioc="http://rdfs.org/sioc/ns#" xmlns:sioct="http://rdfs.org/sioc/types#" xmlns:skos="http://www.w3.org/2004/02/skos/core#" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><xsl:output method="html" indent="yes" omit-xml-decleration="no"/><xsl:output media-type="application/atom+xml"/><xsl:template match="/atom:feed"><xsl:variable name="rtl" select="@xml:lang[ contains(self::node(),&quot;ar&quot;) or contains(self::node(),&quot;fa&quot;) or contains(self::node(),&quot;he&quot;) or contains(self::node(),&quot;ji&quot;) or contains(self::node(),&quot;ku&quot;) or contains(self::node(),&quot;ur&quot;) or contains(self::node(),&quot;yi&quot;)]"/><html><head><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;description&quot;"/><xsl:with-param name="content" select="atom:subtitle"/></xsl:call-template><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;generator&quot;"/><xsl:with-param name="content" select="&quot;syndication4humans https://sjehuda.github.io/&quot;"/></xsl:call-template><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;mimetype&quot;"/><xsl:with-param name="content" select="&quot;application/atom+xml&quot;"/></xsl:call-template><title><xsl:choose><xsl:when test="atom:title and not(atom:title=&quot;&quot;)"><xsl:value-of select="atom:title"/></xsl:when><xsl:otherwise>Streamburner News Reader</xsl:otherwise></xsl:choose></title><xsl:if test="$rtl"><meta name="semitic" content="true"/></xsl:if></head><body><div id="feed"><header><div id="title"><xsl:choose><xsl:when test="atom:title and not(atom:title=&quot;&quot;)"><xsl:attribute name="title"><xsl:value-of select="atom:title"/></xsl:attribute><xsl:value-of select="atom:title"/></xsl:when><xsl:otherwise><div class="empty"/></xsl:otherwise></xsl:choose></div><div id="subtitle"><xsl:attribute name="title"><xsl:value-of select="atom:subtitle"/></xsl:attribute><xsl:value-of select="atom:subtitle"/></div></header><section id="links"><a title="Click to get the latest updates and news" onclick="location.href = &quot;feed:&quot; + location.href"></a><a title="Subscribe via SubToMe"><xsl:attribute name="href">https://www.subtome.com/#/subscribe?feeds=<xsl:value-of select="atom:link[@rel=&quot;self&quot;]/@href"/></xsl:attribute><xsl:attribute name="onclick">(function(btn){var z=document.createElement(&quot;script&quot;);document.subtomeBtn=btn;z.src=&quot;https://www.subtome.com/load.js&quot;;document.body.appendChild(z);})(this);return false;</xsl:attribute></a><a title="Learn about syndication feed" onclick="document.getElementById(&quot;aboutfeed&quot;).classList.toggle(&quot;show&quot;);" class="popup"><span class="popuptext" id="aboutfeed"/></a><a href="https://wikiless.org/wiki/Template:Aggregators" title="Get a feed reader for desktop and mobile"/><a href="https://wikiless.org/wiki/RSS" title="Learn the benefits of using feeds for personal and corporates"/></section><xsl:choose><xsl:when test="atom:entry"><div id="toc"><xsl:for-each select="atom:entry[not(position() &gt;20)]"><xsl:if test="atom:title"><xsl:element name="a"><xsl:attribute name="href"><xsl:text>#newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="atom:title"/></xsl:element></xsl:if></xsl:for-each></div></xsl:when></xsl:choose><section id="articles"><xsl:choose><xsl:when test="atom:entry"><xsl:for-each select="atom:entry[not(position() &gt;20)]"><div class="entry"><xsl:if test="atom:title"><div class="title"><xsl:element name="a"><xsl:attribute name="href"><xsl:choose><xsl:when test="atom:link[contains(@rel,&quot;alternate&quot;)]"><xsl:value-of select="atom:link[contains(@rel,&quot;alternate&quot;)]/@href"/></xsl:when><xsl:otherwise><xsl:value-of select="atom:link/@href"/></xsl:otherwise></xsl:choose></xsl:attribute><xsl:attribute name="title"><xsl:value-of select="atom:title"/></xsl:attribute><xsl:attribute name="id"><xsl:text>newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="atom:title"/></xsl:element></div></xsl:if><xsl:choose><xsl:when test="geo:lat and geo:long"><xsl:variable name="lat" select="geo:lat"/><xsl:variable name="lng" select="geo:long"/><span class="geolocation"><a href="geo:{$lat},{$lng}">๐Ÿ“</a></span></xsl:when><xsl:when test="geo:Point"><xsl:variable name="lat" select="geo:Point/geo:lat"/><xsl:variable name="lng" select="geo:Point/geo:long"/><span class="geolocation"><a href="geo:{$lat},{$lng}">๐Ÿ“</a></span></xsl:when><xsl:when test="georss:point"><xsl:variable name="lat" select="substring-before(georss:point, &quot; &quot;)"/><xsl:variable name="lng" select="substring-after(georss:point, &quot; &quot;)"/><xsl:variable name="name" select="georss:featurename"/><span class="geolocation"><a href="geo:{$lat},{$lng}" title="{$name}">๐Ÿ“</a></span></xsl:when></xsl:choose><xsl:choose><xsl:when test="atom:updated"><div class="updated"><xsl:value-of select="atom:updated"/></div></xsl:when><xsl:when test="atom:published"><div class="published"><xsl:value-of select="atom:published"/></div></xsl:when><xsl:otherwise><div class="warning atom1 published"/></xsl:otherwise></xsl:choose><xsl:if test="atom:content or atom:summary"><div class="content"><xsl:choose><xsl:when test="atom:summary[contains(@type,&quot;text&quot;)]"><xsl:attribute name="type"><xsl:value-of select="atom:summary/@type"/></xsl:attribute><xsl:value-of select="atom:summary"/></xsl:when><xsl:when test="atom:summary[contains(@type,&quot;base64&quot;)]"></xsl:when><xsl:when test="atom:content[contains(@type,&quot;text&quot;)]"><xsl:attribute name="type"><xsl:value-of select="atom:content/@type"/></xsl:attribute><xsl:value-of select="atom:content"/></xsl:when><xsl:when test="atom:content[contains(@type,&quot;base64&quot;)]"></xsl:when><xsl:otherwise><xsl:choose><xsl:when test="atom:summary and not(atom:summary=&quot;&quot;)"><xsl:value-of select="atom:summary" disable-output-escaping="yes"/></xsl:when><xsl:otherwise><xsl:value-of select="atom:content" disable-output-escaping="yes"/></xsl:otherwise></xsl:choose></xsl:otherwise></xsl:choose></div></xsl:if><xsl:if test="atom:link[contains(@rel,&quot;enclosure&quot;)]"><div class="enclosure" title="Right-click and Save link asโ€ฆ"><xsl:for-each select="atom:link[contains(@rel,&quot;enclosure&quot;)]"><xsl:element name="span"><xsl:attribute name="icon"><xsl:value-of select="substring-before(@type,&quot;/&quot;)"/></xsl:attribute></xsl:element><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="@href"/></xsl:attribute><xsl:attribute name="download"/><xsl:call-template name="extract-filename"><xsl:with-param name="url" select="@href"/></xsl:call-template></xsl:element><xsl:element name="span"><xsl:attribute name="class"><xsl:value-of select="substring-before(@type,&quot;/&quot;)"/></xsl:attribute></xsl:element><xsl:if test="@length &gt; 0"><xsl:call-template name="transform-filesize"><xsl:with-param name="length" select="@length"/></xsl:call-template></xsl:if><xsl:element name="br"/></xsl:for-each><xsl:for-each select="media:content"><xsl:element name="span"><xsl:attribute name="icon"><xsl:value-of select="@medium"/></xsl:attribute></xsl:element><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="@url"/></xsl:attribute><xsl:attribute name="download"/><xsl:call-template name="extract-filename"><xsl:with-param name="url" select="@url"/></xsl:call-template></xsl:element><xsl:element name="span"><xsl:attribute name="class"><xsl:value-of select="@medium"/></xsl:attribute></xsl:element><xsl:if test="@fileSize &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></div><xsl:if test="not(atom:id)"><div class="warning atom1 id"/></xsl:if></xsl:for-each></xsl:when><xsl:otherwise><div class="notice no-entry"/></xsl:otherwise></xsl:choose></section></div></body></html></xsl:template><xsl:output media-type="application/rdf+xml"/><xsl:template match="/rdf:RDF"><xsl:variable name="rtl" select="channel/language[ contains(text(),&quot;ar&quot;) or contains(text(),&quot;fa&quot;) or contains(text(),&quot;he&quot;) or contains(text(),&quot;ji&quot;) or contains(text(),&quot;ku&quot;) or contains(text(),&quot;ur&quot;) or contains(text(),&quot;yi&quot;)]"/><html><head><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;description&quot;"/><xsl:with-param name="content" select="foo:channel/foo:description"/></xsl:call-template><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;generator&quot;"/><xsl:with-param name="content" select="&quot;syndication4humans https://sjehuda.github.io/&quot;"/></xsl:call-template><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;mimetype&quot;"/><xsl:with-param name="content" select="&quot;application/rdf+xml&quot;"/></xsl:call-template><title><xsl:choose><xsl:when test="foo:channel/foo:title and not(foo:channel/foo:title=&quot;&quot;)"><xsl:value-of select="foo:channel/foo:title"/></xsl:when><xsl:otherwise>Streamburner News Reader</xsl:otherwise></xsl:choose></title><xsl:if test="$rtl"><meta name="semitic" content="true"/></xsl:if></head><body><div id="feed"><header><div id="title"><xsl:choose><xsl:when test="foo:channel/foo:title and not(foo:channel/foo:title=&quot;&quot;)"><xsl:attribute name="title"><xsl:value-of select="foo:channel/foo:title"/></xsl:attribute><xsl:value-of select="foo:channel/foo:title"/></xsl:when><xsl:otherwise><div class="empty"/></xsl:otherwise></xsl:choose></div><xsl:choose><xsl:when test="foo:channel/itunes:subtitle"><div id="subtitle"><xsl:attribute name="title"><xsl:value-of select="foo:channel/itunes:subtitle"/></xsl:attribute><xsl:value-of select="foo:channel/itunes:subtitle"/></div></xsl:when><xsl:when test="foo:channel/foo:description"><div id="subtitle"><xsl:attribute name="title"><xsl:value-of select="foo:channel/foo:description"/></xsl:attribute><xsl:value-of select="foo:channel/foo:description"/></div></xsl:when></xsl:choose></header><section id="links"><a title="Click to get the latest updates and news" onclick="location.href = &quot;feed:&quot; + location.href"></a><a title="Subscribe via SubToMe"><xsl:attribute name="href">https://www.subtome.com/#/subscribe?feeds=<xsl:value-of select="atom:link[@rel=&quot;self&quot;]/@href"/></xsl:attribute><xsl:attribute name="onclick">(function(btn){var z=document.createElement(&quot;script&quot;);document.subtomeBtn=btn;z.src=&quot;https://www.subtome.com/load.js&quot;;document.body.appendChild(z);})(this);return false;</xsl:attribute></a><a title="Learn about syndication feed" onclick="document.getElementById(&quot;aboutfeed&quot;).classList.toggle(&quot;show&quot;);" class="popup"><span class="popuptext" id="aboutfeed"/></a><a href="https://wikiless.org/wiki/Template:Aggregators" title="Get a feed reader for desktop and mobile"/><a href="https://wikiless.org/wiki/RSS" title="Learn the benefits of using feeds for personal and corporates"/></section><xsl:choose><xsl:when test="foo:item"><div id="toc"><xsl:for-each select="foo:item[not(position() &gt;20)]"><xsl:choose><xsl:when test="itunes:subtitle"><xsl:element name="a"><xsl:attribute name="href"><xsl:text>#newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="itunes:subtitle"/></xsl:element></xsl:when><xsl:when test="foo:title"><xsl:element name="a"><xsl:attribute name="href"><xsl:text>#newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="foo:title"/></xsl:element></xsl:when></xsl:choose></xsl:for-each></div></xsl:when></xsl:choose><section id="articles"><xsl:choose><xsl:when test="foo:item"><xsl:for-each select="foo:item[not(position() &gt;20)]"><div class="entry"><xsl:choose><xsl:when test="itunes:subtitle"><div class="title"><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="foo:link"/></xsl:attribute><xsl:attribute name="title"><xsl:value-of select="itunes:subtitle"/></xsl:attribute><xsl:attribute name="id"><xsl:text>newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="itunes:subtitle"/></xsl:element></div></xsl:when><xsl:when test="foo:title"><div class="title"><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="foo:link"/></xsl:attribute><xsl:attribute name="title"><xsl:value-of select="foo:title"/></xsl:attribute><xsl:attribute name="id"><xsl:text>newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="foo:title"/></xsl:element></div></xsl:when><xsl:otherwise><div class="warning rss title"/></xsl:otherwise></xsl:choose><xsl:choose><xsl:when test="geo:lat and geo:long"><xsl:variable name="lat" select="geo:lat"/><xsl:variable name="lng" select="geo:long"/><span class="geolocation"><a href="geo:{$lat},{$lng}">๐Ÿ“</a></span></xsl:when><xsl:when test="geo:Point"><xsl:variable name="lat" select="geo:Point/geo:lat"/><xsl:variable name="lng" select="geo:Point/geo:long"/><span class="geolocation"><a href="geo:{$lat},{$lng}">๐Ÿ“</a></span></xsl:when><xsl:when test="georss:point"><xsl:variable name="lat" select="substring-before(georss:point, &quot; &quot;)"/><xsl:variable name="lng" select="substring-after(georss:point, &quot; &quot;)"/><xsl:variable name="name" select="georss:featurename"/><span class="geolocation"><a href="geo:{$lat},{$lng}" title="{$name}">๐Ÿ“</a></span></xsl:when></xsl:choose><xsl:if test="dc:date"><div class="published"><xsl:value-of select="dc:date"/></div></xsl:if><xsl:choose><xsl:when test="content:encoded"><div class="content"><xsl:value-of select="content:encoded" disable-output-escaping="yes"/></div></xsl:when><xsl:when test="foo:description"><div class="content"><xsl:value-of select="foo:description" disable-output-escaping="yes"/></div></xsl:when><xsl:when test="itunes:summary"><div class="content"><xsl:value-of select="itunes:summary" disable-output-escaping="yes"/></div></xsl:when><xsl:otherwise><div class="warning rss description"/></xsl:otherwise></xsl:choose><xsl:if test="enc:enclosure"><div class="enclosure" title="Right-click and Save link asโ€ฆ"><xsl:for-each select="enc:enclosure"><xsl:element name="span"><xsl:attribute name="icon"><xsl:value-of select="substring-before(@enc:type,&quot;/&quot;)"/></xsl:attribute></xsl:element><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="@rdf:resource"/></xsl:attribute><xsl:attribute name="download"/><xsl:call-template name="extract-filename"><xsl:with-param name="url" select="@rdf:resource"/></xsl:call-template></xsl:element><xsl:element name="span"><xsl:attribute name="class"><xsl:value-of select="substring-before(@enc:type,&quot;/&quot;)"/></xsl:attribute></xsl:element><xsl:if test="@enc:length &gt; 0"><xsl:call-template name="transform-filesize"><xsl:with-param name="length" select="@enc:length"/></xsl:call-template></xsl:if><xsl:element name="br"/></xsl:for-each></div></xsl:if></div></xsl:for-each></xsl:when><xsl:otherwise><div class="notice no-entry"/></xsl:otherwise></xsl:choose></section></div></body></html></xsl:template><xsl:output media-type="application/rss+xml"/><xsl:template match="/rss"><xsl:variable name="rtl" select="channel/language[ contains(text(),&quot;ar&quot;) or contains(text(),&quot;fa&quot;) or contains(text(),&quot;he&quot;) or contains(text(),&quot;ji&quot;) or contains(text(),&quot;ku&quot;) or contains(text(),&quot;ur&quot;) or contains(text(),&quot;yi&quot;)]"/><html><head><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;description&quot;"/><xsl:with-param name="content" select="channel/description"/></xsl:call-template><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;generator&quot;"/><xsl:with-param name="content" select="&quot;syndication4humans https://sjehuda.github.io/&quot;"/></xsl:call-template><xsl:call-template name="metadata"><xsl:with-param name="name" select="&quot;mimetype&quot;"/><xsl:with-param name="content" select="&quot;application/rss+xml&quot;"/></xsl:call-template><title><xsl:choose><xsl:when test="channel/title and not(channel/title=&quot;&quot;)"><xsl:value-of select="channel/title"/></xsl:when><xsl:otherwise>Streamburner News Reader</xsl:otherwise></xsl:choose></title><xsl:if test="$rtl"><meta name="semitic" content="true"/></xsl:if></head><body><div id="feed"><header><div id="title"><xsl:choose><xsl:when test="channel/title and not(channel/title=&quot;&quot;)"><xsl:attribute name="title"><xsl:value-of select="channel/title"/></xsl:attribute><xsl:value-of select="channel/title"/></xsl:when><xsl:otherwise><div class="empty"/></xsl:otherwise></xsl:choose></div><xsl:choose><xsl:when test="channel/itunes:subtitle"><div id="subtitle"><xsl:attribute name="title"><xsl:value-of select="channel/itunes:subtitle"/></xsl:attribute><xsl:value-of select="channel/itunes:subtitle"/></div></xsl:when><xsl:when test="channel/description"><div id="subtitle"><xsl:attribute name="title"><xsl:value-of select="channel/description"/></xsl:attribute><xsl:value-of select="channel/description"/></div></xsl:when></xsl:choose></header><section id="links"><a title="Click to get the latest updates and news" onclick="location.href = &quot;feed:&quot; + location.href"></a><a title="Subscribe via SubToMe"><xsl:attribute name="href">https://www.subtome.com/#/subscribe?feeds=<xsl:value-of select="atom:link[@rel=&quot;self&quot;]/@href"/></xsl:attribute><xsl:attribute name="onclick">(function(btn){var z=document.createElement(&quot;script&quot;);document.subtomeBtn=btn;z.src=&quot;https://www.subtome.com/load.js&quot;;document.body.appendChild(z);})(this);return false;</xsl:attribute></a><a title="Learn about syndication feed" onclick="document.getElementById(&quot;aboutfeed&quot;).classList.toggle(&quot;show&quot;);" class="popup"><span class="popuptext" id="aboutfeed"/></a><a href="https://wikiless.org/wiki/Template:Aggregators" title="Get a feed reader for desktop and mobile"/><a href="https://wikiless.org/wiki/RSS" title="Learn the benefits of using feeds for personal and corporates"/></section><xsl:choose><xsl:when test="channel/item"><div id="toc"><xsl:for-each select="channel/item[not(position() &gt;20)]"><xsl:choose><xsl:when test="itunes:subtitle"><xsl:element name="a"><xsl:attribute name="href"><xsl:text>#newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="itunes:subtitle"/></xsl:element></xsl:when><xsl:when test="title"><xsl:element name="a"><xsl:attribute name="href"><xsl:text>#newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="title"/></xsl:element></xsl:when></xsl:choose></xsl:for-each></div></xsl:when></xsl:choose><section id="articles"><xsl:choose><xsl:when test="channel/item"><xsl:for-each select="channel/item[not(position() &gt;20)]"><div class="entry"><xsl:choose><xsl:when test="itunes:subtitle"><div class="title"><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="link"/></xsl:attribute><xsl:attribute name="title"><xsl:value-of select="itunes:subtitle"/></xsl:attribute><xsl:attribute name="id"><xsl:text>newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="itunes:subtitle"/></xsl:element></div></xsl:when><xsl:when test="title"><div class="title"><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="link"/></xsl:attribute><xsl:attribute name="title"><xsl:value-of select="title"/></xsl:attribute><xsl:attribute name="id"><xsl:text>newspaper-oujs-</xsl:text><xsl:value-of select="position()"/></xsl:attribute><xsl:value-of select="title"/></xsl:element></div></xsl:when><xsl:otherwise><div class="warning rss2 title"/></xsl:otherwise></xsl:choose><xsl:choose><xsl:when test="geo:lat and geo:long"><xsl:variable name="lat" select="geo:lat"/><xsl:variable name="lng" select="geo:long"/><span class="geolocation"><a href="geo:{$lat},{$lng}">๐Ÿ“</a></span></xsl:when><xsl:when test="geo:Point"><xsl:variable name="lat" select="geo:Point/geo:lat"/><xsl:variable name="lng" select="geo:Point/geo:long"/><span class="geolocation"><a href="geo:{$lat},{$lng}">๐Ÿ“</a></span></xsl:when><xsl:when test="georss:point"><xsl:variable name="lat" select="substring-before(georss:point, &quot; &quot;)"/><xsl:variable name="lng" select="substring-after(georss:point, &quot; &quot;)"/><xsl:variable name="name" select="georss:featurename"/><span class="geolocation"><a href="geo:{$lat},{$lng}" title="{$name}">๐Ÿ“</a></span></xsl:when></xsl:choose><xsl:if test="pubDate"><div class="published"><xsl:value-of select="pubDate"/></div></xsl:if><xsl:choose><xsl:when test="content:encoded"><div class="content"><xsl:value-of select="content:encoded" disable-output-escaping="yes"/></div></xsl:when><xsl:when test="description"><div class="content"><xsl:value-of select="description" disable-output-escaping="yes"/></div></xsl:when><xsl:when test="itunes:summary"><div class="content"><xsl:value-of select="itunes:summary" disable-output-escaping="yes"/></div></xsl:when><xsl:otherwise><div class="warning rss2 description"/></xsl:otherwise></xsl:choose><xsl:if test="enclosure or media:content"><div class="enclosure" title="Right-click and Save link asโ€ฆ"><xsl:for-each select="enclosure"><xsl:element name="span"><xsl:attribute name="icon"><xsl:value-of select="substring-before(@type,&quot;/&quot;)"/></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="substring-before(@type,&quot;/&quot;)"/></xsl:attribute></xsl:element><xsl:if test="@length &gt; 0"><xsl:call-template name="transform-filesize"><xsl:with-param name="length" select="@length"/></xsl:call-template></xsl:if><xsl:element name="br"/></xsl:for-each><xsl:for-each select="media:content"><xsl:element name="span"><xsl:attribute name="icon"><xsl:value-of select="@medium"/></xsl:attribute></xsl:element><xsl:element name="a"><xsl:attribute name="href"><xsl:value-of select="@url"/></xsl:attribute><xsl:attribute name="download"/><xsl:call-template name="extract-filename"><xsl:with-param name="url" select="@url"/></xsl:call-template></xsl:element><xsl:element name="span"><xsl:attribute name="class"><xsl:value-of select="@medium"/></xsl:attribute></xsl:element><xsl:if test="@fileSize &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></div></xsl:for-each></xsl:when><xsl:otherwise><div class="notice no-entry"/></xsl:otherwise></xsl:choose></section></div></body></html></xsl:template><xsl:template name="metadata"><xsl:param name="name"/><xsl:param name="content"/><xsl:if test="$content and not($content=&quot;&quot;)"><xsl:element name="meta"><xsl:attribute name="name"><xsl:value-of select="$name"/></xsl:attribute><xsl:attribute name="content"><xsl:value-of select="$content"/></xsl:attribute></xsl:element></xsl:if></xsl:template><xsl:template name="extract-filename"><xsl:param name="url"/><xsl:choose><xsl:when test="substring($url, string-length($url) - string-length(&quot;/&quot;) + 1) = &quot;/&quot;"><xsl:value-of select="substring-after(substring($url, 1, string-length($url) - 1),&quot;://&quot;)"/></xsl:when><xsl:otherwise><xsl:choose><xsl:when test="contains($url,&quot;/&quot;) and contains(substring-after($url,&quot;/&quot;),&quot;.&quot;)"><xsl:call-template name="extract-filename"><xsl:with-param name="url" select="substring-after($url,&quot;/&quot;)"/></xsl:call-template></xsl:when><xsl:otherwise><xsl:value-of select="$url"/></xsl:otherwise></xsl:choose></xsl:otherwise></xsl:choose></xsl:template><xsl:template name="transform-filesize"><xsl:param name="length"/><xsl:choose><xsl:when test="$length &lt; 2"><xsl:value-of select="$length"/>Byte</xsl:when><xsl:when test="floor($length div 1024) &lt; 1"><xsl:value-of select="$length"/>Bytes</xsl:when><xsl:when test="floor($length div (1024 * 1024)) &lt; 1"><xsl:value-of select="floor($length div 1024)"/>.<xsl:value-of select="substring($length mod 1024,0,2)"/>KiB</xsl:when><xsl:when test="floor($length div (1024 * 1024 * 1024)) &lt; 1"><xsl:value-of select="floor($length div (1024 * 1024))"/>.<xsl:value-of select="substring($length mod (1024 * 1024),0,2)"/>MiB</xsl:when><xsl:otherwise><xsl:value-of select="floor($length div (1024 * 1024 * 1024))"/>.<xsl:value-of select="substring($length mod (1024 * 1024 * 1024),0,2)"/>GiB</xsl:otherwise></xsl:choose></xsl:template></xsl:stylesheet>';

  const xsltStylesheet =
    domParser
    .parseFromString(
      xslFile, 'application/xml' // application/xslt+xml or text/xsl are Not Valid
    );

  xsltProcessor
  .importStylesheet(
    xsltStylesheet
  );

  newDocument =
    xsltProcessor
    .transformToDocument(
      xmlDocument
    );

  // FIXME
  // Blocked due to server policy
  // https://archlinux.org/feeds/packages/
  // https://www.openstreetmap.org/traces/rss
  // NOTE
  // We can use inline style (i.e. style="")
  // but that would make the code lesser manageable
  stylesheet_ltr = "html,body{padding:0;margin:0}body{background:WhiteSmoke;color:#333;hyphens:auto}header{margin:0 -1em 1em -1em;margin-bottom:1em;padding:1em 1em 0 1em}#feed{min-width:400px;overflow:auto;position:relative}*{max-width:100%;object-fit:contain;height:auto}#logo{display:inline-block;float:left;overflow:hidden;position:relative;height:60px;width:60px;margin-right:9;padding-top:12}#logo>a>img{margin:auto;max-width:100%;position:absolute;width:5em;bottom:0;right:0;left:0;top:0}#title{font-variant:small-caps;text-align:center}#title{font-weight:bold;margin-bottom:0;overflow:hidden;-webkit-line-clamp:2;white-space:nowrap;margin:0;font-size:2.2em}#title .empty:before{font-variant:small-caps;content:'Streamburner News Dashboard';text-align:center}#subtitle{overflow:hidden;-webkit-line-clamp:2;white-space:wrap;font-weight:bold;font-size:1.5em;text-align:center;font-variant:small-caps}.container{display:flex}#links{border-top:.1em solid;margin:auto;width:50%;text-align:center;direction:ltr}#links>a{padding:10px;text-decoration:none;font-size:70%}#links>a:nth-child(1):after{content:'Follow';cursor:pointer;border-color:grey;border-left-style:solid;border-radius:1px;padding:6px;background:#eee !important}#links>a:nth-child(2):after{content:'SubToMe';cursor:pointer}#links>a:nth-child(3):after{content:'Whatโ€™s This?';cursor:help}#links>a:nth-child(4):after{content:'Get a News Reader'}#links>a:nth-child(5):after{content:'More Info...'}#toc{margin-left:3%;padding:5px}#toc:before{content:'Latest Updates';font-size:76%;font-weight:bold}#toc>a{text-decoration:none;font-size:66%;display:block;padding:5px 0;margin-left:1%}#toc>a:hover{text-decoration:underline}#toc>a:visited{text-decoration:line-through}.popup{position:relative;display:inline-block;cursor:pointer}.popup .popuptext{visibility:hidden;width:160px;color:#fff;border-radius:6px;padding:18px;position:absolute;z-index:1;background-color:#555;position:absolute;right:4%;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:50%;text-align:left}.popup .popuptext:after{content:'๐Ÿ“ฐ Web Syndication News Feed \\A\\A ' ' Syndication Feed is a mean for content and media publishers ' 'to reach a wider audience easily. ' ' It allows you to receive information directly without the ' 'going from site to site. \\A\\A ' ' Essentially, a feed is a function that allows โ€œFeed Readersโ€ ' 'to access multiple websites automatically looking for new ' 'content and then posting the information about new content ' 'and updates to another website, mobile app or desktop software ' 'at the office. \\A\\A ' ' Feeds provide a way for users to keep up with the latest ' 'news, events and package and delivery status information ' 'posted on different websites such as social networks, torrent ' 'indexers or podcasts in one spot.';white-space:pre-wrap;line-height:1.8;font-size:150%}.popup .show{visibility:visible;-webkit-animation:fadeIn 1s;animation:fadeIn 1s}@-webkit-keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}#articles{justify-content:space-between;max-width:90%;margin:0 auto;padding:10px 0}#articles>*{margin:.5em;white-space:normal;vertical-align:top;margin-bottom:50px}.entry{border-bottom:inset;margin-left:auto;margin-right:auto;overflow:auto;line-height:1.6;font-size:85%;overflow-x:hidden}.entry:hover{background:#f8f9fa}.entry *{max-width:98%}.entry>a{white-space:normal}.title{cursor:pointer;display:inline-block;font-size:110%;font-weight:bold;text-decoration:underline;overflow:visible;text-overflow:ellipsis;white-space:nowrap;font-variant:small-caps}a{color:#333;display:inline-block}.geolocation>a{text-decoration:none;padding-left:6px}.author{font-size:75%;margin:0 auto 0 auto}.author:before{content:'By '}.author:after{content:' / '}.published,.updated{font-size:75%;margin:0 auto 0 auto}.content{margin:15px auto 15px 1%;inline-size:95%;text-indent:3px}.content[type='text']{font-family:monospace}code{overflow:auto;display:block;max-height:300px;border-radius:4px}.enclosure{background:#eee;border:1px solid GrayText;border-radius:4px;clear:both;color:#525c66;cursor:help;direction:ltr;font-size:.8em;margin:5px auto 15px 1%;padding:15px;vertical-align:middle;border:1px solid #aaa;border-radius:.5em;max-width:100%;moz-border-radius:.5em;padding:1em}.enclosure>span:after{content:' (Document file) '}.enclosure>span.executable:after{content:' (Executable file) '}.enclosure>span.image:after{content:' (Image file) '}.enclosure>span.audio:after{content:' (Audio file) '}.enclosure>span.video:after{content:' (Video file) '}.enclosure>span[icon]:after{content:'๐Ÿ“„๏ธ';margin:3px}.enclosure>span[icon='executable']:after{content:'๐Ÿ“ฆ๏ธ';margin:3px}.enclosure>span[icon='image']:after{content:'๐Ÿ–ผ๏ธ';margin:3px}.enclosure>span[icon='audio']:after{content:'๐ŸŽผ๏ธ';margin:3px}.enclosure>span[icon='video']:after{content:'๐Ÿ“ฝ๏ธ';margin:3px}.notice{text-align:center;display:block;font-size:130%;font-weight:lighter;font-variant-caps:small-caps;font-style:oblique;color:FireBrick}.warning{display:block;font-size:60%;font-weight:bold;color:DarkRed}.no-entry:after{content:'This news feed is empty'}.atom1.author:after{content:'Atom 1.0 Warning: Element </author> is missing'}.atom1.id:after{content:'Atom 1.0 Warning: Element </id> is missing'}.atom1.link:after{content:'Atom 1.0 Warning: Element </link> is missing'}.atom1.published:after{content:'Atom 1.0 Warning: Element </published> is missing'}.atom1.title:after{content:'Atom 1.0 Warning: Element </title> is missing'}.rss2.description:after{content:'RSS 2.0 Warning: Element </description> is missing'}.rss2.link:after{content:'RSS 2.0 Warning: Element </link> is missing'}.rss2.title:after{content:'RSS 2.0 Warning: Element </title> is missing'}abbr,acronym{border-bottom:1px dotted #c30}dt{font-weight:bold}";
  stylesheet_rtl = "html,body{text-align:right}#feed{direction:rtl}#logo{float:right;margin-left:9}.geolocation>a{padding-right:6px}.image{float:right;margin-left:40px;margin-right:auto}";
  style = newDocument.createElement('style');
  newDocument.head.append(style);
  style.type = 'text/css';

  // NOTE
  // Because we use JS, maybe it is better to set direction by
  // the letters of the initial set of words.
  isRTL =
    newDocument
    .querySelector(
      'meta[name="semitic"]'
    );

  if (isRTL) {
    style.innerHTML =
      stylesheet_ltr +
      stylesheet_rtl;
  } else {
    style.innerHTML =
      stylesheet_ltr;
  }

  //var newDoc = document.adoptNode(newDoc.documentElement, true);
  insertDocument =
    document
    .importNode(
      newDocument
      .documentElement,
      true
    );

  removeDocument =
    document
    .documentElement;

  document
  .replaceChild(
    insertDocument,
    removeDocument
  );

}

function queryByXPath(queries) {
  var result;
  let i = 0;
  do {
    result = document.evaluate(
      queries[i], document,
      null, XPathResult.STRING_TYPE);
    i = i + 1;
    result = result.stringValue;
  } while (!result && i < queries.length);
  return result;
}