From f279d5a78a0c8d145570eef4e88c5e719187bd49 Mon Sep 17 00:00:00 2001 From: Kevin Costner <120246174+kevincostner17@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:56:44 +0530 Subject: [PATCH] fix(parsers): report an unknown XML encoding as invalid XML ET.parse raises LookupError when the XML declaration names an encoding Python does not know. The GPX and SDMX parsers caught only ValueError and ET.ParseError around that call, so the LookupError escaped instead of producing the existing "invalid GPX/SDMX XML" warning with empty frames. Catch LookupError in the same clause as ET.ParseError. Closes #315 --- src/freshdata/parsers/gpx.py | 2 +- src/freshdata/parsers/sdmx.py | 2 +- tests/parsers/test_gpx.py | 10 ++++++++++ tests/parsers/test_sdmx.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/freshdata/parsers/gpx.py b/src/freshdata/parsers/gpx.py index 8c758258..355035fc 100644 --- a/src/freshdata/parsers/gpx.py +++ b/src/freshdata/parsers/gpx.py @@ -41,7 +41,7 @@ def parse(self, source: Any) -> ParseResult: except ValueError as exc: return ParseResult(self.format, {v: pd.DataFrame() for v in _POINT_KIND.values()}, self.suggested_domain, {}, [f"unsafe GPX XML: {exc}"]) - except ET.ParseError as exc: + except (ET.ParseError, LookupError) as exc: return ParseResult(self.format, {v: pd.DataFrame() for v in _POINT_KIND.values()}, self.suggested_domain, {}, [f"invalid GPX XML: {exc}"]) finally: diff --git a/src/freshdata/parsers/sdmx.py b/src/freshdata/parsers/sdmx.py index 025f1a2a..d34cd7ad 100644 --- a/src/freshdata/parsers/sdmx.py +++ b/src/freshdata/parsers/sdmx.py @@ -40,7 +40,7 @@ def parse(self, source: Any) -> ParseResult: except ValueError as exc: return ParseResult(self.format, {"observations": pd.DataFrame()}, None, {}, [f"unsafe SDMX XML: {exc} (audit only)"]) - except ET.ParseError as exc: + except (ET.ParseError, LookupError) as exc: return ParseResult(self.format, {"observations": pd.DataFrame()}, None, {}, [f"invalid SDMX XML: {exc} (audit only)"]) finally: diff --git a/tests/parsers/test_gpx.py b/tests/parsers/test_gpx.py index 9005b3d2..4c8e6df7 100644 --- a/tests/parsers/test_gpx.py +++ b/tests/parsers/test_gpx.py @@ -60,6 +60,16 @@ def test_malformed_xml_returns_warning_not_exception(): assert any("invalid GPX XML" in w for w in result.warnings) +def test_unknown_xml_encoding_returns_warning_not_exception(): + bad = ( + '' + '' + ) + result = fd.parse_domain(bad, format="gpx") + assert all(df.empty for df in result.frames.values()) + assert any("invalid GPX XML" in w for w in result.warnings) + + def test_doctype_entities_are_rejected(): entity_gpx = """]> diff --git a/tests/parsers/test_sdmx.py b/tests/parsers/test_sdmx.py index ba13facb..f0d9c4b9 100644 --- a/tests/parsers/test_sdmx.py +++ b/tests/parsers/test_sdmx.py @@ -58,6 +58,16 @@ def test_invalid_xml_is_audit_only(): assert any("invalid SDMX XML" in w and "audit only" in w for w in result.warnings) +def test_unknown_xml_encoding_is_audit_only(): + bad = ( + '' + "" + ) + result = fd.parse_domain(bad, format="sdmx") + assert result.frames["observations"].empty + assert any("invalid SDMX XML" in w and "audit only" in w for w in result.warnings) + + def test_doctype_entities_are_rejected(): entity_sdmx = """]>