Skip to content

Release 3.4.0: multi-language code-page correctness (#11, #12, #13) - #14

Merged
WilliamSmithEdward merged 4 commits into
mainfrom
multilang-3.4.0
Aug 3, 2026
Merged

Release 3.4.0: multi-language code-page correctness (#11, #12, #13)#14
WilliamSmithEdward merged 4 commits into
mainfrom
multilang-3.4.0

Conversation

@WilliamSmithEdward

Copy link
Copy Markdown
Owner

Closes #11, closes #12, closes #13.

#11 — PROJECT stream hardcoded cp1252. Reproduced first: a cp1251 project with a module named МодульТест emitted Module=?????????? while the dir stream kept the real name. Four sites now take the project's code_page. Verified in live Excel — the workbook compiles and returns Привет, мир from a Cyrillic-named function in a Cyrillic-named module.

#13 — cp1258 Vietnamese destroyed on encode. New encode_mbcs() folds combining marks back into the base until the charmap accepts (ê 0xEA + dot-below 0xF2). Byte-identical wherever direct encoding already worked.

#12 — GB18030 alias, loud fallback, ANSI/Unicode reconciliation. All three items.

Test matrix + CI. 21 code pages × (zero-substitution, NFC round trip, full workbook cycle), plus native module names for cp1251/932/936, running in a dedicated cross-OS languages job mirroring the port's CI.

One correction to file on #11: the Mac Roman aside does not hold on modern Python — cp10000 resolves. 54936 was the only real gap.

Verification: 462 tests + pyright strict + ruff; 25-case save matrix byte-identical against a v3.3.0 worktree; both live Excel gates green.

Three related multi-language defects (issues #11, #12, #13), all found
by auditing pyOpenVBA after the same bug class hit the TypeScript port.

PROJECT stream (#11). It is code-page ANSI per [MS-OVBA] 2.3.1, but
four sites hardcoded cp1252, so every rewrite -- add, rename, delete --
re-encoded module names with errors="replace". A cp1251 project holding
МодульТест emitted Module=?????????? while the dir stream kept the real
name, leaving declarations Excel cross-checks in disagreement.
serialize_project_stream, parse_project_stream, and parse_projectwm now
accept the project's code_page (default 1252 for standalone callers)
and the save path threads it through.

Vietnamese (#13). Python's charmap codecs do no composition, so
'Tiếng Việt'.encode('cp1258') destroyed every stacked-diacritic
character. NFD alone does not help: cp1258 stores ệ as precomposed ê
plus a combining dot-below, which is not that character's canonical
decomposition. New encode_mbcs() decomposes an unmappable character and
folds each combining mark back into the base until the codec accepts
the result, emitting the remaining marks as combining bytes. Text that
already encodes directly is returned byte-for-byte unchanged, so this
can only affect characters that were becoming '?'.

Codec resolution (#12). 54936 (GB18030) is the one page VBA hosts write
that Python does not spell cp<N>; it now maps to the gb18030 codec.
Unresolvable pages warn instead of silently falling back to latin-1.
And when a module's name, stream name, or doc string disagrees between
its ANSI record and its UTF-16 partner, the Unicode record -- lossless
by construction -- now wins.

Byte output for existing cp1252/ASCII files is unchanged: the 25-case
save matrix across every live fixture hashes identically against a
v3.3.0 worktree. Verified in live Excel: a cp1251 workbook whose module
is NAMED МодульТест compiles and returns Привет, мир from a
Cyrillic-named function.
The cross-OS language matrix earned its keep on its first CI run: five
pages (10000 Mac Roman, 20866 KOI8-R, 21866 KOI8-U, 28592 ISO-8859-2,
28595 ISO-8859-5) passed on windows-latest and failed on ubuntu.

Cause: on Windows, CPython falls through to the operating system's
code-page registry, so codecs.lookup("cp28592") succeeds there while
raising LookupError on Linux and macOS. Text in those pages therefore
decoded correctly on one platform and became latin-1 mojibake on
another -- the same silent-degradation bug class the matrix exists to
catch, in a form no single-platform test run could see.

_CODEPAGE_ALIASES now maps 30 Windows code-page identifiers to portable
Python codec names (Macintosh, KOI8, the ISO-8859 family, ISO-2022,
EUC, GB, UTF-7) and is consulted before the cp<N> spelling, so every
platform resolves identically. Two new tests assert portability against
encodings.search_function -- the pure-Python registry, identical
everywhere -- so this fails on any platform rather than only the
affected one.
'\c' is not a recognized Python escape, so the string happened to work
while pyright and ruff flagged it -- and pyright runs inside the main
CI test jobs, which is why they all failed while the language matrix
passed.
@WilliamSmithEdward
WilliamSmithEdward merged commit 347fcc9 into main Aug 3, 2026
10 checks passed
@WilliamSmithEdward
WilliamSmithEdward deleted the multilang-3.4.0 branch August 3, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment