Hi,

----- Original Message -----
> From: "Peter Hutterer" <peter.hutte...@who-t.net>
> To: "Joaquim Rocha" <jro...@redhat.com>
> Cc: linuxwacom-devel@lists.sourceforge.net
> Sent: Wednesday, May 22, 2013 6:41:40 AM
> Subject: Re: [Linuxwacom-devel] Script to clean new SVG layouts
> 
> On Thu, May 16, 2013 at 09:29:07AM -0400, Joaquim Rocha wrote:
> > Producing a new SVG layout for the tablets can be a laborious task and
> > should be automated.
> > 
> > With that in mind I wrote a Python script to clean the SVG produced by
> > tools such as Inkscape and to automate certain things, like naming
> > elements of interest.
> > No other libs/modules are needs apart from Python itself.
> > 
> > The script if far from perfect and needs to be tested but it has been
> > already helpful to me.
> > I also updated the README in the data/layouts to show how to use it.
> > 
> > Hopefully you'll also find it useful and we'll include it in the next
> > release.
> 
> yep, very useful. thanks.
> 
> I noticed that the output format is different to the current layout (well,
> for the bamboo one I looked at anyway). it would make sense to run this over
> all the current layouts to get a unified description - any disagreement?

What do you mean different?

I don't think we should run it because the order of the buttons needs to be 
manually change as the script doesn't, of course, know the real order that it 
should be and it's often not in alphabetic order.

> 
> > From 903c811af2532452ad8a6aed2ca479f09fd52354 Mon Sep 17 00:00:00 2001
> > From: Joaquim Rocha <jro...@redhat.com>
> > Date: Thu, 16 May 2013 15:21:32 +0200
> > Subject: [PATCH] tools: Add clean_svg.py script
> > 
> > To help cleaning the SVG produced by editors such as Inkscape.
> > It also automates certain things like naming the elements.
> 
> please sign-off patches to libwacom (or xf86-input-wacom, for that matter)

Okay.

> 
> > ---
> >  data/layouts/README |  23 +++++
> >  tools/clean_svg.py  | 280
> >  ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 303 insertions(+)
> >  create mode 100755 tools/clean_svg.py
> > 
> > diff --git a/data/layouts/README b/data/layouts/README
> > index 9da2321..ae23343 100644
> > --- a/data/layouts/README
> > +++ b/data/layouts/README
> > @@ -227,3 +227,26 @@ Second touch-strip:
> >  
> >      id="LeaderStrip2Down"
> >      class="Strip2Down Strip2 Leader"
> > +
> > +* Tips For Creating New Layouts
> > +
> > +Layouts use very simple SVG rules. WISIWYG editors such as Inkscape are
> > +very convenient to design new layouts but usually produce a much more
> > +complex SVG markup so files that are produced with those editors should
> > +be cleaned. To help with this task, there is a script called clean_svg.py
> > +in the tools folder.
> > +Besides cleaning the markup and removing editor specific tags,
> > clean_svg.py
> > +also automates the naming of the elements.
> > +
> > +  * Automatic Naming with Inkscape and clean_svg.py
> > +
> > +  On Inkscape, be sure to group the button, leader and label elements
> > +  and assign the group's ID to the desired logical name. E.g.: Assigning
> > +  "A" to the group's ID and running the clean_svg.py script with that
> > +  SVG, will assign "ButtonA"/"B Button" to the ID and class of the first
> > +  rect/circle element found in the group; it also analogously assigns the
> > ID and class of the first path and text elements found in that group.
> 
> I've moved this onto two shorter lines.

Where did you move it?

> 
> > +
> > +clean_svg.py needs two arguments, the SVG file path and the name of
> > +the tablet, e.g.:
> > +
> > +  ./clean_svg.py /path/to/svg_file.svg "My Brand New Tablet Name"
> > diff --git a/tools/clean_svg.py b/tools/clean_svg.py
> > new file mode 100755
> > index 0000000..0ad72c6
> > --- /dev/null
> > +++ b/tools/clean_svg.py
> > @@ -0,0 +1,280 @@
> > +#! /usr/bin/env python
> > +#
> > +# Copyright (c) 2011 Red Hat, Inc.
> 
> is this supposed to be 2013?

