From a9edbf75e7538c3a1ed63ab8339df2917fc15322 Mon Sep 17 00:00:00 2001 From: Zerthick Date: Sun, 9 Aug 2026 23:11:14 -0700 Subject: [PATCH 1/2] fix: use dc:creator instead of author for item authors RSS 2.0 spec requires to be an email address; the dc namespace is already declared and is the correct element for names. - Change to in rss.xml.jinja2 template - Update XSL HTML preview to read dc:creator and managingEditor - Add test verifying dc:creator usage --- mkdocs_rss_plugin/templates/default.xsl | 8 +- mkdocs_rss_plugin/templates/rss.xml.jinja2 | 4 +- tests/test_dc_creator.py | 97 ++++++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 tests/test_dc_creator.py diff --git a/mkdocs_rss_plugin/templates/default.xsl b/mkdocs_rss_plugin/templates/default.xsl index 3a11d00..5f989c0 100644 --- a/mkdocs_rss_plugin/templates/default.xsl +++ b/mkdocs_rss_plugin/templates/default.xsl @@ -131,8 +131,8 @@ a:hover {

- - By + + By @@ -162,8 +162,8 @@ a:hover {
- - Par + + By — diff --git a/mkdocs_rss_plugin/templates/rss.xml.jinja2 b/mkdocs_rss_plugin/templates/rss.xml.jinja2 index 5328672..9fd63c5 100644 --- a/mkdocs_rss_plugin/templates/rss.xml.jinja2 +++ b/mkdocs_rss_plugin/templates/rss.xml.jinja2 @@ -34,10 +34,10 @@ {% for item in feed.entries %} {{ item.title|e }} - {# Authors loop #} + {# Authors loop - use dc:creator for names (RSS requires email) #} {% if item.authors is not none %} {% for author in item.authors %} - {{ author }} + {{ author }} {% endfor %} {% endif %} {# Categories loop #} diff --git a/tests/test_dc_creator.py b/tests/test_dc_creator.py new file mode 100644 index 0000000..a6fb358 --- /dev/null +++ b/tests/test_dc_creator.py @@ -0,0 +1,97 @@ +#! python3 # noqa: E265 + +"""Test that item authors use instead of . + +RSS 2.0 spec requires to be an email address. +Human-readable names should use . + +Usage from the repo root folder: + + python -m unittest tests.test_dc_creator + +""" + +# ############################################################################# +# ########## Libraries ############# +# ################################## + +# Standard library +import logging +import tempfile +from pathlib import Path +from traceback import format_exception +from xml.etree import ElementTree + +# test suite +from tests.base import BaseTest + +# -- Globals -- +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +OUTPUT_RSS_FEED_CREATED = "feed_rss_created.xml" + +# ############################################################################# +# ########## Classes ############### +# ################################## + + +class TestDcCreator(BaseTest): + """Test that is used instead of for item authors.""" + + def test_dc_creator_instead_of_author(self): + """Verify that item authors use instead of . + + RSS 2.0 spec requires to be an email address. + Human-readable names should use . + """ + with tempfile.TemporaryDirectory() as tmpdirname: + cli_result = self.build_docs_setup( + testproject_path="docs", + mkdocs_yml_filepath=Path("tests/fixtures/mkdocs_complete.yml"), + output_path=tmpdirname, + strict=True, + ) + + if cli_result.exception is not None: + e = cli_result.exception + logger.debug(format_exception(type(e), e, e.__traceback__)) + + self.assertEqual(cli_result.exit_code, 0) + self.assertIsNone(cli_result.exception) + + # Parse the raw XML to check element names + rss_path = Path(tmpdirname) / OUTPUT_RSS_FEED_CREATED + tree = ElementTree.parse(rss_path) + root = tree.getroot() + + # RSS namespace + ns = { + "rss": "http://www.w3.org/2005/Atom", + "dc": "http://purl.org/dc/elements/1.1/", + } + + # Find all item elements + items = root.findall(".//item") + self.assertGreater(len(items), 0, "Expected at least one item in feed") + + for item in items: + # Check that is NOT used for item authors + author_elem = item.find("author") + self.assertIsNone( + author_elem, + "Found element in item. RSS 2.0 requires to be " + "an email address. Use for human-readable names.", + ) + + # Check that IS used when authors are present + # The page_with_meta.md has authors, so at least one item should have dc:creator + dc_creators = item.findall("dc:creator", ns) + # Items with authors should have dc:creator elements + # Items without authors may not have any + if item.find("title").text == "Page With Explicit Metadata": + self.assertGreater( + len(dc_creators), + 0, + "Expected elements for item with authors.", + ) From 841af0d341ed928ee5364c8f26fa5ee73f9dff20 Mon Sep 17 00:00:00 2001 From: Zerthick Date: Sun, 25 Oct 2026 13:29:12 -0700 Subject: [PATCH 2/2] Address review feedback - escape author names in template (consistent with title and managingEditor) - fold dc:creator assertions into test_simple_build_complete and drop dedicated test module --- mkdocs_rss_plugin/templates/rss.xml.jinja2 | 2 +- tests/test_build.py | 14 ++++ tests/test_dc_creator.py | 97 ---------------------- 3 files changed, 15 insertions(+), 98 deletions(-) delete mode 100644 tests/test_dc_creator.py diff --git a/mkdocs_rss_plugin/templates/rss.xml.jinja2 b/mkdocs_rss_plugin/templates/rss.xml.jinja2 index 9fd63c5..a81c50b 100644 --- a/mkdocs_rss_plugin/templates/rss.xml.jinja2 +++ b/mkdocs_rss_plugin/templates/rss.xml.jinja2 @@ -37,7 +37,7 @@ {# Authors loop - use dc:creator for names (RSS requires email) #} {% if item.authors is not none %} {% for author in item.authors %} - {{ author }} + {{ author|e }} {% endfor %} {% endif %} {# Categories loop #} diff --git a/tests/test_build.py b/tests/test_build.py index 8b18057..2a1d088 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -20,6 +20,7 @@ import unittest from pathlib import Path from traceback import format_exception +from xml.etree import ElementTree # 3rd party import feedparser @@ -172,6 +173,19 @@ def test_simple_build_complete(self): "Page with complete meta", {e.title for e in feed_parsed.entries} ) + # authors: RSS 2.0 requires to be an email address, + # so human-readable names must use instead + tree = ElementTree.parse(Path(tmpdirname) / OUTPUT_RSS_FEED_CREATED) + dc_namespace = "http://purl.org/dc/elements/1.1/" + for item in tree.getroot().findall(".//item"): + self.assertIsNone(item.find("author")) + if item.find("title").text == "Page with complete meta": + creators = item.findall(f"{{{dc_namespace}}}creator") + self.assertEqual( + [c.text for c in creators], + ["Guts", "Tim Vink", "liang2kl"], + ) + def test_simple_build_disabled(self): with tempfile.TemporaryDirectory() as tmpdirname: cli_result = self.build_docs_setup( diff --git a/tests/test_dc_creator.py b/tests/test_dc_creator.py deleted file mode 100644 index a6fb358..0000000 --- a/tests/test_dc_creator.py +++ /dev/null @@ -1,97 +0,0 @@ -#! python3 # noqa: E265 - -"""Test that item authors use instead of . - -RSS 2.0 spec requires to be an email address. -Human-readable names should use . - -Usage from the repo root folder: - - python -m unittest tests.test_dc_creator - -""" - -# ############################################################################# -# ########## Libraries ############# -# ################################## - -# Standard library -import logging -import tempfile -from pathlib import Path -from traceback import format_exception -from xml.etree import ElementTree - -# test suite -from tests.base import BaseTest - -# -- Globals -- -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - -OUTPUT_RSS_FEED_CREATED = "feed_rss_created.xml" - -# ############################################################################# -# ########## Classes ############### -# ################################## - - -class TestDcCreator(BaseTest): - """Test that is used instead of for item authors.""" - - def test_dc_creator_instead_of_author(self): - """Verify that item authors use instead of . - - RSS 2.0 spec requires to be an email address. - Human-readable names should use . - """ - with tempfile.TemporaryDirectory() as tmpdirname: - cli_result = self.build_docs_setup( - testproject_path="docs", - mkdocs_yml_filepath=Path("tests/fixtures/mkdocs_complete.yml"), - output_path=tmpdirname, - strict=True, - ) - - if cli_result.exception is not None: - e = cli_result.exception - logger.debug(format_exception(type(e), e, e.__traceback__)) - - self.assertEqual(cli_result.exit_code, 0) - self.assertIsNone(cli_result.exception) - - # Parse the raw XML to check element names - rss_path = Path(tmpdirname) / OUTPUT_RSS_FEED_CREATED - tree = ElementTree.parse(rss_path) - root = tree.getroot() - - # RSS namespace - ns = { - "rss": "http://www.w3.org/2005/Atom", - "dc": "http://purl.org/dc/elements/1.1/", - } - - # Find all item elements - items = root.findall(".//item") - self.assertGreater(len(items), 0, "Expected at least one item in feed") - - for item in items: - # Check that is NOT used for item authors - author_elem = item.find("author") - self.assertIsNone( - author_elem, - "Found element in item. RSS 2.0 requires to be " - "an email address. Use for human-readable names.", - ) - - # Check that IS used when authors are present - # The page_with_meta.md has authors, so at least one item should have dc:creator - dc_creators = item.findall("dc:creator", ns) - # Items with authors should have dc:creator elements - # Items without authors may not have any - if item.find("title").text == "Page With Explicit Metadata": - self.assertGreater( - len(dc_creators), - 0, - "Expected elements for item with authors.", - )