From 4031c3c1cc7cf86e5daf2337107c31bcb33751a1 Mon Sep 17 00:00:00 2001 From: lindsay stevens Date: Sat, 14 Jun 2025 12:47:12 +1000 Subject: [PATCH] chg: restore pre-py3.13 xml attribute escaping behaviour for \r\n\t - in py3.13, minidom started escaping \r\n\t in attributes in order to comply with the XML spec, which pyxform adopted for all python versions in an earlier commit. But it was found that this change caused a HTTP 500 error for Enketo (not Collect/Webforms). If a user had choice filter in a multi-language form, and the filter expression used `select()`, then Enketo could not display the form but displayed an error saying "Cannot read properties of undefined ( reading length)". Details in XLSForm/pyxform/#771 - many test cases were explored such as constraint, calculate, relevant, single or multi-language forms, choice filter expressions and operators, but only the one mentioned above seemed to trigger the error. Since it seems a reasonably common trigger (using newlines to format expressions, and using `selected()` in choice filters), and it may take some time for both Enketo to fix the issue and for that release to be adopted by users, pyxform reverts to the previous behaviour. --- pyxform/utils.py | 6 ++---- tests/xform_test_case/test_xml.py | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pyxform/utils.py b/pyxform/utils.py index a15e4bfd..bc5faeae 100644 --- a/pyxform/utils.py +++ b/pyxform/utils.py @@ -29,8 +29,6 @@ SPACE_TRANS_TABLE = str.maketrans({" ": "_"}) XML_TEXT_SUBS = {"&": "&", "<": "<", ">": ">"} XML_TEXT_TABLE = str.maketrans(XML_TEXT_SUBS) -XML_ATTR_SUBS = {'"': """, "\r": " ", "\n": " ", "\t": " "} -XML_ATTR_TABLE = str.maketrans(XML_ATTR_SUBS) class DetachableElement(Element): @@ -85,8 +83,8 @@ def escape_text_for_xml(text: str, attribute: bool = False) -> str: chars = set(text) if any(c in chars for c in XML_TEXT_SUBS): text = text.translate(XML_TEXT_TABLE) - if attribute and any(c in chars for c in XML_ATTR_SUBS): - text = text.translate(XML_ATTR_TABLE) + if attribute and '"' in chars: + text = text.replace('"', """) return text diff --git a/tests/xform_test_case/test_xml.py b/tests/xform_test_case/test_xml.py index 5faa3e30..c7374bfa 100644 --- a/tests/xform_test_case/test_xml.py +++ b/tests/xform_test_case/test_xml.py @@ -90,9 +90,9 @@ class MinidomTextWriterMonkeyPatchTest(TestCase): maxDiff = None def test_patch_lets_node_func_escape_only_necessary(self): - """Should find that pyxform escapes ["&<>\r\n\t] in attrs and [&<>] in text.""" + """Should find that pyxform escapes ["&<>] in attrs and [&<>] in text.""" replaceable_chars = "' \" & < > \r \n \t" - expected = """' " & < > \r \n \t""" + expected = """' " & < > \r \n \t""" observed = node("root", replaceable_chars, attr=replaceable_chars).toprettyxml( indent="", newl="" )