It is.

> 
> > +#
> > +# 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.
> > +# Author: Joaquim Rocha <jro...@redhat.com>
> > +#
> > +
> > +import sys
> > +from argparse import ArgumentParser
> > +from xml.etree import ElementTree as ET
> > +
> > +NAMESPACE = "http://www.w3.org/2000/svg";
> > +BRACKETS_NAMESPACE = '{' + NAMESPACE + '}'
> > +
> > +# List with the attributes as they should be sorted
> > +ATTRS_ORDER = ['id', 'class', 'x', 'y', 'cx', 'cy', 'width', 'height']
> > +
> > +def human_round(number):
> > +    '''
> > +    Round to closest .5
> > +    '''
> > +    return round(number * 2) / 2.0
> > +
> > +def traverse_and_clean(node):
> > +    '''
> > +    Clean the tree recursively
> > +    '''
> > +    # Remove any non-SVG namespace attributes
> > +    for key in node.attrib.keys():
> > +        if key.startswith('{'):
> > +            del node.attrib[key]
> > +    if node.tag == BRACKETS_NAMESPACE + 'g' and node.attrib.has_key('id'):
> 
> node_tag_is?
> or better even a node_tag(foo) function that returns the non-namespaced tag
> so you can write
> 
>     if node_tag(node) == 'g':
> 
> which is more intuitive to read (imho)
> 
> or, if this is actually possible, run through the tree once and change every
> node.tag to a NS-stripped tag so you don't have to worry about it. this is a
> once-off script, we can afford it if it takes a bit longer.

I hadn't stripped it that way because of the way I was outputting it at the 
beginning would put the namespace back again.

> 
> > +        apply_id_and_class_from_group(node)
> > +        del node.attrib['id']
> > +    if node.attrib.has_key('style'):
> > +        if node.tag == BRACKETS_NAMESPACE + 'text':
> > +            node.attrib['style'] = 'text-anchor:start;'
> > +        elif node.tag != BRACKETS_NAMESPACE + 'svg':
> > +            del node.attrib['style']
> > +
> > +    remove_transform_if_exists(node)
> > +
> > +    round_attrib(node, 'd', 'x', 'y', 'rx', 'ry', 'width', 'height', 'cx',
> > 'cy', 'r')
> > +
> > +    nodes = node.getchildren()
> > +    for node in nodes:
> > +        traverse_and_clean(node)
> 
> this overrides the 'node' arg. no big deal, but could lead to confusing
> later.
> for n in node.getchildren(): would be better here

I don't think that's a problem but I can change it.

> 
> > +
> > +def round_attrib(node, *attrs):
> > +    for attr_name in attrs:
> > +        attr_value = node.attrib.get(attr_name)
> > +        if attr_value is None:
> > +            continue
> > +        if attr_name == 'd':
> > +            d = attr_value.replace(',', ' ')
> > +            values = [round_if_number(value) for value in d.split()]
> 
> couldn't you just use d.split(",") here?

Yes but you can separate values by ' ' or ',' so if you use split(','), you'll 
have to split each divided string by ' ' and join them with the list.
I prefer to make the split factor uniform.

> 
> > +            node.attrib[attr_name] = ' '.join(values)
> > +        else:
> > +            node.attrib[attr_name] = round_if_number(attr_value)
> 
>     if attr_name == 'd':
>         values = d.split(",")
>     else:
>         values = [] + [attr_value]
>     node.attrib(" ".join([round_if_number(value) for value in values]))
> 
> (ok, this is really just compressing things but was a good idea in my head :)

Right. I don't think we should change that.

