"""(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)