diff --git a/scour/scour.py b/scour/scour.py index 661dd9a..4b71ad1 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -1134,6 +1134,75 @@ def moveCommonAttributesToParentGroup(elem, referencedElements): return num +def mergeSiblingGroupsWithCommonAttributes(elem): + """ + Merge two or more sibling elements with the identical attributes. + + This function acts recursively on the given element. + """ + + num = 0 + i = elem.childNodes.length - 1 + while i >= 0: + currentNode = elem.childNodes.item(i) + if currentNode.nodeType != Node.ELEMENT_NODE or currentNode.nodeName != 'g' or \ + currentNode.namespaceURI != NS['SVG']: + i -= 1 + continue + attributes = {a.nodeName: a.nodeValue for a in currentNode.attributes.values()} + runStart, runEnd = i, i + runElements = 1 + while runStart > 0: + nextNode = elem.childNodes.item(runStart - 1) + if nextNode.nodeType == Node.ELEMENT_NODE: + if nextNode.nodeName != 'g' or nextNode.namespaceURI != NS['SVG']: + break + nextAttributes = {a.nodeName: a.nodeValue for a in nextNode.attributes.values()} + hasNoMergeTags = (True for n in nextNode.childNodes + if n.nodeType == Node.ELEMENT_NODE + and n.nodeName in ('title', 'desc') + and n.namespaceURI == NS['SVG']) + if attributes != nextAttributes or any(hasNoMergeTags): + break + else: + runElements += 1 + runStart -= 1 + else: + runStart -= 1 + + # Next loop will start from here + i = runStart - 1 + + if runElements < 2: + continue + + # Find the entry that starts the run (we might have run + # past it into a text node or a comment node. + while True: + node = elem.childNodes.item(runStart) + if node.nodeType == Node.ELEMENT_NODE and node.nodeName == 'g' and node.namespaceURI == NS['SVG']: + break + runStart += 1 + primaryGroup = elem.childNodes.item(runStart) + runStart += 1 + nodes = elem.childNodes[runStart:runEnd+1] + for node in nodes: + if node.nodeType == Node.ELEMENT_NODE and node.nodeName == 'g' and node.namespaceURI == NS['SVG']: + # Merge + primaryGroup.childNodes.extend(node.childNodes) + node.childNodes = [] + else: + primaryGroup.childNodes.append(node) + elem.childNodes.remove(node) + + # each child gets the same treatment, recursively + for childNode in elem.childNodes: + if childNode.nodeType == Node.ELEMENT_NODE: + num += mergeSiblingGroupsWithCommonAttributes(childNode) + + return num + + def createGroupsForCommonAttributes(elem): """ Creates elements to contain runs of 3 or more @@ -3658,6 +3727,8 @@ def scourString(in_string, options=None): while removeDuplicateGradients(doc) > 0: pass + if options.group_collapse: + _num_elements_removed += mergeSiblingGroupsWithCommonAttributes(doc.documentElement) # create elements if there are runs of elements with the same attributes. # this MUST be before moveCommonAttributesToParentGroup. if options.group_create: diff --git a/testscour.py b/testscour.py index 68521ce..d9a460b 100755 --- a/testscour.py +++ b/testscour.py @@ -2075,6 +2075,21 @@ class MustKeepGInSwitch2(unittest.TestCase): 'Erroneously removed a in a ') +class GroupSiblingMerge(unittest.TestCase): + + def test_sibling_merge(self): + doc = scourXmlFile('unittests/group-sibling-merge.svg', + parse_args([])) + self.assertEqual(doc.getElementsByTagName('g').length, 5, + 'Merged sibling tags with similar values') + + def test_sibling_merge_disabled(self): + doc = scourXmlFile('unittests/group-sibling-merge.svg', + parse_args(['--disable-group-collapsing'])) + self.assertEqual(doc.getElementsByTagName('g').length, 8, + 'Sibling merging is disabled by --disable-group-collapsing') + + class GroupCreation(unittest.TestCase): def runTest(self): diff --git a/unittests/group-sibling-merge.svg b/unittests/group-sibling-merge.svg new file mode 100644 index 0000000..0a2181c --- /dev/null +++ b/unittests/group-sibling-merge.svg @@ -0,0 +1,29 @@ + + +Produced by GNUPLOT 5.2 patchlevel 8 + + + + +0 + + + + + +5000 + + + + + +10000 + + + + + +15000 + + +