Merge branch lp:~louis-simard/scour/rework. Summary of changes:

scour.py, scour.inkscape.py, scour.inx:
 * Add options --quiet, --enable-comment-stripping, --shorten-ids, --remove-metadata, --renderer-workaround.
scour.py:
 * Optimisations in time (so Scour runs faster) and space (so Scour allocates less memory, less often).
 * Change #E+# to #e#, #E-# to #e-#, 0.# to .# and -0.# into -.# in path/polygon/polyline data + lengths, if renderer workarounds are disabled. Use spaces instead of commas in path/polygon/polyline data. Use lower-case #rrggbb and #rgb instead of upper-case. All of this makes gzip work better, since the rest of SVG documents mostly has lower-case letters in tag names and spaces to separate XML attributes etc.
 * Fix a bug whereby an SVG document would become filled with black if all elements had the same fill color.
 * Fix a bug whereby a path's second command would not start at the right coordinates if the first command was a relative moveto 'm' with at least 1 implied lineto.
 * Fix a bug whereby a path's absolute lineto 'L' commands would not become the right relative lineto 'l' commands.
 * Allow the implicit linetos after a path's moveto 'M'/'m' to be converted into relative horizontal linetos 'h' and vertical 'v' too.
scour.inx:
 * Fix help typos. Make options more descriptive in the plugin option window. Add something about enable-group-collapsing requiring enable-id-stripping.
testscour.py:
 * Rework tests that relied on #E+#, #E-#, 0.# and -0.# so that they accept the changes to scour.py. Add unit tests for strip-xml-prolog, enable-comment-stripping and remove-metadata.
This commit is contained in:
Cynthia Gauthier 2010-06-15 20:58:57 -04:00
parent 00804fb833
commit f4cca44faf
9 changed files with 1153 additions and 518 deletions

View file

@ -22,12 +22,18 @@ class ScourInkscape (inkex.Effect):
self.OptionParser.add_option("--enable-id-stripping", type="inkbool", self.OptionParser.add_option("--enable-id-stripping", type="inkbool",
action="store", dest="strip_ids", default=False, action="store", dest="strip_ids", default=False,
help="remove all un-referenced ID attributes") help="remove all un-referenced ID attributes")
self.OptionParser.add_option("--shorten-ids", type="inkbool",
action="store", dest="shorten_ids", default=False,
help="shorten all ID attributes to the least number of letters possible")
self.OptionParser.add_option("--embed-rasters", type="inkbool", self.OptionParser.add_option("--embed-rasters", type="inkbool",
action="store", dest="embed_rasters", default=True, action="store", dest="embed_rasters", default=True,
help="won't embed rasters as base64-encoded data") help="won't embed rasters as base64-encoded data")
self.OptionParser.add_option("--keep-editor-data", type="inkbool", self.OptionParser.add_option("--keep-editor-data", type="inkbool",
action="store", dest="keep_editor_data", default=False, action="store", dest="keep_editor_data", default=False,
help="won't remove Inkscape, Sodipodi or Adobe Illustrator elements and attributes") help="won't remove Inkscape, Sodipodi or Adobe Illustrator elements and attributes")
self.OptionParser.add_option("--remove-metadata", type="inkbool",
action="store", dest="remove_metadata", default=False,
help="remove <metadata> elements (which may contain license metadata etc.)")
self.OptionParser.add_option("--strip-xml-prolog", type="inkbool", self.OptionParser.add_option("--strip-xml-prolog", type="inkbool",
action="store", dest="strip_xml_prolog", default=False, action="store", dest="strip_xml_prolog", default=False,
help="won't output the <?xml ?> prolog") help="won't output the <?xml ?> prolog")
@ -40,7 +46,12 @@ class ScourInkscape (inkex.Effect):
self.OptionParser.add_option("--enable-viewboxing", type="inkbool", self.OptionParser.add_option("--enable-viewboxing", type="inkbool",
action="store", dest="enable_viewboxing", default=False, action="store", dest="enable_viewboxing", default=False,
help="changes document width/height to 100%/100% and creates viewbox coordinates") help="changes document width/height to 100%/100% and creates viewbox coordinates")
self.OptionParser.add_option("--enable-comment-stripping", type="inkbool",
action="store", dest="strip_comments", default=False,
help="remove all <!-- --> comments")
self.OptionParser.add_option("--renderer-workaround", type="inkbool",
action="store", dest="renderer_workaround", default=False,
help="work around various renderer bugs (currently only librsvg)")
def effect(self): def effect(self):
input = file(sys.argv[12], "r") input = file(sys.argv[12], "r")

View file

