Repository navigation
fix: prefer cp1252 when the charset detector ties - #2679
Muhammad Usman Mateen (usmanmateen) wants to merge 2 commits into
Conversation
Western European text in cp1252 often scores exactly like cp1250 or a non-Latin code page in charset_normalizer, and best() then picks by name. "Café crème" came out as "Cafﻠ crﻟme" and "crème" as "crčme" in plain text, CSV and ZIP members. Add best_charset_match(): on a tie in both chaos and coherence, take cp1252, unless the data has S or Z with caron, which point to cp1250. Use it for the stream charset guess and the plain text and CSV fallbacks.
|
@microsoft-github-policy-service agree |
PRABHU KIRAN VANDRANKI (VANDRANKI)
left a comment
There was a problem hiding this comment.
Community review, not a maintainer review and it does not clear the merge gate. I read the full diff and ran it on Windows with Python 3.13 and charset-normalizer 3.5.2.
What I ran:
- With the PR's
tests/test_module_misc.pyapplied on top of main (4cc9fa1), the 5test_western_european_cp1252_text_is_decodedcases andtest_western_european_cp1252_csv_is_decodedfail. On the PR branch they pass, and the 4 cp1250 cases pass on both. - Full
packages/markitdownsuite: main 1097 passed, PR 1107 passed. The same 7 tests fail on both (test_speech_transcriptionand 6test_cli_vectorscases for vectors 13 and 15). I did not look into why they fail here, so I can only say they are identical on both branches.
Extra probe, converting text through convert_stream(..., file_extension=".txt") on main and on the PR. These are my own short sentences, so treat the counts as anecdotal:
- cp1252 gains: "naïve façade", "Sjöberg ångström" and "Café crème" (latin-1 encoded) go from garbled to correct.
- cp1250 regressions: "Česká republika" and a Hungarian sentence ("Minden ember szabadnak születik és egyenlő méltósággal...") decode correctly on main but not on the PR. "Česká republika" comes out as "Èeská republika", and the Hungarian sentence gets "egyenlõ" instead of "egyenlő". Neither contains S, s, Z or z with caron, so the guard in
best_charset_matchdoes not apply, and the cp1252 tie wins. - The PR description names Croatian, Slovene and Bosnian as the trade-off. From what I ran, Czech and Hungarian text without š/ž is affected the same way, so that might be worth listing in the description and in #2678.
- Of the 13 cp1250 strings I tried, 6 decoded correctly on main and 4 on the PR. Several others are wrong on both (for example the Polish sentence "Wszyscy ludzie rodzą się wolni...", and short strings like "Kraków" that detect as a CJK code page). This is a small sample, not a benchmark.
Not fixed by this PR, probably out of scope: in my probe, short cp1252 strings such as "Café", "Müller Straße" and "Ürün" still decode to unrelated characters (CJK or Arabic presentation forms). I did not dig into why, so I do not know whether it is the same tie.
Small things:
_markitdown.pystill hasimport charset_normalizerat line 18, and after this change nothing else in that file references it (grep shows only the import line). It can probably go.OutlookMsgConverterstill usesfrom_bytes(...).best(). The description explains why, so I have no objection.
Verdict: the change does what it says and the new tests are meaningful (fail on main, pass here). My concern is only that the heuristic moves some cp1250 text from correct to wrong, which seems an acceptable trade if the maintainers want it, but it should be stated plainly.
Prefer cp1250 on a tie when cp1252 would read a Central European letter as a symbol before a letter (ą, ł, Ľ, ...), or ő as õ outside the Portuguese "õe". Remove the charset_normalizer import that _markitdown.py no longer uses.
|
Thanks for the thorough review, and for running it on Windows. You were right about Czech and Hungarian, and some of it was fixable, so I pushed 9e6b615:
On my own set of 17 short strings, comparing main → previous commit → now:
Your two examples behave differently:
I've updated the description and #2678 to list Czech and Hungarian alongside Croatian, Slovene and Bosnian, and to say plainly that this moves some short cp1250 text from correct to wrong. The short cp1252 strings that still decode as CJK or Arabic ("Müller Straße", "m³") are a different case: cp1252 isn't among the detector's tied matches there. I agree that's out of scope here. Full suite on Linux, Python 3.12: 1111 passed, 12 skipped. |
|
Thanks for the update. I re-checked 9e6b615 (community check, not a maintainer review; it does not clear the merge gate). What I ran on Windows, Python 3.13, charset-normalizer 3.5.2,
I did not run the full package suite. I did not read the PR description again, so I have not checked that it lists the Czech case. Whether to accept the remaining Czech case is for the maintainers. |
|
Hi Adam Fourney (@afourney), gentle ping on this when you have a moment. The community re-check above on Windows confirms the fixes, with the Czech trade-off already described in #2678. Happy to adjust anything. |
Fixes #2678
Western European text in Windows-1252 often scores exactly like
cp1250(or a non-Latin code page) incharset_normalizer, andbest()then returns whichever comes first. "Café crème" came out as "Cafﻠ crﻟme" and "crème" as "crčme".Changes
New
best_charset_match()in_charset_utils.py. Whencp1252orcp1250ties with the best match in both chaos and coherence, it picks between them from the bytes. It takescp1250when the data has:0x8A 0x8E 0x9A 0x9E), which are the same in both code pages;Otherwise it takes
cp1252. With no tie, it returnsbest()as before._get_stream_info_guessesand the fallbacks inPlainTextConverterandCsvConverteruse it instead offrom_bytes(...).best().I left the fallback in
OutlookMsgConverterunchanged. It only runs when a declared code page fails to decode, and its tests patchfrom_bytesin that module. I'm happy to switch it too if you prefer.The trade-off is in #2678. Some short cp1250 samples now decode as cp1252, so they move from correct to wrong. This happens when the only non-ASCII letters are ones that are also Western letters (č/è, Č/È, ř/ø, ű/û), or when ą only ends words. That includes Croatian, Slovene, Bosnian, Czech and Hungarian text without the letters above. For example, "Česká republika" decodes as "Èeská republika".
Tests
test_western_european_cp1252_text_is_decoded(French, Spanish, Portuguese, Danish) andtest_western_european_cp1252_csv_is_decoded: fail onmain, pass here.test_central_european_cp1250_text_is_still_decoded(Polish, Czech, Hungarian, Croatian): pass onmainand here.test_central_european_cp1250_tie_is_decoded(Polish, Slovak, Hungarian samples that tie with cp1252): pass onmain, would fail with the cp1252 preference alone, pass here.test_western_european_cp1252_lookalike_is_decoded("As razões", "¿Qué tal?", "Smith¹", "Nº 5"): the same bytes in Western positions still decode as cp1252.packages/markitdown: 1111 passed, 12 skipped.test_speech_transcriptionfails both onmainand here, because it needs the speech recognition service.black23.7.0 (the pre-commit version) leaves the changed files unchanged.