mathandy / svgpathtools

A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
MIT License
557 stars 142 forks source link

Provide Access to Parents and Other Tags (diff) #209

Open bcwhite-code opened 1 year ago

bcwhite-code commented 1 year ago

In processing an SVG file in preparation for display, I find myself in need of information from parent "g" tags as well as the global "style" tag. What do you think of this change that allows for exporting all that information to the caller?

diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py
index 65591af..9edf200 100644
--- a/svgpathtools/svg_to_paths.py
+++ b/svgpathtools/svg_to_paths.py
@@ -3,7 +3,7 @@ The main tool being the svg2paths() function."""

 # 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
@@ -136,6 +136,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,
@@ -182,61 +184,86 @@ 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)))

+    # Short name for all the dom2dict calls below
+    ip = include_parent_info
+
     # Use minidom to extract path strings from input SVG
-    paths = [dom2dict(el) for el in doc.getElementsByTagName('path')]
+    paths = [dom2dict(el,ip) 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')]
+        plins = [dom2dict(el,ip) 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')]
+        pgons = [dom2dict(el,ip) 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')]
+        lines = [dom2dict(el,ip) 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')]
+        ellipses = [dom2dict(el,ip) 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')]
+        circles = [dom2dict(el,ip) 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')]
+        rectangles = [dom2dict(el,ip) for el in doc.getElementsByTagName('rect')]
         d_strings += [rect2pathd(r) for r in rectangles]
         attribute_dictionary_list += rectangles

+    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,
@@ -262,6 +289,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,
@@ -275,6 +304,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,

I can create a pull request if you don't think any changes are necessary.