@ -7,16 +7,20 @@
<dependency type="executable" location="extensions">yocto_css.py</dependency> <dependency type="executable" location="extensions">yocto_css.py</dependency>
<param name="tab" type="notebook"> <param name="tab" type="notebook">
<page name="Options" _gui-text="Options"> <page name="Options" _gui-text="Options">
<param name="simplify-colors" type="boolean" _gui-text="Simplify colors">true</param> <param name="simplify-colors" type="boolean" _gui-text="Shorten color values">true</param>
<param name="style-to-xml" type="boolean" _gui-text="Style to xml">true</param> <param name="style-to-xml" type="boolean" _gui-text="Convert CSS attributes to XML attributes">true</param>
<param name="group-collapsing" type="boolean" _gui-text="Group collapsing">true</param> <param name="group-collapsing" type="boolean" _gui-text="Group collapsing">true</param>
<param name="enable-id-stripping" type="boolean" _gui-text="Enable id stripping">false</param> <param name="enable-id-stripping" type="boolean" _gui-text="Remove unused ID names for elements">false</param>
<param name="shorten-ids" type="boolean" _gui-text="Shorten IDs">false</param>
<param name="embed-rasters" type="boolean" _gui-text="Embed rasters">true</param> <param name="embed-rasters" type="boolean" _gui-text="Embed rasters">true</param>
<param name="keep-editor-data" type="boolean" _gui-text="Keep editor data">false</param> <param name="keep-editor-data" type="boolean" _gui-text="Keep editor data">false</param>
<param name="remove-metadata" type="boolean" _gui-text="Remove metadata">false</param>
<param name="enable-comment-stripping" type="boolean" _gui-text="Remove comments">false</param>
<param name="renderer-workaround" type="boolean" _gui-text="Work around renderer bugs">false</param>
<param name="enable-viewboxing" type="boolean" _gui-text="Enable viewboxing">false</param> <param name="enable-viewboxing" type="boolean" _gui-text="Enable viewboxing">false</param>
<param name="strip-xml-prolog" type="boolean" _gui-text="Strip xml prolog">false</param> <param name="strip-xml-prolog" type="boolean" _gui-text="Remove the <?xml?> declaration">false</param>
<param name="set-precision" type="int" _gui-text="Set precision">5</param> <param name="set-precision" type="int" _gui-text="Number of significant digits for coords">5</param>
<param name="indent" type="enum" _gui-text="Indent"> <param name="indent" type="enum" _gui-text="XML indentation (pretty-printing)">
<_item value="space">Space</_item> <_item value="space">Space</_item>
<_item value="tab">Tab</_item> <_item value="tab">Tab</_item>
<_item value="none">None</_item> <_item value="none">None</_item>
@ -24,16 +28,19 @@
</page> </page>
<page name="Help" _gui-text="Help"> <page name="Help" _gui-text="Help">
<_param name="instructions" type="description" xml:space="preserve">This extension optimizes the SVG file according to the following options: <_param name="instructions" type="description" xml:space="preserve">This extension optimizes the SVG file according to the following options:
* Simplify colors: convert all colors to #RRGGBB format. * Shorten color names: convert all colors to #RRGGBB or #RGB format.
* Style to xml: convert styles into XML attributes. * Convert CSS attributes to XML attributes: convert styles from <style> tags and inline style="" declarations into XML attributes.
* Group collapsing: collapse group elements. * Group collapsing: removes useless <g> elements, promoting their contents up one level. Requires "Remove unused ID names for elements" to be set.
* Enable id stripping: remove all un-referenced ID attributes. * Remove unused ID names for elements: remove all unreferenced ID attributes.
* Embed rasters: embed rasters as base64-encoded data. * Shorten IDs: reduce the length of all ID attributes, assigning the shortest to the most-referenced elements. For instance, #linearGradient5621, referenced 100 times, can become #a.
* Embed rasters: embed raster images as base64-encoded data URLs.
* Keep editor data: don't remove Inkscape, Sodipodi or Adobe Illustrator elements and attributes. * Keep editor data: don't remove Inkscape, Sodipodi or Adobe Illustrator elements and attributes.
* Enable viewboxing: size image to 100%/100% and introduce a viewBox * Remove metadata: remove &lt;metadata&gt; tags along with all the information in them, which may include license metadata, alternate versions for non-SVG-enabled browsers, etc.
* Strip xml prolog: don't output the xml prolog. * Remove comments: remove &lt;!-- --&gt; tags.
* Set precision: set number of significant digits (default: 5). * Work around renderer bugs: emits slightly larger SVG data, but works around a bug in librsvg's renderer, which is used in Eye of GNOME and other various applications.
* Indent: indentation of the output: none, space, tab (default: space).</_param> * Enable viewboxing: size image to 100%/100% and introduce a viewBox.
* Number of significant digits for coords: all coordinates are output with that number of significant digits. For example, if 3 is specified, the coordinate 3.5153 is output as 3.51 and the coordinate 471.55 is output as 472.
* XML indentation (pretty-printing): either None for no indentation, Space to use one space per nesting level, or Tab to use one tab per nesting level.</_param>
</page> </page>
</param> </param>
<output> <output>

1154
scour.py

File diff suppressed because it is too large Load diff

View file