> 
> > +
> > +def round_if_number(value):
> > +    try:
> > +        value = human_round(float(value.strip()))
> > +    except ValueError:
> > +        pass
> > +    return str(value)
> > +
> > +def remove_non_svg_nodes(root):
> > +    for elem in root.getchildren():
> > +        if not elem.tag.startswith(BRACKETS_NAMESPACE) or \
> > +           elem.tag == BRACKETS_NAMESPACE + 'metadata':
> 
> node_tag_is (or node_tag)

I have deleted the node_tag_is in the new patch and this might be the only 
place doing that concatenation, so, no big deal.

> 
> > +            root.remove(elem)
> > +        else:
> > +            remove_non_svg_nodes(elem)
> > +
> > +def remove_transform_if_exists(node):
> > +    TRANSLATE = 'translate'
> > +    MATRIX = 'matrix'
> > +
> > +    transform = node.attrib.get('transform')
> > +    if transform is None:
> > +        return
> > +    transform = transform.strip()
> > +
> > +    if transform.startswith(TRANSLATE):
> > +        values = transform[len(TRANSLATE) + 1:-1].split(',')
> > +        try:
> > +            x, y = float(values[0]), float(values[1])
> > +        except:
> > +            return
> > +
> > +        apply_translation(node, 1, 0, 0, 1, x, y)
> > +    elif transform.startswith(MATRIX):
> > +        values = transform[len(MATRIX) + 1:-1].split(',')
> > +        a, b, c, d, e, f = [float(value.strip()) for value in values]
> > +        apply_translation(node, a, b, c, d, e, f)
> > +        apply_scaling(node, a, d)
> > +    del node.attrib['transform']
> 
> you have a try/except for TRANSLATE but not for MATRIX. is this intentional?

No. Changed.

> 
> > +
> > +def apply_translation(node, a, b, c, d, e, f):
> > +    x_attr, y_attr = 'x', 'y'
> > +    if node_tag_is(node, 'circle'):
> > +        x_attr, y_attr = 'cx', 'cy'
> > +    elif node_tag_is(node, 'path'):
> > +        apply_translation_to_path(node, e, f)
> > +        return
> > +    try:
> > +        x, y = float(node.attrib[x_attr]), float(node.attrib[y_attr])
> > +        new_x = x * a + y * c + 1 * e
> > +        new_y = x * b + y * d + 1 * f
> > +        node.attrib[x_attr] = str(new_x)
> > +        node.attrib[y_attr] = str(new_y)
> > +    except:
> > +        pass
> > +
> > +def apply_translation_to_path(node, x, y):
> > +    d = node.attrib.get('d')
> > +    if d is None:
> > +        return
> > +    d = d.replace(',', ' ').split()
> 
> split(',')

See explanation above. I know one can split by whatever string but we need to 
make the split factor uniform.

> 
> > +    m_init_index = 0
> > +    length = len(d)
> > +    m_end_index = length
> > +    operation = 'M'
> > +    i = 0
> > +    while i < length:
> > +        value = d[i]
> > +        if value.lower() == 'm':
> > +            operation = value
> > +            m_init_index = i + 1
> > +        elif len(value) == 1 and value.isalpha():
> > +            m_end_index = i
> > +            break
> > +        i += 1
> > +    for i in range(m_init_index, m_end_index, 2):
> > +        d[i] = str(float(d[i]) + x)
> > +        d[i + 1] = str(float(d[i + 1]) + y)
> > +        if operation == 'm':
> > +            break
> > +    node.attrib['d'] = ' '.join(d[:m_init_index]) + ' ' + '
> > '.join(d[m_init_index:m_end_index]) + ' '.join(d[m_end_index:])
> 
> this could really do with a comment on at least what type of data this
> handles.
> looking at the first bamboo svg, data is e.g.
>     M 58 100 l 20 0
> and this function addds x/y to 58/100.
> except that it tries to cope with anything that's not M and 2 values anyway?
> 
> having said that, it would be more intuitive to split this up into multiple
> lists so you don't have to handle the init/end index.

I know it might look confusing if you don't know what the m/M are but that's 
just SVG related stuff.
In this we are just caring for the m/M operation, that's why I extract its 
values and, according to whether it is m/M I apply the translation.

