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