@ -43,6 +43,7 @@ Out[5]: [('M', [(100.0, -200.0)])]
""" """
import re import re
from decimal import *
# Sentinel. # Sentinel.
@ -52,8 +53,8 @@ class _EOF(object):
EOF = _EOF() EOF = _EOF()
lexicon = [ lexicon = [
('float', r'[-\+]?(?:(?:[0-9]*\.[0-9]+)|(?:[0-9]+\.?))(?:[Ee][-\+]?[0-9]+)?'), ('float', r'[-+]?(?:(?:[0-9]*\.[0-9]+)|(?:[0-9]+\.?))(?:[Ee][-+]?[0-9]+)?'),
('int', r'[-\+]?[0-9]+'), ('int', r'[-+]?[0-9]+'),
('command', r'[AaCcHhLlMmQqSsTtVvZz]'), ('command', r'[AaCcHhLlMmQqSsTtVvZz]'),
] ]
@ -161,7 +162,7 @@ class SVGPathParser(object):
def rule_closepath(self, next, token): def rule_closepath(self, next, token):
command = token[1] command = token[1]
token = next() token = next()
return (command, None), token return (command, []), token
def rule_moveto_or_lineto(self, next, token): def rule_moveto_or_lineto(self, next, token):
command = token[1] command = token[1]
@ -169,7 +170,7 @@ class SVGPathParser(object):
coordinates = [] coordinates = []
while token[0] in self.number_tokens: while token[0] in self.number_tokens:
pair, token = self.rule_coordinate_pair(next, token) pair, token = self.rule_coordinate_pair(next, token)
coordinates.append(pair) coordinates.extend(pair)
return (command, coordinates), token return (command, coordinates), token
def rule_orthogonal_lineto(self, next, token): def rule_orthogonal_lineto(self, next, token):
@ -189,7 +190,9 @@ class SVGPathParser(object):
pair1, token = self.rule_coordinate_pair(next, token) pair1, token = self.rule_coordinate_pair(next, token)
pair2, token = self.rule_coordinate_pair(next, token) pair2, token = self.rule_coordinate_pair(next, token)
pair3, token = self.rule_coordinate_pair(next, token) pair3, token = self.rule_coordinate_pair(next, token)
coordinates.append((pair1, pair2, pair3)) coordinates.extend(pair1)
coordinates.extend(pair2)
coordinates.extend(pair3)
return (command, coordinates), token return (command, coordinates), token
def rule_curveto2(self, next, token): def rule_curveto2(self, next, token):
@ -199,7 +202,8 @@ class SVGPathParser(object):
while token[0] in self.number_tokens: while token[0] in self.number_tokens:
pair1, token = self.rule_coordinate_pair(next, token) pair1, token = self.rule_coordinate_pair(next, token)
pair2, token = self.rule_coordinate_pair(next, token) pair2, token = self.rule_coordinate_pair(next, token)
coordinates.append((pair1, pair2)) coordinates.extend(pair1)
coordinates.extend(pair2)
return (command, coordinates), token return (command, coordinates), token
def rule_curveto1(self, next, token): def rule_curveto1(self, next, token):
@ -208,7 +212,7 @@ class SVGPathParser(object):
coordinates = [] coordinates = []
while token[0] in self.number_tokens: while token[0] in self.number_tokens:
pair1, token = self.rule_coordinate_pair(next, token) pair1, token = self.rule_coordinate_pair(next, token)
coordinates.append(pair1) coordinates.extend(pair1)
return (command, coordinates), token return (command, coordinates), token
def rule_elliptical_arc(self, next, token): def rule_elliptical_arc(self, next, token):
@ -216,51 +220,51 @@ class SVGPathParser(object):
token = next() token = next()
arguments = [] arguments = []
while token[0] in self.number_tokens: while token[0] in self.number_tokens:
rx = float(token[1]) rx = Decimal(token[1]) * 1
if rx < 0.0: if rx < 0.0:
raise SyntaxError("expecting a nonnegative number; got %r" % (token,)) raise SyntaxError("expecting a nonnegative number; got %r" % (token,))
token = next() token = next()
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
ry = float(token[1]) ry = Decimal(token[1]) * 1
if ry < 0.0: if ry < 0.0:
raise SyntaxError("expecting a nonnegative number; got %r" % (token,)) raise SyntaxError("expecting a nonnegative number; got %r" % (token,))
token = next() token = next()
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
axis_rotation = float(token[1]) axis_rotation = Decimal(token[1]) * 1
token = next() token = next()
if token[1] not in ('0', '1'): if token[1] not in ('0', '1'):
raise SyntaxError("expecting a boolean flag; got %r" % (token,)) raise SyntaxError("expecting a boolean flag; got %r" % (token,))
large_arc_flag = bool(int(token[1])) large_arc_flag = Decimal(token[1]) * 1
token = next() token = next()
if token[1] not in ('0', '1'): if token[1] not in ('0', '1'):
raise SyntaxError("expecting a boolean flag; got %r" % (token,)) raise SyntaxError("expecting a boolean flag; got %r" % (token,))
sweep_flag = bool(int(token[1])) sweep_flag = Decimal(token[1]) * 1
token = next() token = next()
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
x = float(token[1]) x = Decimal(token[1]) * 1
token = next() token = next()
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
y = float(token[1]) y = Decimal(token[1]) * 1
token = next() token = next()
arguments.append(((rx,ry), axis_rotation, large_arc_flag, sweep_flag, (x,y))) arguments.extend([rx, ry, axis_rotation, large_arc_flag, sweep_flag, x, y])
return (command, arguments), token return (command, arguments), token
def rule_coordinate(self, next, token): def rule_coordinate(self, next, token):
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
x = float(token[1]) x = getcontext().create_decimal(token[1])
token = next() token = next()
return x, token return x, token
@ -269,13 +273,13 @@ class SVGPathParser(object):
# Inline these since this rule is so common. # Inline these since this rule is so common.
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
x = float(token[1]) x = getcontext().create_decimal(token[1])
token = next() token = next()
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))
y = float(token[1]) y = getcontext().create_decimal(token[1])
token = next() token = next()
return (x,y), token return [x, y], token
svg_parser = SVGPathParser() svg_parser = SVGPathParser()

233
svg_transform.py Normal file
View file