> 
> > +
> > +
> > +def apply_scaling(node, x, y):
> > +    w_attr, h_attr = 'width', 'height'
> > +    if node_tag_is(node, 'circle'):
> > +        r = float(node.attrib.get('r', 1.0))
> > +        node.attrib['r'] = str(r * x)
> > +    try:
> > +        w = float(node.attrib[w_attr])
> > +        h = float(node.attrib[h_attr])
> > +        node.attrib[w_attr] = str(w * x)
> > +        node.attrib[h_attr] = str(h * y)
> > +    except:
> > +        pass
> > +
> > +def node_tag_is(node, tag):
> > +    return node.tag == BRACKETS_NAMESPACE + tag
> > +
> > +def to_string_rec(node, level=0):
> > +    i = '\n' + level * '  '
> 
> please don't use 'i' here, that's for loops only

I took that from an indent function from a the guy who wrote ElementTree.. but 
I can change it.

> 
> > +
> > +    tag_name = node.tag[len(BRACKETS_NAMESPACE):]
> > +
> > +    # Remove 'defs' element. This cannot be done in the traver_and_clean
> > +    # because somehow it is never found
> > +    if tag_name == 'defs':
> > +        return ''
> > +
> > +    # use a list to put id and class as the first arguments
> > +    attribs = []
> > +    for attr in ATTRS_ORDER:
> > +        attr_value = node.attrib.get(attr)
> > +        if attr_value is not None:
> > +            attribs.append(i + '   %s="%s"' % (attr, attr_value))
> > +            del node.attrib[attr]
> > +    attribs += [i + '   %s="%s"' % (key, value) for key, value in
> > node.attrib.items()]
> > +
> > +    string = i + '<' + tag_name + ''.join(attribs)
> > +    if len(node) or node.text:
> > +        string += '>'
> > +        if not node.text or not node.text.strip():
> > +            node.text = i + '  '
> > +        else:
> > +            string += node.text
> > +        if list(node):
> > +            for child in get_node_children_sorted(node):
> > +                string += to_string_rec(child, level+1)
> > +            string += i
> > +        string += '</%s>' % tag_name
> > +    else:
> > +        string += ' />'
> > +    return string
> > +
> > +def get_node_children_sorted(node):
> > +    '''
> > +    Returns a list with the children rect nodes,
> > +    the path nodes and any other nodes, in this order.
> > +    '''
> > +    other_nodes_index = 1
> > +    children = []
> > +    for child in node.getchildren():
> > +        if node_tag_is(child, 'rect') or node_tag_is(child, 'circle'):
> > +            children = [child] + children
> > +            other_nodes_index += 1
> > +        elif node_tag_is(child, 'path'):
> > +            children.insert(other_nodes_index, child)
> > +        else:
> > +            children.append(child)
> > +    return children
> 
> 
> try this (with adjustment to get the tag into the right namespaced tag)
> 
>     def custom_tag_sort(arg):
>         '''
>         Use as key functon in sorted().
> 
>         Pre-fix arg tag name by a number in the sort order we want. Anything
>         unspecified defaults to 9. i.e. circle → 1circle, thus sorts lower
>         than
>         other tags.
>         '''
>         tag_order = { 'circle' : 1,
>                       'rect' : 2,
>                       'path' : 3 }
>         return str(tag_order.get(arg.tag, 9)) + arg.tag
> 
>     def get_node_children_sorted(node):
>         return sorted(children, key=custom_tag_sort)
> 
> easier to maintain, I think. plus we're guaranteed that all other children
> will
> be sorted too, not sure if node.getchildren() returns a sorted list by
> default.

Okay. I don't think the attributes' one is that important but I've still 
changed it.


Check out the attached new patch,

--
Joaquim Rocha
http://www.joaquimrocha.com
From a0cf8b14529ccd89f32b3db32c852f3173069604 Mon Sep 17 00:00:00 2001
From: Joaquim Rocha <jro...@redhat.com>
Date: Thu, 16 May 2013 15:21:32 +0200
Subject: [PATCH] tools: Add clean_svg.py script

