Debugging xml namespace behavior -- needs improvement
parent
be675f1b1c
commit
a29b392234
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue