fix(ipynb): strip the UTF-8 BOM so a notebook is not emitted as raw JSON - #2425
fix(ipynb): strip the UTF-8 BOM so a notebook is not emitted as raw JSON#2425kevin (kevin9327) wants to merge 1 commit into
Conversation
Diogo Damasceno (Diogo-Damasceno)
left a comment
There was a problem hiding this comment.
Verified this on a local checkout (Arch Linux, Python 3.14.7, markitdown from source at b6e8bbd).
The bug reproduces before the fix. With the test file from this PR applied to main without the source change, 2 tests fail with the BOM surfacing in the output:
'{"nbformat": 4, "nbformat_minor": 5, ... }' = DocumentConverterResult.markdown
2 failed, 3 passed
Calling the converter directly on main raises JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig), so the raw-JSON output seen through the public API is the fallback after that exception — consistent with the description.
With the fix, the 5 new tests pass. I also ran the full test_module_misc.py: 13 failed / 55 passed here vs 13 failed / 51 passed on main — same 13 failures before and after (docx, xlsx, remote, speech, llm tests), so they are pre-existing in my environment and unrelated to this change. The PR adds 4 passing tests and no regressions.
One note, not a blocker: lstrip("\ufeff") strips any number of leading BOMs, where at most one is expected. Harmless here, and it matches the existing CSV converter at _csv_converter.py:82.
What this changes
A
.ipynbsaved with a UTF-8 BOM is not converted as a notebook. It comes back as its ownraw JSON:
instead of:
Why
IpynbConverter.converthands the decoded text straight tojson.loads:json.loadsrefuses a leadingU+FEFFby design:The failure is not visible to the caller.
_convertin_markitdown.pyrecords the exceptionas a failed attempt and keeps walking the converter list, and
PlainTextConverter-- whichaccepts anything textual -- succeeds. So the notebook is silently emitted as JSON source, and
a caller that only looks at the markdown has no way to tell.
A BOM on a
.ipynbis not exotic: Windows PowerShell'sOut-File/>and several editorswrite UTF-8 with a signature, and nbformat itself tolerates the file on the way back in.
Fix
Strip the BOM before parsing, which is what
_csv_converteralready does with the sameone-liner for the same reason:
Nothing else moves.
lstripon a string with no BOM is a no-op, so a notebook without one isbyte-identical; a caller that declares
charset="utf-8-sig"has already consumed the BOM atdecode time and is likewise unaffected.
Tests
Added to
packages/markitdown/tests/test_module_misc.py:MarkItDown().convert_streamdoes not come back as raw JSONagainst the full string
charset="utf-8-sig"still works and is not double-strippedAgainst unmodified
main:With the fix:
How I tested
Windows 11, Python 3.12, editable install of
packages/markitdown[all].Those 18 are unchanged by this PR:
maingives18 failed, 426 passed, 4 skippedon thismachine before any edit, and the 4 added tests account for the difference. They are the CLI
stdout-encoding, Windows file-URI and speech-transcription tests that need a UTF-8 console, a
case-sensitive path and network/ffmpeg.
black --checkclean on both files.