From 16d016e3fdfe1271d98958772eeae5de3e572467 Mon Sep 17 00:00:00 2001 From: Vlad Mencl Date: Thu, 3 Sep 2026 12:04:00 +1200 Subject: [PATCH 1/2] fix: builtins/publish: keep xs+xsi NS in cleanup Fixes #333 The lxml.etree.cleanup_namespaces function only considers namespaces used in XML Element and Attribute names, but not in attribute values. The xs namespaces is used only in values of xsi:type attributes and is thus not considered as used by LXML - and would get removed. Keep it explicitly to avoid creating invalid metadata. --- src/pyff/builtins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pyff/builtins.py b/src/pyff/builtins.py index a3a69ae2..3052dddc 100644 --- a/src/pyff/builtins.py +++ b/src/pyff/builtins.py @@ -554,7 +554,10 @@ def _nop(x): # clean unused namespaces - the working document isn't always XML (eg discojson* produce JSON) if isinstance(data, (etree._Element, etree._ElementTree)): - etree.cleanup_namespaces(data) + # Keep xs: and xsi:, as these are crucial - and xs: is used only + # in attribute values, so may appear as unused. + keep_ns_prefixes = ['xs', 'xsi'] + etree.cleanup_namespaces(data, keep_ns_prefixes=keep_ns_prefixes) if not req.args.get('raw'): data = dumptree(req.t, pretty_print=req.args.get('pretty_print')) From a2b3dfc296e6109f893645c6371987b5976711f7 Mon Sep 17 00:00:00 2001 From: Vlad Mencl Date: Thu, 3 Sep 2026 15:49:54 +1200 Subject: [PATCH 2/2] fix: move namespace cleanup to source The namespace cleanup was being ignored for MDQ per-entity publication - it did nothing when invoked on element other than the root element. It would also be resource intensive to clean up when publishing each individual element. Clean up instead at the source - and there, the guard condition is no longer required (the working document is always XML) --- src/pyff/builtins.py | 7 ------- src/pyff/samlmd.py | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pyff/builtins.py b/src/pyff/builtins.py index 3052dddc..89b623fb 100644 --- a/src/pyff/builtins.py +++ b/src/pyff/builtins.py @@ -552,13 +552,6 @@ def _nop(x): out = output_file data = req.t - # clean unused namespaces - the working document isn't always XML (eg discojson* produce JSON) - if isinstance(data, (etree._Element, etree._ElementTree)): - # Keep xs: and xsi:, as these are crucial - and xs: is used only - # in attribute values, so may appear as unused. - keep_ns_prefixes = ['xs', 'xsi'] - etree.cleanup_namespaces(data, keep_ns_prefixes=keep_ns_prefixes) - if not req.args.get('raw'): data = dumptree(req.t, pretty_print=req.args.get('pretty_print')) diff --git a/src/pyff/samlmd.py b/src/pyff/samlmd.py index b23f41b4..ae00ce6b 100644 --- a/src/pyff/samlmd.py +++ b/src/pyff/samlmd.py @@ -488,6 +488,12 @@ def entitiesdescriptor( ent_insert = deepcopy(ent_insert) t.append(ent_insert) + # clean unused namespaces + # Keep xs: and xsi:, as these are crucial - and xs: is used only + # in attribute values, so may appear as unused. + keep_ns_prefixes = ['xs', 'xsi'] + etree.cleanup_namespaces(t, keep_ns_prefixes=keep_ns_prefixes) + if config.devel_write_xml_to_file: import os