Debugging xml namespace behavior -- needs improvement
parent
be675f1b1c
commit
a29b392234
|
@ -7,7 +7,6 @@ Author: Andy Port
|
||||||
Author-email: AndyAPort@gmail.com
|
Author-email: AndyAPort@gmail.com
|
||||||
License: MIT
|
License: MIT
|
||||||
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.2
|
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.2
|
||||||
Description-Content-Type: UNKNOWN
|
|
||||||
Description:
|
Description:
|
||||||
svgpathtools
|
svgpathtools
|
||||||
============
|
============
|
||||||
|
@ -595,9 +594,8 @@ Description:
|
||||||
of the 'parallel' offset curve."""
|
of the 'parallel' offset curve."""
|
||||||
nls = []
|
nls = []
|
||||||
for seg in path:
|
for seg in path:
|
||||||
ct = 1
|
|
||||||
for k in range(steps):
|
for k in range(steps):
|
||||||
t = k / steps
|
t = k / float(steps)
|
||||||
offset_vector = offset_distance * seg.normal(t)
|
offset_vector = offset_distance * seg.normal(t)
|
||||||
nl = Line(seg.point(t), seg.point(t) + offset_vector)
|
nl = Line(seg.point(t), seg.point(t) + offset_vector)
|
||||||
nls.append(nl)
|
nls.append(nl)
|
||||||
|
|
|
@ -15,6 +15,7 @@ test.svg
|
||||||
vectorframes.svg
|
vectorframes.svg
|
||||||
svgpathtools/__init__.py
|
svgpathtools/__init__.py
|
||||||
svgpathtools/bezier.py
|
svgpathtools/bezier.py
|
||||||
|
svgpathtools/document.py
|
||||||
svgpathtools/misctools.py
|
svgpathtools/misctools.py
|
||||||
svgpathtools/parser.py
|
svgpathtools/parser.py
|
||||||
svgpathtools/path.py
|
svgpathtools/path.py
|
||||||
|
@ -29,11 +30,13 @@ svgpathtools.egg-info/requires.txt
|
||||||
svgpathtools.egg-info/top_level.txt
|
svgpathtools.egg-info/top_level.txt
|
||||||
test/circle.svg
|
test/circle.svg
|
||||||
test/ellipse.svg
|
test/ellipse.svg
|
||||||
|
test/groups.svg
|
||||||
test/polygons.svg
|
test/polygons.svg
|
||||||
test/rects.svg
|
test/rects.svg
|
||||||
test/test.svg
|
test/test.svg
|
||||||
test/test_bezier.py
|
test/test_bezier.py
|
||||||
test/test_generation.py
|
test/test_generation.py
|
||||||
|
test/test_groups.py
|
||||||
test/test_parsing.py
|
test/test_parsing.py
|
||||||
test/test_path.py
|
test/test_path.py
|
||||||
test/test_polytools.py
|
test/test_polytools.py
|
||||||
|
|
|
@ -17,4 +17,4 @@ from .document import Document
|
||||||
try:
|
try:
|
||||||
from .svg2paths import svg2paths, svg2paths2
|
from .svg2paths import svg2paths, svg2paths2
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -52,9 +52,8 @@ A Big Problem:
|
||||||
from __future__ import division, absolute_import, print_function
|
from __future__ import division, absolute_import, print_function
|
||||||
import os
|
import os
|
||||||
import collections
|
import collections
|
||||||
import xml.etree.cElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
import xml.etree.ElementTree.Element as Element
|
from xml.etree.ElementTree import Element, SubElement, register_namespace, _namespace_map
|
||||||
import xml.etree.ElementTree.SubElement as SubElement
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
# Internal dependencies
|
# Internal dependencies
|
||||||
|
@ -65,6 +64,11 @@ from .svg2paths import (path2pathd, ellipse2pathd, line2pathd, polyline2pathd,
|
||||||
from .misctools import open_in_browser
|
from .misctools import open_in_browser
|
||||||
from .path import *
|
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
|
# THESE MUST BE WRAPPED TO OUTPUT ElementTree.element objects
|
||||||
CONVERSIONS = {'path': path2pathd,
|
CONVERSIONS = {'path': path2pathd,
|
||||||
'circle': ellipse2pathd,
|
'circle': ellipse2pathd,
|
||||||
|
@ -74,12 +78,15 @@ CONVERSIONS = {'path': path2pathd,
|
||||||
'polygon': polygon2pathd,
|
'polygon': polygon2pathd,
|
||||||
'rect': rect2pathd}
|
'rect': rect2pathd}
|
||||||
|
|
||||||
|
ONLY_PATHS = {'path': path2pathd}
|
||||||
|
|
||||||
|
|
||||||
def flatten_all_paths(
|
def flatten_all_paths(
|
||||||
group,
|
group,
|
||||||
group_filter=lambda x: True,
|
group_filter=lambda x: True,
|
||||||
path_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.
|
"""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)
|
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).
|
that are not included in this dictionary will be ignored (including the `path` tag).
|
||||||
"""
|
"""
|
||||||
if not isinstance(group, Element):
|
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
|
# Stop right away if the group_selector rejects this group
|
||||||
if not group_filter(group):
|
if not group_filter(group):
|
||||||
|
@ -108,7 +116,7 @@ def flatten_all_paths(
|
||||||
|
|
||||||
def get_relevant_children(parent, last_tf):
|
def get_relevant_children(parent, last_tf):
|
||||||
children = []
|
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))
|
children.append(new_stack_element(elem, last_tf))
|
||||||
return children
|
return children
|
||||||
|
|
||||||
|
@ -120,10 +128,13 @@ def flatten_all_paths(
|
||||||
while stack:
|
while stack:
|
||||||
top = stack.pop()
|
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
|
# For each element type that we know how to convert into path data, parse the element after confirming that
|
||||||
# the path_filter accepts it.
|
# the path_filter accepts it.
|
||||||
for key, converter in path_conversions:
|
for key, converter in path_conversions.iteritems():
|
||||||
for path_elem in filter(path_filter, top.group.iterfind(key)):
|
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_tf = top.transform * parse_transform(path_elem.get('transform'))
|
||||||
path = transform(parse_path(converter(path_elem)), path_tf)
|
path = transform(parse_path(converter(path_elem)), path_tf)
|
||||||
paths.append(FlattenedPath(path, path_elem.attrib, path_tf))
|
paths.append(FlattenedPath(path, path_elem.attrib, path_tf))
|
||||||
|
@ -139,7 +150,8 @@ def flatten_group(
|
||||||
recursive=True,
|
recursive=True,
|
||||||
group_filter=lambda x: True,
|
group_filter=lambda x: True,
|
||||||
path_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.
|
"""Flatten all the paths in a specific group.
|
||||||
|
|
||||||
The paths will be flattened into the 'root' frame. Note that root needs to be
|
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.
|
# We will shortcut here, because it is impossible for any paths to be returned anyhow.
|
||||||
return []
|
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.
|
# Any groups outside of this set will be skipped while we flatten the paths.
|
||||||
desired_groups = set()
|
desired_groups = set()
|
||||||
if recursive:
|
if recursive:
|
||||||
|
@ -162,7 +174,7 @@ def flatten_group(
|
||||||
def desired_group_filter(x):
|
def desired_group_filter(x):
|
||||||
return (id(x) in desired_groups) and 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:
|
class Document:
|
||||||
|
@ -190,14 +202,6 @@ class Document:
|
||||||
self.tree = etree.parse(filename)
|
self.tree = etree.parse(filename)
|
||||||
self.root = self.tree.getroot()
|
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,
|
def flatten_all_paths(self,
|
||||||
group_filter=lambda x: True,
|
group_filter=lambda x: True,
|
||||||
path_filter=lambda x: True,
|
path_filter=lambda x: True,
|
||||||
|
@ -215,7 +219,7 @@ class Document:
|
||||||
group = self.get_or_add_group(group)
|
group = self.get_or_add_group(group)
|
||||||
elif not isinstance(group, Element):
|
elif not isinstance(group, Element):
|
||||||
raise TypeError('Must provide a list of strings that represent a nested group name, '
|
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)
|
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)
|
group = self.get_or_add_group(group)
|
||||||
|
|
||||||
elif not isinstance(group, Element):
|
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:
|
else:
|
||||||
# Make sure that the group belongs to this Document object
|
# 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?
|
# Assume this is a valid d-string. TODO: Should we sanity check the input string?
|
||||||
path_svg = path
|
path_svg = path
|
||||||
else:
|
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:
|
if attribs is None:
|
||||||
attribs = {}
|
attribs = {}
|
||||||
|
@ -313,7 +319,7 @@ class Document:
|
||||||
if parent is None:
|
if parent is None:
|
||||||
parent = self.tree.getroot()
|
parent = self.tree.getroot()
|
||||||
elif not self.contains_group(parent):
|
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:
|
if group_attribs is None:
|
||||||
group_attribs = {}
|
group_attribs = {}
|
||||||
|
|
|
@ -206,30 +206,38 @@ def _check_num_parsed_values(values, allowed):
|
||||||
if len(allowed) > 1:
|
if len(allowed) > 1:
|
||||||
warnings.warn('Expected one of the following number of values {0}, found {1}: {2}'
|
warnings.warn('Expected one of the following number of values {0}, found {1}: {2}'
|
||||||
.format(allowed, len(values), values))
|
.format(allowed, len(values), values))
|
||||||
elif allowed != 1:
|
elif allowed[0] != 1:
|
||||||
warnings.warn('Expected {0} values, found {1}: {2}'.format(allowed, len(values), values))
|
warnings.warn('Expected {0} values, found {1}: {2}'.format(allowed[0], len(values), values))
|
||||||
else:
|
else:
|
||||||
warnings.warn('Expected 1 value, found {0}: {1}'.format(len(values), values))
|
warnings.warn('Expected 1 value, found {0}: {1}'.format(len(values), values))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _parse_transform_substr(transform_substr):
|
def _parse_transform_substr(transform_substr):
|
||||||
|
|
||||||
type_str, value_str = transform_substr.split('(')
|
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)
|
transform = np.identity(3)
|
||||||
if 'matrix' in type_str:
|
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]])
|
transform[0:2, 0:3] = np.matrix([values[0:6:2], values[1:6:2]])
|
||||||
|
|
||||||
elif 'translate' in transform_substr:
|
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]
|
transform[0, 2] = values[0]
|
||||||
if len(values) > 1:
|
if len(values) > 1:
|
||||||
transform[1, 2] = values[1]
|
transform[1, 2] = values[1]
|
||||||
|
|
||||||
elif 'scale' in transform_substr:
|
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]
|
x_scale = values[0]
|
||||||
y_scale = values[1] if (len(values) > 1) else x_scale
|
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
|
transform[1, 1] = y_scale
|
||||||
|
|
||||||
elif 'rotate' in transform_substr:
|
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
|
angle = values[0] * np.pi / 180.0
|
||||||
if len(values) == 3:
|
if len(values) == 3:
|
||||||
offset = values[1:3]
|
offset = values[1:3]
|
||||||
else:
|
else:
|
||||||
offset = (0, 0)
|
offset = (0, 0)
|
||||||
T_offset = np.identity(3)
|
tf_offset = np.identity(3)
|
||||||
T_offset[0:2, 2] = np.matrix([[offset[0]], [offset[1]]])
|
tf_offset[0:2, 2:3] = np.matrix([[offset[0]], [offset[1]]])
|
||||||
T_rotate = np.identity(3)
|
tf_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_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:
|
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)
|
transform[0, 1] = np.tan(values[0] * np.pi / 180.0)
|
||||||
|
|
||||||
elif 'skewY' in transform_substr:
|
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)
|
transform[1, 0] = np.tan(values[0] * np.pi / 180.0)
|
||||||
else:
|
else:
|
||||||
# Return an identity matrix if the type of transform is unknown, and warn the user
|
# 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')
|
raise TypeError('Must provide a string to parse')
|
||||||
|
|
||||||
total_transform = np.identity(3)
|
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:
|
for substr in transform_substrs:
|
||||||
total_transform *= _parse_transform_substr(substr)
|
total_transform *= _parse_transform_substr(substr)
|
||||||
|
|
||||||
|
return total_transform
|
||||||
|
|
|
@ -5,13 +5,11 @@
|
||||||
viewBox="0 0 365 365"
|
viewBox="0 0 365 365"
|
||||||
height="100%"
|
height="100%"
|
||||||
width="100%"
|
width="100%"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
xmlns:ev="http://www.w3.org/2001/xml-events"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs/>
|
<defs/>
|
||||||
<g
|
<g
|
||||||
id="matrix group"
|
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
|
<path
|
||||||
d="M 183,183 l 0,-50"
|
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
|
from __future__ import division, absolute_import, print_function
|
||||||
import unittest
|
import unittest
|
||||||
from svgpathtools import *
|
from svgpathtools import *
|
||||||
|
from os.path import join, dirname
|
||||||
|
|
||||||
|
|
||||||
class TestGroups(unittest.TestCase):
|
class TestGroups(unittest.TestCase):
|
||||||
|
|
||||||
def test_group_transform(self):
|
def test_group_flatten(self):
|
||||||
pass
|
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):
|
def test_errors(self):
|
||||||
self.assertRaises(ValueError, parse_path, 'M 100 100 L 200 200 Z 100 200')
|
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