From 88b7091d718c01e925b212e931d566472eca6136 Mon Sep 17 00:00:00 2001 From: Martin Owens Date: Mon, 28 Nov 2022 02:30:55 -0500 Subject: [PATCH 1/2] Add namespace registration to our svg base element The lxml namespace handling is always messy, this allows us to provide the extension developer with an easier way to register namespaces. --- inkex/elements/_svg.py | 31 +++++++++++++++++++++++++++++++ inkex/elements/_utils.py | 6 ++++++ tests/test_inkex_svg.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/inkex/elements/_svg.py b/inkex/elements/_svg.py index 08eea84b8..7f31537e5 100644 --- a/inkex/elements/_svg.py +++ b/inkex/elements/_svg.py @@ -42,6 +42,7 @@ from ..styles import StyleSheets from ._base import BaseElement, ViewboxMixin from ._meta import StyleElement, NamedView +from ._utils import registerNS from typing import Optional, List, Tuple @@ -150,6 +151,36 @@ class SvgDocumentElement( return self return layer + def add_namespace(self, prefix, url): + """Adds an xml namespace to the xml parser with the desired prefix. + + If the prefix or url are already in use with different values, this + function will raise an error. Remove any attributes or elements using + this namespace before calling this function in order to rename it. + + .. versionadded:: 1.3 + """ + if self.nsmap.get(prefix, None) == url: + registerNS(prefix, url) + return + + # Attempt to clean any existing namespaces + if prefix in self.nsmap or url in self.nsmap.values(): + nskeep = [k for k, v in self.nsmap.items() if k != prefix and v != url] + etree.cleanup_namespaces(self, keep_ns_prefixes=nskeep) + if prefix in self.nsmap: + raise KeyError("ns prefix already used with a different url") + if url in self.nsmap.values(): + raise ValueError("ns url already used with a different prefix") + + # These are globals, but both will overwrite previous uses. + registerNS(prefix, url) + etree.register_namespace(prefix, url) + + # Set and unset an attribute to add the namespace to this root element. + self.set(f"{prefix}:temp", "1") + self.set(f"{prefix}:temp", None) + def getElement(self, xpath): # pylint: disable=invalid-name """Gets a single element from the given xpath or returns None""" return self.findone(xpath) diff --git a/inkex/elements/_utils.py b/inkex/elements/_utils.py index a6613debe..bc72c6b22 100644 --- a/inkex/elements/_utils.py +++ b/inkex/elements/_utils.py @@ -41,6 +41,12 @@ NSS = { SSN = dict((b, a) for (a, b) in NSS.items()) +def registerNS(prefix, url): + """Register the given prefix as a namespace url.""" + NSS[prefix] = url + SSN[url] = prefix + + def addNS(tag, ns=None, namespaces=NSS): # pylint: disable=invalid-name """Add a known namespace to a name for use with lxml""" if tag.startswith("{") and ns: diff --git a/tests/test_inkex_svg.py b/tests/test_inkex_svg.py index 09a922851..09542913e 100644 --- a/tests/test_inkex_svg.py +++ b/tests/test_inkex_svg.py @@ -46,6 +46,43 @@ class BasicSvgTest(TestCase): ) self.assertEqual(addNS("{p}j"), "{p}j") + def test_register_ns(self): + """Test adding a namespace prefix to a root tag""" + root = svg() + + # Namespace is not currently in use + self.assertNotIn("hotel", root.nsmap) + self.assertEqual(root.attrib.keys(), []) + + # We can add a namespace to the document + root.add_namespace("hotel", "http://www.inkscape.org/namespaces/hotel") + self.assertEqual(root.attrib.keys(), []) + self.assertEqual( + root.nsmap["hotel"], "http://www.inkscape.org/namespaces/hotel" + ) + + # We can use the new namespace + root.set("hotel:name", "value") + self.assertEqual( + root.attrib.keys(), ["{http://www.inkscape.org/namespaces/hotel}name"] + ) + + # We will fail to set the namespace if it's already used + root.add_namespace("hotel", "http://www.inkscape.org/namespaces/hotel") + self.assertRaises(KeyError, root.add_namespace, "hotel", "http://other.url/") + self.assertRaises( + ValueError, + root.add_namespace, + "other", + "http://www.inkscape.org/namespaces/hotel", + ) + + # Releasing the use, will allow us to replace the namespace + root.set("hotel:name", None) + self.assertEqual(root.attrib.keys(), []) + root.add_namespace("hotel", "http://other.url/") + self.assertEqual(root.nsmap["hotel"], "http://other.url/") + def test_svg_ids(self): """Test a list of ids from an svg document""" self.assertEqual(svg('id="apples"').get_ids(), {"apples"}) -- GitLab From e05df0dd0bb110beb44cd6e6cf8564b36ab65e3e Mon Sep 17 00:00:00 2001 From: Jonathan Neuhauser Date: Mon, 28 Nov 2022 20:51:45 +0100 Subject: [PATCH 2/2] add unit test for children + namespace editing --- tests/test_inkex_svg.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/test_inkex_svg.py b/tests/test_inkex_svg.py index 09542913e..9a24b5459 100644 --- a/tests/test_inkex_svg.py +++ b/tests/test_inkex_svg.py @@ -21,7 +21,7 @@ Test the svg interface for inkscape extensions. """ from inkex.transforms import Vector2d -from inkex import Guide +from inkex import Guide, Rectangle from inkex.tester import TestCase from inkex.tester.svg import svg, svg_file, svg_unit_scaled from inkex import addNS @@ -83,6 +83,31 @@ class BasicSvgTest(TestCase): root.add_namespace("hotel", "http://other.url/") self.assertEqual(root.nsmap["hotel"], "http://other.url/") + def test_register_ns_children(self): + """Test namespace registration when children are added before / after + modification of the namespaces""" + root = svg() + + rect = root.add(Rectangle.new(10, 10, 10, 10)) + self.assertNotIn("hotel", rect.nsmap) + root.add_namespace("hotel", "http://www.inkscape.org/namespaces/hotel") + + # The namespace should also be available on children that were added before + # the add_namespace method was called. + self.assertIn("hotel", rect.nsmap) + rect.set("hotel:name", "value") + self.assertIn( + "{http://www.inkscape.org/namespaces/hotel}name", rect.attrib.keys() + ) + + # and also on children created afterwards + rect2 = root.add(Rectangle.new(10, 10, 10, 10)) + self.assertIn("hotel", rect2.nsmap) + rect2.set("hotel:name2", "value2") + self.assertIn( + "{http://www.inkscape.org/namespaces/hotel}name2", rect2.attrib.keys() + ) + def test_svg_ids(self): """Test a list of ids from an svg document""" self.assertEqual(svg('id="apples"').get_ids(), {"apples"}) -- GitLab