# coding=utf-8 # # Copyright (c) 2023 David Burghoff # Martin Owens # Sergei Izmailov # Thomas Holder # Jonathan Neuhauser # 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. """Patches for speeding up native Inkex functions after import""" import inkex from inkex import Transform import re, lxml """ _base.py """ # Inkex's get does a lot of namespace adding that can be cached for speed # This can be bypassed altogether for known attributes (by using fget instead) # Wrap gradientTransform and patternTransform inkex.BaseElement.WRAPPED_ATTRS = ( ("transform", inkex.Transform), ("style", inkex.Style), ("classes", "class", inkex.styles.Classes), ("gradientTransform", inkex.Transform), ("patternTransform", inkex.Transform), ) fget = lxml.etree.ElementBase.get fset = lxml.etree.ElementBase.set wrapped_props = {row[0]: (row[-2], row[-1]) for row in inkex.BaseElement.WRAPPED_ATTRS} wrapped_props_keys = set(wrapped_props.keys()) wrapped_attrs = {row[-2]: (row[0], row[-1]) for row in inkex.BaseElement.WRAPPED_ATTRS} wrapped_attrs_keys = set(wrapped_attrs.keys()) from typing import Dict wrprops: Dict[str, str] = dict() inkexget = inkex.BaseElement.get def fast_get(self, attr, default=None): try: return fget(self, inkex.addNS(attr), default) except: try: value = getattr(self, wrprops[attr], None) ret = str(value) if value else (default or None) return ret except: if attr in wrapped_attrs_keys: (wrprops[attr], _) = wrapped_attrs[attr] return inkexget(self, attr, default) inkex.BaseElement.get = fast_get # type: ignore def fast_set(self, attr, value): """Set element attribute named, with addNS support""" if attr in wrapped_attrs: # Always keep the local wrapped class up to date. (prop, cls) = wrapped_attrs[attr] setattr(self, prop, cls(value)) value = getattr(self, prop) if not value: return NSattr = inkex.addNS(attr) if value is None: self.attrib.pop(NSattr, None) # pylint: disable=no-member else: value = str(value) fset(self, NSattr, value) inkex.BaseElement.set = fast_set # type: ignore def fast_getattr(self, name): """Get the attribute, but load it if it is not available yet""" # if name in wrapped_props_keys: # always satisfied (attr, cls) = wrapped_props[name] def _set_attr(new_item): if new_item: self.set(attr, str(new_item)) else: self.attrib.pop(attr, None) # pylint: disable=no-member # pylint: disable=no-member value = cls(self.attrib.get(attr, None), callback=_set_attr) if name == "style": value.element = self fast_setattr(self, name, value) return value # raise AttributeError(f"Can't find attribute {self.typename}.{name}") def fast_setattr(self, name, value): """Set the attribute, update it if needed""" # if name in wrapped_props_keys: # always satisfied (attr, cls) = wrapped_props[name] # Don't call self.set or self.get (infinate loop) if value: if not isinstance(value, cls): value = cls(value) self.attrib[attr] = str(value) else: self.attrib.pop(attr, None) # pylint: disable=no-member # _base.py overloads __setattr__ and __getattr__, which adds a lot of overhead # since they're invoked for all class attributes, not just transform etc. # We remove the overloading and replicate it using properties. Since there # are only a few attributes to overload, this is fine. del inkex.BaseElement.__setattr__ del inkex.BaseElement.__getattr__ for prop in wrapped_props_keys: get_func = lambda self, attr=prop: fast_getattr(self, attr) set_func = lambda self, value, attr=prop: fast_setattr(self, attr, value) setattr(inkex.BaseElement, str(prop), property(get_func, set_func)) """ paths.py """ # A faster version of Vector2d that only allows for 2 input args V2d = inkex.transforms.Vector2d class Vector2da(V2d): __slots__ = ("_x", "_y") # preallocation speeds def __init__(self, x, y): self._x = float(x) self._y = float(y) def horz_end_point(self, first, prev): return Vector2da(self.x, prev.y) def line_move_arc_end_point(self, first, prev): return Vector2da(self.x, self.y) def vert_end_point(self, first, prev): return Vector2da(prev.x, self.y) def curve_smooth_end_point(self, first, prev): return Vector2da(self.x4, self.y4) def quadratic_tepid_quadratic_end_point(self, first, prev): return Vector2da(self.x3, self.y3) inkex.paths.Line.end_point = line_move_arc_end_point # type: ignore inkex.paths.Move.end_point = line_move_arc_end_point # type: ignore inkex.paths.Arc.end_point = line_move_arc_end_point # type: ignore inkex.paths.Horz.end_point = horz_end_point # type: ignore inkex.paths.Vert.end_point = vert_end_point # type: ignore inkex.paths.Curve.end_point = curve_smooth_end_point # type: ignore inkex.paths.Smooth.end_point = curve_smooth_end_point # type: ignore inkex.paths.Quadratic.end_point = quadratic_tepid_quadratic_end_point # type: ignore inkex.paths.TepidQuadratic.end_point = quadratic_tepid_quadratic_end_point # type: ignore # A version of end_points that avoids unnecessary instance checks zZmM = {"z", "Z", "m", "M"} def fast_end_points(self): prev = Vector2da(0, 0) first = Vector2da(0, 0) for seg in self: end_point = seg.end_point(first, prev) if seg.letter in zZmM: first = end_point prev = end_point yield end_point inkex.paths.Path.end_points = property(fast_end_points) # type: ignore ctqsCTQS = {"c", "t", "q", "s", "C", "T", "Q", "S"} def fast_proxy_iterator(self): previous = V2d() prev_prev = V2d() first = V2d() for seg in self: if seg.letter in zZmM: first = seg.end_point(first, previous) yield inkex.paths.Path.PathCommandProxy(seg, first, previous, prev_prev) if seg.letter in ctqsCTQS: prev_prev = list(seg.control_points(first, previous, prev_prev))[-2] previous = seg.end_point(first, previous) if not hasattr(inkex.transforms.ImmutableVector2d, "c2t"): # The new complex implementation of vector2ds is fast inkex.paths.Path.proxy_iterator = fast_proxy_iterator # type: ignore def fast_control_points(self): """Returns all control points of the Path""" prev = Vector2da(0, 0) prev_prev = Vector2da(0, 0) first = Vector2da(0, 0) for seg in self: for cpt in seg.control_points(first, prev, prev_prev): prev_prev = prev prev = cpt yield cpt if seg.letter in zZmM: first = cpt inkex.paths.Path.control_points = property(fast_control_points) # type: ignore from typing import ( Union, List, Generator, ) def fast_control_points_move( self, first: Vector2da, prev: Vector2da, prev_prev: Vector2da ) -> Generator[Vector2da, None, None]: yield Vector2da(prev.x + self.dx, prev.y + self.dy) inkex.paths.move.control_points = fast_control_points_move # type: ignore def fast_control_points_line( self, first: Vector2da, prev: Vector2da, prev_prev: Vector2da ) -> Generator[Vector2da, None, None]: yield Vector2da(prev.x + self.dx, prev.y + self.dy) inkex.paths.line.control_points = fast_control_points_line # type: ignore def fast_control_points_Vert(self, first, prev, prev_prev): yield Vector2da(prev.x, self.y) inkex.paths.Vert.control_points = fast_control_points_Vert # type: ignore def fast_control_points_vert(self, first, prev, prev_prev): yield Vector2da(prev.x, prev.y + self.dy) inkex.paths.vert.control_points = fast_control_points_vert # type: ignore def fast_control_points_Horz(self, first, prev, prev_prev): yield Vector2da(self.x, prev.y) inkex.paths.Horz.control_points = fast_control_points_Horz # type: ignore def fast_control_points_horz(self, first, prev, prev_prev): yield Vector2da(prev.x + self.dx, prev.y) inkex.paths.horz.control_points = fast_control_points_horz # type: ignore # Optimize Path's init to avoid calls to append and reduce instance checks # About 50% faster ipcspth, ipln, ipmv = inkex.paths.CubicSuperPath, inkex.paths.Line, inkex.paths.Move ipPC = inkex.paths.PathCommand letter_to_class = ipPC._letter_to_class PCsubs = set(letter_to_class.values()) # precache all types that are instances of PathCommand def process_items(items, slf): for item in items: # if isinstance(item, ipPC): itemtype = type(item) if itemtype in PCsubs: yield item elif isinstance(item, (list, tuple)) and len(item) == 2: if isinstance(item[1], (list, tuple)): yield ipPC.letter_to_class(item[0])(*item[1]) else: if len(slf) == 0: yield ipmv(*item) else: yield ipln(*item) else: raise TypeError( f"Bad path type: {type(items).__name__}" f"({type(item).__name__}, ...): {item}" ) from functools import lru_cache # @lru_cache(maxsize=None) def fast_init(self, path_d=None): list.__init__(self) if isinstance(path_d, str): self.extend(cached_parse_string(path_d)) else: if isinstance(path_d, ipcspth): path_d = path_d.to_path() self.extend(process_items(path_d or (), self)) inkex.paths.Path.__init__ = fast_init # type: ignore # Cache PathCommand letters and remove property letts = dict() for pc in PCsubs: letts[pc] = pc.letter del ipPC.letter for pc in PCsubs: pc.letter = letts[pc] # Make parse_string faster by combining with strargs (about 20% faster) LEX_REX = ( inkex.paths.LEX_REX if hasattr(inkex.paths, "LEX_REX") else inkex.paths.path.LEX_REX ) # type: ignore try: NUMBER_REX = inkex.utils.NUMBER_REX except: DIGIT_REX_PART = r"[0-9]" DIGIT_SEQUENCE_REX_PART = rf"(?:{DIGIT_REX_PART}+)" INTEGER_CONSTANT_REX_PART = DIGIT_SEQUENCE_REX_PART SIGN_REX_PART = r"[+-]" EXPONENT_REX_PART = rf"(?:[eE]{SIGN_REX_PART}?{DIGIT_SEQUENCE_REX_PART})" FRACTIONAL_CONSTANT_REX_PART = rf"(?:{DIGIT_SEQUENCE_REX_PART}?\.{DIGIT_SEQUENCE_REX_PART}|{DIGIT_SEQUENCE_REX_PART}\.)" FLOATING_POINT_CONSTANT_REX_PART = rf"(?:{FRACTIONAL_CONSTANT_REX_PART}{EXPONENT_REX_PART}?|{DIGIT_SEQUENCE_REX_PART}{EXPONENT_REX_PART})" NUMBER_REX = re.compile( rf"(?:{SIGN_REX_PART}?{FLOATING_POINT_CONSTANT_REX_PART}|{SIGN_REX_PART}?{INTEGER_CONSTANT_REX_PART})" ) nargs_cache = {cmd: cmd.nargs for cmd in letter_to_class.values()} next_command_cache = {cmd: cmd.next_command for cmd in letter_to_class.values()} # Generator version def fast_parse_string(cls, path_d): for cmd, numbers in LEX_REX.findall(path_d): args = [float(val) for val in NUMBER_REX.findall(numbers)] cmd = letter_to_class[cmd] cmd_nargs = nargs_cache[cmd] i = 0 args_len = len(args) while i < args_len or cmd_nargs == 0: if args_len < i + cmd_nargs: return seg = cmd(*args[i : i + cmd_nargs]) i += cmd_nargs cmd = next_command_cache[type(seg)] cmd_nargs = nargs_cache[cmd] yield seg inkex.paths.Path.parse_string = fast_parse_string # type: ignore @lru_cache(maxsize=None) def cached_parse_string(path_d): ret = [] for cmd, numbers in LEX_REX.findall(path_d): args = [float(val) for val in NUMBER_REX.findall(numbers)] cmd = letter_to_class[cmd] cmd_nargs = nargs_cache[cmd] i = 0 args_len = len(args) while i < args_len or cmd_nargs == 0: if args_len < i + cmd_nargs: return ret seg = cmd(*args[i : i + cmd_nargs]) i += cmd_nargs cmd = next_command_cache[type(seg)] cmd_nargs = nargs_cache[cmd] ret.append(seg) return ret """ transforms.py """ # Faster apply_to_point that gets rid of property calls def apply_to_point_mod(obj, pt): try: ptx, pty = pt except: try: ptx, pty = (pt.x, pt.y) except: raise ValueError x = obj.matrix[0][0] * ptx + obj.matrix[0][1] * pty + obj.matrix[0][2] y = obj.matrix[1][0] * ptx + obj.matrix[1][1] * pty + obj.matrix[1][2] return Vector2da(x, y) old_atp = inkex.Transform.apply_to_point inkex.Transform.apply_to_point = apply_to_point_mod # type: ignore # Applies inverse of transform to point without making a new Transform def applyI_to_point(obj, pt): m = obj.matrix det = m[0][0] * m[1][1] - m[0][1] * m[1][0] inv_det = 1 / det sx = pt.x - m[0][2] # pt.x is sometimes a numpy float64? sy = pt.y - m[1][2] x = (m[1][1] * sx - m[0][1] * sy) * inv_det y = (m[0][0] * sy - m[1][0] * sx) * inv_det return Vector2da(x, y) inkex.Transform.applyI_to_point = applyI_to_point # type: ignore # Built-in bool initializes multiple Transforms Itmat = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0)) atol = inkex.Transform.absolute_tolerance natol = -atol atp1 = atol + 1 natp1 = -atol + 1 def Tbool(obj): # return obj.matrix != Itmat # exact, not within tolerance. I think this is fine return not ( natp1 < obj.matrix[0][0] < atp1 and natol < obj.matrix[0][1] < atol and natol < obj.matrix[0][2] < atol and natol < obj.matrix[1][0] < atol and natp1 < obj.matrix[1][1] < atp1 and natol < obj.matrix[1][2] < atol ) inkex.Transform.__bool__ = Tbool # type: ignore # Reduce Transform conversions during transform multiplication def matmul2(obj, matrix): if isinstance(matrix, (Transform)): othermat = matrix.matrix elif isinstance(matrix, (tuple)): othermat = matrix else: othermat = Transform(matrix).matrix # I think this is never called return Transform( ( obj.matrix[0][0] * othermat[0][0] + obj.matrix[0][1] * othermat[1][0], obj.matrix[1][0] * othermat[0][0] + obj.matrix[1][1] * othermat[1][0], obj.matrix[0][0] * othermat[0][1] + obj.matrix[0][1] * othermat[1][1], obj.matrix[1][0] * othermat[0][1] + obj.matrix[1][1] * othermat[1][1], obj.matrix[0][0] * othermat[0][2] + obj.matrix[0][1] * othermat[1][2] + obj.matrix[0][2], obj.matrix[1][0] * othermat[0][2] + obj.matrix[1][1] * othermat[1][2] + obj.matrix[1][2], ) ) inkex.transforms.Transform.__matmul__ = matmul2 # type: ignore def imatmul2(self, othermat): if isinstance(othermat, (Transform)): othermat = othermat.matrix self.matrix = ( ( self.matrix[0][0] * othermat[0][0] + self.matrix[0][1] * othermat[1][0], self.matrix[0][0] * othermat[0][1] + self.matrix[0][1] * othermat[1][1], self.matrix[0][0] * othermat[0][2] + self.matrix[0][1] * othermat[1][2] + self.matrix[0][2], ), ( self.matrix[1][0] * othermat[0][0] + self.matrix[1][1] * othermat[1][0], self.matrix[1][0] * othermat[0][1] + self.matrix[1][1] * othermat[1][1], self.matrix[1][0] * othermat[0][2] + self.matrix[1][1] * othermat[1][2] + self.matrix[1][2], ), ) if self.callback is not None: self.callback(self) return self inkex.transforms.Transform.__imatmul__ = imatmul2 # type: ignore # Rewrite ImmutableVector2d since 2 arguments most common IV2d = inkex.transforms.ImmutableVector2d def IV2d_init(self, *args, fallback=None): try: self._x, self._y = map(float, args) except: try: if len(args) == 0: x, y = 0.0, 0.0 elif len(args) == 1: try: x, y = self._parse(args[0]) except: x, y = float(args[0]), float(args[0]) else: raise ValueError("too many arguments") except (ValueError, TypeError) as error: if fallback is None: raise ValueError("Cannot parse vector and no fallback given") from error x, y = IV2d(fallback) self._x, self._y = float(x), float(y) inkex.transforms.ImmutableVector2d.__init__ = IV2d_init # type: ignore import math def matrix_multiply(a, b): return ( ( a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1], a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2], ), ( a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1], a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2], ), ) trpattern = re.compile(r"\b(scale|translate|rotate|skewX|skewY|matrix)\(([^\)]*)\)") split_pattern = re.compile(r"[\s,]+") # Converts a transform string into a standard matrix @lru_cache(maxsize=None) def transform_to_matrix(transform): null = ((1, 0, 0), (0, 1, 0)) matrix = ((1, 0, 0), (0, 1, 0)) if "none" not in transform: matches = list(trpattern.finditer(transform)) if not matches: return null else: for match in matches: transform_type = match.group(1) transform_args = [ float(arg) for arg in split_pattern.split(match.group(2)) ] if transform_type == "scale": # Scale transform if len(transform_args) == 1: sx = sy = transform_args[0] elif len(transform_args) == 2: sx, sy = transform_args else: return null # matrix = matrix_multiply(matrix, [[sx, 0, 0], [0, sy, 0], [0, 0, 1]]) matrix = ( (matrix[0][0] * sx, matrix[0][1] * sy, matrix[0][2]), (matrix[1][0] * sx, matrix[1][1] * sy, matrix[1][2]), ) elif transform_type == "translate": # Translation transform if len(transform_args) == 1: tx = transform_args[0] ty = 0 elif len(transform_args) == 2: tx, ty = transform_args else: return null # matrix = matrix_multiply(matrix, [[1, 0, tx], [0, 1, ty], [0, 0, 1]]) matrix = ( ( matrix[0][0], matrix[0][1], matrix[0][0] * tx + matrix[0][1] * ty + matrix[0][2], ), ( matrix[1][0], matrix[1][1], matrix[1][0] * tx + matrix[1][1] * ty + matrix[1][2], ), ) elif transform_type == "rotate": # Rotation transform if len(transform_args) == 1: angle = transform_args[0] cx = cy = 0 elif len(transform_args) == 3: angle, cx, cy = transform_args else: return null angle = angle * math.pi / 180 # Convert angle to radians matrix = matrix_multiply(matrix, ((1, 0, cx), (0, 1, cy))) matrix = matrix_multiply( matrix, ( (math.cos(angle), -math.sin(angle), 0), (math.sin(angle), math.cos(angle), 0), ), ) matrix = matrix_multiply(matrix, ((1, 0, -cx), (0, 1, -cy))) elif transform_type == "skewX": # SkewX transform if len(transform_args) == 1: angle = transform_args[0] else: return null angle = angle * math.pi / 180 # Convert angle to radians matrix = matrix_multiply( matrix, ((1, math.tan(angle), 0), (0, 1, 0)) ) elif transform_type == "skewY": # SkewY transform if len(transform_args) == 1: angle = transform_args[0] else: return null angle = angle * math.pi / 180 # Convert angle to radians matrix = matrix_multiply( matrix, ((1, 0, 0), (math.tan(angle), 1, 0)) ) elif transform_type == "matrix": # Matrix transform if len(transform_args) == 6: a, b, c, d, e, f = transform_args else: return null # matrix = matrix_multiply(matrix, [[a, c, e], [b, d, f], [0, 0, 1]]) matrix = ( ( matrix[0][0] * a + matrix[0][1] * b, matrix[0][0] * c + matrix[0][1] * d, matrix[0][0] * e + matrix[0][1] * f + matrix[0][2], ), ( matrix[1][0] * a + matrix[1][1] * b, matrix[1][0] * c + matrix[1][1] * d, matrix[1][0] * e + matrix[1][1] * f + matrix[1][2], ), ) # Return the final matrix return matrix if isinstance(getattr(inkex.transforms.Transform, "matrix", None), property): # If Transform.matrix is a property, is stored in new complex format # Give it a setter that converts tuple of the form ((a,c,e),(b,d,f)) into the new complex format if necessary def matrixset(self, mat): self.arg1 = (mat[0][0] + mat[1][1]) / 2 + 1j * (mat[1][0] - mat[0][1]) / 2 self.arg2 = (mat[0][0] - mat[1][1]) / 2 + 1j * (mat[1][0] + mat[0][1]) / 2 self.arg3 = mat[0][2] + mat[1][2] * 1j setfcn = inkex.transforms.Transform.matrix.fget # type: ignore inkex.transforms.Transform.matrix = property(setfcn, matrixset) # type: ignore from typing import cast, Tuple def fast_set_matrix(self, matrix): """Parse a given string as an svg transformation instruction. .. version added:: 1.1""" if isinstance(matrix, str): self.matrix = transform_to_matrix(matrix) elif isinstance(matrix, (list, tuple)) and len(matrix) == 6: self.matrix = ( (float(matrix[0]), float(matrix[2]), float(matrix[4])), (float(matrix[1]), float(matrix[3]), float(matrix[5])), ) elif isinstance(matrix, Transform): self.matrix = matrix.matrix elif isinstance(matrix, (tuple, list)) and len(matrix) == 2: row1 = matrix[0] row2 = matrix[1] if isinstance(row1, (tuple, list)) and isinstance(row2, (tuple, list)): if len(row1) == 3 and len(row2) == 3: row1 = cast(Tuple[float, float, float], tuple(map(float, row1))) row2 = cast(Tuple[float, float, float], tuple(map(float, row2))) self.matrix = (row1, row2) else: raise ValueError( f"Matrix '{matrix}' is not a valid transformation matrix" ) else: raise ValueError(f"Matrix '{matrix}' is not a valid transformation matrix") elif not isinstance(matrix, (list, tuple)): raise ValueError(f"Invalid transform type: {type(matrix).__name__}") else: raise ValueError(f"Matrix '{matrix}' is not a valid transformation matrix") inkex.transforms.Transform._set_matrix = fast_set_matrix # type: ignore """ _utils.py """ # Cache the namespace function results NSloc = inkex.utils if hasattr(inkex.utils, "addNS") else inkex.elements._utils orig_addNS = getattr(NSloc, "addNS") orig_removeNS = getattr(NSloc, "removeNS") orig_splitNS = getattr(NSloc, "splitNS") @lru_cache(maxsize=None) def cached_addNS(*args, **kwargs): return orig_addNS(*args, **kwargs) @lru_cache(maxsize=None) def cached_removeNS(*args, **kwargs): return orig_removeNS(*args, **kwargs) @lru_cache(maxsize=None) def cached_splitNS(*args, **kwargs): return orig_splitNS(*args, **kwargs) def clear_caches(): cached_addNS.cache_clear() cached_removeNS.cache_clear() cached_splitNS.cache_clear() # Reset the cache before namespace modifications orig_registerNS = getattr(NSloc, "registerNS", None) if orig_registerNS: def mod_registerNS(*args, **kwargs): clear_caches() orig_registerNS(*args, **kwargs) NSloc.registerNS = mod_registerNS # type: ignore orig_add_namespace = getattr(inkex.SvgDocumentElement, "add_namespace", None) if orig_add_namespace: def mod_add_namespace(*args, **kwargs): clear_caches() orig_add_namespace(*args, **kwargs) inkex.SvgDocumentElement.add_namespace = mod_add_namespace # type: ignore NSloc.addNS = ( # type: ignore inkex.addNS ) = inkex.elements._base.addNS = inkex.elements._groups.addNS = ( inkex.elements._filters.addNS ) = inkex.elements._polygons.addNS = cached_addNS NSloc.removeNS = inkex.elements._base.removeNS = cached_removeNS # type: ignore NSloc.splitNS = inkex.elements._base.splitNS = cached_splitNS # type: ignore try: inkex.elements._parser.splitNS = cached_splitNS # new versions only except: pass """_parser.py""" try: lup1 = inkex.elements._parser.NodeBasedLookup.lookup_table lup2 = dict() for k, v in lup1.items(): lup2[cached_addNS(k[1], k[0])] = list( reversed(lup1[cached_splitNS(cached_addNS(k[1], k[0]))]) ) def fast_lookup(self, doc, element): try: for kls in lup2[element.tag]: if kls.is_class_element(element): # pylint: disable=protected-access return kls except KeyError: try: lup2[element.tag] = list(reversed(lup1[cached_splitNS(element.tag)])) for kls in lup2[element.tag]: if kls.is_class_element(element): # pylint: disable=protected-access return kls except TypeError: lup2[element.tag] = None # handle comments return None except TypeError: # Handle non-element proxies case "" return None return inkex.elements._parser.NodeBasedLookup.default inkex.elements._parser.NodeBasedLookup.lookup = fast_lookup # type: ignore # new versions only except: pass