diff options
Diffstat (limited to 'xdg/MenuEditor.py')
-rw-r--r-- | xdg/MenuEditor.py | 256 |
1 files changed, 144 insertions, 112 deletions
diff --git a/xdg/MenuEditor.py b/xdg/MenuEditor.py index cc5ce54..0324f40 100644 --- a/xdg/MenuEditor.py +++ b/xdg/MenuEditor.py @@ -1,15 +1,17 @@ """ CLass to edit XDG Menus """ +import os +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree +#FIXME avoid importing all from all modules from xdg.Menu import * from xdg.BaseDirectory import * from xdg.Exceptions import * from xdg.DesktopEntry import * from xdg.Config import * -import xml.dom.minidom -import os -import re - # XML-Cleanups: Move / Exclude # FIXME: proper reverte/delete # FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions @@ -20,28 +22,31 @@ import re # FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile # Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs -class MenuEditor: + +class MenuEditor(object): + def __init__(self, menu=None, filename=None, root=False): self.menu = None self.filename = None - self.doc = None + self.tree = None + self.parser = Parser() self.parse(menu, filename, root) # fix for creating two menus with the same name on the fly self.filenames = [] def parse(self, menu=None, filename=None, root=False): - if root == True: + if root: setRootMode(True) if isinstance(menu, Menu): self.menu = menu elif menu: - self.menu = parse(menu) + self.menu = self.parser.parse(menu) else: - self.menu = parse() + self.menu = self.parser.parse() - if root == True: + if root: self.filename = self.menu.Filename elif filename: self.filename = filename @@ -49,13 +54,21 @@ class MenuEditor: self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1]) try: - self.doc = xml.dom.minidom.parse(self.filename) + self.tree = etree.parse(self.filename) except IOError: - self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>') - except xml.parsers.expat.ExpatError: + root = etree.fromtring(""" +<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"> + <Menu> + <Name>Applications</Name> + <MergeFile type="parent">%s</MergeFile> + </Menu> +""" % self.menu.Filename) + self.tree = etree.ElementTree(root) + except ParseError: raise ParsingError('Not a valid .menu file', self.filename) - self.__remove_whilespace_nodes(self.doc) + #FIXME: is this needed with etree ? + self.__remove_whitespace_nodes(self.tree) def save(self): self.__saveEntries(self.menu) @@ -67,7 +80,7 @@ class MenuEditor: self.__addEntry(parent, menuentry, after, before) - sort(self.menu) + self.menu.sort() return menuentry @@ -83,7 +96,7 @@ class MenuEditor: self.__addEntry(parent, menu, after, before) - sort(self.menu) + self.menu.sort() return menu @@ -92,7 +105,7 @@ class MenuEditor: self.__addEntry(parent, separator, after, before) - sort(self.menu) + self.menu.sort() return separator @@ -100,7 +113,7 @@ class MenuEditor: self.__deleteEntry(oldparent, menuentry, after, before) self.__addEntry(newparent, menuentry, after, before) - sort(self.menu) + self.menu.sort() return menuentry @@ -112,7 +125,7 @@ class MenuEditor: if oldparent.getPath(True) != newparent.getPath(True): self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name)) - sort(self.menu) + self.menu.sort() return menu @@ -120,14 +133,14 @@ class MenuEditor: self.__deleteEntry(parent, separator, after, before) self.__addEntry(parent, separator, after, before) - sort(self.menu) + self.menu.sort() return separator def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): self.__addEntry(newparent, menuentry, after, before) - sort(self.menu) + self.menu.sort() return menuentry @@ -137,39 +150,39 @@ class MenuEditor: if name: if not deskentry.hasKey("Name"): deskentry.set("Name", name) - deskentry.set("Name", name, locale = True) + deskentry.set("Name", name, locale=True) if comment: if not deskentry.hasKey("Comment"): deskentry.set("Comment", comment) - deskentry.set("Comment", comment, locale = True) + deskentry.set("Comment", comment, locale=True) if genericname: - if not deskentry.hasKey("GnericNe"): + if not deskentry.hasKey("GenericName"): deskentry.set("GenericName", genericname) - deskentry.set("GenericName", genericname, locale = True) + deskentry.set("GenericName", genericname, locale=True) if command: deskentry.set("Exec", command) if icon: deskentry.set("Icon", icon) - if terminal == True: + if terminal: deskentry.set("Terminal", "true") - elif terminal == False: + elif not terminal: deskentry.set("Terminal", "false") - if nodisplay == True: + if nodisplay is True: deskentry.set("NoDisplay", "true") - elif nodisplay == False: + elif nodisplay is False: deskentry.set("NoDisplay", "false") - if hidden == True: + if hidden is True: deskentry.set("Hidden", "true") - elif hidden == False: + elif hidden is False: deskentry.set("Hidden", "false") menuentry.updateAttributes() if len(menuentry.Parents) > 0: - sort(self.menu) + self.menu.sort() return menuentry @@ -195,56 +208,58 @@ class MenuEditor: if name: if not deskentry.hasKey("Name"): deskentry.set("Name", name) - deskentry.set("Name", name, locale = True) + deskentry.set("Name", name, locale=True) if genericname: if not deskentry.hasKey("GenericName"): deskentry.set("GenericName", genericname) - deskentry.set("GenericName", genericname, locale = True) + deskentry.set("GenericName", genericname, locale=True) if comment: if not deskentry.hasKey("Comment"): deskentry.set("Comment", comment) - deskentry.set("Comment", comment, locale = True) + deskentry.set("Comment", comment, locale=True) if icon: deskentry.set("Icon", icon) - if nodisplay == True: + if nodisplay is True: deskentry.set("NoDisplay", "true") - elif nodisplay == False: + elif nodisplay is False: deskentry.set("NoDisplay", "false") - if hidden == True: + if hidden is True: deskentry.set("Hidden", "true") - elif hidden == False: + elif hidden is False: deskentry.set("Hidden", "false") menu.Directory.updateAttributes() if isinstance(menu.Parent, Menu): - sort(self.menu) + self.menu.sort() return menu def hideMenuEntry(self, menuentry): - self.editMenuEntry(menuentry, nodisplay = True) + self.editMenuEntry(menuentry, nodisplay=True) def unhideMenuEntry(self, menuentry): - self.editMenuEntry(menuentry, nodisplay = False, hidden = False) + self.editMenuEntry(menuentry, nodisplay=False, hidden=False) def hideMenu(self, menu): - self.editMenu(menu, nodisplay = True) + self.editMenu(menu, nodisplay=True) def unhideMenu(self, menu): - self.editMenu(menu, nodisplay = False, hidden = False) - xml_menu = self.__getXmlMenu(menu.getPath(True,True), False) - for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu): - node.parentNode.removeChild(node) + self.editMenu(menu, nodisplay=False, hidden=False) + xml_menu = self.__getXmlMenu(menu.getPath(True, True), False) + deleted = xml_menu.findall('Deleted') + not_deleted = xml_menu.findall('NotDeleted') + for node in deleted + not_deleted: + xml_menu.remove(node) def deleteMenuEntry(self, menuentry): if self.getAction(menuentry) == "delete": self.__deleteFile(menuentry.DesktopEntry.filename) for parent in menuentry.Parents: self.__deleteEntry(parent, menuentry) - sort(self.menu) + self.menu.sort() return menuentry def revertMenuEntry(self, menuentry): @@ -257,7 +272,7 @@ class MenuEditor: index = parent.MenuEntries.index(menuentry) parent.MenuEntries[index] = menuentry.Original menuentry.Original.Parents.append(parent) - sort(self.menu) + self.menu.sort() return menuentry def deleteMenu(self, menu): @@ -265,21 +280,22 @@ class MenuEditor: self.__deleteFile(menu.Directory.DesktopEntry.filename) self.__deleteEntry(menu.Parent, menu) xml_menu = self.__getXmlMenu(menu.getPath(True, True)) - xml_menu.parentNode.removeChild(xml_menu) - sort(self.menu) + parent = self.__get_parent_node(xml_menu) + parent.remove(xml_menu) + self.menu.sort() return menu def revertMenu(self, menu): if self.getAction(menu) == "revert": self.__deleteFile(menu.Directory.DesktopEntry.filename) menu.Directory = menu.Directory.Original - sort(self.menu) + self.menu.sort() return menu def deleteSeparator(self, separator): self.__deleteEntry(separator.Parent, separator, after=True) - sort(self.menu) + self.menu.sort() return separator @@ -290,8 +306,9 @@ class MenuEditor: return "none" elif entry.Directory.getType() == "Both": return "revert" - elif entry.Directory.getType() == "User" \ - and (len(entry.Submenus) + len(entry.MenuEntries)) == 0: + elif entry.Directory.getType() == "User" and ( + len(entry.Submenus) + len(entry.MenuEntries) + ) == 0: return "delete" elif isinstance(entry, MenuEntry): @@ -318,9 +335,7 @@ class MenuEditor: def __saveMenu(self): if not os.path.isdir(os.path.dirname(self.filename)): os.makedirs(os.path.dirname(self.filename)) - fd = open(self.filename, 'w') - fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', ''))) - fd.close() + self.tree.write(self.filename, encoding='utf-8') def __getFileName(self, name, extension): postfix = 0 @@ -333,8 +348,9 @@ class MenuEditor: dir = "applications" elif extension == ".directory": dir = "desktop-directories" - if not filename in self.filenames and not \ - os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)): + if not filename in self.filenames and not os.path.isfile( + os.path.join(xdg_data_dirs[0], dir, filename) + ): self.filenames.append(filename) break else: @@ -343,8 +359,11 @@ class MenuEditor: return filename def __getXmlMenu(self, path, create=True, element=None): + # FIXME: we should also return the menu's parent, + # to avoid looking for it later on + # @see Element.getiterator() if not element: - element = self.doc + element = self.tree if "/" in path: (name, path) = path.split("/", 1) @@ -353,17 +372,16 @@ class MenuEditor: path = "" found = None - for node in self.__getXmlNodesByName("Menu", element): - for child in self.__getXmlNodesByName("Name", node): - if child.childNodes[0].nodeValue == name: - if path: - found = self.__getXmlMenu(path, create, node) - else: - found = node - break + for node in element.findall("Menu"): + name_node = node.find('Name') + if name_node.text == name: + if path: + found = self.__getXmlMenu(path, create, node) + else: + found = node if found: break - if not found and create == True: + if not found and create: node = self.__addXmlMenuElement(element, name) if path: found = self.__getXmlMenu(path, create, node) @@ -373,58 +391,62 @@ class MenuEditor: return found def __addXmlMenuElement(self, element, name): - node = self.doc.createElement('Menu') - self.__addXmlTextElement(node, 'Name', name) - return element.appendChild(node) + menu_node = etree.SubElement('Menu', element) + name_node = etree.SubElement('Name', menu_node) + name_node.text = name + return menu_node def __addXmlTextElement(self, element, name, text): - node = self.doc.createElement(name) - text = self.doc.createTextNode(text) - node.appendChild(text) - return element.appendChild(node) + node = etree.SubElement(name, element) + node.text = text + return node - def __addXmlFilename(self, element, filename, type = "Include"): + def __addXmlFilename(self, element, filename, type_="Include"): # remove old filenames - for node in self.__getXmlNodesByName(["Include", "Exclude"], element): - if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename: - element.removeChild(node) + includes = element.findall('Include') + excludes = element.findall('Exclude') + rules = includes + excludes + for rule in rules: + #FIXME: this finds only Rules whose FIRST child is a Filename element + if rule[0].tag == "Filename" and rule[0].text == filename: + element.remove(rule) + # shouldn't it remove all occurences, like the following: + #filename_nodes = rule.findall('.//Filename'): + #for fn in filename_nodes: + #if fn.text == filename: + ##element.remove(rule) + #parent = self.__get_parent_node(fn) + #parent.remove(fn) # add new filename - node = self.doc.createElement(type) - node.appendChild(self.__addXmlTextElement(node, 'Filename', filename)) - return element.appendChild(node) + node = etree.SubElement(type_, element) + self.__addXmlTextElement(node, 'Filename', filename) + return node def __addXmlMove(self, element, old, new): - node = self.doc.createElement("Move") - node.appendChild(self.__addXmlTextElement(node, 'Old', old)) - node.appendChild(self.__addXmlTextElement(node, 'New', new)) - return element.appendChild(node) + node = etree.SubElement("Move", element) + self.__addXmlTextElement(node, 'Old', old) + self.__addXmlTextElement(node, 'New', new) + return node def __addXmlLayout(self, element, layout): # remove old layout - for node in self.__getXmlNodesByName("Layout", element): - element.removeChild(node) + for node in element.findall("Layout"): + element.remove(node) # add new layout - node = self.doc.createElement("Layout") + node = etree.SubElement("Layout", element) for order in layout.order: if order[0] == "Separator": - child = self.doc.createElement("Separator") - node.appendChild(child) + child = etree.SubElement("Separator", node) elif order[0] == "Filename": child = self.__addXmlTextElement(node, "Filename", order[1]) elif order[0] == "Menuname": child = self.__addXmlTextElement(node, "Menuname", order[1]) elif order[0] == "Merge": - child = self.doc.createElement("Merge") - child.setAttribute("type", order[1]) - node.appendChild(child) - return element.appendChild(node) - - def __getXmlNodesByName(self, name, element): - for child in element.childNodes: - if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name: - yield child + child = etree.SubElement("Merge", node) + child.attrib["type"] = order[1] + return node def __addLayout(self, parent): layout = Layout() @@ -498,14 +520,24 @@ class MenuEditor: except ValueError: pass - def __remove_whilespace_nodes(self, node): - remove_list = [] - for child in node.childNodes: - if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: - child.data = child.data.strip() - if not child.data.strip(): - remove_list.append(child) - elif child.hasChildNodes(): + def __remove_whitespace_nodes(self, node): + for child in node: + text = child.text.strip() + if not text: + child.text = '' + tail = child.tail.strip() + if not tail: + child.tail = '' + if len(child): self.__remove_whilespace_nodes(child) - for node in remove_list: - node.parentNode.removeChild(node) + + def __get_parent_node(self, node): + # elements in ElementTree doesn't hold a reference to their parent + for parent, child in self.__iter_parent(): + if child is node: + return child + + def __iter_parent(self): + for parent in self.tree.getiterator(): + for child in parent: + yield parent, child |