From d1c66cc75bbe474b5a45a7cd45ed27207311d897 Mon Sep 17 00:00:00 2001 From: Andy Levisay Date: Tue, 28 Apr 2015 15:26:55 -0500 Subject: [PATCH] Python 3 Updates Ran python-modernizer, this fixed most of the Python 3 incompatibilities. Had to manually rework the iteration in svg_regex and svg_transform; .next doesn't exist in Python 3. Wrapped the builtin next function in a partial for equivalent functionality. --- scour/__init__.py | 5 -- scour/scour.py | 160 +++++++++++++++++++++-------------------- scour/svg_regex.py | 82 ++++++++++----------- scour/svg_transform.py | 59 ++++++++------- setup.py | 2 +- 5 files changed, 158 insertions(+), 150 deletions(-) diff --git a/scour/__init__.py b/scour/__init__.py index f3f6b3e..1282886 100644 --- a/scour/__init__.py +++ b/scour/__init__.py @@ -15,8 +15,3 @@ ## limitations under the License. ## ############################################################################### - -import scour -import svg_regex -import svg_transform -import yocto_css diff --git a/scour/scour.py b/scour/scour.py index d79f0bf..34e571d 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -47,21 +47,27 @@ # necessary to get true division from __future__ import division +# Needed for Python 2/3 compatible print function. +from __future__ import print_function +from __future__ import absolute_import + import os import sys import xml.dom.minidom import re import math -from svg_regex import svg_parser -from svg_transform import svg_transform_parser +from .svg_regex import svg_parser +from .svg_transform import svg_transform_parser import optparse -from yocto_css import parseCssString +from .yocto_css import parseCssString +import six +from six.moves import range # Python 2.3- did not have Decimal try: - from decimal import * + from decimal import Decimal, InvalidOperation, getcontext except ImportError: - print >>sys.stderr, "Scour requires Python 2.4." + print("Scour requires Python 2.4.", file=sys.stderr) # Import Psyco if available try: @@ -531,7 +537,7 @@ def findReferencingProperty(node, prop, val, ids): if prop in referencingProps and val != '' : if len(val) >= 7 and val[0:5] == 'url(#' : id = val[5:val.find(')')] - if ids.has_key(id) : + if id in ids : ids[id][0] += 1 ids[id][1].append(node) else: @@ -546,7 +552,7 @@ def findReferencingProperty(node, prop, val, ids): elif val[0:6] == "url('#" : id = val[6:val.find("')")] if id != None: - if ids.has_key(id) : + if id in ids : ids[id][0] += 1 ids[id][1].append(node) else: @@ -762,7 +768,7 @@ def unprotected_ids(doc, options): protect_ids_list = options.protect_ids_list.split(",") if options.protect_ids_prefix: protect_ids_prefixes = options.protect_ids_prefix.split(",") - for id in identifiedElements.keys(): + for id in list(identifiedElements.keys()): protected = False if options.protect_ids_noninkscape and not id[-1].isdigit(): protected = True @@ -785,9 +791,9 @@ def removeUnreferencedIDs(referencedIDs, identifiedElements): global numIDsRemoved keepTags = ['font'] num = 0; - for id in identifiedElements.keys(): + for id in list(identifiedElements.keys()): node = identifiedElements[id] - if referencedIDs.has_key(id) == False and not node.nodeName in keepTags: + if (id in referencedIDs) == False and not node.nodeName in keepTags: node.removeAttribute('id') numIDsRemoved += 1 num += 1 @@ -800,7 +806,7 @@ def removeNamespacedAttributes(node, namespaces): # remove all namespace'd attributes from this element attrList = node.attributes attrsToRemove = [] - for attrNum in xrange(attrList.length): + for attrNum in range(attrList.length): attr = attrList.item(attrNum) if attr != None and attr.namespaceURI in namespaces: attrsToRemove.append(attr.nodeName) @@ -915,7 +921,7 @@ def moveCommonAttributesToParentGroup(elem, referencedElements): # its fill attribute is not what we want to look at, we should look for the first # non-animate/set element attrList = childElements[0].attributes - for num in xrange(attrList.length): + 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 @@ -934,7 +940,7 @@ def moveCommonAttributesToParentGroup(elem, referencedElements): commonAttrs[attr.nodeName] = attr.nodeValue # for each subsequent child element - for childNum in xrange(len(childElements)): + for childNum in range(len(childElements)): # skip first child if childNum == 0: continue @@ -946,7 +952,7 @@ def moveCommonAttributesToParentGroup(elem, referencedElements): distinctAttrs = [] # loop through all current 'common' attributes - for name in commonAttrs.keys(): + for name in list(commonAttrs.keys()): # if this child doesn't match that attribute, schedule it for removal if child.getAttribute(name) != commonAttrs[name]: distinctAttrs.append(name) @@ -955,7 +961,7 @@ def moveCommonAttributesToParentGroup(elem, referencedElements): del commonAttrs[name] # commonAttrs now has all the inheritable attributes which are common among all child elements - for name in commonAttrs.keys(): + for name in list(commonAttrs.keys()): for child in childElements: child.removeAttribute(name) elem.setAttribute(name, commonAttrs[name]) @@ -1088,7 +1094,7 @@ def removeUnusedAttributesOnParent(elem): # get all attribute values on this parent attrList = elem.attributes unusedAttrs = {} - for num in xrange(attrList.length): + for num in range(attrList.length): attr = attrList.item(num) if attr.nodeName in ['clip-rule', 'display-align', @@ -1104,10 +1110,10 @@ def removeUnusedAttributesOnParent(elem): unusedAttrs[attr.nodeName] = attr.nodeValue # for each child, if at least one child inherits the parent's attribute, then remove - for childNum in xrange(len(childElements)): + for childNum in range(len(childElements)): child = childElements[childNum] inheritedAttrs = [] - for name in unusedAttrs.keys(): + for name in list(unusedAttrs.keys()): val = child.getAttribute(name) if val == '' or val == None or val == 'inherit': inheritedAttrs.append(name) @@ -1115,7 +1121,7 @@ def removeUnusedAttributesOnParent(elem): del unusedAttrs[a] # unusedAttrs now has all the parent attributes that are unused - for name in unusedAttrs.keys(): + for name in list(unusedAttrs.keys()): elem.removeAttribute(name) num += 1 @@ -1145,7 +1151,7 @@ def removeDuplicateGradientStops(doc): color = stop.getAttribute('stop-color') opacity = stop.getAttribute('stop-opacity') style = stop.getAttribute('style') - if stops.has_key(offset) : + if offset in stops : oldStop = stops[offset] if oldStop[0] == color and oldStop[1] == opacity and oldStop[2] == style: stopsToRemove.append(stop) @@ -1166,7 +1172,7 @@ def collapseSinglyReferencedGradients(doc): identifiedElements = findElementsWithId(doc.documentElement) # make sure to reset the ref'ed ids for when we are running this in testscour - for rid,nodeCount in findReferencedElements(doc.documentElement).iteritems(): + for rid,nodeCount in six.iteritems(findReferencedElements(doc.documentElement)): count = nodeCount[0] nodes = nodeCount[1] # Make sure that there's actually a defining element for the current ID name. @@ -1253,7 +1259,7 @@ def removeDuplicateGradients(doc): # now compare stops stopsNotEqual = False - for i in xrange(stops.length): + for i in range(stops.length): if stopsNotEqual: break stop = stops.item(i) ostop = ostops.item(i) @@ -1265,16 +1271,16 @@ def removeDuplicateGradients(doc): # ograd is a duplicate of grad, we schedule it to be removed UNLESS # ograd is ALREADY considered a 'master' element - if not gradientsToRemove.has_key(ograd): - if not duplicateToMaster.has_key(ograd): - if not gradientsToRemove.has_key(grad): + if ograd not in gradientsToRemove: + if ograd not in duplicateToMaster: + if grad not in gradientsToRemove: gradientsToRemove[grad] = [] gradientsToRemove[grad].append( ograd ) duplicateToMaster[ograd] = grad # get a collection of all elements that are referenced and their referencing elements referencedIDs = findReferencedElements(doc.documentElement) - for masterGrad in gradientsToRemove.keys(): + for masterGrad in list(gradientsToRemove.keys()): master_id = masterGrad.getAttribute('id') # print 'master='+master_id for dupGrad in gradientsToRemove[masterGrad]: @@ -1322,7 +1328,7 @@ def _getStyle(node): def _setStyle(node, styleMap): u"""Sets the style attribute of a node to the dictionary ``styleMap``.""" - fixedStyle = ';'.join([prop + ':' + styleMap[prop] for prop in styleMap.keys()]) + fixedStyle = ';'.join([prop + ':' + styleMap[prop] for prop in list(styleMap.keys())]) if fixedStyle != '' : node.setAttribute('style', fixedStyle) elif node.getAttribute('style'): @@ -1337,7 +1343,7 @@ def repairStyle(node, options): # I've seen this enough to know that I need to correct it: # fill: url(#linearGradient4918) rgb(0, 0, 0); for prop in ['fill', 'stroke'] : - if styleMap.has_key(prop) : + if prop in styleMap : chunk = styleMap[prop].split(') ') if len(chunk) == 2 and (chunk[0][:5] == 'url(#' or chunk[0][:6] == 'url("#' or chunk[0][:6] == "url('#") and chunk[1] == 'rgb(0, 0, 0)' : styleMap[prop] = chunk[0] + ')' @@ -1345,23 +1351,23 @@ def repairStyle(node, options): # Here is where we can weed out unnecessary styles like: # opacity:1 - if styleMap.has_key('opacity') : + if 'opacity' in styleMap : opacity = float(styleMap['opacity']) # if opacity='0' then all fill and stroke properties are useless, remove them if opacity == 0.0 : for uselessStyle in ['fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-linejoin', 'stroke-opacity', 'stroke-miterlimit', 'stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity'] : - if styleMap.has_key(uselessStyle): + if uselessStyle in styleMap: del styleMap[uselessStyle] num += 1 # if stroke:none, then remove all stroke-related properties (stroke-width, etc) # TODO: should also detect if the computed value of this element is stroke="none" - if styleMap.has_key('stroke') and styleMap['stroke'] == 'none' : + if 'stroke' in styleMap and styleMap['stroke'] == 'none' : for strokestyle in [ 'stroke-width', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity'] : - if styleMap.has_key(strokestyle) : + if strokestyle in styleMap : del styleMap[strokestyle] num += 1 # TODO: This is actually a problem if a parent element has a specified stroke @@ -1369,38 +1375,38 @@ def repairStyle(node, options): del styleMap['stroke'] # if fill:none, then remove all fill-related properties (fill-rule, etc) - if styleMap.has_key('fill') and styleMap['fill'] == 'none' : + if 'fill' in styleMap and styleMap['fill'] == 'none' : for fillstyle in [ 'fill-rule', 'fill-opacity' ] : - if styleMap.has_key(fillstyle) : + if fillstyle in styleMap : del styleMap[fillstyle] num += 1 # fill-opacity: 0 - if styleMap.has_key('fill-opacity') : + if 'fill-opacity' in styleMap : fillOpacity = float(styleMap['fill-opacity']) if fillOpacity == 0.0 : for uselessFillStyle in [ 'fill', 'fill-rule' ] : - if styleMap.has_key(uselessFillStyle): + if uselessFillStyle in styleMap: del styleMap[uselessFillStyle] num += 1 # stroke-opacity: 0 - if styleMap.has_key('stroke-opacity') : + if 'stroke-opacity' in styleMap : strokeOpacity = float(styleMap['stroke-opacity']) if strokeOpacity == 0.0 : for uselessStrokeStyle in [ 'stroke', 'stroke-width', 'stroke-linejoin', 'stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset' ] : - if styleMap.has_key(uselessStrokeStyle): + if uselessStrokeStyle in styleMap: del styleMap[uselessStrokeStyle] num += 1 # stroke-width: 0 - if styleMap.has_key('stroke-width') : + if 'stroke-width' in styleMap : strokeWidth = SVGLength(styleMap['stroke-width']) if strokeWidth.value == 0.0 : for uselessStrokeStyle in [ 'stroke', 'stroke-linejoin', 'stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity' ] : - if styleMap.has_key(uselessStrokeStyle): + if uselessStrokeStyle in styleMap: del styleMap[uselessStrokeStyle] num += 1 @@ -1413,18 +1419,18 @@ def repairStyle(node, options): 'text-align', 'text-anchor', 'text-decoration', 'text-rendering', 'unicode-bidi', 'word-spacing', 'writing-mode'] : - if styleMap.has_key(fontstyle) : + if fontstyle in styleMap : del styleMap[fontstyle] num += 1 # remove inkscape-specific styles # TODO: need to get a full list of these for inkscapeStyle in ['-inkscape-font-specification']: - if styleMap.has_key(inkscapeStyle): + if inkscapeStyle in styleMap: del styleMap[inkscapeStyle] num += 1 - if styleMap.has_key('overflow') : + if 'overflow' in styleMap : # overflow specified on element other than svg, marker, pattern if not node.nodeName in ['svg','marker','pattern']: del styleMap['overflow'] @@ -1445,7 +1451,7 @@ def repairStyle(node, options): # now if any of the properties match known SVG attributes we prefer attributes # over style so emit them and remove them from the style map if options.style_to_xml: - for propName in styleMap.keys() : + for propName in list(styleMap.keys()) : if propName in svgAttributes : node.setAttribute(propName, styleMap[propName]) del styleMap[propName] @@ -1594,7 +1600,7 @@ def removeDefaultAttributeValues(node, options, tainted=set()): for i in range(node.attributes.length)] for attribute in attributes: if attribute not in tainted: - if attribute in default_attributes.keys(): + if attribute in list(default_attributes.keys()): if node.getAttribute(attribute) == default_attributes[attribute]: node.removeAttribute(attribute) num += 1 @@ -1602,9 +1608,9 @@ def removeDefaultAttributeValues(node, options, tainted=set()): tainted = taint(tainted, attribute) # These attributes might also occur as styles styles = _getStyle(node) - for attribute in styles.keys(): + for attribute in list(styles.keys()): if attribute not in tainted: - if attribute in default_attributes.keys(): + if attribute in list(default_attributes.keys()): if styles[attribute] == default_attributes[attribute]: del styles[attribute] num += 1 @@ -1626,7 +1632,7 @@ def convertColor(value): """ s = value - if s in colors.keys(): + if s in list(colors.keys()): s = colors[s] rgbpMatch = rgbp.match(s) @@ -1680,7 +1686,7 @@ def convertColors(element) : element.setAttribute(attr, newColorValue) numBytes += (oldBytes - len(element.getAttribute(attr))) # colors might also hide in styles - if attr in styles.keys(): + if attr in list(styles.keys()): oldColorValue = styles[attr] newColorValue = convertColor(oldColorValue) oldBytes = len(oldColorValue) @@ -1722,13 +1728,13 @@ def cleanPath(element, options) : # convert absolute coordinates into relative ones. # Reuse the data structure 'path', since we're not adding or removing subcommands. # Also reuse the coordinate lists since we're not adding or removing any. - for pathIndex in xrange(0, len(path)): + for pathIndex in range(0, len(path)): cmd, data = path[pathIndex] # Changes to cmd don't get through to the data structure i = 0 # adjust abs to rel # only the A command has some values that we don't want to adjust (radii, rotation, flags) if cmd == 'A': - for i in xrange(i, len(data), 7): + for i in range(i, len(data), 7): data[i+5] -= x data[i+6] -= y x += data[i+5] @@ -1738,14 +1744,14 @@ def cleanPath(element, options) : x += sum(data[5::7]) y += sum(data[6::7]) elif cmd == 'H': - for i in xrange(i, len(data)): + for i in range(i, len(data)): data[i] -= x x += data[i] path[pathIndex] = ('h', data) elif cmd == 'h': x += sum(data) elif cmd == 'V': - for i in xrange(i, len(data)): + for i in range(i, len(data)): data[i] -= y y += data[i] path[pathIndex] = ('v', data) @@ -1761,14 +1767,14 @@ def cleanPath(element, options) : x, y = startx, starty i = 2 - for i in xrange(i, len(data), 2): + for i in range(i, len(data), 2): data[i] -= x data[i+1] -= y x += data[i] y += data[i+1] path[pathIndex] = ('m', data) elif cmd in ['L','T']: - for i in xrange(i, len(data), 2): + for i in range(i, len(data), 2): data[i] -= x data[i+1] -= y x += data[i] @@ -1784,14 +1790,14 @@ def cleanPath(element, options) : else: startx = x + data[0] starty = y + data[1] - for i in xrange(i, len(data), 2): + for i in range(i, len(data), 2): x += data[i] y += data[i+1] elif cmd in ['l','t']: x += sum(data[0::2]) y += sum(data[1::2]) elif cmd in ['S','Q']: - for i in xrange(i, len(data), 4): + for i in range(i, len(data), 4): data[i] -= x data[i+1] -= y data[i+2] -= x @@ -1803,7 +1809,7 @@ def cleanPath(element, options) : x += sum(data[2::4]) y += sum(data[3::4]) elif cmd == 'C': - for i in xrange(i, len(data), 6): + for i in range(i, len(data), 6): data[i] -= x data[i+1] -= y data[i+2] -= x @@ -1824,7 +1830,7 @@ def cleanPath(element, options) : # Reuse the data structure 'path' and the coordinate lists, even if we're # deleting items, because these deletions are relatively cheap. if not withRoundLineCaps: - for pathIndex in xrange(0, len(path)): + for pathIndex in range(0, len(path)): cmd, data = path[pathIndex] i = 0 if cmd in ['m','l','t']: @@ -2059,7 +2065,7 @@ def cleanPath(element, options) : # Reuse the data structure 'path', since we're not adding or removing subcommands. # Also reuse the coordinate lists, even if we're deleting items, because these # deletions are relatively cheap. - for pathIndex in xrange(1, len(path)): + for pathIndex in range(1, len(path)): cmd, data = path[pathIndex] if cmd in ['h','v'] and len(data) > 1: coordIndex = 1 @@ -2120,7 +2126,7 @@ def parseListOfPoints(s): # also, if 100-100 is found, split it into two also # - for i in xrange(len(ws_nums)): + for i in range(len(ws_nums)): negcoords = ws_nums[i].split("-") # this string didn't have any negative coordinates @@ -2128,7 +2134,7 @@ def parseListOfPoints(s): nums.append(negcoords[0]) # we got negative coords else: - for j in xrange(len(negcoords)): + for j in range(len(negcoords)): # first number could be positive if j == 0: if negcoords[0] != '': @@ -2152,7 +2158,7 @@ def parseListOfPoints(s): try: nums[i] = getcontext().create_decimal(nums[i]) nums[i + 1] = getcontext().create_decimal(nums[i + 1]) - except decimal.InvalidOperation: # one of the lengths had a unit or is an invalid number + except InvalidOperation: # one of the lengths had a unit or is an invalid number return [] i += 2 @@ -2251,7 +2257,7 @@ def scourCoordinates(data, options, forceCommaWsp = False): # separate from the next number. if options.renderer_workaround: if len(newData) > 0: - for i in xrange(1, len(newData)): + for i in range(1, len(newData)): if newData[i][0] == '-' and 'e' in newData[i - 1]: newData[i - 1] += ' ' return ''.join(newData) @@ -2290,7 +2296,7 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a # gather the non-scientific notation version of the coordinate. # this may actually be in scientific notation if the value is # sufficiently large or small, so this is a misnomer. - nonsci = unicode(length).lower().replace("e+", "e") + nonsci = six.text_type(length).lower().replace("e+", "e") if not needsRendererWorkaround: if len(nonsci) > 2 and nonsci[:2] == '0.': nonsci = nonsci[1:] # remove the 0, leave the dot @@ -2300,7 +2306,7 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a if len(nonsci) > 3: # avoid calling normalize unless strictly necessary # and then the scientific notation version, with E+NUMBER replaced with # just eNUMBER, since SVG accepts this. - sci = unicode(length.normalize()).lower().replace("e+", "e") + sci = six.text_type(length.normalize()).lower().replace("e+", "e") if len(sci) < len(nonsci): return sci else: return nonsci @@ -2337,7 +2343,7 @@ def reducePrecision(element) : num += len(val) - len(newVal) element.setAttribute(lengthAttr, newVal) # repeat for attributes hidden in styles - if lengthAttr in styles.keys(): + if lengthAttr in list(styles.keys()): val = styles[lengthAttr] valLen = SVGLength(val) if valLen.units != Unit.INVALID: @@ -2714,7 +2720,7 @@ def remapNamespacePrefix(node, oldprefix, newprefix): # add all the attributes attrList = node.attributes - for i in xrange(attrList.length): + for i in range(attrList.length): attr = attrList.item(i) newNode.setAttributeNS( attr.namespaceURI, attr.localName, attr.nodeValue) @@ -2778,7 +2784,7 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False): # now serialize the other attributes attrList = element.attributes - for num in xrange(attrList.length) : + for num in range(attrList.length) : attr = attrList.item(num) if attr.nodeName == 'id' or attr.nodeName == 'xml:id': continue # if the attribute value contains a double-quote, use single-quotes @@ -2877,7 +2883,7 @@ def scourString(in_string, options=None): # remove the xmlns: declarations now xmlnsDeclsToRemove = [] attrList = doc.documentElement.attributes - for num in xrange(attrList.length) : + for num in range(attrList.length) : if attrList.item(num).nodeValue in unwanted_ns : xmlnsDeclsToRemove.append(attrList.item(num).nodeName) @@ -2895,7 +2901,7 @@ def scourString(in_string, options=None): attrList = doc.documentElement.attributes xmlnsDeclsToRemove = [] redundantPrefixes = [] - for i in xrange(attrList.length): + for i in range(attrList.length): attr = attrList.item(i) name = attr.nodeName val = attr.nodeValue @@ -3174,7 +3180,7 @@ def maybe_gziped_file(filename, mode="r"): if os.path.splitext(filename)[1].lower() in (".svgz", ".gz"): import gzip return gzip.GzipFile(filename, mode) - return file(filename, mode) + return open(filename, mode) @@ -3245,7 +3251,7 @@ def start(options, input, output): start = get_tick() if not options.quiet: - print >>sys.stderr, "%s %s\n%s" % (APP, VER, COPYRIGHT) + print("%s %s\n%s" % (APP, VER, COPYRIGHT), file=sys.stderr) # do the work in_string = input.read() @@ -3260,15 +3266,15 @@ def start(options, input, output): # GZ: not using globals would be good too if not options.quiet: - print >>sys.stderr, ' File:', input.name, \ + print(' File:', input.name, \ os.linesep + ' Time taken:', str(end-start) + 's' + os.linesep, \ - getReport() + getReport(), file=sys.stderr) oldsize = len(in_string) newsize = len(out_string) sizediff = (newsize / oldsize) * 100 - print >>sys.stderr, ' Original file size:', oldsize, 'bytes;', \ - 'new file size:', newsize, 'bytes (' + str(sizediff)[:5] + '%)' + print(' Original file size:', oldsize, 'bytes;', \ + 'new file size:', newsize, 'bytes (' + str(sizediff)[:5] + '%)', file=sys.stderr) diff --git a/scour/svg_regex.py b/scour/svg_regex.py index ce83c7b..e6659e5 100644 --- a/scour/svg_regex.py +++ b/scour/svg_regex.py @@ -41,10 +41,11 @@ Out[4]: [('M', [(0.60509999999999997, 0.5)])] In [5]: svg_parser.parse('M 100-200') # Another edge case Out[5]: [('M', [(100.0, -200.0)])] """ +from __future__ import absolute_import import re from decimal import * - +from functools import partial # Sentinel. class _EOF(object): @@ -145,140 +146,141 @@ class SVGPathParser(object): def parse(self, text): """ Parse a string of SVG data. """ - next = self.lexer.lex(text).next - token = next() - return self.rule_svg_path(next, token) + gen = self.lexer.lex(text) + next_val_fn = partial(next, *(gen,)) + token = next_val_fn() + return self.rule_svg_path(next_val_fn, token) - def rule_svg_path(self, next, token): + def rule_svg_path(self, next_val_fn, token): commands = [] while token[0] is not EOF: if token[0] != 'command': raise SyntaxError("expecting a command; got %r" % (token,)) rule = self.command_dispatch[token[1]] - command_group, token = rule(next, token) + command_group, token = rule(next_val_fn, token) commands.append(command_group) return commands - def rule_closepath(self, next, token): + def rule_closepath(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() return (command, []), token - def rule_moveto_or_lineto(self, next, token): + def rule_moveto_or_lineto(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() coordinates = [] while token[0] in self.number_tokens: - pair, token = self.rule_coordinate_pair(next, token) + pair, token = self.rule_coordinate_pair(next_val_fn, token) coordinates.extend(pair) return (command, coordinates), token - def rule_orthogonal_lineto(self, next, token): + def rule_orthogonal_lineto(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() coordinates = [] while token[0] in self.number_tokens: - coord, token = self.rule_coordinate(next, token) + coord, token = self.rule_coordinate(next_val_fn, token) coordinates.append(coord) return (command, coordinates), token - def rule_curveto3(self, next, token): + def rule_curveto3(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() coordinates = [] while token[0] in self.number_tokens: - pair1, token = self.rule_coordinate_pair(next, token) - pair2, token = self.rule_coordinate_pair(next, token) - pair3, token = self.rule_coordinate_pair(next, token) + pair1, token = self.rule_coordinate_pair(next_val_fn, token) + pair2, token = self.rule_coordinate_pair(next_val_fn, token) + pair3, token = self.rule_coordinate_pair(next_val_fn, token) coordinates.extend(pair1) coordinates.extend(pair2) coordinates.extend(pair3) return (command, coordinates), token - def rule_curveto2(self, next, token): + def rule_curveto2(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() coordinates = [] while token[0] in self.number_tokens: - pair1, token = self.rule_coordinate_pair(next, token) - pair2, token = self.rule_coordinate_pair(next, token) + pair1, token = self.rule_coordinate_pair(next_val_fn, token) + pair2, token = self.rule_coordinate_pair(next_val_fn, token) coordinates.extend(pair1) coordinates.extend(pair2) return (command, coordinates), token - def rule_curveto1(self, next, token): + def rule_curveto1(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() coordinates = [] while token[0] in self.number_tokens: - pair1, token = self.rule_coordinate_pair(next, token) + pair1, token = self.rule_coordinate_pair(next_val_fn, token) coordinates.extend(pair1) return (command, coordinates), token - def rule_elliptical_arc(self, next, token): + def rule_elliptical_arc(self, next_val_fn, token): command = token[1] - token = next() + token = next_val_fn() arguments = [] while token[0] in self.number_tokens: rx = Decimal(token[1]) * 1 if rx < Decimal("0.0"): raise SyntaxError("expecting a nonnegative number; got %r" % (token,)) - token = next() + token = next_val_fn() if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) ry = Decimal(token[1]) * 1 if ry < Decimal("0.0"): raise SyntaxError("expecting a nonnegative number; got %r" % (token,)) - token = next() + token = next_val_fn() if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) axis_rotation = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() if token[1] not in ('0', '1'): raise SyntaxError("expecting a boolean flag; got %r" % (token,)) large_arc_flag = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() if token[1] not in ('0', '1'): raise SyntaxError("expecting a boolean flag; got %r" % (token,)) sweep_flag = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) x = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) y = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() arguments.extend([rx, ry, axis_rotation, large_arc_flag, sweep_flag, x, y]) return (command, arguments), token - def rule_coordinate(self, next, token): + def rule_coordinate(self, next_val_fn, token): if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) x = getcontext().create_decimal(token[1]) - token = next() + token = next_val_fn() return x, token - def rule_coordinate_pair(self, next, 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: raise SyntaxError("expecting a number; got %r" % (token,)) x = getcontext().create_decimal(token[1]) - token = next() + token = next_val_fn() if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) y = getcontext().create_decimal(token[1]) - token = next() + token = next_val_fn() return [x, y], token diff --git a/scour/svg_transform.py b/scour/svg_transform.py index 72fd06f..85507ca 100644 --- a/scour/svg_transform.py +++ b/scour/svg_transform.py @@ -56,9 +56,12 @@ Multiple transformations are supported: In [12]: svg_transform_parser.parse('translate(30 -30) rotate(36)') Out[12]: [('translate', [30.0, -30.0]), ('rotate', [36.0])] """ +from __future__ import absolute_import import re from decimal import * +from six.moves import range +from functools import partial # Sentinel. @@ -145,88 +148,90 @@ class SVGTransformationParser(object): def parse(self, text): """ Parse a string of SVG transform="" data. """ - next = self.lexer.lex(text).next + gen = self.lexer.lex(text) + next_val_fn = partial(next, *(gen,)) + commands = [] - token = next() + token = next_val_fn() while token[0] is not EOF: - command, token = self.rule_svg_transform(next, token) + command, token = self.rule_svg_transform(next_val_fn, token) commands.append(command) return commands - def rule_svg_transform(self, next, token): + def rule_svg_transform(self, next_val_fn, token): if token[0] != 'command': raise SyntaxError("expecting a transformation type; got %r" % (token,)) command = token[1] rule = self.command_dispatch[command] - token = next() + token = next_val_fn() if token[0] != 'coordstart': raise SyntaxError("expecting '('; got %r" % (token,)) - numbers, token = rule(next, token) + numbers, token = rule(next_val_fn, token) if token[0] != 'coordend': raise SyntaxError("expecting ')'; got %r" % (token,)) - token = next() + token = next_val_fn() return (command, numbers), token - def rule_1or2numbers(self, next, token): + def rule_1or2numbers(self, next_val_fn, token): numbers = [] # 1st number is mandatory - token = next() - number, token = self.rule_number(next, token) + token = next_val_fn() + number, token = self.rule_number(next_val_fn, token) numbers.append(number) # 2nd number is optional - number, token = self.rule_optional_number(next, token) + number, token = self.rule_optional_number(next_val_fn, token) if number is not None: numbers.append(number) return numbers, token - def rule_1number(self, next, token): + def rule_1number(self, next_val_fn, token): # this number is mandatory - token = next() - number, token = self.rule_number(next, token) + token = next_val_fn() + number, token = self.rule_number(next_val_fn, token) numbers = [number] return numbers, token - def rule_1or3numbers(self, next, token): + def rule_1or3numbers(self, next_val_fn, token): numbers = [] # 1st number is mandatory - token = next() - number, token = self.rule_number(next, token) + token = next_val_fn() + number, token = self.rule_number(next_val_fn, token) numbers.append(number) # 2nd number is optional - number, token = self.rule_optional_number(next, token) + number, token = self.rule_optional_number(next_val_fn, token) if number is not None: # but, if the 2nd number is provided, the 3rd is mandatory. # we can't have just 2. numbers.append(number) - number, token = self.rule_number(next, token) + number, token = self.rule_number(next_val_fn, token) numbers.append(number) return numbers, token - def rule_6numbers(self, next, token): + def rule_6numbers(self, next_val_fn, token): numbers = [] - token = next() + token = next_val_fn() # all numbers are mandatory - for i in xrange(6): - number, token = self.rule_number(next, token) + for i in range(6): + number, token = self.rule_number(next_val_fn, token) numbers.append(number) return numbers, token - def rule_number(self, next, token): + def rule_number(self, next_val_fn, token): if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) x = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() return x, token - def rule_optional_number(self, next, token): + def rule_optional_number(self, next_val_fn, token): if token[0] not in self.number_tokens: return None, token else: x = Decimal(token[1]) * 1 - token = next() + token = next_val_fn() return x, token diff --git a/setup.py b/setup.py index 4802ba1..1ced1ee 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ setup ( author_email = 'codedread@gmail.com', url = 'https://github.com/oberstet/scour', platforms = ('Any'), - install_requires = [], + install_requires = ['six>=1.9.0'], packages = find_packages(), zip_safe = True, entry_points = {