summaryrefslogtreecommitdiffstats
path: root/share/extensions/inkex/elements/_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'share/extensions/inkex/elements/_utils.py')
-rw-r--r--share/extensions/inkex/elements/_utils.py144
1 files changed, 144 insertions, 0 deletions
diff --git a/share/extensions/inkex/elements/_utils.py b/share/extensions/inkex/elements/_utils.py
new file mode 100644
index 0000000..56e1e12
--- /dev/null
+++ b/share/extensions/inkex/elements/_utils.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021 Martin Owens <doctormo@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+"""
+Useful utilities specifically for elements (that aren't base classes)
+
+.. versionadded:: 1.1
+ Most of the methods in this module were moved from inkex.utils.
+"""
+
+from collections import defaultdict
+import re
+
+# a dictionary of all of the xmlns prefixes in a standard inkscape doc
+NSS = {
+ "sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
+ "cc": "http://creativecommons.org/ns#",
+ "ccOLD": "http://web.resource.org/cc/",
+ "svg": "http://www.w3.org/2000/svg",
+ "dc": "http://purl.org/dc/elements/1.1/",
+ "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ "inkscape": "http://www.inkscape.org/namespaces/inkscape",
+ "xlink": "http://www.w3.org/1999/xlink",
+ "xml": "http://www.w3.org/XML/1998/namespace",
+}
+SSN = dict((b, a) for (a, b) in NSS.items())
+
+
+def addNS(tag, ns=None): # pylint: disable=invalid-name
+ """Add a known namespace to a name for use with lxml"""
+ if tag.startswith("{") and ns:
+ _, tag = removeNS(tag)
+ if not tag.startswith("{"):
+ tag = tag.replace("__", ":")
+ if ":" in tag:
+ (ns, tag) = tag.rsplit(":", 1)
+ ns = NSS.get(ns, None) or ns
+ if ns is not None:
+ return f"{{{ns}}}{tag}"
+ return tag
+
+
+def removeNS(name): # pylint: disable=invalid-name
+ """The reverse of addNS, finds any namespace and returns tuple (ns, tag)"""
+ if name[0] == "{":
+ (url, tag) = name[1:].split("}", 1)
+ return SSN.get(url, "svg"), tag
+ if ":" in name:
+ return name.rsplit(":", 1)
+ return "svg", name
+
+
+def splitNS(name): # pylint: disable=invalid-name
+ """Like removeNS, but returns a url instead of a prefix"""
+ (prefix, tag) = removeNS(name)
+ return (NSS[prefix], tag)
+
+
+def natural_sort_key(key, _nsre=re.compile("([0-9]+)")):
+ """Helper for a natural sort, see
+ https://stackoverflow.com/a/16090640/3298143"""
+ return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(key)]
+
+
+class ChildToProperty(property):
+ """Use when you have a singleton child element who's text
+ content is the canonical value for the property"""
+
+ def __init__(self, tag, prepend=False):
+ super().__init__()
+ self.tag = tag
+ self.prepend = prepend
+
+ def __get__(self, obj, klass=None):
+ elem = obj.findone(self.tag)
+ return elem.text if elem is not None else None
+
+ def __set__(self, obj, value):
+ elem = obj.get_or_create(self.tag, prepend=self.prepend)
+ elem.text = value
+
+ def __delete__(self, obj):
+ obj.remove_all(self.tag)
+
+ @property
+ def __doc__(self):
+ return f"Get, set or delete the {self.tag} property."
+
+
+class CloningVat:
+ """
+ When modifying defs, sometimes we want to know if every backlink would have
+ needed changing, or it was just some of them.
+
+ This tracks the def elements, their promises and creates clones if needed.
+ """
+
+ def __init__(self, svg):
+ self.svg = svg
+ self.tracks = defaultdict(set)
+ self.set_ids = defaultdict(list)
+
+ def track(self, elem, parent, set_id=None, **kwargs):
+ """Track the element and connected parent"""
+ elem_id = elem.get("id")
+ parent_id = parent.get("id")
+ self.tracks[elem_id].add(parent_id)
+ self.set_ids[elem_id].append((set_id, kwargs))
+
+ def process(self, process, types=(), make_clones=True, **kwargs):
+ """
+ Process each tracked item if the backlinks match the parents
+
+ Optionally make clones, process the clone and set the new id.
+ """
+ for elem_id in list(self.tracks):
+ parents = self.tracks[elem_id]
+ elem = self.svg.getElementById(elem_id)
+ backlinks = {blk.get("id") for blk in elem.backlinks(*types)}
+ if backlinks == parents:
+ # No need to clone, we're processing on-behalf of all parents
+ process(elem, **kwargs)
+ elif make_clones:
+ clone = elem.copy()
+ elem.getparent().append(clone)
+ clone.set_random_id()
+ for update, upkw in self.set_ids.get(elem_id, ()):
+ update(elem.get("id"), clone.get("id"), **upkw)
+ process(clone, **kwargs)