From a29b392234084ec4066ca19ada651a2d7faea91d Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 10 May 2018 16:45:41 -0700 Subject: [PATCH] Debugging xml namespace behavior -- needs improvement --- svgpathtools.egg-info/PKG-INFO | 4 +-- svgpathtools.egg-info/SOURCES.txt | 3 ++ svgpathtools/__init__.py | 2 +- svgpathtools/document.py | 52 +++++++++++++++++-------------- svgpathtools/parser.py | 45 +++++++++++++++++--------- test/groups.svg | 6 ++-- test/test_groups.py | 10 ++++-- test/test_parsing.py | 4 +++ 8 files changed, 78 insertions(+), 48 deletions(-) diff --git a/svgpathtools.egg-info/PKG-INFO b/svgpathtools.egg-info/PKG-INFO index 29ff89b..9409782 100644 --- a/svgpathtools.egg-info/PKG-INFO +++ b/svgpathtools.egg-info/PKG-INFO @@ -7,7 +7,6 @@ Author: Andy Port Author-email: AndyAPort@gmail.com License: MIT Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.2 -Description-Content-Type: UNKNOWN Description: svgpathtools ============ @@ -595,9 +594,8 @@ Description: of the 'parallel' offset curve.""" nls = [] for seg in path: - ct = 1 for k in range(steps): - t = k / steps + t = k / float(steps) offset_vector = offset_distance * seg.normal(t) nl = Line(seg.point(t), seg.point(t) + offset_vector) nls.append(nl) diff --git a/svgpathtools.egg-info/SOURCES.txt b/svgpathtools.egg-info/SOURCES.txt index c1dde0f..1eb4a50 100644 --- a/svgpathtools.egg-info/SOURCES.txt +++ b/svgpathtools.egg-info/SOURCES.txt @@ -15,6 +15,7 @@ test.svg vectorframes.svg svgpathtools/__init__.py svgpathtools/bezier.py +svgpathtools/document.py svgpathtools/misctools.py svgpathtools/parser.py svgpathtools/path.py @@ -29,11 +30,13 @@ svgpathtools.egg-info/requires.txt svgpathtools.egg-info/top_level.txt test/circle.svg test/ellipse.svg +test/groups.svg test/polygons.svg test/rects.svg test/test.svg test/test_bezier.py test/test_generation.py +test/test_groups.py test/test_parsing.py test/test_path.py test/test_polytools.py diff --git a/svgpathtools/__init__.py b/svgpathtools/__init__.py index 207b33a..e678bb2 100644 --- a/svgpathtools/__init__.py +++ b/svgpathtools/__init__.py @@ -17,4 +17,4 @@ from .document import Document try: from .svg2paths import svg2paths, svg2paths2 except ImportError: - pass \ No newline at end of file + pass diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 1dbaf18..69ef34f 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -52,9 +52,8 @@ A Big Problem: from __future__ import division, absolute_import, print_function import os import collections -import xml.etree.cElementTree as etree -import xml.etree.ElementTree.Element as Element -import xml.etree.ElementTree.SubElement as SubElement +import xml.etree.ElementTree as etree +from xml.etree.ElementTree import Element, SubElement, register_namespace, _namespace_map import warnings # Internal dependencies @@ -65,6 +64,11 @@ from .svg2paths import (path2pathd, ellipse2pathd, line2pathd, polyline2pathd, from .misctools import open_in_browser from .path import * +# Let xml.etree.ElementTree know about the SVG namespace +print(' ------------------ about to register the svg namespace') +register_namespace('svg', 'http://www.w3.org/2000/svg') +print('namespace map: {0}'.format(_namespace_map)) + # THESE MUST BE WRAPPED TO OUTPUT ElementTree.element objects CONVERSIONS = {'path': path2pathd, 'circle': ellipse2pathd, @@ -74,12 +78,15 @@ CONVERSIONS = {'path': path2pathd, 'polygon': polygon2pathd, 'rect': rect2pathd} +ONLY_PATHS = {'path': path2pathd} + def flatten_all_paths( group, group_filter=lambda x: True, path_filter=lambda x: True, - path_conversions=CONVERSIONS): + path_conversions=CONVERSIONS, + search_xpath='{http://www.w3.org/2000/svg}g'): """Returns the paths inside a group (recursively), expressing the paths in the base coordinates. Note that if the group being passed in is nested inside some parent group(s), we cannot take the parent group(s) @@ -92,7 +99,8 @@ def flatten_all_paths( that are not included in this dictionary will be ignored (including the `path` tag). """ if not isinstance(group, Element): - raise TypeError('Must provide an xml.etree.Element object') + raise TypeError('Must provide an xml.etree.Element object. Instead you provided {0} : compared to {1}' + .format(type(group), type(Element('some tag')))) # Stop right away if the group_selector rejects this group if not group_filter(group): @@ -108,7 +116,7 @@ def flatten_all_paths( def get_relevant_children(parent, last_tf): children = [] - for elem in filter(group_filter, parent.iterfind('g')): + for elem in filter(group_filter, parent.iterfind(search_xpath)): children.append(new_stack_element(elem, last_tf)) return children @@ -120,10 +128,13 @@ def flatten_all_paths( while stack: top = stack.pop() + print('popping group {0}'.format(top.group.attrib)) + print('has children: {0}'.format(list(elem.tag for elem in top.group.iter()))) + # For each element type that we know how to convert into path data, parse the element after confirming that # the path_filter accepts it. - for key, converter in path_conversions: - for path_elem in filter(path_filter, top.group.iterfind(key)): + for key, converter in path_conversions.iteritems(): + for path_elem in filter(path_filter, top.group.iterfind('{http://www.w3.org/2000/svg}'+key)): path_tf = top.transform * parse_transform(path_elem.get('transform')) path = transform(parse_path(converter(path_elem)), path_tf) paths.append(FlattenedPath(path, path_elem.attrib, path_tf)) @@ -139,7 +150,8 @@ def flatten_group( recursive=True, group_filter=lambda x: True, path_filter=lambda x: True, - path_conversions=CONVERSIONS): + path_conversions=CONVERSIONS, + search_xpath='g'): """Flatten all the paths in a specific group. The paths will be flattened into the 'root' frame. Note that root needs to be @@ -150,7 +162,7 @@ def flatten_group( # We will shortcut here, because it is impossible for any paths to be returned anyhow. return [] - # We create a set of the unique IDs of each group object that we want to flatten. + # We create a set of the unique IDs of each element that we wish to flatten, if those elements are groups. # Any groups outside of this set will be skipped while we flatten the paths. desired_groups = set() if recursive: @@ -162,7 +174,7 @@ def flatten_group( def desired_group_filter(x): return (id(x) in desired_groups) and group_filter(x) - return flatten_all_paths(root, desired_group_filter, path_filter, path_conversions) + return flatten_all_paths(root, desired_group_filter, path_filter, path_conversions, search_xpath) class Document: @@ -190,14 +202,6 @@ class Document: self.tree = etree.parse(filename) self.root = self.tree.getroot() - # get URI namespace (only necessary in OS X?) - root_tag = self.tree.getroot().tag - if root_tag[0] == "{": - self._prefix = root_tag[:root_tag.find('}') + 1] - else: - self._prefix = '' - # etree.register_namespace('', prefix) - def flatten_all_paths(self, group_filter=lambda x: True, path_filter=lambda x: True, @@ -215,7 +219,7 @@ class Document: group = self.get_or_add_group(group) elif not isinstance(group, Element): raise TypeError('Must provide a list of strings that represent a nested group name, ' - 'or provide an xml.etree.Element object') + 'or provide an xml.etree.Element object. Instead you provided {0}'.format(group)) return flatten_group(group, self.tree.getroot(), recursive, group_filter, path_filter, path_conversions) @@ -247,7 +251,8 @@ class Document: group = self.get_or_add_group(group) elif not isinstance(group, Element): - raise TypeError('Must provide a list of strings or an xml.etree.Element object') + raise TypeError('Must provide a list of strings or an xml.etree.Element object. ' + 'Instead you provided {0}'.format(group)) else: # Make sure that the group belongs to this Document object @@ -262,7 +267,8 @@ class Document: # Assume this is a valid d-string. TODO: Should we sanity check the input string? path_svg = path else: - raise TypeError('Must provide a Path, a path segment type, or a valid SVG path d-string') + raise TypeError('Must provide a Path, a path segment type, or a valid SVG path d-string. ' + 'Instead you provided {0}'.format(path)) if attribs is None: attribs = {} @@ -313,7 +319,7 @@ class Document: if parent is None: parent = self.tree.getroot() elif not self.contains_group(parent): - warnings.warn('The requested group does not belong to this Document') + warnings.warn('The requested group {0} does not belong to this Document'.format(parent)) if group_attribs is None: group_attribs = {} diff --git a/svgpathtools/parser.py b/svgpathtools/parser.py index 70f0358..f01aee5 100644 --- a/svgpathtools/parser.py +++ b/svgpathtools/parser.py @@ -206,30 +206,38 @@ def _check_num_parsed_values(values, allowed): if len(allowed) > 1: warnings.warn('Expected one of the following number of values {0}, found {1}: {2}' .format(allowed, len(values), values)) - elif allowed != 1: - warnings.warn('Expected {0} values, found {1}: {2}'.format(allowed, len(values), values)) + elif allowed[0] != 1: + warnings.warn('Expected {0} values, found {1}: {2}'.format(allowed[0], len(values), values)) else: warnings.warn('Expected 1 value, found {0}: {1}'.format(len(values), values)) + return False + return True def _parse_transform_substr(transform_substr): + type_str, value_str = transform_substr.split('(') - values = list(map(float, value_str.split(','))) + value_str = value_str.replace(',', ' ') + values = list(map(float, filter(None, value_str.split(' ')))) + transform = np.identity(3) if 'matrix' in type_str: - _check_num_parsed_values(values, 6) + if not _check_num_parsed_values(values, [6]): + return transform transform[0:2, 0:3] = np.matrix([values[0:6:2], values[1:6:2]]) elif 'translate' in transform_substr: - _check_num_parsed_values(values, [1, 2]) + if not _check_num_parsed_values(values, [1, 2]): + return transform transform[0, 2] = values[0] if len(values) > 1: transform[1, 2] = values[1] elif 'scale' in transform_substr: - _check_num_parsed_values(values, [1, 2]) + if not _check_num_parsed_values(values, [1, 2]): + return transform x_scale = values[0] y_scale = values[1] if (len(values) > 1) else x_scale @@ -237,26 +245,31 @@ def _parse_transform_substr(transform_substr): transform[1, 1] = y_scale elif 'rotate' in transform_substr: - _check_num_parsed_values(values, [1, 3]) + if not _check_num_parsed_values(values, [1, 3]): + return transform angle = values[0] * np.pi / 180.0 if len(values) == 3: offset = values[1:3] else: offset = (0, 0) - T_offset = np.identity(3) - T_offset[0:2, 2] = np.matrix([[offset[0]], [offset[1]]]) - T_rotate = np.identity(3) - T_rotate[0:2, 0:2] = np.matrix([np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]) + tf_offset = np.identity(3) + tf_offset[0:2, 2:3] = np.matrix([[offset[0]], [offset[1]]]) + tf_rotate = np.identity(3) + tf_rotate[0:2, 0:2] = np.matrix([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]) - transform = T_offset * T_rotate * (-T_offset) + transform = tf_offset * tf_rotate * (-tf_offset) elif 'skewX' in transform_substr: - _check_num_parsed_values(values, 1) + if not _check_num_parsed_values(values, [1]): + return transform + transform[0, 1] = np.tan(values[0] * np.pi / 180.0) elif 'skewY' in transform_substr: - _check_num_parsed_values(values, 1) + if not _check_num_parsed_values(values, [1]): + return transform + transform[1, 0] = np.tan(values[0] * np.pi / 180.0) else: # Return an identity matrix if the type of transform is unknown, and warn the user @@ -274,6 +287,8 @@ def parse_transform(transform_str): raise TypeError('Must provide a string to parse') total_transform = np.identity(3) - transform_substrs = transform_str.split(')') + transform_substrs = transform_str.split(')')[:-1] # Skip the last element, because it should be empty for substr in transform_substrs: total_transform *= _parse_transform_substr(substr) + + return total_transform diff --git a/test/groups.svg b/test/groups.svg index 6b3cc8e..f5492d1 100644 --- a/test/groups.svg +++ b/test/groups.svg @@ -5,13 +5,11 @@ viewBox="0 0 365 365" height="100%" width="100%" - xmlns="http://www.w3.org/2000/svg" - xmlns:ev="http://www.w3.org/2001/xml-events" - xmlns:xlink="http://www.w3.org/1999/xlink"> + xmlns="http://www.w3.org/2000/svg"> + transform="matrix(1.5 0.0 0.0 0.5 -40.0 20.0)">