From 1a249e750ab7e30169f79beb35b7503c01d994f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrik=20St=C3=B6ckli?=
Date: Wed, 16 Sep 2026 11:17:44 +0200
Subject: [PATCH 1/2] Fix UTF-8 file I/O on IronPython by using codecs.open
instead of io.open
---
src/util.py | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/util.py b/src/util.py
index f937be4..5b8c164 100644
--- a/src/util.py
+++ b/src/util.py
@@ -4,6 +4,7 @@
import shutil
import sys
import traceback
+import codecs
import scriptengine # type: ignore
@@ -90,8 +91,16 @@ def ui_error_with_traceback(message):
def open_utf8(path, mode):
# ScriptEngine runs on Python 2.7; the builtin open() writes a byte stream and would
# implicitly encode unicode text as ascii, crashing on Cyrillic / accented chars / smart
- # punctuation. io.open with an explicit encoding keeps everything UTF-8 round-trippable.
- return io.open(path, mode, encoding="utf-8")
+ # punctuation. codecs.open with an excplicit encoding keeps everything UTF-8 round-trippable.
+ #
+ # io.open(path, mode, encoding="utf-8") looks equivalent but is unreliable on the
+ # IronPython 2.7 runtime CODESYS ScriptEngine uses: encoding resolution can silently
+ # fall back to the OS ANSI code page instead of true UTF-8, which corrupts any
+ # non ASCII character(umlauts, accented letters, smart punctuation, etc.) on export and
+ # then fail to decode on the next import ("'unknown' codec can't decode byte ...").
+ # codec.open is the long-standing, well-supported way to do UTF-8 text I/O on
+ # Python 2 / IronPython and does not have this problems.
+ return codecs.open(path, mode, encoding="utf-8")
def print_python_version():
From a534b82a571c154729092cdb63d0a091247b46a7 Mon Sep 17 00:00:00 2001
From: pstoeckli <129617828+pstoeckli@users.noreply.github.com>
Date: Wed, 16 Sep 2026 11:23:25 +0200
Subject: [PATCH 2/2] Fix typo in open_utf8 function comment
Corrected a typo in the comment regarding codecs.open.
---
src/util.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/util.py b/src/util.py
index 5b8c164..bb2e677 100644
--- a/src/util.py
+++ b/src/util.py
@@ -91,7 +91,7 @@ def ui_error_with_traceback(message):
def open_utf8(path, mode):
# ScriptEngine runs on Python 2.7; the builtin open() writes a byte stream and would
# implicitly encode unicode text as ascii, crashing on Cyrillic / accented chars / smart
- # punctuation. codecs.open with an excplicit encoding keeps everything UTF-8 round-trippable.
+ # punctuation. codecs.open with an explicit encoding keeps everything UTF-8 round-trippable.
#
# io.open(path, mode, encoding="utf-8") looks equivalent but is unreliable on the
# IronPython 2.7 runtime CODESYS ScriptEngine uses: encoding resolution can silently