diff options
| author | Baptiste Mispelon <bmispelon@gmail.com> | 2024-04-26 22:10:40 +0200 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-06-18 17:25:43 +0200 |
| commit | 62300b81cf7687d176af3b40aa6101942801292d (patch) | |
| tree | 12190b41d01e55f0799a439a6a690d7c644e0464 /django/utils/feedgenerator.py | |
| parent | ce1ad98565d98b7939367b2bcab755c3555ceb42 (diff) | |
Fixed #12978 -- Added support for RSS feed stylesheets.
Diffstat (limited to 'django/utils/feedgenerator.py')
| -rw-r--r-- | django/utils/feedgenerator.py | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index 3bd456ca68..fae3271430 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -24,6 +24,7 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 import datetime import email +import mimetypes from io import StringIO from urllib.parse import urlparse @@ -57,6 +58,53 @@ def get_tag_uri(url, date): return "tag:%s%s:%s/%s" % (bits.hostname, d, bits.path, bits.fragment) +def _guess_stylesheet_mimetype(url): + """ + Return the given stylesheet's mimetype tuple, using a slightly custom + version of Python's mimetypes.guess_type(). + """ + mimetypedb = mimetypes.MimeTypes() + + # The official mimetype for XSLT files is technically `application/xslt+xml` + # but as of 2024 almost no browser supports that (they all expect text/xsl). + # On top of that, windows seems to assume that the type for xsl is text/xml. + mimetypedb.readfp(StringIO("text/xsl\txsl\ntext/xsl\txslt")) + + return mimetypedb.guess_type(url) + + +class Stylesheet: + """An RSS stylesheet""" + + def __init__(self, url, mimetype="", media="screen"): + self._url = url + self._mimetype = mimetype + self.media = media + + # Using a property to delay the evaluation of self._url as late as possible + # in case of a lazy object (like reverse_lazy(...) for example). + @property + def url(self): + return iri_to_uri(self._url) + + @property + def mimetype(self): + if self._mimetype == "": + return _guess_stylesheet_mimetype(self.url)[0] + return self._mimetype + + def __str__(self): + data = [f'href="{self.url}"'] + if self.mimetype is not None: + data.append(f'type="{self.mimetype}"') + if self.media is not None: + data.append(f'media="{self.media}"') + return " ".join(data) + + def __repr__(self): + return repr((self.url, self.mimetype, self.media)) + + class SyndicationFeed: "Base class for all syndication feeds. Subclasses should provide write()" @@ -75,12 +123,24 @@ class SyndicationFeed: feed_copyright=None, feed_guid=None, ttl=None, + stylesheets=None, **kwargs, ): def to_str(s): return str(s) if s is not None else s + def to_stylesheet(s): + return s if isinstance(s, Stylesheet) else Stylesheet(s) + categories = categories and [str(c) for c in categories] + + if stylesheets is not None: + if isinstance(stylesheets, (Stylesheet, str)): + raise TypeError( + f"stylesheets should be a list, not {stylesheets.__class__}" + ) + stylesheets = [to_stylesheet(s) for s in stylesheets] + self.feed = { "title": to_str(title), "link": iri_to_uri(link), @@ -95,6 +155,7 @@ class SyndicationFeed: "feed_copyright": to_str(feed_copyright), "id": feed_guid or link, "ttl": to_str(ttl), + "stylesheets": stylesheets, **kwargs, } self.items = [] @@ -166,6 +227,12 @@ class SyndicationFeed: """ pass + def add_stylesheets(self, handler): + """ + Add stylesheet(s) to the feed. Called from write(). + """ + pass + def item_attributes(self, item): """ Return extra attributes to place on each item (i.e. item/entry) element. @@ -228,6 +295,9 @@ class RssFeed(SyndicationFeed): def write(self, outfile, encoding): handler = SimplerXMLGenerator(outfile, encoding, short_empty_elements=True) handler.startDocument() + # Any stylesheet must come after the start of the document but before any tag. + # https://www.w3.org/Style/styling-XML.en.html + self.add_stylesheets(handler) handler.startElement("rss", self.rss_attributes()) handler.startElement("channel", self.root_attributes()) self.add_root_elements(handler) @@ -247,6 +317,10 @@ class RssFeed(SyndicationFeed): self.add_item_elements(handler, item) handler.endElement("item") + def add_stylesheets(self, handler): + for stylesheet in self.feed["stylesheets"] or []: + handler.processingInstruction("xml-stylesheet", stylesheet) + def add_root_elements(self, handler): handler.addQuickElement("title", self.feed["title"]) handler.addQuickElement("link", self.feed["link"]) |
