Skip to content

Commit df9ced4

Browse files
geeno16radarherehugovk
authored
Add strip_namespaces argument to Image.getxmp() (#9882)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
1 parent e76b081 commit df9ced4

3 files changed

Lines changed: 52 additions & 7 deletions

File tree

Tests/test_image.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,34 @@ def test_empty_xmp(self) -> None:
10151015
xmp = im.getxmp()
10161016
assert xmp == {}
10171017

1018+
@pytest.mark.skipif(ElementTree is None, reason="defusedxml is not installed")
1019+
def test_getxmp_strip_namespaces(self) -> None:
1020+
im = Image.new("RGB", (1, 1))
1021+
im.info["xmp"] = (
1022+
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
1023+
b'<x:xmpmeta xmlns:x="adobe:ns:meta/">'
1024+
b'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'
1025+
b'<rdf:Description rdf:about=""'
1026+
b' xmlns:a="http://example.com/ns/a/"'
1027+
b' xmlns:b="http://example.com/ns/b/">'
1028+
b"<a:id>from-a</a:id>"
1029+
b"<b:id>from-b</b:id>"
1030+
b"</rdf:Description>"
1031+
b"</rdf:RDF>"
1032+
b'</x:xmpmeta>\n<?xpacket end="w"?>'
1033+
)
1034+
1035+
stripped = im.getxmp()
1036+
desc = stripped["xmpmeta"]["RDF"]["Description"]
1037+
assert desc["id"] == ["from-a", "from-b"]
1038+
1039+
full = im.getxmp(strip_namespaces=False)
1040+
desc_full = full["{adobe:ns:meta/}xmpmeta"][
1041+
"{http://www.w3.org/1999/02/22-rdf-syntax-ns#}RDF"
1042+
]["{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description"]
1043+
assert desc_full["{http://example.com/ns/a/}id"] == "from-a"
1044+
assert desc_full["{http://example.com/ns/b/}id"] == "from-b"
1045+
10181046
def test_getxmp_padded(self) -> None:
10191047
im = Image.new("RGB", (1, 1))
10201048
im.info["xmp"] = (

docs/releasenotes/13.0.0.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,14 @@ TODO
8585
API additions
8686
=============
8787

88-
TODO
89-
^^^^
90-
91-
TODO
88+
Added ``strip_namespaces`` argument to ``Image.getxmp()``
89+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90+
91+
:py:meth:`~PIL.Image.Image.getxmp` now accepts an optional keyword argument of
92+
``strip_namespaces``. It is set to ``True`` by default, stripping each tag's XML
93+
namespace URI prefix as before. If set to ``False``, each tag's full
94+
``{namespace-uri}local-name`` form is kept instead, avoiding collisions between tags
95+
that share a local name across different namespaces.
9296

9397
Other changes
9498
=============

src/PIL/Image.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,16 +1571,29 @@ def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]:
15711571
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
15721572
return self.im.getextrema()
15731573

1574-
def getxmp(self) -> dict[str, Any]:
1574+
def getxmp(self, *, strip_namespaces: bool = True) -> dict[str, Any]:
15751575
"""
15761576
Returns a dictionary containing the XMP tags.
15771577
Requires defusedxml to be installed.
15781578
1579+
:param strip_namespaces: If ``False``, keep each tag's full
1580+
``{namespace-uri}local-name`` form instead of stripping the namespace
1581+
prefix.
1582+
1583+
.. versionadded:: 13.0.0
1584+
15791585
:returns: XMP tags in a dictionary.
15801586
"""
15811587

1582-
def get_name(tag: str) -> str:
1583-
return re.sub("^{[^}]+}", "", tag)
1588+
if strip_namespaces:
1589+
1590+
def get_name(tag: str) -> str:
1591+
return re.sub("^{[^}]+}", "", tag)
1592+
1593+
else:
1594+
1595+
def get_name(tag: str) -> str:
1596+
return tag
15841597

15851598
def get_value(element: Element) -> str | dict[str, Any] | None:
15861599
value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}

0 commit comments

Comments
 (0)