#!/usr/bin/python # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """ Yaroslav Halchenko web: http://www.onerussian.com e-mail: yoh@onerussian.com DESCRIPTION (NOTES): See README.rst shipped with this tool. "Homepage" for the tool is http://github.com/yarikoptic/svgtune Copyright (C) 2009, Yaroslav Halchenko LICENSE: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. On Debian system see /usr/share/common-licenses/GPL for the full license. """ __author__ = 'Yaroslav Halchenko' __version__ = (0, 1, 0) __copyright__ = 'Copyright (c) 2009-2010 Yaroslav Halchenko' __license__ = 'GPL' import os, re from optparse import OptionParser def split2(l, msg, sep=' '): """Split l into 2 pieces using ' ' as a separator """ p = [x.strip() for x in (l.split(sep, 1)) if x != ''] if len(p) != 2: raise ValueError, msg + '. Got %s for "%s"' % (str(p), l) return p def replace_key(attr_value, key, value): """Replace value for a given key If key is not found, new one is added """ v = attr_value.split(';') changed = False newval = '%s:%s' % (key, value) for i in xrange(len(v)): if v[i].startswith(key + ':'): v[i] = newval changed = True if not changed: # Wasn't found -- add one v += [newval] return ';'.join([x for x in v if x.strip() != '']) def change_attr(el, attr, values): """Change values listed within attribute attr """ v = el.attrib.get(attr, '') changed = False for value in values.split(';'): k, newv = split2(value, "Each value must be in the form x:y", ":") v = replace_key(v, k, newv) if v == '': # there were no such yet v = "%s:%s" % (k, newv) #print "Changing %s : %s, got %s" % (attr, values, str(v)) el.attrib[attr] = v def verbose(level, str_): if level <= options.verbose: print " "*level, str_ def version(): print "svgtune v" + ".".join(__version__) raise SystemExit def process_options(params, options): # For now simply split by space option_values = params.split(' ') for option_value in option_values: t = option_value.split('=', 1) option = t[0] if len(t) > 1: value = t[1] else: value = None if option == "previews": # if only said "previews" assume that we want them if value is None: value = True options.previews = bool(value) else: raise ValueError, "Unknown option %s", option pass def load_svg(svgfile): verbose(1, "Processing file %s for tuning" % svgfile) svgdoc = etree.parse(svgfile) svg = svgdoc.getroot() dname = '%s_tuned' % os.path.splitext(svgfile)[0] try: os.mkdir(dname) except: pass # if directory exists or hell with everything return svg, svgdoc, dname parser = OptionParser(usage="%prog [options] inputfile.svgtune", version="%prog version " + ".".join([str(i) for i in __version__])) parser.add_option("-v", action="count", dest="verbose", help="Increase verbosity with multiple -v") parser.add_option("-p", "--previews", action="store_true", dest="previews", help="Store preview png's") (options, args) = parser.parse_args() if len(args) != 1: raise SystemExit, \ "Error: Please provide single input file with instructions." # Now we can load lxml from lxml import etree ifile = args[0] # ifile = '/tmp/1.svgi' svgfile = None svg = None dname = None for line_ in open(ifile).readlines(): line = line_.strip() if line.startswith('#') or line == '': continue # parse out first element cmd, params = split2(line, "Each line must be 'command parameters'") if cmd == '%file': svg, svgdoc, dname = load_svg(params) continue elif cmd == "%options": process_options(params, options) continue # We must have file loaded by now if svg is None: svgfile = os.path.splitext(ifile)[0] + '.svg' try: svg, svgdoc, dname = load_svg(svgfile) except Exception, e: raise RuntimeError, \ "Tried to load from %s but failed due to %s.\n" \ "Please provide %%file directive to load the file prior %s " \ "or have .svg file with the same name as .svgtune." \ % (svgfile, e, cmd) if cmd == '%save': ofile = '%s/%s.svg' % (dname, params) verbose(1, " Storing into %s" % ofile) file(ofile, 'w').write( etree.tostring(svgdoc, pretty_print=True)) if options.previews: verbose(3, "Generating preview") os.system('inkscape -z -f %s -e %s -d 90 >/dev/null 2>&1' % (ofile, ofile.replace('.svg', '_preview.png'))) continue # # Figure out the victims for changes -- victims victims = None if cmd == 'layers': changes = params victims = svg.findall('.//{%s}g[@{%s}groupmode="layer"]' % (svg.nsmap['svg'], svg.nsmap['inkscape'])) elif cmd in ['layer', 'g', 'text']: # parse out first element identifier, changes = split2(params, "For each layer or g you must list id or label + changes") # determine the victims sid = '' if cmd == 'layer': sid += '[@{%s}groupmode="layer"]' % svg.nsmap['inkscape'] id1, id2 = identifier.split('=') sid_re_attr = None # either we need to do re.search sid_re_str = id2 if id1 == 'label': sid += '[@{%s}label="%s"]' % (svg.nsmap['inkscape'], id2) elif id1 == 'id': sid += '[@id="%s"]' % (id2) elif id1 == 'href': sid += '[@{%s}href="%s"]' % (svg.nsmap['xlink'], id2) elif id1 == 'href:re': sid += '{%s}href' % svg.nsmap['xlink'] print svg.nsmap['xlink'] print sid elif id1 == 'label:re': sid_re_attr = '{%s}label' % svg.nsmap['inkscape'] elif id1 == 'id:re': sid_re_attr = 'id' else: raise ValueError, "Unknown identifier %s in %s" % (id1, line) victims = svg.findall('.//{%s}g%s' % (svg.nsmap['svg'], sid)) # optionally perform search using re if sid_re_attr is not None: regexp = re.compile(sid_re_str) victims = [v for v in victims if regexp.search(v.attrib[sid_re_attr])] nvictims = len(victims) if nvictims == 0: raise ValueError, "Cannot find any victim for '%s'" % identifier elif nvictims > 1 and sid_re_attr is None: raise ValueError, "We should get a single victim for %s " \ "but got %d of them" % (identifier, nvictims) else: raise ValueError, "Unknown command '%s'." % cmd # # Figure out the actual changes to take and do them for change in changes.split(' '): if change == '': continue attr, values = split2(change, 'Each change should be listed as attribute=value', '=') if attr == 'style': func = lambda vic: change_attr(vic, attr, values) else: raise ValueError, "Don't yet handle attribute %s" % attr for victim in victims: func(victim)