What to know
- Parse a bounded trusted local fixture without retrieving a feed.
- Reject unapproved publishers, ambiguous duplicates and out-of-window dates.
- Citation cards are research inputs, not verified reporting or permission to publish.
- limits
- Local bounded fixtures passed; no external model or production benchmark tested. Not an arbitrary untrusted-XML parser, feed fetcher, plagiarism checker or editorial approval system.
- duration
- 20–30 minutes
- difficulty
- Intermediate
- environment
- Python 3.12.10; standard library; local synthetic fixtures
- verified at
- 2026-10-16
- prerequisites
- Python 3.12; a saved trusted synthetic RSS fixture; date-window policy.
The outcome
A research assistant should hand an editor evidence that can be checked, rather than immediately generating a finished story. This fixture converts a small local RSS document into citation cards containing a title, source URL and reported date. It never opens a network connection. The publisher domains and date window are explicit inputs to the policy, while publication remains a separate human decision. The code is an educational component, not an automated newsroom or a claim that a feed item is accurate.
Prepare the bounded input
Save the supplied file as rss_pipeline.py and run it with Python 3.12. Its main block contains one synthetic item using a real documentation domain; the item does not claim that a release actually happened. The function accepts a string of at most 64 KiB and a start/end date. It expects one RSS channel with at most 50 items. These small limits make the example inspectable. An arbitrary downloaded document requires a separate hardened ingestion design; Python’s XML documentation explicitly warns about malicious XML.
Read the output as a citation contract
A successful call returns a list sorted by reported date and URL. Duplicate items with identical card values collapse to one card. Reusing a URL with a different title or date raises an error instead of silently choosing a preferred version. The date comes from pubDate, which must include a timezone. It records the source’s calendar date, not the time BijliCoin would publish a later article. A feed’s report date can still differ from the event date inside the story.
Understand the URL and XML restrictions
The example rejects unsupported declarations, null characters, unapproved hosts and links outside its narrow HTTPS policy. It also sends percent-encoded or non-ASCII links to manual review by rejecting them. Those choices deliberately exclude some legitimate feeds. They reduce ambiguity in a controlled exercise and should not be advertised as a complete XML or SSRF defense. An allowlisted URL is still only a string until retrieval, redirects, content and publisher identity have been separately examined.
Failure and recovery
Malformed XML, missing dates, oversized input and conflicting duplicates fail the whole call. Preserve the original fixture and record the failing item outside any public log before correcting it. Do not change a date simply to make a record fit the window. The tests include valid input, duplicates, conflicting titles, bad links, absent timezones and old reports. Passing them establishes the behavior of these examples, not compatibility with every RSS dialect or protection against all hostile documents.
Extend without moving the boundary
A next version could add a separate editor-reviewed event-date field and an explicit reason for excluding an item. Keep those fields distinct from the source-provided values. If retrieval is later required, build it as a separately authorized, bounded component with redirect, DNS and content-size controls. The parser should still return inspectable cards. A model may help explain a source, but it should never replace the date/URL checks or convert successful parsing into an instruction to publish.
Runnable Python fixture
"""Trusted local RSS fixture -> dated citation cards; no network requests."""
from datetime import date
from email.utils import parsedate_to_datetime
from urllib.parse import urlsplit, urlunsplit
import xml.etree.ElementTree as ET
PUBLISHERS = frozenset({"www.python.org", "www.postgresql.org"})
def citation_url(raw):
if type(raw) is not str or not raw or len(raw) > 2048:
raise ValueError("invalid link")
if any(ord(c) < 33 or ord(c) > 126 for c in raw) or "\\" in raw or "%" in raw:
raise ValueError("restricted link characters")
value = urlsplit(raw)
host = (value.hostname or "").lower()
if (value.scheme != "https" or host not in PUBLISHERS
or value.username is not None or value.password is not None
or value.netloc.lower() not in {host, host + ":443"}):
raise ValueError("publisher not permitted")
return urlunsplit(("https", host, value.path or "/", value.query, value.fragment))
def citation_cards(xml_text, start, end):
if type(xml_text) is not str or len(xml_text.encode("utf-8")) > 65536:
raise ValueError("fixture exceeds size bound")
if start > end:
raise ValueError("inverted date window")
upper = xml_text.upper()
if "<!DOCTYPE" in upper or "<!ENTITY" in upper or "\x00" in xml_text:
raise ValueError("unsupported XML declaration")
try:
root = ET.fromstring(xml_text)
except ET.ParseError:
raise ValueError("invalid RSS fixture") from None
if root.tag != "rss" or len(root.findall("channel")) != 1:
raise ValueError("expected one RSS channel")
items = root.findall("channel/item")
if len(items) > 50:
raise ValueError("too many items")
cards = {}
for item in items:
title = " ".join((item.findtext("title") or "").split())
if not title or len(title) > 200:
raise ValueError("invalid title")
url = citation_url(item.findtext("link") or "")
try:
stamp = parsedate_to_datetime(item.findtext("pubDate") or "")
except (TypeError, ValueError, IndexError):
raise ValueError("invalid report date") from None
if stamp.tzinfo is None:
raise ValueError("report date needs timezone")
day = stamp.date()
if not start <= day <= end:
raise ValueError("report outside window")
card = {"title": title, "url": url, "reported_date": day.isoformat()}
if url in cards and cards[url] != card:
raise ValueError("conflicting duplicate citation")
cards[url] = card
return sorted(cards.values(), key=lambda card: (card["reported_date"], card["url"]))
if __name__ == "__main__":
import json
fixture = """<rss><channel><item><title>Fixture release</title>
<link>https://www.python.org/downloads/</link>
<pubDate>Fri, 16 Oct 2026 09:00:00 +0000</pubDate>
</item></channel></rss>"""
print(json.dumps(citation_cards(fixture, date(2026, 9, 17), date(2026, 10, 16)), indent=2))
Verification and references
The RSS parser passed five fixture tests on 16 October 2026 using Python 3.12.10, covering valid cards, duplicates and rejected input. No feed was retrieved. Run rss_pipeline.py for the example and test_workflows.py for the complete suite.
- Python 3.12 ElementTree documentation (living reference; accessed 2026-10-16)
- Python 3.12 email date utilities (living reference; accessed 2026-10-16)
Editorial note: AI-assisted original BijliCoin educational workflow with locally tested synthetic fixtures. No external model, publisher feed, production service or private account was tested. AI-generated conceptual illustration.
Sources & context
See our editorial policy or report a correction.



