diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 3a2ac63e..e7b20dae 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -3,8 +3,11 @@ What's new? *********** -.. Next release -.. ============ +Next release +============ + +- Bug fix for reading :class:`.Agency` from SDMX-ML 2.1: name of the parent :class:`.Organisation` would be incorrectly attached to the Contact (:pull:`159`). +- Bug fix for writing :class:`.Contact` to SDMX-ML 2.1: :attr:`.Contact.uri` and :attr:`.Contact.email` would be written as, for instance, :xml:`` instead of :xml:`https://example.com` (:pull:`159`). v2.13.0 (2024-01-23) ==================== diff --git a/sdmx/reader/xml/v21.py b/sdmx/reader/xml/v21.py index ec1e34b5..7c2dce3d 100644 --- a/sdmx/reader/xml/v21.py +++ b/sdmx/reader/xml/v21.py @@ -1026,9 +1026,6 @@ def _a(reader, elem): only=False, ) def _item_start(reader, elem): - # Avoid stealing the name & description of the parent ItemScheme from the stack - # TODO check this works for annotations - try: if elem[0].tag in ("Ref", "URN"): # `elem` is a reference, so it has no name/etc.; don't stash @@ -1037,6 +1034,8 @@ def _item_start(reader, elem): # No child elements; stash() anyway, but it will be a no-op pass + # Avoid stealing the name & description of the parent ItemScheme from the stack + # TODO check this works for annotations reader.stash(model.Annotation, "Name", "Description") @@ -1298,16 +1297,26 @@ def _cat(reader, elem): # ยง4.6: Organisations -@end("mes:Contact str:Contact") +@start("mes:Contact str:Contact", only=False) +def _contact_start(reader, elem): + # Avoid stealing the name of the parent Item + reader.stash("Name") + + +@end("mes:Contact str:Contact", only=False) def _contact(reader, elem): contact = model.Contact( telephone=reader.pop_single("Telephone"), uri=reader.pop_all("URI"), email=reader.pop_all("Email"), ) + add_localizations(contact.name, reader.pop_all("Name")) add_localizations(contact.org_unit, reader.pop_all("Department")) add_localizations(contact.responsibility, reader.pop_all("Role")) + + reader.unstash() + return contact diff --git a/sdmx/tests/reader/test_reader_xml_v21.py b/sdmx/tests/reader/test_reader_xml_v21.py index e6b9925d..a858d96d 100644 --- a/sdmx/tests/reader/test_reader_xml_v21.py +++ b/sdmx/tests/reader/test_reader_xml_v21.py @@ -8,6 +8,7 @@ import sdmx from sdmx.format.xml.v21 import qname +from sdmx.model import common from sdmx.model.v21 import Facet, FacetType, FacetValueType from sdmx.reader.xml.v21 import Reader, XMLParseError from sdmx.writer.xml import Element as E @@ -132,6 +133,28 @@ def test_gh_142(specimen): assert all(0 == len(code.annotations) for code in cl) +def test_gh_159(): + """Test of https://github.com/khaeru/sdmx/pull/159.""" + # Agency and contained Contact with distinct names + elem = E( + qname("str:Agency"), + E(qname("com:Name"), "Foo Agency"), + E(qname("str:Contact"), E(qname("com:Name"), "Jane Smith")), + id="FOO", + ) + + # - Create a reader + # - Convert to a file-like object compatible with read_message() + # - Parse the element + # - Retrieve the resulting object + reader = Reader() + reader.read_message(BytesIO(etree.tostring(elem))) + obj = reader.pop_single(common.Agency) + + assert "Foo Agency" == str(obj.name) + assert "Jane Smith" == str(obj.contact[0].name) + + # Each entry is a tuple with 2 elements: # 1. an instance of lxml.etree.Element to be parsed. # 2. Either: diff --git a/sdmx/tests/writer/test_writer_xml.py b/sdmx/tests/writer/test_writer_xml.py index 867ce269..af0c8ad7 100644 --- a/sdmx/tests/writer/test_writer_xml.py +++ b/sdmx/tests/writer/test_writer_xml.py @@ -50,7 +50,11 @@ def dks(dsd): def test_contact() -> None: c = m.Contact( - name="John Smith", org_unit="Human Resources", telephone="+1234567890" + name="John Smith", + org_unit="Human Resources", + telephone="+1234567890", + uri=["https://example.org"], + email=["john.smith@example.org"], ) result = sdmx.to_xml(c, pretty_print=True) @@ -60,6 +64,8 @@ def test_contact() -> None: John Smith Human Resources +1234567890 + https://example.org + john.smith@example.org """ ) diff --git a/sdmx/writer/xml.py b/sdmx/writer/xml.py index f779b374..10bd43bb 100644 --- a/sdmx/writer/xml.py +++ b/sdmx/writer/xml.py @@ -406,8 +406,8 @@ def _contact(obj: model.Contact): + i11lstring(obj.org_unit, "str:Department") + i11lstring(obj.responsibility, "str:Role") + ([Element("str:Telephone", obj.telephone)] if obj.telephone else []) - + [Element("str:URI", text=value) for value in obj.uri] - + [Element("str:Email", text=value) for value in obj.email] + + [Element("str:URI", value) for value in obj.uri] + + [Element("str:Email", value) for value in obj.email] ) return elem