Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Empty Polylines, Preserve Path Order, and Optionally Return Parent Info #211

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 78 additions & 51 deletions svgpathtools/svg_to_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# External dependencies
from __future__ import division, absolute_import, print_function
from xml.dom.minidom import parse
from xml.dom.minidom import parse, Node
import os
from io import StringIO
import re
Expand Down Expand Up @@ -62,6 +62,7 @@ def polyline2pathd(polyline, is_polygon=False):
else:
points = COORD_PAIR_TMPLT.findall(polyline.get('points', ''))

if not points or len(points) < 2: return ""
closed = (float(points[0][0]) == float(points[-1][0]) and
float(points[0][1]) == float(points[-1][1]))

Expand Down Expand Up @@ -136,6 +137,8 @@ def line2pathd(l):

def svg2paths(svg_file_location,
return_svg_attributes=False,
return_other_tags=None,
include_parent_info=False,
convert_circles_to_paths=True,
convert_ellipses_to_paths=True,
convert_lines_to_paths=True,
Expand Down Expand Up @@ -182,61 +185,81 @@ def svg2paths(svg_file_location,

doc = parse(svg_file_location)

def dom2dict(element):
"""Converts DOM elements to dictionaries of attributes."""
keys = list(element.attributes.keys())
values = [val.value for val in list(element.attributes.values())]
def dom2dict(element, include_parents=False):
"""Converts DOM elements to dictionaries of attributes, including parent information."""
if element.attributes is not None:
keys = list(element.attributes.keys())
values = [val.value for val in list(element.attributes.values())]
else:
keys = []
values = []
if include_parents:
keys.append('_tag')
values.append(element.tagName)
parent = element.parentNode
if parent is not None and parent.nodeType == Node.ELEMENT_NODE and parent.tagName != 'svg':
keys.append('_parent')
values.append(dom2dict(parent, True))
return dict(list(zip(keys, values)))

# Use minidom to extract path strings from input SVG
paths = [dom2dict(el) for el in doc.getElementsByTagName('path')]
d_strings = [el['d'] for el in paths]
attribute_dictionary_list = paths

# Use minidom to extract polyline strings from input SVG, convert to
# path strings, add to list
if convert_polylines_to_paths:
plins = [dom2dict(el) for el in doc.getElementsByTagName('polyline')]
d_strings += [polyline2pathd(pl) for pl in plins]
attribute_dictionary_list += plins

# Use minidom to extract polygon strings from input SVG, convert to
# path strings, add to list
if convert_polygons_to_paths:
pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')]
d_strings += [polygon2pathd(pg) for pg in pgons]
attribute_dictionary_list += pgons

if convert_lines_to_paths:
lines = [dom2dict(el) for el in doc.getElementsByTagName('line')]
d_strings += [('M' + l['x1'] + ' ' + l['y1'] +
'L' + l['x2'] + ' ' + l['y2']) for l in lines]
attribute_dictionary_list += lines

if convert_ellipses_to_paths:
ellipses = [dom2dict(el) for el in doc.getElementsByTagName('ellipse')]
d_strings += [ellipse2pathd(e) for e in ellipses]
attribute_dictionary_list += ellipses

if convert_circles_to_paths:
circles = [dom2dict(el) for el in doc.getElementsByTagName('circle')]
d_strings += [ellipse2pathd(c) for c in circles]
attribute_dictionary_list += circles

if convert_rectangles_to_paths:
rectangles = [dom2dict(el) for el in doc.getElementsByTagName('rect')]
d_strings += [rect2pathd(r) for r in rectangles]
attribute_dictionary_list += rectangles
# Short name for all the dom2dict calls below
ip = include_parent_info

d_strings = []
attribute_dictionary_list = []

# Use minidom to extract path strings from input SVG.
# Each type is handled seperately but the overall order is preserved.
for el in doc.getElementsByTagName('*'):
if el.tagName == 'path':
path_data = dom2dict(el, ip)
d_strings.append(path_data['d'])
attribute_dictionary_list.append(path_data)
elif el.tagName == 'polyline' and convert_polylines_to_paths:
polyline_data = dom2dict(el, ip)
d_strings.append(polyline2pathd(polyline_data))
attribute_dictionary_list.append(polyline_data)
elif el.tagName == 'polygon' and convert_polygons_to_paths:
polygon_data = dom2dict(el, ip)
d_strings.append(polygon2pathd(polygon_data))
attribute_dictionary_list.append(polygon_data)
elif el.tagName == 'line' and convert_lines_to_paths:
line_data = dom2dict(el, ip)
d_strings.append(f"M{line_data['x1']} {line_data['y1']} L{line_data['x2']} {line_data['y2']}")
attribute_dictionary_list.append(line_data)
elif el.tagName == 'ellipse' and convert_ellipses_to_paths:
ellipse_data = dom2dict(el, ip)
d_strings.append(ellipse2pathd(ellipse_data))
attribute_dictionary_list.append(ellipse_data)
elif el.tagName == 'circle' and convert_circles_to_paths:
circle_data = dom2dict(el, ip)
d_strings.append(ellipse2pathd(circle_data))
attribute_dictionary_list.append(circle_data)
elif el.tagName == 'rect' and convert_rectangles_to_paths:
rect_data = dom2dict(el, ip)
d_strings.append(rect2pathd(rect_data))
attribute_dictionary_list.append(rect_data)

path_list = [parse_path(d) for d in d_strings]
retval = [path_list, attribute_dictionary_list]

if return_svg_attributes:
svg_attributes = dom2dict(doc.getElementsByTagName('svg')[0])
doc.unlink()
path_list = [parse_path(d) for d in d_strings]
return path_list, attribute_dictionary_list, svg_attributes
else:
doc.unlink()
path_list = [parse_path(d) for d in d_strings]
return path_list, attribute_dictionary_list
retval.append(svg_attributes)

if return_other_tags is not None:
other_tags = {}
for other in return_other_tags:
other_tags[other] = []
elements = doc.getElementsByTagName(other)
for el in elements:
taginfo = dom2dict(el,ip)
taginfo['_value'] = el.firstChild.nodeValue if el.firstChild is not None else None
other_tags[other].append(taginfo)
retval.append(other_tags)

doc.unlink()
return retval


def svg2paths2(svg_file_location,
Expand All @@ -262,6 +285,8 @@ def svg2paths2(svg_file_location,

def svgstr2paths(svg_string,
return_svg_attributes=False,
return_other_tags=None,
include_parent_info=False,
convert_circles_to_paths=True,
convert_ellipses_to_paths=True,
convert_lines_to_paths=True,
Expand All @@ -275,6 +300,8 @@ def svgstr2paths(svg_string,
svg_file_obj = StringIO(svg_string)
return svg2paths(svg_file_location=svg_file_obj,
return_svg_attributes=return_svg_attributes,
return_other_tags=return_other_tags,
include_parent_info=include_parent_info,
convert_circles_to_paths=convert_circles_to_paths,
convert_ellipses_to_paths=convert_ellipses_to_paths,
convert_lines_to_paths=convert_lines_to_paths,
Expand Down