Debugging xml namespace behavior -- needs improvement

pull/58/head
Michael X. Grey 2018-05-10 16:45:41 -07:00
parent be675f1b1c
commit a29b392234
8 changed files with 78 additions and 48 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 = {}

View File

@ -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

View File

@ -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">
<defs/>
<g
id="matrix group"
transform="matrix(1.5 0 0 0.5 -40.0 20.0)">
transform="matrix(1.5 0.0 0.0 0.5 -40.0 20.0)">
<path
d="M 183,183 l 0,-50"

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,8 +1,14 @@
from __future__ import division, absolute_import, print_function
import unittest
from svgpathtools import *
from os.path import join, dirname
class TestGroups(unittest.TestCase):
def test_group_transform(self):
pass
def test_group_flatten(self):
doc = Document(join(dirname(__file__), 'groups.svg'))
result = doc.flatten_all_paths()
print('\nnumber of paths: '+str(len(result)))
self.assertGreater(len(result), 0)

View File

@ -137,3 +137,7 @@ class TestParser(unittest.TestCase):
def test_errors(self):
self.assertRaises(ValueError, parse_path, 'M 100 100 L 200 200 Z 100 200')
def test_transform(self):
# TODO: Write these tests
pass