Skip to content

RSS/Atom converter silently truncates content to the first child node, dropping CDATA sections #2461

Closed

Description

The Bug

RssConverter._get_data_by_tag_name (packages/markitdown/src/markitdown/converters/_rss_converter.py, lines 308-321), the shared helper used to extract title, description, pubDate, content:encoded (RSS) and title, subtitle, updated, summary/content (Atom), only reads the first child node of a matched element instead of concatenating all of its text-bearing children:

def _get_data_by_tag_name(self, element, tag_name):
    """Get data from first child element with the given tag name.
    Returns None when no such element is found.
    """
    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

In xml.dom.minidom, an element's children are not automatically merged. Any element that mixes plain text with a <![CDATA[...]]> section, or just has whitespace indentation before a CDATA block (extremely common in pretty-printed feeds), produces multiple sibling child nodes. Only the first one is ever read - everything after it is silently dropped, with no error or warning, and the leftover whitespace/text is still truthy so calling code has no signal anything went wrong.

Steps to Reproduce

Case 1, whitespace-indented CDATA (the more dangerous variant - the result is truthy and looks superficially fine):

from xml.dom import minidom

xml_bytes = b"""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>
      <![CDATA[My Real Channel Title]]>
    </title>
    <description>Feed description</description>
  </channel>
</rss>
"""
doc = minidom.parseString(xml_bytes)

def get_data_by_tag_name(element, tag_name):
    nodes = element.getElementsByTagName(tag_name)
    if not nodes:
        return None
    fc = nodes[0].firstChild
    if fc and hasattr(fc, "data"):
        return fc.data
    return None

channel = doc.getElementsByTagName("channel")[0]
print(repr(get_data_by_tag_name(channel, "title")))

Actual: '\n ' - the real title "My Real Channel Title" (held in the CDATASection sibling) is completely lost. _parse_rss_type's if channel_title: md_text += f"# {channel_title}\n" still fires (the whitespace string is truthy) and emits a heading containing only whitespace.

Expected: "My Real Channel Title".

Case 2, mixed text/CDATA content:

<description>Intro text before html. <![CDATA[<p>Rich HTML fragment that matters.</p>]]> Trailing text after html too.</description>

Actual: 'Intro text before html. ' - both the CDATA HTML fragment and the trailing sentence are dropped.
Expected: all three pieces concatenated.

Fix direction

Concatenate every text-bearing child instead of returning only firstChild.data, e.g.:

from xml.dom.minidom import Node

def _get_data_by_tag_name(self, element, tag_name):
    nodes = element.getElementsByTagName(tag_name)
    if not nodes:
        return None
    parts = [
        child.data
        for child in nodes[0].childNodes
        if child.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE)
    ]
    return "".join(parts).strip() if parts else None

Duplicate check

Searched RSS+CDATA, firstChild, RSS+truncat, getElementsByTagName, nodeValue, silently+truncat (all 0 results), plus broader RSS+description (12 hits) and rss+converter (4 hits) whose bodies were read in full - all about different mechanisms: double-HTML-parsing losing literal angle brackets (#2373-2375), Atom XHTML preservation (#2297), a RecursionError fallback (#2333, already implemented), an UnboundLocalError on missing title (#1946, #1989, #2164), RSS metadata leakage plus an unrelated pptx fix (#1785), and two multi-bug grab-bag PRs (#2329, #1723). None touch firstChild, CDATA, or multi-child-node text extraction.

Activity

  1. VANDRANKI commented on Sep 11, 2026

    @VANDRANKI
    Author

    Closing this myself: this doesn't reproduce on current main. PR #2432 (merged 2026-09-09, before I filed this issue on 2026-09-11) already rewrote _get_data_by_tag_name/_read_content to walk all of an element's child nodes (TEXT_NODE and CDATA_SECTION_NODE) and concatenate them, instead of reading only firstChild. I must have been looking at a stale copy of the file when I filed this. Sorry for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions