Consistent whitespace across source files according to PEP 8
(mostly automated by using autopep8, fixes #69)
This commit is contained in:
parent
943319b710
commit
0f1974c1e2
8 changed files with 4787 additions and 4276 deletions
|
|
@ -1,19 +1,19 @@
|
|||
###############################################################################
|
||||
##
|
||||
## Copyright (C) 2010 Jeff Schiller, 2010 Louis Simard, 2013-2015 Tavendo GmbH
|
||||
##
|
||||
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||
## you may not use this file except in compliance with the License.
|
||||
## You may obtain a copy of the License at
|
||||
##
|
||||
## http://www.apache.org/licenses/LICENSE-2.0
|
||||
##
|
||||
## Unless required by applicable law or agreed to in writing, software
|
||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
##
|
||||
#
|
||||
# Copyright (C) 2010 Jeff Schiller, 2010 Louis Simard, 2013-2015 Tavendo GmbH
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
__version__ = u'0.35'
|
||||
|
|
|
|||
175
scour/scour.py
175
scour/scour.py
|
|
@ -400,6 +400,7 @@ default_properties = { # excluded all properties with 'auto' as default
|
|||
'viewport-fill-opacity': '1',
|
||||
}
|
||||
|
||||
|
||||
def isSameSign(a, b): return (a <= 0 and b <= 0) or (a >= 0 and b >= 0)
|
||||
|
||||
scinumber = re.compile(r"[-+]?(\d*\.?)?\d+[eE][-+]?\d+")
|
||||
|
|
@ -407,6 +408,7 @@ number = re.compile(r"[-+]?(\d*\.?)?\d+")
|
|||
sciExponent = re.compile(r"[eE]([-+]?\d+)")
|
||||
unit = re.compile("(em|ex|px|pt|pc|cm|mm|in|%){1,1}$")
|
||||
|
||||
|
||||
class Unit(object):
|
||||
# Integer constants for units.
|
||||
INVALID = -1
|
||||
|
|
@ -451,7 +453,8 @@ class Unit(object):
|
|||
|
||||
# @staticmethod
|
||||
def get(unitstr):
|
||||
if unitstr is None: return Unit.NONE
|
||||
if unitstr is None:
|
||||
return Unit.NONE
|
||||
try:
|
||||
return Unit.s2u[unitstr]
|
||||
except KeyError:
|
||||
|
|
@ -467,7 +470,9 @@ class Unit(object):
|
|||
get = staticmethod(get)
|
||||
str = staticmethod(str)
|
||||
|
||||
|
||||
class SVGLength(object):
|
||||
|
||||
def __init__(self, str):
|
||||
try: # simple unitless and no scientific notation
|
||||
self.value = float(str)
|
||||
|
|
@ -509,6 +514,7 @@ class SVGLength(object):
|
|||
self.value = 0
|
||||
self.units = Unit.INVALID
|
||||
|
||||
|
||||
def findElementsWithId(node, elems=None):
|
||||
"""
|
||||
Returns all elements with id attributes
|
||||
|
|
@ -529,6 +535,7 @@ def findElementsWithId(node, elems=None):
|
|||
referencingProps = ['fill', 'stroke', 'filter', 'clip-path', 'mask', 'marker-start',
|
||||
'marker-end', 'marker-mid']
|
||||
|
||||
|
||||
def findReferencedElements(node, ids=None):
|
||||
"""
|
||||
Returns the number of times an ID is referenced as well as all elements
|
||||
|
|
@ -589,6 +596,7 @@ def findReferencedElements(node, ids=None):
|
|||
findReferencedElements(child, ids)
|
||||
return ids
|
||||
|
||||
|
||||
def findReferencingProperty(node, prop, val, ids):
|
||||
global referencingProps
|
||||
if prop in referencingProps and val != '':
|
||||
|
|
@ -629,6 +637,7 @@ numBytesSavedInTransforms = 0
|
|||
numPointsRemovedFromPolygon = 0
|
||||
numCommentBytes = 0
|
||||
|
||||
|
||||
def removeUnusedDefs(doc, defElem, elemsToRemove=None):
|
||||
if elemsToRemove is None:
|
||||
elemsToRemove = []
|
||||
|
|
@ -639,7 +648,7 @@ def removeUnusedDefs(doc, defElem, elemsToRemove=None):
|
|||
keepTags = ['font', 'style', 'metadata', 'script', 'title', 'desc']
|
||||
for elem in defElem.childNodes:
|
||||
# only look at it if an element and not referenced anywhere else
|
||||
if elem.nodeType == 1 and (elem.getAttribute('id') == '' or \
|
||||
if elem.nodeType == 1 and (elem.getAttribute('id') == '' or
|
||||
(not elem.getAttribute('id') in referencedIDs)):
|
||||
|
||||
# we only inspect the children of a group in a defs if the group
|
||||
|
|
@ -651,6 +660,7 @@ def removeUnusedDefs(doc, defElem, elemsToRemove=None):
|
|||
elemsToRemove.append(elem)
|
||||
return elemsToRemove
|
||||
|
||||
|
||||
def removeUnreferencedElements(doc, keepDefs):
|
||||
"""
|
||||
Removes all unreferenced elements except for <svg>, <font>, <metadata>, <title>, and <desc>.
|
||||
|
|
@ -687,6 +697,7 @@ def removeUnreferencedElements(doc, keepDefs):
|
|||
num += 1
|
||||
return num
|
||||
|
||||
|
||||
def shortenIDs(doc, prefix, unprotectedElements=None):
|
||||
"""
|
||||
Shortens ID names used in the document. ID names referenced the most often are assigned the
|
||||
|
|
@ -731,6 +742,7 @@ def shortenIDs(doc, prefix, unprotectedElements=None):
|
|||
|
||||
return num
|
||||
|
||||
|
||||
def intToID(idnum, prefix):
|
||||
"""
|
||||
Returns the ID name for the given ID number, spreadsheet-style, i.e. from a to z,
|
||||
|
|
@ -745,6 +757,7 @@ def intToID(idnum, prefix):
|
|||
|
||||
return prefix + rid
|
||||
|
||||
|
||||
def renameID(doc, idFrom, idTo, identifiedElements, referencedIDs):
|
||||
"""
|
||||
Changes the ID name from idFrom to idTo, on the declaring element
|
||||
|
|
@ -822,6 +835,7 @@ def renameID(doc, idFrom, idTo, identifiedElements, referencedIDs):
|
|||
|
||||
return num
|
||||
|
||||
|
||||
def unprotected_ids(doc, options):
|
||||
u"""Returns a list of unprotected IDs within the document doc."""
|
||||
identifiedElements = findElementsWithId(doc.documentElement)
|
||||
|
|
@ -847,6 +861,7 @@ def unprotected_ids(doc, options):
|
|||
del identifiedElements[id]
|
||||
return identifiedElements
|
||||
|
||||
|
||||
def removeUnreferencedIDs(referencedIDs, identifiedElements):
|
||||
"""
|
||||
Removes the unreferenced ID attributes.
|
||||
|
|
@ -855,7 +870,7 @@ def removeUnreferencedIDs(referencedIDs, identifiedElements):
|
|||
"""
|
||||
global numIDsRemoved
|
||||
keepTags = ['font']
|
||||
num = 0;
|
||||
num = 0
|
||||
for id in list(identifiedElements.keys()):
|
||||
node = identifiedElements[id]
|
||||
if (id in referencedIDs) == False and not node.nodeName in keepTags:
|
||||
|
|
@ -864,6 +879,7 @@ def removeUnreferencedIDs(referencedIDs, identifiedElements):
|
|||
num += 1
|
||||
return num
|
||||
|
||||
|
||||
def removeNamespacedAttributes(node, namespaces):
|
||||
global numAttrsRemoved
|
||||
num = 0
|
||||
|
|
@ -885,6 +901,7 @@ def removeNamespacedAttributes(node, namespaces):
|
|||
num += removeNamespacedAttributes(child, namespaces)
|
||||
return num
|
||||
|
||||
|
||||
def removeNamespacedElements(node, namespaces):
|
||||
global numElemsRemoved
|
||||
num = 0
|
||||
|
|
@ -905,6 +922,7 @@ def removeNamespacedElements(node, namespaces):
|
|||
num += removeNamespacedElements(child, namespaces)
|
||||
return num
|
||||
|
||||
|
||||
def removeDescriptiveElements(doc, options):
|
||||
elementTypes = []
|
||||
if options.remove_descriptive_elements:
|
||||
|
|
@ -932,6 +950,7 @@ def removeDescriptiveElements(doc, options):
|
|||
|
||||
return num
|
||||
|
||||
|
||||
def removeNestedGroups(node):
|
||||
"""
|
||||
This walks further and further down the tree, removing groups
|
||||
|
|
@ -968,6 +987,7 @@ def removeNestedGroups(node):
|
|||
num += removeNestedGroups(child)
|
||||
return num
|
||||
|
||||
|
||||
def moveCommonAttributesToParentGroup(elem, referencedElements):
|
||||
"""
|
||||
This recursively calls this function on all children of the passed in element
|
||||
|
|
@ -992,7 +1012,8 @@ def moveCommonAttributesToParentGroup(elem, referencedElements):
|
|||
return num
|
||||
|
||||
# only process the children if there are more than one element
|
||||
if len(childElements) <= 1: return num
|
||||
if len(childElements) <= 1:
|
||||
return num
|
||||
|
||||
commonAttrs = {}
|
||||
# add all inheritable properties of the first child element
|
||||
|
|
@ -1049,6 +1070,7 @@ def moveCommonAttributesToParentGroup(elem, referencedElements):
|
|||
num += (len(childElements) - 1) * len(commonAttrs)
|
||||
return num
|
||||
|
||||
|
||||
def createGroupsForCommonAttributes(elem):
|
||||
"""
|
||||
Creates <g> elements to contain runs of 3 or more
|
||||
|
|
@ -1114,17 +1136,21 @@ def createGroupsForCommonAttributes(elem):
|
|||
while runStart > 0:
|
||||
nextNode = elem.childNodes.item(runStart - 1)
|
||||
if nextNode.nodeType == 1:
|
||||
if nextNode.getAttribute(curAttr) != value: break
|
||||
if nextNode.getAttribute(curAttr) != value:
|
||||
break
|
||||
else:
|
||||
runElements += 1
|
||||
runStart -= 1
|
||||
else: runStart -= 1
|
||||
else:
|
||||
runStart -= 1
|
||||
|
||||
if runElements >= 3:
|
||||
# Include whitespace/comment/etc. nodes in the run.
|
||||
while runEnd < elem.childNodes.length - 1:
|
||||
if elem.childNodes.item(runEnd + 1).nodeType == 1: break
|
||||
else: runEnd += 1
|
||||
if elem.childNodes.item(runEnd + 1).nodeType == 1:
|
||||
break
|
||||
else:
|
||||
runEnd += 1
|
||||
|
||||
runLength = runEnd - runStart + 1
|
||||
if runLength == elem.childNodes.length: # Every child has this
|
||||
|
|
@ -1170,6 +1196,7 @@ def createGroupsForCommonAttributes(elem):
|
|||
|
||||
return num
|
||||
|
||||
|
||||
def removeUnusedAttributesOnParent(elem):
|
||||
"""
|
||||
This recursively calls this function on all children of the element passed in,
|
||||
|
|
@ -1185,7 +1212,8 @@ def removeUnusedAttributesOnParent(elem):
|
|||
num += removeUnusedAttributesOnParent(child)
|
||||
|
||||
# only process the children if there are more than one element
|
||||
if len(childElements) <= 1: return num
|
||||
if len(childElements) <= 1:
|
||||
return num
|
||||
|
||||
# get all attribute values on this parent
|
||||
attrList = elem.attributes
|
||||
|
|
@ -1223,6 +1251,7 @@ def removeUnusedAttributesOnParent(elem):
|
|||
|
||||
return num
|
||||
|
||||
|
||||
def removeDuplicateGradientStops(doc):
|
||||
global numElemsRemoved
|
||||
num = 0
|
||||
|
|
@ -1241,8 +1270,10 @@ def removeDuplicateGradientStops(doc):
|
|||
else:
|
||||
offset = 0
|
||||
# set the stop offset value to the integer or floating point equivalent
|
||||
if int(offset) == offset: stop.setAttribute('offset', str(int(offset)))
|
||||
else: stop.setAttribute('offset', str(offset))
|
||||
if int(offset) == offset:
|
||||
stop.setAttribute('offset', str(int(offset)))
|
||||
else:
|
||||
stop.setAttribute('offset', str(offset))
|
||||
|
||||
color = stop.getAttribute('stop-color')
|
||||
opacity = stop.getAttribute('stop-opacity')
|
||||
|
|
@ -1261,6 +1292,7 @@ def removeDuplicateGradientStops(doc):
|
|||
# linear gradients
|
||||
return num
|
||||
|
||||
|
||||
def collapseSinglyReferencedGradients(doc):
|
||||
global numElemsRemoved
|
||||
num = 0
|
||||
|
|
@ -1318,6 +1350,7 @@ def collapseSinglyReferencedGradients(doc):
|
|||
num += 1
|
||||
return num
|
||||
|
||||
|
||||
def removeDuplicateGradients(doc):
|
||||
global numElemsRemoved
|
||||
num = 0
|
||||
|
|
@ -1331,7 +1364,8 @@ def removeDuplicateGradients(doc):
|
|||
# TODO: should slice grads from 'grad' here to optimize
|
||||
for ograd in grads:
|
||||
# do not compare gradient to itself
|
||||
if grad == ograd: continue
|
||||
if grad == ograd:
|
||||
continue
|
||||
|
||||
# compare grad to ograd (all properties, then all stops)
|
||||
# if attributes do not match, go to next gradient
|
||||
|
|
@ -1339,9 +1373,10 @@ def removeDuplicateGradients(doc):
|
|||
for attr in ['gradientUnits', 'spreadMethod', 'gradientTransform', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy', 'r']:
|
||||
if grad.getAttribute(attr) != ograd.getAttribute(attr):
|
||||
someGradAttrsDoNotMatch = True
|
||||
break;
|
||||
break
|
||||
|
||||
if someGradAttrsDoNotMatch: continue
|
||||
if someGradAttrsDoNotMatch:
|
||||
continue
|
||||
|
||||
# compare xlink:href values too
|
||||
if grad.getAttributeNS(NS['XLINK'], 'href') != ograd.getAttributeNS(NS['XLINK'], 'href'):
|
||||
|
|
@ -1351,19 +1386,22 @@ def removeDuplicateGradients(doc):
|
|||
stops = grad.getElementsByTagName('stop')
|
||||
ostops = ograd.getElementsByTagName('stop')
|
||||
|
||||
if stops.length != ostops.length: continue
|
||||
if stops.length != ostops.length:
|
||||
continue
|
||||
|
||||
# now compare stops
|
||||
stopsNotEqual = False
|
||||
for i in range(stops.length):
|
||||
if stopsNotEqual: break
|
||||
if stopsNotEqual:
|
||||
break
|
||||
stop = stops.item(i)
|
||||
ostop = ostops.item(i)
|
||||
for attr in ['offset', 'stop-color', 'stop-opacity', 'style']:
|
||||
if stop.getAttribute(attr) != ostop.getAttribute(attr):
|
||||
stopsNotEqual = True
|
||||
break
|
||||
if stopsNotEqual: continue
|
||||
if stopsNotEqual:
|
||||
continue
|
||||
|
||||
# ograd is a duplicate of grad, we schedule it to be removed UNLESS
|
||||
# ograd is ALREADY considered a 'master' element
|
||||
|
|
@ -1411,6 +1449,7 @@ def removeDuplicateGradients(doc):
|
|||
num += 1
|
||||
return num
|
||||
|
||||
|
||||
def _getStyle(node):
|
||||
u"""Returns the style attribute of a node as a dictionary."""
|
||||
if node.nodeType == 1 and len(node.getAttribute('style')) > 0:
|
||||
|
|
@ -1424,6 +1463,7 @@ def _getStyle(node):
|
|||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def _setStyle(node, styleMap):
|
||||
u"""Sets the style attribute of a node to the dictionary ``styleMap``."""
|
||||
fixedStyle = ';'.join([prop + ':' + styleMap[prop] for prop in list(styleMap.keys())])
|
||||
|
|
@ -1433,6 +1473,7 @@ def _setStyle(node, styleMap):
|
|||
node.removeAttribute('style')
|
||||
return node
|
||||
|
||||
|
||||
def repairStyle(node, options):
|
||||
num = 0
|
||||
styleMap = _getStyle(node)
|
||||
|
|
@ -1564,6 +1605,7 @@ def repairStyle(node, options):
|
|||
|
||||
return num
|
||||
|
||||
|
||||
def styleInheritedFromParent(node, style):
|
||||
"""
|
||||
Returns the value of 'style' that is inherited from the parents of the passed-in node
|
||||
|
|
@ -1571,7 +1613,7 @@ def styleInheritedFromParent(node, style):
|
|||
Warning: This method only considers presentation attributes and inline styles,
|
||||
any style sheets are ignored!
|
||||
"""
|
||||
parentNode = node.parentNode;
|
||||
parentNode = node.parentNode
|
||||
|
||||
# return None if we reached the Document element
|
||||
if parentNode.nodeType == 9:
|
||||
|
|
@ -1592,6 +1634,7 @@ def styleInheritedFromParent(node, style):
|
|||
# check the next parent recursively if we did not find a value yet
|
||||
return styleInheritedFromParent(parentNode, style)
|
||||
|
||||
|
||||
def styleInheritedByChild(node, style, nodeIsChild=False):
|
||||
"""
|
||||
Returns whether 'style' is inherited by any children of the passed-in node
|
||||
|
|
@ -1609,7 +1652,6 @@ def styleInheritedByChild(node, style, nodeIsChild=False):
|
|||
if node.nodeType != 1:
|
||||
return False
|
||||
|
||||
|
||||
if nodeIsChild:
|
||||
# if the current child node sets a new value for 'style'
|
||||
# we can stop the search in the current branch of the DOM tree
|
||||
|
|
@ -1641,6 +1683,7 @@ def styleInheritedByChild(node, style, nodeIsChild=False):
|
|||
# (e.g nodes without children at the end of the DOM tree, text nodes, ...)
|
||||
return True
|
||||
|
||||
|
||||
def mayContainTextNodes(node):
|
||||
"""
|
||||
Returns True if the passed-in node is probably a text element, or at least
|
||||
|
|
@ -1814,6 +1857,7 @@ default_attributes = [
|
|||
DefaultAttribute('yChannelSelector', 'A', elements='feDisplacementMap')
|
||||
]
|
||||
|
||||
|
||||
def taint(taintedSet, taintedAttribute):
|
||||
u"""Adds an attribute to a set of attributes.
|
||||
|
||||
|
|
@ -1825,6 +1869,7 @@ def taint(taintedSet, taintedAttribute):
|
|||
taintedSet.add('marker')
|
||||
return taintedSet
|
||||
|
||||
|
||||
def removeDefaultAttributeValue(node, attribute):
|
||||
"""
|
||||
Removes the DefaultAttribute 'attribute' from 'node' if specified conditions are fulfilled
|
||||
|
|
@ -1851,12 +1896,14 @@ def removeDefaultAttributeValue(node, attribute):
|
|||
|
||||
return 0
|
||||
|
||||
|
||||
def removeDefaultAttributeValues(node, options, tainted=set()):
|
||||
u"""'tainted' keeps a set of attributes defined in parent nodes.
|
||||
|
||||
For such attributes, we don't delete attributes with default values."""
|
||||
num = 0
|
||||
if node.nodeType != 1: return 0
|
||||
if node.nodeType != 1:
|
||||
return 0
|
||||
|
||||
# Conditionally remove all default attributes defined in 'default_attributes' (a list of 'DefaultAttribute's)
|
||||
for attribute in default_attributes:
|
||||
|
|
@ -1892,6 +1939,8 @@ def removeDefaultAttributeValues(node, options, tainted=set()):
|
|||
|
||||
rgb = re.compile(r"\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*")
|
||||
rgbp = re.compile(r"\s*rgb\(\s*(\d*\.?\d+)%\s*,\s*(\d*\.?\d+)%\s*,\s*(\d*\.?\d+)%\s*\)\s*")
|
||||
|
||||
|
||||
def convertColor(value):
|
||||
"""
|
||||
Converts the input color string and returns a #RRGGBB (or #RGB if possible) string
|
||||
|
|
@ -1922,17 +1971,19 @@ def convertColor(value):
|
|||
|
||||
return s
|
||||
|
||||
|
||||
def convertColors(element):
|
||||
"""
|
||||
Recursively converts all color properties into #RRGGBB format if shorter
|
||||
"""
|
||||
numBytes = 0
|
||||
|
||||
if element.nodeType != 1: return 0
|
||||
if element.nodeType != 1:
|
||||
return 0
|
||||
|
||||
# set up list of color attributes for each element type
|
||||
attrsToConvert = []
|
||||
if element.nodeName in ['rect', 'circle', 'ellipse', 'polygon', \
|
||||
if element.nodeName in ['rect', 'circle', 'ellipse', 'polygon',
|
||||
'line', 'polyline', 'path', 'g', 'a']:
|
||||
attrsToConvert = ['fill', 'stroke']
|
||||
elif element.nodeName in ['stop']:
|
||||
|
|
@ -1971,6 +2022,8 @@ def convertColors(element):
|
|||
# TODO: go over what this method does and see if there is a way to optimize it
|
||||
# TODO: go over the performance of this method and see if I can save memory/speed by
|
||||
# reusing data structures, etc
|
||||
|
||||
|
||||
def cleanPath(element, options):
|
||||
"""
|
||||
Cleans the path string (d attribute) of the element
|
||||
|
|
@ -2318,7 +2371,7 @@ def cleanPath(element, options):
|
|||
newPath.append(('t', [data[i + 2], data[i + 3]]))
|
||||
numPathSegmentsReduced += 1
|
||||
else:
|
||||
j = 0;
|
||||
j = 0
|
||||
while j <= 3:
|
||||
curveTuples.append(data[i + j])
|
||||
j += 1
|
||||
|
|
@ -2384,7 +2437,6 @@ def cleanPath(element, options):
|
|||
element.setAttribute('d', newPathStr)
|
||||
|
||||
|
||||
|
||||
def parseListOfPoints(s):
|
||||
"""
|
||||
Parse string into a list of points.
|
||||
|
|
@ -2419,7 +2471,7 @@ def parseListOfPoints(s):
|
|||
else:
|
||||
# unless we accidentally split a number that was in scientific notation
|
||||
# and had a negative exponent (500.00e-1)
|
||||
prev = "";
|
||||
prev = ""
|
||||
if len(nums):
|
||||
prev = nums[len(nums) - 1]
|
||||
if prev and prev[len(prev) - 1] in ['e', 'E']:
|
||||
|
|
@ -2428,7 +2480,8 @@ def parseListOfPoints(s):
|
|||
nums.append('-' + negcoords[j])
|
||||
|
||||
# if we have an odd number of points, return empty
|
||||
if len(nums) % 2 != 0: return []
|
||||
if len(nums) % 2 != 0:
|
||||
return []
|
||||
|
||||
# now resolve into Decimal values
|
||||
i = 0
|
||||
|
|
@ -2444,7 +2497,6 @@ def parseListOfPoints(s):
|
|||
return nums
|
||||
|
||||
|
||||
|
||||
def cleanPolygon(elem, options):
|
||||
"""
|
||||
Remove unnecessary closing point of polygon points attribute
|
||||
|
|
@ -2462,7 +2514,6 @@ def cleanPolygon(elem, options):
|
|||
elem.setAttribute('points', scourCoordinates(pts, options, True))
|
||||
|
||||
|
||||
|
||||
def cleanPolyline(elem, options):
|
||||
"""
|
||||
Scour the polyline points attribute
|
||||
|
|
@ -2471,7 +2522,6 @@ def cleanPolyline(elem, options):
|
|||
elem.setAttribute('points', scourCoordinates(pts, options, True))
|
||||
|
||||
|
||||
|
||||
def serializePath(pathObj, options):
|
||||
"""
|
||||
Reserializes the path data with some cleanups.
|
||||
|
|
@ -2481,7 +2531,6 @@ def serializePath(pathObj, options):
|
|||
return ''.join([cmd + scourCoordinates(data, options, (cmd == 'a')) for cmd, data in pathObj])
|
||||
|
||||
|
||||
|
||||
def serializeTransform(transformObj):
|
||||
"""
|
||||
Reserializes the transform data with some cleanups.
|
||||
|
|
@ -2494,7 +2543,6 @@ def serializeTransform(transformObj):
|
|||
)
|
||||
|
||||
|
||||
|
||||
def scourCoordinates(data, options, forceCommaWsp=False):
|
||||
"""
|
||||
Serializes coordinate data with some cleanups:
|
||||
|
|
@ -2545,7 +2593,6 @@ def scourCoordinates(data, options, forceCommaWsp = False):
|
|||
return ''
|
||||
|
||||
|
||||
|
||||
def scourLength(length):
|
||||
"""
|
||||
Scours a length. Accepts units.
|
||||
|
|
@ -2555,7 +2602,6 @@ def scourLength(length):
|
|||
return scourUnitlessLength(length.value) + Unit.str(length.units)
|
||||
|
||||
|
||||
|
||||
def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a numeric type
|
||||
"""
|
||||
Scours the numeric part of a length only. Does not accept units.
|
||||
|
|
@ -2605,7 +2651,6 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a
|
|||
return nonsci
|
||||
|
||||
|
||||
|
||||
def reducePrecision(element):
|
||||
"""
|
||||
Because opacities, letter spacings, stroke widths and all that don't need
|
||||
|
|
@ -2652,7 +2697,6 @@ def reducePrecision(element):
|
|||
return num
|
||||
|
||||
|
||||
|
||||
def optimizeAngle(angle):
|
||||
"""
|
||||
Because any rotation can be expressed within 360 degrees
|
||||
|
|
@ -2664,18 +2708,21 @@ def optimizeAngle(angle):
|
|||
# The modulo operator yields results with the sign of the
|
||||
# divisor, so for negative dividends, we preserve the sign
|
||||
# of the angle.
|
||||
if angle < 0: angle %= -360
|
||||
else: angle %= 360
|
||||
if angle < 0:
|
||||
angle %= -360
|
||||
else:
|
||||
angle %= 360
|
||||
# 720 degrees is unneccessary, as 360 covers all angles.
|
||||
# As "-x" is shorter than "35x" and "-xxx" one character
|
||||
# longer than positive angles <= 260, we constrain angle
|
||||
# range to [-90, 270[ (or, equally valid: ]-100, 260]).
|
||||
if angle >= 270: angle -= 360
|
||||
elif angle < -90: angle += 360
|
||||
if angle >= 270:
|
||||
angle -= 360
|
||||
elif angle < -90:
|
||||
angle += 360
|
||||
return angle
|
||||
|
||||
|
||||
|
||||
def optimizeTransform(transform):
|
||||
"""
|
||||
Optimises a series of transformations parsed from a single
|
||||
|
|
@ -2836,7 +2883,6 @@ def optimizeTransform(transform):
|
|||
i += 1
|
||||
|
||||
|
||||
|
||||
def optimizeTransforms(element, options):
|
||||
"""
|
||||
Attempts to optimise transform specifications on the given node and its children.
|
||||
|
|
@ -2868,7 +2914,6 @@ def optimizeTransforms(element, options):
|
|||
return num
|
||||
|
||||
|
||||
|
||||
def removeComments(element):
|
||||
"""
|
||||
Removes comments from the element and its children.
|
||||
|
|
@ -2883,7 +2928,6 @@ def removeComments(element):
|
|||
removeComments(subelement)
|
||||
|
||||
|
||||
|
||||
def embedRasters(element, options):
|
||||
import base64
|
||||
import urllib
|
||||
|
|
@ -2911,7 +2955,8 @@ def embedRasters(element, options):
|
|||
# if this is not an absolute path, set path relative
|
||||
# to script file based on input arg
|
||||
infilename = '.'
|
||||
if options.infilename: infilename = options.infilename
|
||||
if options.infilename:
|
||||
infilename = options.infilename
|
||||
href = os.path.join(os.path.dirname(infilename), href)
|
||||
|
||||
rasterdata = ''
|
||||
|
|
@ -2942,7 +2987,6 @@ def embedRasters(element, options):
|
|||
del b64eRaster
|
||||
|
||||
|
||||
|
||||
def properlySizeDoc(docElement, options):
|
||||
# get doc width and height
|
||||
w = SVGLength(docElement.getAttribute('width'))
|
||||
|
|
@ -2984,9 +3028,9 @@ def properlySizeDoc(docElement, options):
|
|||
docElement.removeAttribute('height')
|
||||
|
||||
|
||||
|
||||
def remapNamespacePrefix(node, oldprefix, newprefix):
|
||||
if node == None or node.nodeType != 1: return
|
||||
if node == None or node.nodeType != 1:
|
||||
return
|
||||
|
||||
if node.prefix == oldprefix:
|
||||
localName = node.localName
|
||||
|
|
@ -2999,7 +3043,7 @@ def remapNamespacePrefix(node, oldprefix, newprefix):
|
|||
if newprefix != '':
|
||||
newNode = doc.createElementNS(namespace, newprefix + ":" + localName)
|
||||
else:
|
||||
newNode = doc.createElement(localName);
|
||||
newNode = doc.createElement(localName)
|
||||
|
||||
# add all the attributes
|
||||
attrList = node.attributes
|
||||
|
|
@ -3021,7 +3065,6 @@ def remapNamespacePrefix(node, oldprefix, newprefix):
|
|||
remapNamespacePrefix(child, oldprefix, newprefix)
|
||||
|
||||
|
||||
|
||||
def makeWellFormed(str):
|
||||
# Don't escape quotation marks for now as they are fine in text nodes
|
||||
# as well as in attributes if used reciprocally
|
||||
|
|
@ -3039,7 +3082,6 @@ def makeWellFormed(str):
|
|||
return ''.join([xml_ents[c] if c in xml_ents else c for c in str])
|
||||
|
||||
|
||||
|
||||
# hand-rolled serialization function that has the following benefits:
|
||||
# - pretty printing
|
||||
# - somewhat judicious use of whitespace
|
||||
|
|
@ -3051,8 +3093,10 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False):
|
|||
I = ''
|
||||
newline = ''
|
||||
if options.newlines:
|
||||
if options.indent_type == 'tab': I='\t'
|
||||
elif options.indent_type == 'space': I=' '
|
||||
if options.indent_type == 'tab':
|
||||
I = '\t'
|
||||
elif options.indent_type == 'space':
|
||||
I = ' '
|
||||
I *= options.indent_depth
|
||||
newline = '\n'
|
||||
|
||||
|
|
@ -3096,7 +3140,8 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False):
|
|||
attrIndices += [attrName2Index[name] for name in sorted(attrName2Index.keys())]
|
||||
for index in attrIndices:
|
||||
attr = attrList.item(index)
|
||||
if attr.nodeName == 'id' or attr.nodeName == 'xml:id': continue
|
||||
if attr.nodeName == 'id' or attr.nodeName == 'xml:id':
|
||||
continue
|
||||
# if the attribute value contains a double-quote, use single-quotes
|
||||
quot = '"'
|
||||
if attr.nodeValue.find('"') != -1:
|
||||
|
|
@ -3156,17 +3201,19 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False):
|
|||
else: # ignore the rest
|
||||
pass
|
||||
|
||||
if onNewLine: outParts.append(I * ind)
|
||||
if onNewLine:
|
||||
outParts.append(I * ind)
|
||||
outParts.extend(['</', element.nodeName, '>'])
|
||||
if indent > 0: outParts.append(newline)
|
||||
if indent > 0:
|
||||
outParts.append(newline)
|
||||
else:
|
||||
outParts.append('/>')
|
||||
if indent > 0: outParts.append(newline)
|
||||
if indent > 0:
|
||||
outParts.append(newline)
|
||||
|
||||
return "".join(outParts)
|
||||
|
||||
|
||||
|
||||
# this is the main method
|
||||
# input is a string representation of the input XML
|
||||
# returns a string representation of the output XML
|
||||
|
|
@ -3414,7 +3461,6 @@ def scourString(in_string, options=None):
|
|||
return total_output
|
||||
|
||||
|
||||
|
||||
# used mostly by unit tests
|
||||
# input is a filename
|
||||
# returns the minidom doc representation of the SVG
|
||||
|
|
@ -3438,19 +3484,18 @@ def scourXmlFile(filename, options=None):
|
|||
return doc
|
||||
|
||||
|
||||
|
||||
# GZ: Seems most other commandline tools don't do this, is it really wanted?
|
||||
class HeaderedFormatter(optparse.IndentedHelpFormatter):
|
||||
"""
|
||||
Show application name, version number, and copyright statement
|
||||
above usage information.
|
||||
"""
|
||||
|
||||
def format_usage(self, usage):
|
||||
return "%s %s\n%s\n%s" % (APP, VER, COPYRIGHT,
|
||||
optparse.IndentedHelpFormatter.format_usage(self, usage))
|
||||
|
||||
|
||||
|
||||
# GZ: would prefer this to be in a function or class scope, but tests etc need
|
||||
# access to the defaults anyway
|
||||
_options_parser = optparse.OptionParser(
|
||||
|
|
@ -3575,7 +3620,6 @@ _option_group_compatibility.add_option("--error-on-flowtext",
|
|||
_options_parser.add_option_group(_option_group_compatibility)
|
||||
|
||||
|
||||
|
||||
def parse_args(args=None, ignore_additional_args=False):
|
||||
options, rargs = _options_parser.parse_args(args)
|
||||
|
||||
|
|
@ -3598,10 +3642,10 @@ def parse_args(args=None, ignore_additional_args=False):
|
|||
return options
|
||||
|
||||
|
||||
|
||||
def generateDefaultOptions():
|
||||
## FIXME: clean up this mess/hack and refactor arg parsing to argparse
|
||||
# FIXME: clean up this mess/hack and refactor arg parsing to argparse
|
||||
class Struct:
|
||||
|
||||
def __init__(self, **entries):
|
||||
self.__dict__.update(entries)
|
||||
|
||||
|
|
@ -3610,7 +3654,6 @@ def generateDefaultOptions():
|
|||
return Struct(**d)
|
||||
|
||||
|
||||
|
||||
# sanitizes options by updating attributes in a set of defaults options while discarding unknown attributes
|
||||
def sanitizeOptions(options):
|
||||
optionsDict = dict((key, getattr(options, key)) for key in dir(options) if not key.startswith('__'))
|
||||
|
|
@ -3621,7 +3664,6 @@ def sanitizeOptions(options):
|
|||
return sanitizedOptions
|
||||
|
||||
|
||||
|
||||
def maybe_gziped_file(filename, mode="r"):
|
||||
if os.path.splitext(filename)[1].lower() in (".svgz", ".gz"):
|
||||
import gzip
|
||||
|
|
@ -3629,7 +3671,6 @@ def maybe_gziped_file(filename, mode="r"):
|
|||
return open(filename, mode)
|
||||
|
||||
|
||||
|
||||
def getInOut(options):
|
||||
if options.infilename:
|
||||
infile = maybe_gziped_file(options.infilename, "rb")
|
||||
|
|
@ -3660,7 +3701,6 @@ def getInOut(options):
|
|||
return [infile, outfile]
|
||||
|
||||
|
||||
|
||||
def getReport():
|
||||
return ' Number of elements removed: ' + str(numElemsRemoved) + os.linesep + \
|
||||
' Number of attributes removed: ' + str(numAttrsRemoved) + os.linesep + \
|
||||
|
|
@ -3677,7 +3717,6 @@ def getReport():
|
|||
' Number of bytes saved in transformations: ' + str(numBytesSavedInTransforms)
|
||||
|
||||
|
||||
|
||||
def start(options, input, output):
|
||||
|
||||
start = walltime()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@ from decimal import *
|
|||
from functools import partial
|
||||
|
||||
# Sentinel.
|
||||
|
||||
|
||||
class _EOF(object):
|
||||
|
||||
def __repr__(self):
|
||||
return 'EOF'
|
||||
EOF = _EOF()
|
||||
|
|
@ -70,6 +73,7 @@ class Lexer(object):
|
|||
|
||||
http://www.gooli.org/blog/a-simple-lexer-in-python/
|
||||
"""
|
||||
|
||||
def __init__(self, lexicon):
|
||||
self.lexicon = lexicon
|
||||
parts = []
|
||||
|
|
@ -270,7 +274,6 @@ class SVGPathParser(object):
|
|||
token = next_val_fn()
|
||||
return x, token
|
||||
|
||||
|
||||
def rule_coordinate_pair(self, next_val_fn, token):
|
||||
# Inline these since this rule is so common.
|
||||
if token[0] not in self.number_tokens:
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ from functools import partial
|
|||
|
||||
# Sentinel.
|
||||
class _EOF(object):
|
||||
|
||||
def __repr__(self):
|
||||
return 'EOF'
|
||||
EOF = _EOF()
|
||||
|
|
@ -89,6 +90,7 @@ class Lexer(object):
|
|||
|
||||
http://www.gooli.org/blog/a-simple-lexer-in-python/
|
||||
"""
|
||||
|
||||
def __init__(self, lexicon):
|
||||
self.lexicon = lexicon
|
||||
parts = []
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
# | DASHMATCH | FUNCTION S* any* ')'
|
||||
# | '(' S* any* ')' | '[' S* any* ']' ] S*;
|
||||
|
||||
|
||||
def parseCssString(str):
|
||||
rules = []
|
||||
# first, split on } to get the rule chunks
|
||||
|
|
@ -55,17 +56,20 @@ def parseCssString(str):
|
|||
for chunk in chunks:
|
||||
# second, split on { to get the selector and the list of properties
|
||||
bits = chunk.split('{')
|
||||
if len(bits) != 2: continue
|
||||
if len(bits) != 2:
|
||||
continue
|
||||
rule = {}
|
||||
rule['selector'] = bits[0].strip()
|
||||
# third, split on ; to get the property declarations
|
||||
bites = bits[1].strip().split(';')
|
||||
if len(bites) < 1: continue
|
||||
if len(bites) < 1:
|
||||
continue
|
||||
props = {}
|
||||
for bite in bites:
|
||||
# fourth, split on : to get the property name and value
|
||||
nibbles = bite.strip().split(':')
|
||||
if len(nibbles) != 2: continue
|
||||
if len(nibbles) != 2:
|
||||
continue
|
||||
props[nibbles[0].strip()] = nibbles[1].strip()
|
||||
rule['properties'] = props
|
||||
rules.append(rule)
|
||||
|
|
|
|||
30
setup.py
30
setup.py
|
|
@ -1,19 +1,19 @@
|
|||
###############################################################################
|
||||
##
|
||||
## Copyright (C) 2013-2014 Tavendo GmbH
|
||||
##
|
||||
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||
## you may not use this file except in compliance with the License.
|
||||
## You may obtain a copy of the License at
|
||||
##
|
||||
## http://www.apache.org/licenses/LICENSE-2.0
|
||||
##
|
||||
## Unless required by applicable law or agreed to in writing, software
|
||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
##
|
||||
#
|
||||
# Copyright (C) 2013-2014 Tavendo GmbH
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -27,19 +27,24 @@ from scour.yocto_css import parseCssString
|
|||
|
||||
|
||||
class Blank(unittest.TestCase):
|
||||
|
||||
def runTest(self):
|
||||
r = parseCssString('')
|
||||
self.assertEqual(len(r), 0, 'Blank string returned non-empty list')
|
||||
self.assertEqual(type(r), type([]), 'Blank string returned non list')
|
||||
|
||||
|
||||
class ElementSelector(unittest.TestCase):
|
||||
|
||||
def runTest(self):
|
||||
r = parseCssString('foo {}')
|
||||
self.assertEqual(len(r), 1, 'Element selector not returned')
|
||||
self.assertEqual(r[0]['selector'], 'foo', 'Selector for foo not returned')
|
||||
self.assertEqual(len(r[0]['properties']), 0, 'Property list for foo not empty')
|
||||
|
||||
|
||||
class ElementSelectorWithProperty(unittest.TestCase):
|
||||
|
||||
def runTest(self):
|
||||
r = parseCssString('foo { bar: baz}')
|
||||
self.assertEqual(len(r), 1, 'Element selector not returned')
|
||||
|
|
|
|||
476
testscour.py
476
testscour.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue