Skip to content

fix: Keep single-element RSS feed elements as lists - #65

Open
rex4539 wants to merge 1 commit into
bellingcat:mainfrom
rex4539:fix/xmltodict-force-list
Open

rex4539 wants to merge 1 commit into
bellingcat:mainfrom
rex4539:fix/xmltodict-force-list

Conversation

@rex4539

@rex4539 rex4539 commented Aug 3, 2026

Copy link
Copy Markdown

Fixes #64.

Problem

xmltodict collapses a single-element list into a plain dict. parse_rss_feed_data assumes item and edgar:xbrlFile are always lists, so when the feed carries exactly one of either, the code iterates over dict keys and every item fails to parse:

TypeError: string indices must be integers, not 'str'          # single <item>
AttributeError: 'str' object has no attribute 'get'            # single <edgar:xbrlFile>

The per-item except swallows both, so the command exits 0 and writes a header-only CSV — no rows, no error status.

Fix

Pin the two repeated elements with xmltodict's own force_list:

FORCE_LIST_ELEMENTS = ("item", "edgar:xbrlFile")
...
items = xmltodict.parse(response.content, force_list=FORCE_LIST_ELEMENTS)["rss"]["channel"]["item"]

No call-site changes: the loops already expect lists.

Tests

tests/test_rss_parsing.py parametrises parse_rss_feed_data over the four combinations of many/one <item> × many/one <edgar:xbrlFile>.

Against unpatched main, the three single-element cases fail and the many-items-many-files control passes — so the tests pin this bug specifically rather than merely being red. With the fix, all four pass. Full suite: 788 passed.

One thing the tests document: when a filing has a single XBRL file, xbrl_files is a bare string rather than a one-element list, because unpack_singleton_list unwraps it on the way out. That is existing intended behaviour and this PR does not change it — worth knowing that the two singleton behaviours are deliberate in opposite directions.

Relationship to #63

Independent, and branched from main rather than stacked, so the two can merge in either order. They touch rss.py in different functions (parse_rss_feed_data here, fetch_rss_feed there) and add separate test files, so no conflict either way.

Worth noting the two bugs compound: #63 means the RSS command cannot write rows at all, and this one means it silently parses zero rows on certain feed shapes. Both present as "empty output, exit 0", which is probably why neither surfaced as a bug report.

`xmltodict` collapses a single-element list into a plain dict, so a feed
carrying exactly one `<item>`, or an item carrying exactly one
`<edgar:xbrlFile>`, made `parse_rss_feed_data` iterate over dict keys
instead of elements. Every item then failed with either:

    TypeError: string indices must be integers, not 'str'
    AttributeError: 'str' object has no attribute 'get'

Both are caught by the per-item `except`, so the command still exited 0
and wrote a header-only CSV -- no rows and no error status.

Parsing now pins the two repeated elements with `force_list`, which is
xmltodict's built-in for this, so no call site changes.

Latent against `xbrlrss.all.xml`, which normally carries many items each
with several files, but reachable on a quiet feed, on a filing with a
single XBRL file, or for anyone calling `parse_rss_feed_data` directly.

Fixes bellingcat#64

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jordan-gillard

Copy link
Copy Markdown
Collaborator

Hi @rex4539! Thank you for making this PR. I apologize for the wait. I've been out of town.

I really appreciate the internal contribution. I will review/merge by tomorrow EOD.

@rex4539

rex4539 commented Sep 13, 2026

Copy link
Copy Markdown
Author

Any update?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RSS feed parsing drops every item when the feed has a single <item> or a single <edgar:xbrlFile>

2 participants