Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check xlink:href for gradientUnits="userSpaceOnUse" #271

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 46 additions & 10 deletions scour/scour.py
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,37 @@ def mayContainTextNodes(node):
return result


def _gradient_units_is_not_user_space(node):
current_node = node
doc = None
while True:
gradient_unit = current_node.getAttribute('gradientUnits')
if gradient_unit != '':
# The current node has a value, then it decides
break

# The current node did not have value - check if we reference
# another node that might have it.
href = current_node.getAttributeNS(NS['XLINK'], 'href')
if href == '' or not href.startswith('#'):
return True
if doc is None:
doc = current_node.ownerDocument
# Should not happen in practise as our nodes should
# always be attached to a document. However, if it
# does, then we assume we can strip the attribute.
if doc is None:
return True
node_id = href[1:]
current_node = doc.getElementById(node_id)
if current_node is None:
# Assume we can strip the attribute when we cannot find
# the other node
return True

return gradient_unit != 'userSpaceOnUse'


# A list of default attributes that are safe to remove if all conditions are fulfilled
#
# Each default attribute is an object of type 'DefaultAttribute' with the following fields:
Expand Down Expand Up @@ -2006,38 +2037,38 @@ def mayContainTextNodes(node):
# filters and masks
DefaultAttribute('x', -10, Unit.PCT, ['filter', 'mask']),
DefaultAttribute('x', -0.1, Unit.NONE, ['filter', 'mask'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
DefaultAttribute('y', -10, Unit.PCT, ['filter', 'mask']),
DefaultAttribute('y', -0.1, Unit.NONE, ['filter', 'mask'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
DefaultAttribute('width', 120, Unit.PCT, ['filter', 'mask']),
DefaultAttribute('width', 1.2, Unit.NONE, ['filter', 'mask'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
DefaultAttribute('height', 120, Unit.PCT, ['filter', 'mask']),
DefaultAttribute('height', 1.2, Unit.NONE, ['filter', 'mask'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),

# gradients
DefaultAttribute('x1', 0, elements=['linearGradient']),
DefaultAttribute('y1', 0, elements=['linearGradient']),
DefaultAttribute('y2', 0, elements=['linearGradient']),
DefaultAttribute('x2', 100, Unit.PCT, elements=['linearGradient']),
DefaultAttribute('x2', 1, Unit.NONE, elements=['linearGradient'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
# remove fx/fy before cx/cy to catch the case where fx = cx = 50% or fy = cy = 50% respectively
DefaultAttribute('fx', elements=['radialGradient'],
conditions=lambda node: node.getAttribute('fx') == node.getAttribute('cx')),
DefaultAttribute('fy', elements=['radialGradient'],
conditions=lambda node: node.getAttribute('fy') == node.getAttribute('cy')),
DefaultAttribute('r', 50, Unit.PCT, elements=['radialGradient']),
DefaultAttribute('r', 0.5, Unit.NONE, elements=['radialGradient'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
DefaultAttribute('cx', 50, Unit.PCT, elements=['radialGradient']),
DefaultAttribute('cx', 0.5, Unit.NONE, elements=['radialGradient'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
DefaultAttribute('cy', 50, Unit.PCT, elements=['radialGradient']),
DefaultAttribute('cy', 0.5, Unit.NONE, elements=['radialGradient'],
conditions=lambda node: node.getAttribute('gradientUnits') != 'userSpaceOnUse'),
conditions=_gradient_units_is_not_user_space),
DefaultAttribute('spreadMethod', 'pad', elements=['linearGradient', 'radialGradient']),

# filter effects
Expand Down Expand Up @@ -3641,6 +3672,7 @@ def scourString(in_string, options=None, stats=None):
scouringContextC = Context(prec=options.cdigits)

doc = xml.dom.minidom.parseString(in_string)
_mark_id_attributes(doc)

# determine number of flowRoot elements in input document
# flowRoot elements don't render at all on current browsers (04/2016)
Expand Down Expand Up @@ -3882,6 +3914,12 @@ def scourXmlFile(filename, options=None, stats=None):
# prepare the output xml.dom.minidom object
doc = xml.dom.minidom.parseString(out_string.encode('utf-8'))

_mark_id_attributes(doc)

return doc


def _mark_id_attributes(doc):
# since minidom does not seem to parse DTDs properly
# manually declare all attributes with name "id" to be of type ID
# (otherwise things like doc.getElementById() won't work)
Expand All @@ -3892,8 +3930,6 @@ def scourXmlFile(filename, options=None, stats=None):
except NotFoundErr:
pass

return doc


# GZ: Seems most other commandline tools don't do this, is it really wanted?
class HeaderedFormatter(optparse.IndentedHelpFormatter):
Expand Down
17 changes: 15 additions & 2 deletions test_scour.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,8 +1517,21 @@ def runTest(self):
class RemoveDefaultGradX1Value(unittest.TestCase):

def runTest(self):
g = scourXmlFile('unittests/gradient-default-attrs.svg').getElementById('grad1')
self.assertEqual(g.getAttribute('x1'), '',
doc = scourXmlFile('unittests/gradient-default-attrs.svg')
g1 = doc.getElementById('grad1')
g1b = doc.getElementById('grad1b')
g1c = doc.getElementById('grad1c')
g1d = doc.getElementById('grad1d')
g1e = doc.getElementById('grad1e')
self.assertEqual(g1.getAttribute('x1'), '',
'x1="0" not removed')
self.assertEqual(g1b.getAttribute('x1'), '',
'x1="0" not removed')
self.assertEqual(g1c.getAttribute('x1'), '',
'x1="0" removed (but should not be due to gradientUnits)')
self.assertEqual(g1d.getAttribute('x1'), '',
'x1="0" removed (but should not be due to href)')
self.assertEqual(g1e.getAttribute('x1'), '',
'x1="0" not removed')


Expand Down
10 changes: 10 additions & 0 deletions unittests/gradient-default-attrs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.