To help cleaning the SVG produced by editors such as Inkscape.
It also automates certain things like naming the elements.

Signed-off-by: Joaquim Rocha <jro...@redhat.com>
---
 data/layouts/README |  23 +++++
 tools/clean_svg.py  | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 314 insertions(+)
 create mode 100755 tools/clean_svg.py

diff --git a/data/layouts/README b/data/layouts/README
index 9da2321..ae23343 100644
--- a/data/layouts/README
+++ b/data/layouts/README
@@ -227,3 +227,26 @@ Second touch-strip:
 
     id="LeaderStrip2Down"
     class="Strip2Down Strip2 Leader"
+
+* Tips For Creating New Layouts
+
+Layouts use very simple SVG rules. WISIWYG editors such as Inkscape are
+very convenient to design new layouts but usually produce a much more
+complex SVG markup so files that are produced with those editors should
+be cleaned. To help with this task, there is a script called clean_svg.py
+in the tools folder.
+Besides cleaning the markup and removing editor specific tags, clean_svg.py
+also automates the naming of the elements.
+
+  * Automatic Naming with Inkscape and clean_svg.py
+
+  On Inkscape, be sure to group the button, leader and label elements
+  and assign the group's ID to the desired logical name. E.g.: Assigning
+  "A" to the group's ID and running the clean_svg.py script with that
+  SVG, will assign "ButtonA"/"B Button" to the ID and class of the first
+  rect/circle element found in the group; it also analogously assigns the ID and class of the first path and text elements found in that group.
+
+clean_svg.py needs two arguments, the SVG file path and the name of
+the tablet, e.g.:
+
+  ./clean_svg.py /path/to/svg_file.svg "My Brand New Tablet Name"
diff --git a/tools/clean_svg.py b/tools/clean_svg.py
new file mode 100755
index 0000000..b304ad4
--- /dev/null
+++ b/tools/clean_svg.py
@@ -0,0 +1,291 @@
+#! /usr/bin/env python
+#
+# Copyright (c) 2013 Red Hat, Inc.
+#
+# 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.
+# Author: Joaquim Rocha <jro...@redhat.com>
+#
+
+import sys
+from argparse import ArgumentParser
+from xml.etree import ElementTree as ET
+
+NAMESPACE = "http://www.w3.org/2000/svg";
+BRACKETS_NAMESPACE = '{' + NAMESPACE + '}'
+
+def human_round(number):
+    '''
+    Round to closest .5
+    '''
+    return round(number * 2) / 2.0
+
+def traverse_and_clean(node):
+    '''
+    Clean the tree recursively
+    '''
+    # Remove any non-SVG namespace attributes
+    for key in node.attrib.keys():
+        if key.startswith('{'):
+            del node.attrib[key]
+    if node.tag == 'g' and node.attrib.has_key('id'):
+        apply_id_and_class_from_group(node)
+        del node.attrib['id']
+    if node.attrib.has_key('style'):
+        if node.tag == 'text':
+            node.attrib['style'] = 'text-anchor:start;'
+        elif node.tag != 'svg':
+            del node.attrib['style']
+
+    remove_transform_if_exists(node)
+
+    round_attrib(node, 'd', 'x', 'y', 'rx', 'ry', 'width', 'height', 'cx', 'cy', 'r')
+
+    for child in node.getchildren():
+        traverse_and_clean(child)
+
+def round_attrib(node, *attrs):
+    for attr_name in attrs:
+        attr_value = node.attrib.get(attr_name)
+        if attr_value is None:
+            continue
+        if attr_name == 'd':
+            d = attr_value.replace(',', ' ')
+            values = [round_if_number(value) for value in d.split()]
+            node.attrib[attr_name] = ' '.join(values)
+        else:
+            node.attrib[attr_name] = round_if_number(attr_value)
+
+def round_if_number(value):
+    try:
+        value = human_round(float(value.strip()))
+    except ValueError:
+        pass
+    return str(value)
+
+def remove_non_svg_nodes_and_strip_namespace(root):
+    if root.tag.startswith(BRACKETS_NAMESPACE):
+        root.tag = root.tag[len(BRACKETS_NAMESPACE):]
+    for elem in root.getchildren():
+        if not elem.tag.startswith(BRACKETS_NAMESPACE) or \
+           elem.tag == BRACKETS_NAMESPACE + 'metadata':
+            root.remove(elem)
+        else:
+            remove_non_svg_nodes_and_strip_namespace(elem)
+
+def remove_transform_if_exists(node):
+    TRANSLATE = 'translate'
+    MATRIX = 'matrix'
+
+    transform = node.attrib.get('transform')
+    if transform is None:
+        return
+    transform = transform.strip()
+
+    if transform.startswith(TRANSLATE):
+        values = transform[len(TRANSLATE) + 1:-1].split(',')
+        try:
+            x, y = float(values[0]), float(values[1])
+        except:
+            return
+
+        apply_translation(node, 1, 0, 0, 1, x, y)
+    elif transform.startswith(MATRIX):
+        values = transform[len(MATRIX) + 1:-1].split(',')
+        try:
+            a, b, c, d, e, f = [float(value.strip()) for value in values]
+        except:
+            return
+        apply_translation(node, a, b, c, d, e, f)
+        apply_scaling(node, a, d)
+    del node.attrib['transform']
+
+def apply_translation(node, a, b, c, d, e, f):
+    x_attr, y_attr = 'x', 'y'
+    if node.tag == 'circle':
+        x_attr, y_attr = 'cx', 'cy'
+    elif node.tag == 'path':
+        apply_translation_to_path(node, e, f)
+        return
+    try:
+        x, y = float(node.attrib[x_attr]), float(node.attrib[y_attr])
+        new_x = x * a + y * c + 1 * e
+        new_y = x * b + y * d + 1 * f
+        node.attrib[x_attr] = str(new_x)
+        node.attrib[y_attr] = str(new_y)
+    except:
+        pass
+
+def apply_translation_to_path(node, x, y):
+    d = node.attrib.get('d')
+    if d is None:
+        return
+    d = d.replace(',', ' ').split()
+    m_init_index = 0
+    length = len(d)
+    m_end_index = length
+    operation = 'M'
+    i = 0
+    while i < length:
+        value = d[i]
+        if value.lower() == 'm':
+            operation = value
+            m_init_index = i + 1
+        elif len(value) == 1 and value.isalpha():
+            m_end_index = i
+            break
+        i += 1
+    for i in range(m_init_index, m_end_index, 2):
+        d[i] = str(float(d[i]) + x)
+        d[i + 1] = str(float(d[i + 1]) + y)
+        if operation == 'm':
+            break
+    node.attrib['d'] = ' '.join(d[:m_init_index]) + ' ' + ' '.join(d[m_init_index:m_end_index]) + ' '.join(d[m_end_index:])
+
+
+def apply_scaling(node, x, y):
+    w_attr, h_attr = 'width', 'height'
+    if node == 'circle':
+        r = float(node.attrib.get('r', 1.0))
+        node.attrib['r'] = str(r * x)
+    try:
+        w = float(node.attrib[w_attr])
+        h = float(node.attrib[h_attr])
+        node.attrib[w_attr] = str(w * x)
+        node.attrib[h_attr] = str(h * y)
+    except:
+        pass
+
+def to_string_rec(node, level=0):
+    indent = '\n' + level * '  '
+
+    tag_name = node.tag
+
+    # Remove 'defs' element. This cannot be done in the traverse_and_clean
+    # because somehow it is never found
+    if tag_name == 'defs':
+        return ''
+
+    # use a list to put id and class as the first arguments
+    attribs = []
+    for attr in get_node_attrs_sorted(node):
+        attr_value = node.attrib.get(attr)
+        if attr_value is not None:
+            attribs.append(indent + '   %s="%s"' % (attr, attr_value))
+
+    string = indent + '<' + tag_name + ''.join(attribs)
+    if len(node) or node.text:
+        string += '>'
+        if not node.text or not node.text.strip():
+            node.text = indent + '  '
+        else:
+            string += node.text
+        if list(node):
+            for child in get_node_children_sorted(node):
+                string += to_string_rec(child, level+1)
+            string += indent
+        string += '</%s>' % tag_name
+    else:
+        string += ' />'
+    return string
+
+def custom_tag_sort(arg):
+    '''
+    Use as key functon in sorted().
+
+    Pre-fix arg tag name by a number in the sort order we want. Anything
+    unspecified defaults to 9. i.e. circle -> 1circle, thus sorts lower
+    than
+    other tags.
+    '''
+    tag_order = {'title': 0,
+                 'rect': 1,
+                 'circle': 2,
+                 'path': 3 }
+    return str(tag_order.get(arg.tag, 9)) + arg.tag
+
+def get_node_children_sorted(node):
+    children = node.getchildren()
+    return sorted(children, key=custom_tag_sort)
+
+def custom_attr_sort(arg):
+    '''
+    Same as custom_tag_sort but for a node's attributes
+    '''
+    attr_order = {'id': 0, 'class': 1,
+                  'x': 2, 'y': 3, 'cx': 4, 'cy': 5,
+                  'width': 6, 'height': 7}
+    return str(attr_order.get(arg, 9)) + arg
+
+def get_node_attrs_sorted(node):
+    attrs = node.attrib.keys()
+    return sorted(attrs, key=custom_attr_sort)
+
+def apply_id_and_class_from_group(group_node):
+    button_assigned = label_assigned = path_assigned = False
+    _id = group_node.attrib.get('id')
+    if _id is None:
+        return
+    for child in group_node.getchildren():
+        if child == 'rect' or child == 'circle':
+            if button_assigned:
+                continue
+            child.attrib['id'] = 'Button%s' % _id
+            child.attrib['class'] = '%s Button' % _id
+            button_assigned = True
+        elif child == 'path':
+            if path_assigned:
+                continue
+            child.attrib['id'] = 'Leader%s' % _id
+            child.attrib['class'] = '%s Leader' % _id
+            path_assigned = True
+        elif child == 'text':
+            if label_assigned:
+                continue
+            child.attrib['id'] = 'Label%s' % _id
+            child.attrib['class'] = '%s Label' % _id
+            child.text = _id
+            label_assigned = True
+
+def to_string(root):
+    header = '''<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+   "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>'''
+    return header + to_string_rec(root)
+
+def clean_svg(root, tabletname):
+    remove_non_svg_nodes_and_strip_namespace(root)
+    title = root.find('title')
+    if title is not None:
+        title.text = tabletname
+    root.attrib['xmlns'] = 'http://www.w3.org/2000/svg'
+    traverse_and_clean(root)
+
+if __name__ == '__main__':
+    parser = ArgumentParser(description='Clean SVG files for libwacom')
+    parser.add_argument('filename', nargs=1, type=str,
+                        help='SVG file to clean', metavar='FILE')
+    parser.add_argument('tabletname', nargs=1, type=str,
+                        help='The name of the tablet', metavar='TABLET_NAME')
+    args = parser.parse_args()
+
+    ET.register_namespace('', NAMESPACE)
+    try:
+        tree = ET.parse(args.filename[0])
+    except Exception, e:
+        sys.stderr.write(str(e) + '\n')
+        exit(1)
+    root = tree.getroot()
+    clean_svg(root, args.tabletname[0])
+    print to_string(root)
-- 
1.8.1.4

------------------------------------------------------------------------------
Try New Relic Now & We'll Send You this Cool Shirt
New Relic is the only SaaS-based application performance monitoring service 
that delivers powerful full stack analytics. Optimize and monitor your
browser, app, & servers with just a few lines of code. Try New Relic
and get this awesome Nerd Life shirt! http://p.sf.net/sfu/newrelic_d2d_may
_______________________________________________
Linuxwacom-devel mailing list
Linuxwacom-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxwacom-devel

Reply via email to