@ -0,0 +1,233 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# SVG transformation list parser
#
# Copyright 2010
#
# This file is part of Scour, http://www.codedread.com/scour/
#
# 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.
""" Small recursive descent parser for SVG transform="" data.
In [1]: from svg_transform import svg_transform_parser
In [3]: svg_transform_parser.parse('translate(50, 50)')
Out[3]: [('translate', [50.0, 50.0])]
In [4]: svg_transform_parser.parse('translate(50)')
Out[4]: [('translate', [50.0])]
In [5]: svg_transform_parser.parse('rotate(36 50,50)')
Out[5]: [('rotate', [36.0, 50.0, 50.0])]
In [6]: svg_transform_parser.parse('rotate(36)')
Out[6]: [('rotate', [36.0])]
In [7]: svg_transform_parser.parse('skewX(20)')
Out[7]: [('skewX', [20.0])]
In [8]: svg_transform_parser.parse('skewY(40)')
Out[8]: [('skewX', [20.0])]
In [9]: svg_transform_parser.parse('scale(2 .5)')
Out[9]: [('scale', [2.0, 0.5])]
In [10]: svg_transform_parser.parse('scale(.5)')
Out[10]: [('scale', [0.5])]
In [11]: svg_transform_parser.parse('matrix(1 0 50 0 1 80)')
Out[11]: [('matrix', [1.0, 0.0, 50.0, 0.0, 1.0, 80.0])]
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])]
"""
import re
from decimal import *
# Sentinel.
class _EOF(object):
def __repr__(self):
return 'EOF'
EOF = _EOF()
lexicon = [
('float', r'[-+]?(?:(?:[0-9]*\.[0-9]+)|(?:[0-9]+\.?))(?:[Ee][-+]?[0-9]+)?'),
('int', r'[-+]?[0-9]+'),
('command', r'(?:matrix|translate|scale|rotate|skew[XY])'),
('coordstart', r'\('),
('coordend', r'\)'),
]
class Lexer(object):
""" Break SVG path data into tokens.
The SVG spec requires that tokens are greedy. This lexer relies on Python's
regexes defaulting to greediness.
This style of implementation was inspired by this article:
http://www.gooli.org/blog/a-simple-lexer-in-python/
"""
def __init__(self, lexicon):
self.lexicon = lexicon
parts = []
for name, regex in lexicon:
parts.append('(?P<%s>%s)' % (name, regex))
self.regex_string = '|'.join(parts)
self.regex = re.compile(self.regex_string)
def lex(self, text):
""" Yield (token_type, str_data) tokens.
The last token will be (EOF, None) where EOF is the singleton object
defined in this module.
"""
for match in self.regex.finditer(text):
for name, _ in self.lexicon:
m = match.group(name)
if m is not None:
yield (name, m)
break
yield (EOF, None)
svg_lexer = Lexer(lexicon)
class SVGTransformationParser(object):
""" Parse SVG transform="" data into a list of commands.
Each distinct command will take the form of a tuple (type, data). The
`type` is the character string that defines the type of transformation in the
transform data, so either of "translate", "rotate", "scale", "matrix",
"skewX" and "skewY". Data is always a list of numbers contained within the
transformation's parentheses.
See the SVG documentation for the interpretation of the individual elements
for each transformation.
The main method is `parse(text)`. It can only consume actual strings, not
filelike objects or iterators.
"""
def __init__(self, lexer=svg_lexer):
self.lexer = lexer
self.command_dispatch = {
'translate': self.rule_1or2numbers,
'scale': self.rule_1or2numbers,
'skewX': self.rule_1number,
'skewY': self.rule_1number,
'rotate': self.rule_1or3numbers,
'matrix': self.rule_6numbers,
}
# self.number_tokens = set(['int', 'float'])
self.number_tokens = list(['int', 'float'])
def parse(self, text):
""" Parse a string of SVG transform="" data.
"""
next = self.lexer.lex(text).next
commands = []
token = next()
while token[0] is not EOF:
command, token = self.rule_svg_transform(next, token)
commands.append(command)
return commands
def rule_svg_transform(self, next, token):
if token[0] != 'command':
raise SyntaxError("expecting a transformation type; got %r" % (token,))
command = token[1]
rule = self.command_dispatch[command]
token = next()
if token[0] != 'coordstart':
raise SyntaxError("expecting '('; got %r" % (token,))
numbers, token = rule(next, token)
if token[0] != 'coordend':
raise SyntaxError("expecting ')'; got %r" % (token,))
token = next()
return (command, numbers), token
def rule_1or2numbers(self, next, token):
numbers = []
# 1st number is mandatory
token = next()
number, token = self.rule_number(next, token)
numbers.append(number)
# 2nd number is optional
number, token = self.rule_optional_number(next, token)
if number is not None:
numbers.append(number)
return numbers, token
def rule_1number(self, next, token):
# this number is mandatory
token = next()
number, token = self.rule_number(next, token)
numbers = [number]
return numbers, token
def rule_1or3numbers(self, next, token):
numbers = []
# 1st number is mandatory
token = next()
number, token = self.rule_number(next, token)
numbers.append(number)
# 2nd number is optional
number, token = self.rule_optional_number(next, 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)
numbers.append(number)
return numbers, token
def rule_6numbers(self, next, token):
numbers = []
token = next()
# all numbers are mandatory
for i in xrange(6):
number, token = self.rule_number(next, token)
numbers.append(number)
return numbers, token
def rule_number(self, next, token):
if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,))
x = Decimal(token[1]) * 1
token = next()
return x, token
def rule_optional_number(self, next, token):
if token[0] not in self.number_tokens:
return None, token
else:
x = Decimal(token[1]) * 1
token = next()
return x, token
svg_transform_parser = SVGTransformationParser()

