fix: avoid UnicodeDecodeError when non-ASCII bytes follow the first 4k - #2280
Open
André Cia (AndreCia) wants to merge 1 commit into
Open
fix: avoid UnicodeDecodeError when non-ASCII bytes follow the first 4k#2280André Cia (AndreCia) wants to merge 1 commit into
André Cia (AndreCia) wants to merge 1 commit into
Conversation
Charset detection inspects only the first 4096 bytes of a stream. A text file
whose non-ASCII bytes appear past that window is labeled ASCII, and decoding
the full content then fails with UnicodeDecodeError, aborting the conversion:
PlainTextConverter threw UnicodeDecodeError with message: 'ascii' codec
can't decode byte 0xe2 in position 5030: ordinal not in range(128)
Changes:
- _markitdown.py: when detection returns ASCII and the stream continues past
the inspected page, widen the guess to UTF-8. ASCII is a strict subset of
UTF-8, so nothing that decoded before stops decoding. Streams ending within
the page were fully inspected and keep ASCII, preserving existing behavior
and the charset reported by guess_stream_info().
- converter_utils/charset.py: new decode_text() helper that tries the supplied
charset and, on UnicodeDecodeError, re-detects over every byte. This also
covers misdetected non-UTF-8 charsets, where widening to UTF-8 is not
enough, and the case where detection finds no viable encoding, which
previously yielded the literal string "None" as document content.
- _plain_text_converter.py and _csv_converter.py: use the helper.
Adds 5 cases in tests/test_charset_detection.py. Full suite passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
André Cia (@AndreCia) please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Charset detection in
_markitdown.pyinspects only the first 4096 bytes of a stream. A text file whose non-ASCII bytes appear past that window is labeledascii, and decoding the full content then fails:Minimal reproduction: 5000 ASCII characters followed by
café. Detection over the first 4k returnsascii; detection over the full content returnsutf_8.This affects
.md,.txt,.csvand other text formats large enough to exceed the detection window. It is easy to hit with real documents: an English-language file whose first accented character, em dash, or curly quote happens to sit past the 4k mark converts fine until the day that character moves earlier or later.Fix
_markitdown.py— when detection returns ASCII and the stream continues past the inspected page, the guess is widened to UTF-8. ASCII is a strict subset of UTF-8, so nothing that decoded before stops decoding.The size condition is the important part. A stream that ends within the 4096-byte page was fully inspected, so ASCII still holds and is kept. Without that condition,
guess_stream_info()would start reportingutf-8for small, purely ASCII files, which breaks thetest.jsonandtest_notebook.ipynbvectors intest_module_vectors.py. Those vectors are correct as written, so the fix is scoped to the case where detection was actually partial.converter_utils/charset.py(new) —decode_text()centralizes decoding: it tries the supplied charset and, onUnicodeDecodeError, re-detects over every byte. This covers misdetected non-UTF-8 charsets (cp1252, latin-1), where widening to UTF-8 is not enough. It also handlesfrom_bytes(...).best()returningNone, which previously produced the literal string"None"as the document's content._plain_text_converter.pyand_csv_converter.py— use the helper.Tests
5 new cases in
tests/test_charset_detection.py: UTF-8 past the detection window, the same for CSV, recovery from a wrong charset, honoring a valid explicit charset, and decoding with no charset supplied.Full suite: 341 passed, 4 skipped.
blackclean.One test note for macOS: the CLI tests invoke
pythonvia subprocess, and macOS ships onlypython3on PATH, so they need the venv on PATH (PATH="$PWD/.venv/bin:$PATH") or 50 tests fail for environmental reasons unrelated to any change.🤖 Generated with Claude Code