diff --git a/scour.py b/scour.py index 1ac43a0..24114c7 100755 --- a/scour.py +++ b/scour.py @@ -39,24 +39,12 @@ # # # in this case, fill="blue" should be removed -# * Move common attributes up to a parent group: -# -# -# -# -# -# becomes: -# -# -# -# -# # Next Up: -# - analyze all children of a group, if they have common attributes, then move them to the group +# + analyze all children of a group, if they have common inheritable attributes, then move them to the group +# - crunch *opacity, offset, svg:x,y, stroke-miterlimit, stroke-width numbers (remove trailing zeros, reduce prec, integerize) # - analyze a group and its children, if a group's attribute is not being used by any children # (or descendants?) then remove it -# - crunch *opacity, offset, svg:x,y, transform numbers (remove trailing zeros, reduce prec, integerize) # - add an option to remove ids if they match the Inkscape-style of IDs # - investigate point-reducing algorithms # - parse transform attribute @@ -609,10 +597,12 @@ def removeNamespacedElements(node, namespaces): num += removeNamespacedElements(child, namespaces) return num -# this walks further and further down the tree, removing groups -# which do not have any attributes or a title/desc child and -# promoting their children up one level def removeNestedGroups(node): + """ + This walks further and further down the tree, removing groups + which do not have any attributes or a title/desc child and + promoting their children up one level + """ global numElemsRemoved num = 0 @@ -640,6 +630,69 @@ def removeNestedGroups(node): num += removeNestedGroups(child) return num +def moveCommonAttributesToParentGroup(elem): + """ + This iterates through all children of the passed in g element and + removes common inheritable attributes from the children and places + them in the parent group + """ + num = 0 + + childElements = [] + # recurse first into the children (depth-first) + for child in elem.childNodes: + if child.nodeType == 1: + childElements.append(child) + num += moveCommonAttributesToParentGroup(child) + + # only process the children if there are more than one element + if len(childElements) <= 1: return num + + commonAttrs = {} + # add all inheritable properties of the first child element + attrList = childElements[0].attributes + for num in range(attrList.length): + attr = attrList.item(num) + # this is most of the inheritable properties from http://www.w3.org/TR/SVG11/propidx.html + # and http://www.w3.org/TR/SVGTiny12/attributeTable.html + if attr.nodeName in ['clip-rule', + 'display-align', + 'fill', 'fill-opacity', 'fill-rule', + 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', + 'font-style', 'font-variant', 'font-weight', + 'letter-spacing', + 'pointer-events', 'shape-rendering', + 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', + 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', + 'text-anchor', 'text-decoration', 'text-rendering', 'visibility', + 'word-spacing', 'writing-mode']: + # we just add all the attributes from the first child + commonAttrs[attr.nodeName] = attr.nodeValue + + # for each subsequent child element + for childNum in range(len(childElements)): + if childNum == 0: continue + child = childElements[childNum] + distinctAttrs = [] + # loop through all current 'common' attributes + for name in commonAttrs.keys(): + # if this child doesn't match that attribute, schedule it for removal + if child.getAttribute(name) != commonAttrs[name]: + distinctAttrs.append(name) + # remove those attributes which are not common + for name in distinctAttrs: + del commonAttrs[name] + + # commonAttrs now has all the inheritable attributes which are common among all child elements + for name in commonAttrs.keys(): + for child in childElements: + child.removeAttribute(name) + elem.setAttribute(name, commonAttrs[name]) + + # update our statistic (we remove N*M attributes and add back in M attributes) + num += (len(childElements)-1) * len(commonAttrs) + return num + def removeDuplicateGradientStops(doc): global numElemsRemoved num = 0 @@ -2072,6 +2125,10 @@ def scourString(in_string, options=None): while removeNestedGroups(doc.documentElement) > 0: pass + # move common attributes to parent group + # TODO: should make sure this is called with most-nested groups first + numAttrsRemoved += moveCommonAttributesToParentGroup(doc.documentElement) + while removeDuplicateGradientStops(doc) > 0: pass diff --git a/testscour.py b/testscour.py index a7fae40..f284435 100755 --- a/testscour.py +++ b/testscour.py @@ -874,6 +874,24 @@ class MoveSVGElementsToDefaultNamespace(unittest.TestCase): xmlstring = scour.scourString(open('unittests/xml-ns-decl.svg').read()) self.assert_( xmlstring.find(' + + + + + + + \ No newline at end of file