Remove uselessly nested groups, fixed up test script

This commit is contained in:
JSCHILL1 2009-04-16 16:16:06 -05:00
parent 747a282f5f
commit 579a1f2982
3 changed files with 98 additions and 58 deletions

View file

@ -28,8 +28,6 @@
# TODO: Adapt this script into an Inkscape python plugin # TODO: Adapt this script into an Inkscape python plugin
# #
# * Specify a limit to the precision of all positional elements. # * Specify a limit to the precision of all positional elements.
# * Clean up XML Elements
# * Collapse multiple redundent groups
# * Clean up Definitions # * Clean up Definitions
# * Remove duplicate gradient stops # * Remove duplicate gradient stops
# * Collapse duplicate gradient definitions # * Collapse duplicate gradient definitions
@ -48,12 +46,23 @@
# * Collapse all group based transformations # * Collapse all group based transformations
# Next Up: # Next Up:
# - Remove unnecessary nested <g> elements # + Remove unnecessary nested <g> elements
# - Remove duplicate gradient stops # - Remove duplicate gradient stops (same offset, stop-color, stop-opacity)
# - Convert all colors to #RRGGBB format # - Convert all colors to #RRGGBB format
# - Reduce #RRGGBB format to #RGB format when possible
# - rework command-line argument processing so that options are configurable # - rework command-line argument processing so that options are configurable
# - remove unreferenced patterns? https://bugs.edge.launchpad.net/ubuntu/+source/human-icon-theme/+bug/361667/ # - remove unreferenced patterns? https://bugs.edge.launchpad.net/ubuntu/+source/human-icon-theme/+bug/361667/
# Some notes to not forget:
# - removing unreferenced IDs loses some semantic information
# - removing empty nested groups also potentially loses some semantic information
# (i.e. the following button:
# <g>
# <rect .../>
# <text .../>
# </g>
# will be flattened)
# necessary to get true division # necessary to get true division
from __future__ import division from __future__ import division
@ -255,7 +264,7 @@ def removeNamespacedAttributes(node, namespaces):
# now recurse for children # now recurse for children
for child in node.childNodes: for child in node.childNodes:
removeNamespacedAttributes(child, namespaces) num += removeNamespacedAttributes(child, namespaces)
return num return num
def removeNamespacedElements(node, namespaces): def removeNamespacedElements(node, namespaces):
@ -275,10 +284,34 @@ def removeNamespacedElements(node, namespaces):
# now recurse for children # now recurse for children
for child in node.childNodes: for child in node.childNodes:
removeNamespacedElements(child, namespaces) num += removeNamespacedElements(child, namespaces)
return num return num
# TODO: create a class for a SVGLength type (including value and unit) # this walks further and further down the tree, removing groups
# which do not have any attributes and promoting their children
# up one level
def removeNestedGroups(node):
global numElemsRemoved
num = 0
groupsToRemove = []
for child in node.childNodes:
if child.nodeName == 'g' and child.namespaceURI == NS['SVG'] and len(child.attributes) == 0:
groupsToRemove.append(child)
for g in groupsToRemove:
while g.childNodes.length > 0:
g.parentNode.insertBefore(g.firstChild, g)
g.parentNode.removeChild(g)
numElemsRemoved += 1
num += 1
# now recurse for children
for child in node.childNodes:
if child.nodeType == 1:
num += removeNestedGroups(child)
return num
coord = re.compile("\\-?\\d+\\.?\\d*") coord = re.compile("\\-?\\d+\\.?\\d*")
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+") scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
@ -629,13 +662,17 @@ def scourString(in_string):
numElemsRemoved += 1 numElemsRemoved += 1
# remove unreferenced gradients/patterns outside of defs # remove unreferenced gradients/patterns outside of defs
removeUnreferencedElements(doc) while removeUnreferencedElements(doc) > 0:
pass
while removeNestedGroups(doc.documentElement) > 0:
pass
# clean path data # clean path data
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'path') : for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'path') :
cleanPath(elem) cleanPath(elem)
# convert rasters refereces to base64-encoded strings # convert rasters references to base64-encoded strings
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'image') : for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'image') :
embedRasters(elem) embedRasters(elem)

