Merge pull request #176 from FlyingSamson/read-svg-from-file-like-obj

Support reading of SVGs from strings and file-like objects
support-stringio-objects
Andrew Port 2022-06-05 20:12:19 -07:00 committed by GitHub
commit a989c9831d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 10 deletions

View File

@ -17,6 +17,6 @@ from .document import (Document, CONVERSIONS, CONVERT_ONLY_PATHS,
from .svg_io_sax import SaxDocument from .svg_io_sax import SaxDocument
try: try:
from .svg_to_paths import svg2paths, svg2paths2 from .svg_to_paths import svg2paths, svg2paths2, svgstr2paths
except ImportError: except ImportError:
pass pass

View File

@ -41,6 +41,7 @@ import xml.etree.ElementTree as etree
from xml.etree.ElementTree import Element, SubElement, register_namespace from xml.etree.ElementTree import Element, SubElement, register_namespace
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
import warnings import warnings
from io import StringIO
from tempfile import gettempdir from tempfile import gettempdir
from time import time from time import time
@ -235,13 +236,19 @@ class Document:
The output Path objects will be transformed based on their parent groups. The output Path objects will be transformed based on their parent groups.
Args: Args:
filepath (str): The filepath of the DOM-style object. filepath (str or file-like): The filepath of the
DOM-style object or a file-like object containing it.
""" """
self.original_filepath = None
# remember location of original svg file # strings are interpreted as file location everything else is treated as
self.original_filepath = filepath # file-like object and passed to the xml parser directly
if filepath is not None and os.path.dirname(filepath) == '': if isinstance(filepath, str):
self.original_filepath = os.path.join(os.getcwd(), filepath) # remember location of original svg file if any
self.original_filepath = filepath
if os.path.dirname(filepath) == '':
self.original_filepath = os.path.join(
os.getcwd(), filepath)
if filepath is None: if filepath is None:
self.tree = etree.ElementTree(Element('svg')) self.tree = etree.ElementTree(Element('svg'))
@ -251,6 +258,16 @@ class Document:
self.root = self.tree.getroot() self.root = self.tree.getroot()
@classmethod
def from_svg_string(cls, svg_string):
"""Factory method for creating a document from a string holding a svg
object
"""
# wrap string into StringIO object
svg_file_obj = StringIO(svg_string)
# create document from file object
return Document(svg_file_obj)
def paths(self, group_filter=lambda x: True, def paths(self, group_filter=lambda x: True,
path_filter=lambda x: True, path_conversions=CONVERSIONS): path_filter=lambda x: True, path_conversions=CONVERSIONS):
"""Returns a list of all paths in the document. """Returns a list of all paths in the document.

View File

@ -5,6 +5,7 @@ The main tool being the svg2paths() function."""
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
from xml.dom.minidom import parse from xml.dom.minidom import parse
from os import path as os_path, getcwd from os import path as os_path, getcwd
from io import StringIO
import re import re
# Internal dependencies # Internal dependencies
@ -144,7 +145,9 @@ def svg2paths(svg_file_location,
SVG Path, Line, Polyline, Polygon, Circle, and Ellipse elements. SVG Path, Line, Polyline, Polygon, Circle, and Ellipse elements.
Args: Args:
svg_file_location (string): the location of the svg file svg_file_location (string or file-like object): the location of the
svg file on disk or a file-like object containing the content of a
svg file
return_svg_attributes (bool): Set to True and a dictionary of return_svg_attributes (bool): Set to True and a dictionary of
svg-attributes will be extracted and returned. See also the svg-attributes will be extracted and returned. See also the
`svg2paths2()` function. `svg2paths2()` function.
@ -168,8 +171,12 @@ def svg2paths(svg_file_location,
list: The list of corresponding path attribute dictionaries. list: The list of corresponding path attribute dictionaries.
dict (optional): A dictionary of svg-attributes (see `svg2paths2()`). dict (optional): A dictionary of svg-attributes (see `svg2paths2()`).
""" """
if os_path.dirname(svg_file_location) == '': # strings are interpreted as file location everything else is treated as
svg_file_location = os_path.join(getcwd(), svg_file_location) # file-like object and passed to the xml parser directly
if isinstance(svg_file_location, str):
if os_path.dirname(svg_file_location) == '':
svg_file_location = os_path.join(
getcwd(), svg_file_location)
doc = parse(svg_file_location) doc = parse(svg_file_location)
@ -249,3 +256,26 @@ def svg2paths2(svg_file_location,
convert_polylines_to_paths=convert_polylines_to_paths, convert_polylines_to_paths=convert_polylines_to_paths,
convert_polygons_to_paths=convert_polygons_to_paths, convert_polygons_to_paths=convert_polygons_to_paths,
convert_rectangles_to_paths=convert_rectangles_to_paths) convert_rectangles_to_paths=convert_rectangles_to_paths)
def svgstr2paths(svg_string,
return_svg_attributes=False,
convert_circles_to_paths=True,
convert_ellipses_to_paths=True,
convert_lines_to_paths=True,
convert_polylines_to_paths=True,
convert_polygons_to_paths=True,
convert_rectangles_to_paths=True):
"""Convenience function; identical to svg2paths() except that it takes the
svg object as string. See svg2paths() docstring for more
info."""
# wrap string into StringIO object
svg_file_obj = StringIO(svg_string)
return svg2paths(svg_file_location=svg_file_obj,
return_svg_attributes=return_svg_attributes,
convert_circles_to_paths=convert_circles_to_paths,
convert_ellipses_to_paths=convert_ellipses_to_paths,
convert_lines_to_paths=convert_lines_to_paths,
convert_polylines_to_paths=convert_polylines_to_paths,
convert_polygons_to_paths=convert_polygons_to_paths,
convert_rectangles_to_paths=convert_rectangles_to_paths)

44
test/test_document.py Normal file
View File

@ -0,0 +1,44 @@
from __future__ import division, absolute_import, print_function
import unittest
from svgpathtools import *
from io import StringIO
from io import open # overrides build-in open for compatibility with python2
from os.path import join, dirname
class TestDocument(unittest.TestCase):
def test_from_file_path(self):
""" Test reading svg from file provided as path """
doc = Document(join(dirname(__file__), 'polygons.svg'))
self.assertEqual(len(doc.paths()), 2)
def test_from_file_object(self):
""" Test reading svg from file object that has already been opened """
with open(join(dirname(__file__), 'polygons.svg'), 'r') as file:
doc = Document(file)
self.assertEqual(len(doc.paths()), 2)
def test_from_stringio(self):
""" Test reading svg object contained in a StringIO object """
with open(join(dirname(__file__), 'polygons.svg'),
'r', encoding='utf-8') as file:
# read entire file into string
file_content = file.read()
# prepare stringio object
file_as_stringio = StringIO(file_content)
doc = Document(file_as_stringio)
self.assertEqual(len(doc.paths()), 2)
def test_from_string(self):
""" Test reading svg object contained in a string"""
with open(join(dirname(__file__), 'polygons.svg'),
'r', encoding='utf-8') as file:
# read entire file into string
file_content = file.read()
doc = Document.from_svg_string(file_content)
self.assertEqual(len(doc.paths()), 2)

View File

@ -1,6 +1,8 @@
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 io import StringIO
from io import open # overrides build-in open for compatibility with python2
from os.path import join, dirname from os.path import join, dirname
from svgpathtools.svg_to_paths import rect2pathd from svgpathtools.svg_to_paths import rect2pathd
@ -57,4 +59,41 @@ class TestSVG2Paths(unittest.TestCase):
non_rounded = {"x":"10", "y":"10", "width":"100","height":"100"} non_rounded = {"x":"10", "y":"10", "width":"100","height":"100"}
self.assertEqual(rect2pathd(non_rounded), 'M10.0 10.0 L 110.0 10.0 L 110.0 110.0 L 10.0 110.0 z') self.assertEqual(rect2pathd(non_rounded), 'M10.0 10.0 L 110.0 10.0 L 110.0 110.0 L 10.0 110.0 z')
rounded = {"x":"10", "y":"10", "width":"100","height":"100", "rx":"15", "ry": "12"} rounded = {"x":"10", "y":"10", "width":"100","height":"100", "rx":"15", "ry": "12"}
self.assertEqual(rect2pathd(rounded), "M 25.0 10.0 L 95.0 10.0 A 15.0 12.0 0 0 1 110.0 22.0 L 110.0 98.0 A 15.0 12.0 0 0 1 95.0 110.0 L 25.0 110.0 A 15.0 12.0 0 0 1 10.0 98.0 L 10.0 22.0 A 15.0 12.0 0 0 1 25.0 10.0 z") self.assertEqual(rect2pathd(rounded), "M 25.0 10.0 L 95.0 10.0 A 15.0 12.0 0 0 1 110.0 22.0 L 110.0 98.0 A 15.0 12.0 0 0 1 95.0 110.0 L 25.0 110.0 A 15.0 12.0 0 0 1 10.0 98.0 L 10.0 22.0 A 15.0 12.0 0 0 1 25.0 10.0 z")
def test_from_file_path(self):
""" Test reading svg from file provided as path """
paths, _ = svg2paths(join(dirname(__file__), 'polygons.svg'))
self.assertEqual(len(paths), 2)
def test_from_file_object(self):
""" Test reading svg from file object that has already been opened """
with open(join(dirname(__file__), 'polygons.svg'), 'r') as file:
paths, _ = svg2paths(file)
self.assertEqual(len(paths), 2)
def test_from_stringio(self):
""" Test reading svg object contained in a StringIO object """
with open(join(dirname(__file__), 'polygons.svg'),
'r', encoding='utf-8') as file:
# read entire file into string
file_content = file.read()
# prepare stringio object
file_as_stringio = StringIO(file_content)
paths, _ = svg2paths(file_as_stringio)
self.assertEqual(len(paths), 2)
def test_from_string(self):
""" Test reading svg object contained in a string """
with open(join(dirname(__file__), 'polygons.svg'),
'r', encoding='utf-8') as file:
# read entire file into string
file_content = file.read()
paths, _ = svgstr2paths(file_content)
self.assertEqual(len(paths), 2)