191 lines
7.3 KiB
Python
191 lines
7.3 KiB
Python
"""(Experimental) replacement for import/export functionality.
|
|
|
|
This module contains the `Document` class, a container for a DOM-style
|
|
document (e.g. svg, html, xml, etc.) designed to replace and improve
|
|
upon the IO functionality of svgpathtools (i.e. the svg2paths and
|
|
disvg/wsvg functions).
|
|
|
|
An Historic Note:
|
|
The functionality in this module is meant to replace and improve
|
|
upon the IO functionality previously provided by the the
|
|
`svg2paths` and `disvg`/`wsvg` functions.
|
|
|
|
Example:
|
|
Typical usage looks something like the following.
|
|
|
|
>> from svgpathtools import *
|
|
>> doc = Document('my_file.html')
|
|
>> for p in doc.paths:
|
|
>> foo(p) # do stuff using svgpathtools functionality
|
|
>> foo2(doc.tree) # do stuff using ElementTree's functionality
|
|
>> doc.display() # display doc in OS's default application
|
|
>> doc.save('my_new_file.html')
|
|
|
|
Todo: (please see contributor guidelines in CONTRIBUTING.md)
|
|
* Finish "NotImplemented" methods.
|
|
* Find some clever (and easy to implement) way to create a thorough
|
|
set of unittests.
|
|
* Finish Documentation for each method (approximately following the
|
|
Google Python Style Guide, see [1]_ for some nice examples).
|
|
For nice style examples, see [1]_.
|
|
|
|
Some thoughts on this module's direction:
|
|
* The `Document` class should ONLY grab path elements that are
|
|
inside an SVG.
|
|
* To handle transforms... there should be a "get_transform"
|
|
function and also a "flatten_transforms" tool that removes any
|
|
present transform attributes from all SVG-Path elements in the
|
|
document (applying the transformations before to the svgpathtools
|
|
Path objects).
|
|
Note: This ability to "flatten" will ignore CSS files (and any
|
|
relevant files that are not parsed into the tree automatically by
|
|
ElementTree)... that is unless you have any bright ideas on this.
|
|
I really know very little about DOM-style documents.
|
|
|
|
A Big Problem:
|
|
Derivatives and other functions may be messed up by
|
|
transforms unless transforms are flattened (and not included in
|
|
css)
|
|
"""
|
|
|
|
# External dependencies
|
|
from __future__ import division, absolute_import, print_function
|
|
import os
|
|
import xml.etree.cElementTree as etree
|
|
|
|
# Internal dependencies
|
|
from .parser import parse_path
|
|
from .svg2paths import (ellipse2pathd, line2pathd, polyline2pathd,
|
|
polygon2pathd, rect2pathd)
|
|
from .misctools import open_in_browser
|
|
|
|
# THESE MUST BE WRAPPED TO OUPUT ElementTree.element objects
|
|
CONVERSIONS = {'circle': ellipse2pathd,
|
|
'ellipse': ellipse2pathd,
|
|
'line': line2pathd,
|
|
'polyline': polyline2pathd,
|
|
'polygon': polygon2pathd,
|
|
'rect': rect2pathd}
|
|
|
|
|
|
class Document:
|
|
def __init__(self, filename, conversions=False, transform_paths=True):
|
|
"""(EXPERIMENTAL) A container for a DOM-style document.
|
|
|
|
The `Document` class provides a simple interface to modify and analyze
|
|
the path elements in a DOM-style document. The DOM-style document is
|
|
parsed into an ElementTree object (stored in the `tree` attribute and
|
|
all SVG-Path (and, optionally, Path-like) elements are extracted into a
|
|
list of svgpathtools Path objects. For more information on "Path-like"
|
|
objects, see the below explanation of the `conversions` argument.
|
|
|
|
Args:
|
|
merge_transforms (object):
|
|
filename (str): The filename of the DOM-style object.
|
|
conversions (bool or dict): If true, automatically converts
|
|
circle, ellipse, line, polyline, polygon, and rect elements
|
|
into path elements. These changes are saved in the ElementTree
|
|
object. For custom conversions, a dictionary can be passed in instead whose
|
|
keys are the element tags that are to be converted and whose values
|
|
are the corresponding conversion functions. Conversion
|
|
functions should both take in and return an ElementTree.element
|
|
object.
|
|
"""
|
|
|
|
# remember location of original svg file
|
|
if filename is not None and os.path.dirname(filename) == '':
|
|
self.original_filename = os.path.join(os.getcwd(), filename)
|
|
else:
|
|
self.original_filename = filename
|
|
|
|
# parse svg to ElementTree object
|
|
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)
|
|
|
|
self.paths = self._get_paths(conversions)
|
|
|
|
def get_elements_by_tag(self, tag):
|
|
"""Returns a generator of all elements with the given tag.
|
|
|
|
Note: for more advanced XML-related functionality, use the
|
|
`tree` attribute (an ElementTree object).
|
|
"""
|
|
return self.tree.iter(tag=self._prefix + tag)
|
|
|
|
def _get_paths(self, conversions):
|
|
paths = []
|
|
|
|
# Get d-strings for SVG-Path elements
|
|
paths += [el.attrib for el in self.get_elements_by_tag('path')]
|
|
d_strings = [el['d'] for el in paths]
|
|
attribute_dictionary_list = paths
|
|
|
|
# Convert path-like elements to d-strings and attribute dicts
|
|
if conversions:
|
|
for tag, fcn in conversions.items():
|
|
attributes = [l.attrib for l in self.get_elements_by_tag(tag)]
|
|
d_strings += [fcn(d) for d in attributes]
|
|
|
|
path_list = [parse_path(d) for d in d_strings]
|
|
return path_list
|
|
|
|
def convert_pathlike_elements_to_paths(self, conversions=CONVERSIONS):
|
|
raise NotImplementedError
|
|
|
|
def get_svg_attributes(self):
|
|
"""To help with backwards compatibility."""
|
|
return self.get_elements_by_tag('svg')[0].attrib
|
|
|
|
def get_path_attributes(self):
|
|
"""To help with backwards compatibility."""
|
|
return [p.tree_element.attrib for p in self.paths]
|
|
|
|
def add(self, path, attribs={}, parent=None):
|
|
"""Add a new path to the SVG."""
|
|
if parent is None:
|
|
parent = self.tree.getroot()
|
|
# just get root
|
|
# then add new path
|
|
# then record element_tree object in path
|
|
raise NotImplementedError
|
|
|
|
def add_group(self, group_attribs={}, parent=None):
|
|
"""Add an empty group element to the SVG."""
|
|
if parent is None:
|
|
parent = self.tree.getroot()
|
|
raise NotImplementedError
|
|
|
|
def update_tree(self):
|
|
"""Rewrite d-string's for each path in the `tree` attribute."""
|
|
raise NotImplementedError
|
|
|
|
def save(self, filename, update=True):
|
|
"""Write to svg to a file."""
|
|
if update:
|
|
self.update_tree()
|
|
|
|
with open(filename, 'w') as output_svg:
|
|
output_svg.write(etree.tostring(self.tree.getroot()))
|
|
|
|
def display(self, filename=None, update=True):
|
|
"""Displays/opens the doc using the OS's default application."""
|
|
if update:
|
|
self.update_tree()
|
|
|
|
if filename is None:
|
|
raise NotImplementedError
|
|
|
|
# write to a (by default temporary) file
|
|
with open(filename, 'w') as output_svg:
|
|
output_svg.write(etree.tostring(self.tree.getroot()))
|
|
|
|
open_in_browser(filename)
|