View file

@ -21,6 +21,8 @@ import unittest
import scour import scour
import xml.dom.minidom import xml.dom.minidom
SVGNS = 'http://www.w3.org/2000/svg'
# I couldn't figure out how to get ElementTree to work with the following XPath # I couldn't figure out how to get ElementTree to work with the following XPath
# "//*[namespace-uri()='http://example.com']" # "//*[namespace-uri()='http://example.com']"
# so I decided to use minidom and this helper function that performs a test on a given node # so I decided to use minidom and this helper function that performs a test on a given node
@ -95,6 +97,55 @@ class NoAdobeXPathElements(unittest.TestCase):
lambda e: e.namespaceURI != 'http://ns.adobe.com/XPath/1.0/'), False, lambda e: e.namespaceURI != 'http://ns.adobe.com/XPath/1.0/'), False,
'Found Adobe XPath elements' ) 'Found Adobe XPath elements' )
class DoNotRemoveMetadataWithOnlyText(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/metadata-with-text.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'metadata')), 1,
'Removed metadata element with only text child' )
class RemoveEmptyMetadataElement(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/empty-metadata.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'metadata')), 0,
'Did not remove empty metadata element' )
class RemoveEmptyGElements(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/empty-g.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'g')), 1,
'Did not remove empty g element' )
class RemoveUnreferencedPattern(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/unreferenced-pattern.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'pattern')), 0,
'Unreferenced pattern not removed' )
class RemoveUnreferencedLinearGradient(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/unreferenced-linearGradient.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'linearGradient')), 0,
'Unreferenced linearGradient not removed' )
class RemoveUnreferencedRadialGradient(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/unreferenced-radialGradient.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'radialradient')), 0,
'Unreferenced radialGradient not removed' )
class RemoveUselessNestedGroups(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/nested-useless-groups.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'g')), 1,
'Useless nested groups not removed' )
# These tests will fail at present
class RemoveDuplicateGradientStops(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/duplicate-gradient-stops.svg')
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'stop')), 3,
'Duplicate gradient stops not removed' )
#class NoInkscapeAttributes(unittest.TestCase): #class NoInkscapeAttributes(unittest.TestCase):
# def runTest(self): # def runTest(self):
# self.assertNotEquals(walkTree(scour.scourXmlFile('unittests/inkscape.svg').documentElement, # self.assertNotEquals(walkTree(scour.scourXmlFile('unittests/inkscape.svg').documentElement,
@ -102,54 +153,6 @@ class NoAdobeXPathElements(unittest.TestCase):
# False, # False,
# 'Found Inkscape attributes') # 'Found Inkscape attributes')
class DoNotRemoveMetadataWithOnlyText(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/metadata-with-text.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'metadata')), 1,
'Removed metadata element with only text child' )
class RemoveEmptyMetadataElement(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/empty-metadata.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'metadata')), 0,
'Did not remove empty metadata element' )
class RemoveEmptyGElements(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/empty-g.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'g')), 1,
'Did not remove empty g element' )
class RemoveUnreferencedPattern(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/unreferenced-pattern.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'pattern')), 0,
'Unreferenced pattern not removed' )
class RemoveUnreferencedLinearGradient(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/unreferenced-linearGradient.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'linearGradient')), 0,
'Unreferenced linearGradient not removed' )
class RemoveUnreferencedRadialGradient(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/unreferenced-radialGradient.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'radialradient')), 0,
'Unreferenced radialGradient not removed' )
# These tests will fail at present
class RemoveUselessNestedGroups(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/nested-useless-groups.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://wwww.w3.org/2000/svg', 'g')), 1,
'Useless nested groups not removed' )
class RemoveDuplicateGradientStops(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/duplicate-gradient-stops.svg')
self.assertEquals(len(doc.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'stop')), 3,
'Duplicate gradient stops not removed' )
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<g/> <g/>
<g> <g transform="translate(10,10)">
<rect width="300" height="200" fill="green" /> <rect width="300" height="200" fill="green" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 118 B

After

Width:  |  Height:  |  Size: 147 B

Before After
Before After