View file

@ -48,6 +48,9 @@ class ScourOptions:
strip_xml_prolog = False strip_xml_prolog = False
indent_type = "space" indent_type = "space"
enable_viewboxing = False enable_viewboxing = False
shorten_ids = False
strip_comments = False
remove_metadata = False
class NoInkscapeElements(unittest.TestCase): class NoInkscapeElements(unittest.TestCase):
def runTest(self): def runTest(self):
@ -445,7 +448,7 @@ class ConvertFillPropertyToAttr(unittest.TestCase):
class ConvertFillOpacityPropertyToAttr(unittest.TestCase): class ConvertFillOpacityPropertyToAttr(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/fill-none.svg') doc = scour.scourXmlFile('unittests/fill-none.svg')
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'path')[1].getAttribute('fill-opacity'), '0.5', self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'path')[1].getAttribute('fill-opacity'), '.5',
'fill-opacity property not converted to XML attribute' ) 'fill-opacity property not converted to XML attribute' )
class ConvertFillRuleOpacityPropertyToAttr(unittest.TestCase): class ConvertFillRuleOpacityPropertyToAttr(unittest.TestCase):
@ -483,14 +486,14 @@ class RemoveTrailingZerosFromPath(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-truncate-zeros.svg') doc = scour.scourXmlFile('unittests/path-truncate-zeros.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d') path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path[:4] == 'M300' and path[4] != '.', True, self.assertEquals(path[:4] == 'm300' and path[4] != '.', True,
'Trailing zeros not removed from path data' ) 'Trailing zeros not removed from path data' )
class RemoveTrailingZerosFromPathAfterCalculation(unittest.TestCase): class RemoveTrailingZerosFromPathAfterCalculation(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-truncate-zeros-calc.svg') doc = scour.scourXmlFile('unittests/path-truncate-zeros-calc.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d') path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path, 'M5.81,0h0.1', self.assertEquals(path, 'm5.81 0h0.1',
'Trailing zeros not removed from path data after calculation' ) 'Trailing zeros not removed from path data after calculation' )
class RemoveDelimiterBeforeNegativeCoordsInPath(unittest.TestCase): class RemoveDelimiterBeforeNegativeCoordsInPath(unittest.TestCase):
@ -504,7 +507,7 @@ class UseScientificNotationToShortenCoordsInPath(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-use-scientific-notation.svg') doc = scour.scourXmlFile('unittests/path-use-scientific-notation.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d') path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path, 'M1E+4,0', self.assertEquals(path, 'm1e4 0',
'Not using scientific notation for path coord when representation is shorter') 'Not using scientific notation for path coord when representation is shorter')
class ConvertAbsoluteToRelativePathCommands(unittest.TestCase): class ConvertAbsoluteToRelativePathCommands(unittest.TestCase):
@ -513,23 +516,23 @@ class ConvertAbsoluteToRelativePathCommands(unittest.TestCase):
path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')) path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d'))
self.assertEquals(path[1][0], 'v', self.assertEquals(path[1][0], 'v',
'Absolute V command not converted to relative v command') 'Absolute V command not converted to relative v command')
self.assertEquals(path[1][1][0], -20.0, self.assertEquals(float(path[1][1][0]), -20.0,
'Absolute V value not converted to relative v value') 'Absolute V value not converted to relative v value')
class RoundPathData(unittest.TestCase): class RoundPathData(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-precision.svg') doc = scour.scourXmlFile('unittests/path-precision.svg')
path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')) path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d'))
self.assertEquals(path[0][1][0][0], 100.0, self.assertEquals(float(path[0][1][0]), 100.0,
'Not rounding down' ) 'Not rounding down' )
self.assertEquals(path[0][1][0][1], 100.0, self.assertEquals(float(path[0][1][1]), 100.0,
'Not rounding up' ) 'Not rounding up' )
class LimitPrecisionInPathData(unittest.TestCase): class LimitPrecisionInPathData(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-precision.svg') doc = scour.scourXmlFile('unittests/path-precision.svg')
path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')) path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d'))
self.assertEquals(path[1][1][0], 100.01, self.assertEquals(float(path[1][1][0]), 100.01,
'Not correctly limiting precision on path data' ) 'Not correctly limiting precision on path data' )
class RemoveEmptyLineSegmentsFromPath(unittest.TestCase): class RemoveEmptyLineSegmentsFromPath(unittest.TestCase):
@ -545,7 +548,7 @@ class ChangeLineToHorizontalLineSegmentInPath(unittest.TestCase):
path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')) path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d'))
self.assertEquals(path[1][0], 'h', self.assertEquals(path[1][0], 'h',
'Did not change line to horizontal line segment in path' ) 'Did not change line to horizontal line segment in path' )
self.assertEquals(path[1][1][0], 200.0, self.assertEquals(float(path[1][1][0]), 200.0,
'Did not calculate horizontal line segment in path correctly' ) 'Did not calculate horizontal line segment in path correctly' )
class ChangeLineToVerticalLineSegmentInPath(unittest.TestCase): class ChangeLineToVerticalLineSegmentInPath(unittest.TestCase):
@ -554,19 +557,19 @@ class ChangeLineToVerticalLineSegmentInPath(unittest.TestCase):
path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')) path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d'))
self.assertEquals(path[2][0], 'v', self.assertEquals(path[2][0], 'v',
'Did not change line to vertical line segment in path' ) 'Did not change line to vertical line segment in path' )
self.assertEquals(path[2][1][0], 100.0, self.assertEquals(float(path[2][1][0]), 100.0,
'Did not calculate vertical line segment in path correctly' ) 'Did not calculate vertical line segment in path correctly' )
class ChangeBezierToShorthandInPath(unittest.TestCase): class ChangeBezierToShorthandInPath(unittest.TestCase):
def runTest(self): def runTest(self):
path = scour.scourXmlFile('unittests/path-bez-optimize.svg').getElementsByTagNameNS(SVGNS, 'path')[0] path = scour.scourXmlFile('unittests/path-bez-optimize.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals(path.getAttribute('d'), 'm10,100c50-50,50,50,100,0s50,50,100,0', self.assertEquals(path.getAttribute('d'), 'm10 100c50-50 50 50 100 0s50 50 100 0',
'Did not change bezier curves into shorthand curve segments in path') 'Did not change bezier curves into shorthand curve segments in path')
class ChangeQuadToShorthandInPath(unittest.TestCase): class ChangeQuadToShorthandInPath(unittest.TestCase):
def runTest(self): def runTest(self):
path = scour.scourXmlFile('unittests/path-quad-optimize.svg').getElementsByTagNameNS(SVGNS, 'path')[0] path = scour.scourXmlFile('unittests/path-quad-optimize.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals(path.getAttribute('d'), 'm10,100q50-50,100,0t100,0', self.assertEquals(path.getAttribute('d'), 'm10 100q50-50 100 0t100 0',
'Did not change quadratic curves into shorthand curve segments in path') 'Did not change quadratic curves into shorthand curve segments in path')
class HandleNonAsciiUtf8(unittest.TestCase): class HandleNonAsciiUtf8(unittest.TestCase):
@ -585,31 +588,31 @@ class HandleSciNoInPathData(unittest.TestCase):
class TranslateRGBIntoHex(unittest.TestCase): class TranslateRGBIntoHex(unittest.TestCase):
def runTest(self): def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'rect')[0] elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'rect')[0]
self.assertEquals( elem.getAttribute('fill'), '#0F1011', self.assertEquals( elem.getAttribute('fill'), '#0f1011',
'Not converting rgb into hex') 'Not converting rgb into hex')
class TranslateRGBPctIntoHex(unittest.TestCase): class TranslateRGBPctIntoHex(unittest.TestCase):
def runTest(self): def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'stop')[0] elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'stop')[0]
self.assertEquals( elem.getAttribute('stop-color'), '#7F0000', self.assertEquals( elem.getAttribute('stop-color'), '#7f0000',
'Not converting rgb pct into hex') 'Not converting rgb pct into hex')
class TranslateColorNamesIntoHex(unittest.TestCase): class TranslateColorNamesIntoHex(unittest.TestCase):
def runTest(self): def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'rect')[0] elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'rect')[0]
self.assertEquals( elem.getAttribute('stroke'), '#A9A9A9', self.assertEquals( elem.getAttribute('stroke'), '#a9a9a9',
'Not converting standard color names into hex') 'Not converting standard color names into hex')
class TranslateExtendedColorNamesIntoHex(unittest.TestCase): class TranslateExtendedColorNamesIntoHex(unittest.TestCase):
def runTest(self): def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'solidColor')[0] elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'solidColor')[0]
self.assertEquals( elem.getAttribute('solid-color'), '#FAFAD2', self.assertEquals( elem.getAttribute('solid-color'), '#fafad2',
'Not converting extended color names into hex') 'Not converting extended color names into hex')
class TranslateLongHexColorIntoShortHex(unittest.TestCase): class TranslateLongHexColorIntoShortHex(unittest.TestCase):
def runTest(self): def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'ellipse')[0] elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'ellipse')[0]
self.assertEquals( elem.getAttribute('fill'), '#FFF', self.assertEquals( elem.getAttribute('fill'), '#fff',
'Not converting long hex color into short hex') 'Not converting long hex color into short hex')
class DoNotConvertShortColorNames(unittest.TestCase): class DoNotConvertShortColorNames(unittest.TestCase):
@ -633,62 +636,62 @@ class RemoveFontStylesFromNonTextShapes(unittest.TestCase):
class CollapseConsecutiveHLinesSegments(unittest.TestCase): class CollapseConsecutiveHLinesSegments(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/consecutive-hlines.svg').getElementsByTagNameNS(SVGNS, 'path')[0] p = scour.scourXmlFile('unittests/consecutive-hlines.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals( p.getAttribute('d'), 'M100,100h200v100h-200z', self.assertEquals( p.getAttribute('d'), 'm100 100h200v100h-200z',
'Did not collapse consecutive hlines segments') 'Did not collapse consecutive hlines segments')
class CollapseConsecutiveHLinesCoords(unittest.TestCase): class CollapseConsecutiveHLinesCoords(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/consecutive-hlines.svg').getElementsByTagNameNS(SVGNS, 'path')[1] p = scour.scourXmlFile('unittests/consecutive-hlines.svg').getElementsByTagNameNS(SVGNS, 'path')[1]
self.assertEquals( p.getAttribute('d'), 'M100,300h200v100h-200z', self.assertEquals( p.getAttribute('d'), 'm100 300h200v100h-200z',
'Did not collapse consecutive hlines coordinates') 'Did not collapse consecutive hlines coordinates')
class DoNotCollapseConsecutiveHLinesSegsWithDifferingSigns(unittest.TestCase): class DoNotCollapseConsecutiveHLinesSegsWithDifferingSigns(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/consecutive-hlines.svg').getElementsByTagNameNS(SVGNS, 'path')[2] p = scour.scourXmlFile('unittests/consecutive-hlines.svg').getElementsByTagNameNS(SVGNS, 'path')[2]
self.assertEquals( p.getAttribute('d'), 'M100,500h300-100v100h-200z', self.assertEquals( p.getAttribute('d'), 'm100 500h300-100v100h-200z',
'Collapsed consecutive hlines segments with differing signs') 'Collapsed consecutive hlines segments with differing signs')
class ConvertStraightCurvesToLines(unittest.TestCase): class ConvertStraightCurvesToLines(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/straight-curve.svg').getElementsByTagNameNS(SVGNS, 'path')[0] p = scour.scourXmlFile('unittests/straight-curve.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals(p.getAttribute('d'), 'M10,10l40,40,40-40z', self.assertEquals(p.getAttribute('d'), 'm10 10l40 40 40-40z',
'Did not convert straight curves into lines') 'Did not convert straight curves into lines')
class RemoveUnnecessaryPolygonEndPoint(unittest.TestCase): class RemoveUnnecessaryPolygonEndPoint(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0] p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0]
self.assertEquals(p.getAttribute('points'), '50,50,150,50,150,150,50,150', self.assertEquals(p.getAttribute('points'), '50 50 150 50 150 150 50 150',
'Unnecessary polygon end point not removed' ) 'Unnecessary polygon end point not removed' )
class DoNotRemovePolgonLastPoint(unittest.TestCase): class DoNotRemovePolgonLastPoint(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[1] p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[1]
self.assertEquals(p.getAttribute('points'), '200,50,300,50,300,150,200,150', self.assertEquals(p.getAttribute('points'), '200 50 300 50 300 150 200 150',
'Last point of polygon removed' ) 'Last point of polygon removed' )
class ScourPolygonCoordsSciNo(unittest.TestCase): class ScourPolygonCoordsSciNo(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polygon-coord.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0] p = scour.scourXmlFile('unittests/polygon-coord.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0]
self.assertEquals(p.getAttribute('points'), '1E+4,50', self.assertEquals(p.getAttribute('points'), '1e4 50',
'Polygon coordinates not scoured') 'Polygon coordinates not scoured')
class ScourPolylineCoordsSciNo(unittest.TestCase): class ScourPolylineCoordsSciNo(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polyline-coord.svg').getElementsByTagNameNS(SVGNS, 'polyline')[0] p = scour.scourXmlFile('unittests/polyline-coord.svg').getElementsByTagNameNS(SVGNS, 'polyline')[0]
self.assertEquals(p.getAttribute('points'), '1E+4,50', self.assertEquals(p.getAttribute('points'), '1e4 50',
'Polyline coordinates not scoured') 'Polyline coordinates not scoured')
class ScourPolygonNegativeCoords(unittest.TestCase): class ScourPolygonNegativeCoords(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polygon-coord-neg.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0] p = scour.scourXmlFile('unittests/polygon-coord-neg.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0]
# points="100,-100,100-100,100-100-100,-100-100,200" /> # points="100,-100,100-100,100-100-100,-100-100,200" />
self.assertEquals(p.getAttribute('points'), '100,-100,100,-100,100,-100,-100,-100,-100,200', self.assertEquals(p.getAttribute('points'), '100 -100 100 -100 100 -100 -100 -100 -100 200',
'Negative polygon coordinates not properly parsed') 'Negative polygon coordinates not properly parsed')
class ScourPolylineNegativeCoords(unittest.TestCase): class ScourPolylineNegativeCoords(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polyline-coord-neg.svg').getElementsByTagNameNS(SVGNS, 'polyline')[0] p = scour.scourXmlFile('unittests/polyline-coord-neg.svg').getElementsByTagNameNS(SVGNS, 'polyline')[0]
self.assertEquals(p.getAttribute('points'), '100,-100,100,-100,100,-100,-100,-100,-100,200', self.assertEquals(p.getAttribute('points'), '100 -100 100 -100 100 -100 -100 -100 -100 200',
'Negative polyline coordinates not properly parsed') 'Negative polyline coordinates not properly parsed')
class DoNotRemoveGroupsWithIDsInDefs(unittest.TestCase): class DoNotRemoveGroupsWithIDsInDefs(unittest.TestCase):
@ -700,7 +703,7 @@ class DoNotRemoveGroupsWithIDsInDefs(unittest.TestCase):
class AlwaysKeepClosePathSegments(unittest.TestCase): class AlwaysKeepClosePathSegments(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/path-with-closepath.svg').getElementsByTagNameNS(SVGNS, 'path')[0] p = scour.scourXmlFile('unittests/path-with-closepath.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals(p.getAttribute('d'), 'M10,10h100v100h-100z', self.assertEquals(p.getAttribute('d'), 'm10 10h100v100h-100z',
'Path with closepath not preserved') 'Path with closepath not preserved')
class RemoveDuplicateLinearGradients(unittest.TestCase): class RemoveDuplicateLinearGradients(unittest.TestCase):
@ -736,7 +739,7 @@ class RereferenceForRadialGradient(unittest.TestCase):
class CollapseSamePathPoints(unittest.TestCase): class CollapseSamePathPoints(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/collapse-same-path-points.svg').getElementsByTagNameNS(SVGNS, 'path')[0]; p = scour.scourXmlFile('unittests/collapse-same-path-points.svg').getElementsByTagNameNS(SVGNS, 'path')[0];
self.assertEquals(p.getAttribute('d'), "M100,100l100.12,100.12z", self.assertEquals(p.getAttribute('d'), "m100 100l100.12 100.12z",
'Did not collapse same path points') 'Did not collapse same path points')
class ScourUnitlessLengths(unittest.TestCase): class ScourUnitlessLengths(unittest.TestCase):
@ -923,7 +926,7 @@ class PropagateCommonAttributesUp(unittest.TestCase):
class PathEllipticalArcParsingCommaWsp(unittest.TestCase): class PathEllipticalArcParsingCommaWsp(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/path-elliptical-arc-parsing.svg').getElementsByTagNameNS(SVGNS, 'path')[0] p = scour.scourXmlFile('unittests/path-elliptical-arc-parsing.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals( p.getAttribute('d'), 'M100,100a100,100,0,1,1,-50,100z', self.assertEquals( p.getAttribute('d'), 'm100 100a100 100 0 1 1 -50 100z',
'Did not parse elliptical arc command properly') 'Did not parse elliptical arc command properly')
class RemoveUnusedAttributesOnParent(unittest.TestCase): class RemoveUnusedAttributesOnParent(unittest.TestCase):
@ -1022,14 +1025,49 @@ class DoNotStripDoctype(unittest.TestCase):
class PathImplicitLineWithMoveCommands(unittest.TestCase): class PathImplicitLineWithMoveCommands(unittest.TestCase):
def runTest(self): def runTest(self):
path = scour.scourXmlFile('unittests/path-implicit-line.svg').getElementsByTagNameNS(SVGNS, 'path')[0] path = scour.scourXmlFile('unittests/path-implicit-line.svg').getElementsByTagNameNS(SVGNS, 'path')[0]
self.assertEquals( path.getAttribute('d'), "M100,100,100,200m200-100-200,0m200,100,0-100", self.assertEquals( path.getAttribute('d'), "m100 100v100m200-100h-200m200 100v-100",
"Implicit line segments after move not preserved") "Implicit line segments after move not preserved")
class RemoveMetadataOption(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/full-metadata.svg',
scour.parse_args(['--remove-metadata'])[0])
self.assertEquals(doc.childNodes.length, 1,
'Did not remove <metadata> tag with --remove-metadata')
class EnableCommentStrippingOption(unittest.TestCase):
def runTest(self):
docStr = file('unittests/comment-beside-xml-decl.svg').read()
docStr = scour.scourString(docStr,
scour.parse_args(['--enable-comment-stripping'])[0])
self.assertEquals(docStr.find('<!--'), -1,
'Did not remove document-level comment with --enable-comment-stripping')
class StripXmlPrologOption(unittest.TestCase):
def runTest(self):
docStr = file('unittests/comment-beside-xml-decl.svg').read()
docStr = scour.scourString(docStr,
scour.parse_args(['--strip-xml-prolog'])[0])
self.assertEquals(docStr.find('<?xml'), -1,
'Did not remove <?xml?> with --strip-xml-prolog')
class ShortenIDsOption(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/shorten-ids.svg',
scour.parse_args(['--shorten-ids'])[0])
gradientTag = doc.getElementsByTagName('linearGradient')[0]
self.assertEquals(gradientTag.getAttribute('id'), 'a',
"Did not shorten a linear gradient's ID with --shorten-ids")
rectTag = doc.getElementsByTagName('rect')[0]
self.assertEquals(rectTag.getAttribute('fill'), 'url(#a)',
'Did not update reference to shortened ID')
# TODO: write tests for --enable-viewboxing # TODO: write tests for --enable-viewboxing
# TODO; write a test for embedding rasters # TODO; write a test for embedding rasters
# TODO: write a test for --disable-embed-rasters # TODO: write a test for --disable-embed-rasters
# TODO: write tests for --keep-editor-data # TODO: write tests for --keep-editor-data
# TODO: write tests for --strip-xml-prolog # TODO: write tests for scouring transformations
if __name__ == '__main__': if __name__ == '__main__':
testcss = __import__('testcss') testcss = __import__('testcss')

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- Oh look a comment -->
<svg xmlns="http://www.w3.org/2000/svg">
</svg>

After

Width:  |  Height:  |  Size: 130 B

View file

@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
<rdf:RDF
xmlns:rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs = "http://www.w3.org/2000/01/rdf-schema#"
xmlns:dc = "http://purl.org/dc/elements/1.1/" >
<rdf:Description about="http://example.org/myfoo"
dc:title="MyFoo"
dc:description="Unit test for Scour's --remove-metadata option"
dc:publisher="No One"
dc:date="2010-06-09"
dc:format="image/svg+xml"
dc:language="en" >
<dc:creator>
<rdf:Bag>
<rdf:li>No One</rdf:li>
</rdf:Bag>
</dc:creator>
</rdf:Description>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 764 B

10
unittests/shorten-ids.svg Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<defs>
<linearGradient id="this-abomination-should-be-shortened-to-a-single-letter">
<stop offset="0" stop-color="black" />
<stop offset="1" stop-color="white" />
</linearGradient>
</defs>
<rect fill="url(#this-abomination-should-be-shortened-to-a-single-letter)" x="20" y="20" width="160" height="160" />
</svg>

After

Width:  |  Height:  |  Size: 447 B