Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions packages/markitdown/src/markitdown/converters/_rss_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,24 @@ def _get_data_by_tag_name(
) -> Union[str, None]:
"""Get data from first child element with the given tag name.
Returns None when no such element is found.

An element's text is not necessarily a single node: a value written as
``<description>\\n <![CDATA[...]]>\\n</description>`` reaches the parser
as whitespace, then the CDATA section, then more whitespace. Reading
only the first of those returns the layout and drops the value, so all
of the element's own text and CDATA children are joined.
"""
if element.namespaceURI == ATOM_NAMESPACE:
nodes = element.getElementsByTagNameNS(ATOM_NAMESPACE, tag_name)
else:
nodes = element.getElementsByTagName(tag_name)
if not nodes:
return None
fc = nodes[0].firstChild
if fc:
if hasattr(fc, "data"):
return fc.data
return None
parts = [
child.data
for child in nodes[0].childNodes
if child.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE)
]
Comment on lines +343 to +347
if not parts:
return None
return "".join(parts)
94 changes: 94 additions & 0 deletions packages/markitdown/tests/test_rss_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,97 @@ def test_atom_plain_text_layout_whitespace_is_removed() -> None:
"",
"Then check status.",
]


def test_rss_description_survives_a_cdata_section_on_its_own_line() -> None:
"""A pretty-printed CDATA payload is a later child, not the first one."""
feed = b"""<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel>
<title>Example feed</title>
<description>Example feed description</description>
<item>
<title>Example item</title>
<description>
<![CDATA[<p>The <strong>body</strong> of the item.</p>]]>
</description>
</item>
</channel></rss>
"""

result = RssConverter().convert(io.BytesIO(feed), StreamInfo(extension=".rss"))

assert "The **body** of the item." in result.markdown


def test_rss_title_survives_a_cdata_section_in_the_middle() -> None:
"""Text either side of a CDATA section belongs to the same value."""
feed = b"""<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel>
<title>Example feed</title>
<description>Example feed description</description>
<item>
<title>Quarterly <![CDATA[R&D]]> report</title>
<description>Body.</description>
</item>
</channel></rss>
"""

result = RssConverter().convert(io.BytesIO(feed), StreamInfo(extension=".rss"))

assert "## Quarterly R&D report" in result.markdown


def test_atom_summary_survives_a_cdata_section_on_its_own_line() -> None:
feed = b"""<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example feed</title>
<entry>
<title>Example entry</title>
<summary type="html">
<![CDATA[<p>A <em>structured</em> summary.</p>]]>
</summary>
</entry>
</feed>
"""

result = RssConverter().convert(
io.BytesIO(feed), StreamInfo(mimetype="application/atom+xml")
)

assert "A *structured* summary." in result.markdown


def test_rss_cdata_only_description_is_unchanged() -> None:
"""The case that already worked must keep working."""
feed = b"""<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel>
<title>Example feed</title>
<description>Example feed description</description>
<item>
<title>Example item</title>
<description><![CDATA[<p>Only a CDATA section.</p>]]></description>
</item>
</channel></rss>
"""

result = RssConverter().convert(io.BytesIO(feed), StreamInfo(extension=".rss"))

assert "Only a CDATA section." in result.markdown


def test_rss_item_without_a_description_is_still_converted() -> None:
"""An absent element must stay absent, not become an empty string."""
feed = b"""<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel>
<title>Example feed</title>
<description>Example feed description</description>
<item>
<title>Example item</title>
</item>
</channel></rss>
"""

result = RssConverter().convert(io.BytesIO(feed), StreamInfo(extension=".rss"))

assert result.title == "Example feed"
assert "## Example item" in result.markdown