#!/usr/bin/env python
import shutil
from datetime import date
import os
from xml.etree import ElementTree as et
from xml.dom import minidom
from subprocess import call

symbol_dirs = ['/usr/share/X11/xkb/symbols', '/etc/X11/xkb/symbols']
rules_dir = '/usr/share/X11/xkb/rules'


class XMLCombiner(object):
    def __init__(self, filenames):
        assert len(filenames) > 0, 'No filenames!'
        # save all the roots, in order, to be processed later
        self.roots = [et.parse(f).getroot() for f in filenames]

    def combine(self):
        for r in self.roots[1:]:
            # combine each element with the first one, and update that
            self.combine_element(self.roots[0], r)
        # return the string representation
        return et.tostring(self.roots[0],'utf-8', method="xml")

    def combine_element(self, one, other):
        """
        This function recursively updates either the text or the children
        of an element if another element is found in `one`, or adds it
        from `other` if not found.
        """
        # Create a mapping from tag name to element, as that's what we are fltering with
        mapping = {el.tag: el for el in one}
        for el in other:
            if len(el) == 0:
                # Not nested
                try:
                    # Update the text
                    mapping[el.tag].text = el.text
                except KeyError:
                    # An element with this name is not in the mapping
                    mapping[el.tag] = el
                    # Add it
                    one.append(el)
            else:
                try:
                    # Recursively process the element, and update it in the same way
                    self.combine_element(mapping[el.tag], el)
                except KeyError:
                    # Not in the mapping
                    mapping[el.tag] = el
                    # Just add it
                    one.append(el)


def add_layout():
    for symbol_dir in symbol_dirs:
        if os.path.exists(symbol_dir):
            shutil.copyfile('gelatin_ansi-iso.xkb', os.path.join(symbol_dir, 'gelatin'))
            shutil.copyfile(os.path.join(rules_dir, 'evdev.xml'), 'evdev.xml.backup' + date.today().isoformat())
            evdev = XMLCombiner((os.path.join(rules_dir, 'evdev.xml'), 'gelatin_evdev_section.xml')).combine()
            with open(os.path.join(rules_dir, 'evdev2.xml'), 'w') as output:
                output.write(evdev)


def switch_layout():
    if call(['setxkbmap', '-v', 'us', '-variant', 'gelatin']):
        print 'Successfully set keyboard to Gelatin'
    else:
        print 'Failed to set keyboard to Gelatin'

if __name__ == "__main__":
    add_layout()
    switch_layout()

@@ -109,36 +109,8 @@
  et%0A
-from xml.dom import minidom%0A
 from
@@ -248,2093 +248,775 @@
 '%0A%0A%0A
-class XMLCombiner(object):%0A    def __init__(self, filenames
+def add_layout(
 ):%0A    
- 
-   assert len(filenames) %3E 0, 'No filenames!'%0A        # save all the roots, in order, to be processed later%0A        self.roots = %5Bet.parse(f).getroot() for f in filenames%5D%0A%0A    def combine(self):%0A        for r in self.roots%5B1:%5D:%0A            # combine each element with the first one, and update that%0A            self.combine_element(self.roots%5B0%5D, r)%0A        # return the string representation%0A        return et.tostring(self.roots%5B0%5D,'utf-8', method=%22xml%22)%0A%0A    def combine_element(self, one, other):%0A        %22%22%22%0A        This function recursively updates either the text or the children%0A        of an element if another element is found in %60one%60, or adds it%0A        from %60other%60 if not found.%0A        %22%22%22%0A        # Create a mapping from tag name to element, as that's what we are fltering with%0A        mapping = %7Bel.tag: el for el in one%7D%0A        for el in other:%0A            if len(el) == 0:%0A                # Not nested%0A                try:%0A                    # Update the text%0A                    mapping%5Bel.tag%5D.text = el.text%0A                except KeyError:%0A                    # An element with this name is not in the mapping%0A                    mapping%5Bel.tag%5D = el%0A                    # Add it%0A                    one.append(el)%0A            else:%0A                try:%0A                    # Recursively process the element, and update it in the same way%0A                    self.combine_element(mapping%5Bel.tag%5D, el)%0A                except KeyError:%0A                    # Not in the mapping%0A                    mapping%5Bel.tag%5D = el%0A                    # Just add it%0A                    one.append(el)%0A%0A%0Adef add_layout():%0A    for symbol_dir in symbol_dirs:%0A        if os.path.exists(symbol_dir):%0A            shutil.copyfile('gelatin_ansi-iso.xkb', os.path.join(symbol_dir, 'gelatin'))%0A            shutil.copyfile(os.path.join(rules_dir, 'evdev.xml'), 'evdev.xml.backup' + date.today().isoformat())%0A            evdev = XMLCombiner((os.path.join(rules_dir, 'evdev.xml'), 'gelatin_evdev_section.xml')).combine()%0A
+for symbol_dir in symbol_dirs:%0A        if os.path.exists(symbol_dir):%0A            shutil.copyfile('gelatin_ansi-iso.xkb', os.path.join(symbol_dir, 'gelatin'))%0A            shutil.copyfile(os.path.join(rules_dir, 'evdev.xml'), 'evdev.xml.backup' + date.today().isoformat())%0A            first = None%0A            for filename in %5Bos.path.join(rules_dir, 'evdev.xml'), 'gelatin_evdev_section.xml'%5D:%0A                data = et.parse(filename).getroot()%0A                if first is None:%0A                    first = data%0A                    first_layout = first.find('layoutList')%0A                else:%0A                    layout_list = first.find('layoutList')%0A                    first_layout.extend(layout_list)%0A%0A            if first is not None:%0A   
     
@@ -1023,16 +1023,17 @@
         
+ 
 with ope
@@ -1077,24 +1077,23 @@
 '), 
-'w'
+%22w%22
 ) as 
-output:%0A
+f:%0A    
     
@@ -1108,22 +1108,17 @@
     
-output
+f
 .write(e
 vdev
@@ -1113,20 +1113,51 @@
 .write(e
-vdev
+t.tostring(first, encoding='utf-8')
 )%0A%0A%0Adef 

Fix to always add to existing layouts