#!/usr/bin/env python # # Copyright (C) 2014 Martin Owens # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # """ Merges styles into class based styles and removes. """ import inkex import sys from collections import defaultdict class Style(dict): """Controls the style/css mechanics for this effect""" def __init__(self, attr=None): self.weights = defaultdict(int) self.total = [] if attr: self.parse(attr) def parse(self, attr): for name,value in [ a.split(':',1) for a in attr.split(';') if ':' in a ]: self[name.strip()] = value.strip() def entries(self): return [ "%s:%s;" % (n,v) for (n,v) in self.iteritems() ] def to_str(self, sep="\n "): return " " + "\n ".join(self.entries()) def __str__(self): return self.to_str() def css(self, cls): return ".%s {\n%s\n}" % (cls, str(self)) def remove(self, keys): for key in keys: self.pop(key, None) def add(self, c, el): self.total.append( (c, el) ) for name,value in c.iteritems(): if not self.has_key(name): self[name] = value if self[name] == value: self.weights[name] += 1 def clean(self, threshold): """Removes any elements that aren't the same using a weighted threshold""" for attr in self.keys(): if self.weights[attr] < len(self.total) - threshold: self.pop(attr) def all_matches(self): """Returns an iter for each added element who's style matches this style""" for (c, el) in self.total: if self == c: yield (c, el) def __eq__(self, o): """Not equals, prefer to overload 'in' but that doesn't seem possible""" for arg in self.keys(): if o.has_key(arg) and self[arg] != o[arg]: return False return True def get_styles(document): nodes = [] for node in document.getroot().iterchildren(): if node.tag == inkex.addNS('style', 'svg'): return node nodes.append(node) ret = inkex.etree.SubElement(document.getroot(), 'style', {}) # Reorder to make the style element FIRST for node in nodes: document.getroot().append(node) return ret class MergeStyles(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option("-n", "--name", action="store", type="string", dest="name", default='', help="Name of selected element's common class") def effect(self): newclass = self.options.name elements = self.selected.values() common = Style() threshold = 1 for el in elements: common.add(Style(el.attrib['style']), el) common.clean(threshold) if not common: raise KeyError("There are no common styles between these elements.") styles = get_styles(self.document) styles.text = (styles.text or "") + "\n" + common.css( newclass ) for (st, el) in common.all_matches(): st.remove(common.keys()) el.attrib['style'] = st.to_str("") olds = el.attrib.has_key('class') and el.attrib['class'].split() or [] if newclass not in olds: olds.append(newclass) el.attrib['class'] = ' '.join(olds) if __name__ == '__main__': e = MergeStyles() e.affect()