diff options
author | ju1ius <jules.bernable@laposte.net> | 2013-12-05 07:31:57 +0100 |
---|---|---|
committer | ju1ius <jules.bernable@laposte.net> | 2013-12-05 07:31:57 +0100 |
commit | 9cdc7e022bda32a87fe06772b4b85d146e62b109 (patch) | |
tree | 81a161724201e42d3f7014892bf7582227f79057 | |
parent | 0603c32c3342cf328a32ffc5421f25215641a94a (diff) | |
parent | 2475cb23b0538f42c5f348b9cd0e9b20a0063a49 (diff) |
Merge remote-tracking branch 'upstream/master'
-rw-r--r-- | test/test-menu-rules.py | 4 | ||||
-rw-r--r-- | xdg/Menu.py | 349 | ||||
-rw-r--r-- | xdg/MenuEditor.py | 264 |
3 files changed, 325 insertions, 292 deletions
diff --git a/test/test-menu-rules.py b/test/test-menu-rules.py index 1bfafb1..0593d5c 100644 --- a/test/test-menu-rules.py +++ b/test/test-menu-rules.py @@ -1,7 +1,7 @@ import xml.etree.cElementTree as etree import unittest -from xdg.Menu import Parser, Rule +from xdg.Menu import XMLMenuBuilder, Rule _tests = [ @@ -160,7 +160,7 @@ class RulesTest(unittest.TestCase): """Basic rule matching tests""" def test_rule_from_node(self): - parser = Parser(debug=True) + parser = XMLMenuBuilder(debug=True) for i, test in enumerate(_tests): root = etree.fromstring(test['doc']) rule = parser.parse_rule(root) diff --git a/xdg/Menu.py b/xdg/Menu.py index a71381b..01b59f0 100644 --- a/xdg/Menu.py +++ b/xdg/Menu.py @@ -216,6 +216,107 @@ class Menu: except AttributeError: return "" + def sort(self): + self.Entries = [] + self.Visible = 0 + + for submenu in self.Submenus: + submenu.sort() + + _submenus = set() + _entries = set() + + for order in self.Layout.order: + if order[0] == "Filename": + _entries.add(order[1]) + elif order[0] == "Menuname": + _submenus.add(order[1]) + + for order in self.Layout.order: + if order[0] == "Separator": + separator = Separator(self) + if len(self.Entries) > 0 and isinstance(self.Entries[-1], Separator): + separator.Show = False + self.Entries.append(separator) + elif order[0] == "Filename": + menuentry = self.getMenuEntry(order[1]) + if menuentry: + self.Entries.append(menuentry) + elif order[0] == "Menuname": + submenu = self.getMenu(order[1]) + if submenu: + if submenu.Layout.inline: + self.merge_inline(submenu) + else: + self.Entries.append(submenu) + elif order[0] == "Merge": + if order[1] == "files" or order[1] == "all": + self.MenuEntries.sort() + for menuentry in self.MenuEntries: + if menuentry.DesktopFileID not in _entries: + self.Entries.append(menuentry) + elif order[1] == "menus" or order[1] == "all": + self.Submenus.sort() + for submenu in self.Submenus: + if submenu.Name not in _submenus: + if submenu.Layout.inline: + self.merge_inline(submenu) + else: + self.Entries.append(submenu) + + # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec + for entry in self.Entries: + entry.Show = True + self.Visible += 1 + if isinstance(entry, Menu): + if entry.Deleted is True: + entry.Show = "Deleted" + self.Visible -= 1 + elif isinstance(entry.Directory, MenuEntry): + if entry.Directory.DesktopEntry.getNoDisplay(): + entry.Show = "NoDisplay" + self.Visible -= 1 + elif entry.Directory.DesktopEntry.getHidden(): + entry.Show = "Hidden" + self.Visible -= 1 + elif isinstance(entry, MenuEntry): + if entry.DesktopEntry.getNoDisplay(): + entry.Show = "NoDisplay" + self.Visible -= 1 + elif entry.DesktopEntry.getHidden(): + entry.Show = "Hidden" + self.Visible -= 1 + elif entry.DesktopEntry.getTryExec() and not entry.DesktopEntry.findTryExec(): + entry.Show = "NoExec" + self.Visible -= 1 + elif xdg.Config.windowmanager: + if (entry.DesktopEntry.OnlyShowIn != [] and ( + xdg.Config.windowmanager not in entry.DesktopEntry.OnlyShowIn + ) + ) or ( + xdg.Config.windowmanager in entry.DesktopEntry.NotShowIn + ): + entry.Show = "NotShowIn" + self.Visible -= 1 + elif isinstance(entry, Separator): + self.Visible -= 1 + + # remove separators at the beginning and at the end + if len(self.Entries) > 0: + if isinstance(self.Entries[0], Separator): + self.Entries[0].Show = False + if len(self.Entries) > 1: + if isinstance(self.Entries[-1], Separator): + self.Entries[-1].Show = False + + # show_empty tag + for entry in self.Entries[:]: + if isinstance(entry, Menu) and not entry.Layout.show_empty and entry.Visible == 0: + entry.Show = "Empty" + self.Visible -= 1 + if entry.NotInXml is True: + self.Entries.remove(entry) + """ PRIVATE STUFF """ def addSubmenu(self, newmenu): for submenu in self.Submenus: @@ -227,6 +328,26 @@ class Menu: newmenu.Parent = self newmenu.Depth = self.Depth + 1 + # inline tags + def merge_inline(self, submenu): + """Appends a submenu's entries to this menu + See the <Menuname> section of the spec about the "inline" attribute + """ + if len(submenu.Entries) == 1 and submenu.Layout.inline_alias: + menuentry = submenu.Entries[0] + menuentry.DesktopEntry.set("Name", submenu.getName(), locale=True) + menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale=True) + menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale=True) + self.Entries.append(menuentry) + elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0: + if submenu.Layout.inline_header: + header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment()) + self.Entries.append(header) + for entry in submenu.Entries: + self.Entries.append(entry) + else: + self.Entries.append(submenu) + class Move: "A move operation" @@ -442,7 +563,7 @@ def _check_file_path(value, filename, type): return False -def __get_menu_file_path(filename): +def _get_menu_file_path(filename): dirs = list(xdg_config_dirs) if xdg.Config.root_mode is True: dirs.pop(0) @@ -459,15 +580,15 @@ def _to_bool(value): # remove duplicate entries from a list -def _dedupe(list): - set = {} - list.reverse() - list = [set.setdefault(e,e) for e in list if e not in set] - list.reverse() - return list +def _dedupe(_list): + _set = {} + _list.reverse() + _list = [_set.setdefault(e, e) for e in _list if e not in _set] + _list.reverse() + return _list -class Parser(object): +class XMLMenuBuilder(object): def __init__(self, debug=False): self.debug = debug @@ -480,11 +601,11 @@ class Parser(object): """ # convert to absolute path if filename and not os.path.isabs(filename): - filename = __get_menu_file_path(filename) + filename = _get_menu_file_path(filename) # use default if no filename given if not filename: candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu" - filename = __get_menu_file_path(candidate) + filename = _get_menu_file_path(candidate) if not filename: raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate) # check if it is a .menu file @@ -497,33 +618,30 @@ class Parser(object): raise ParsingError('Not a valid .menu file', filename) # parse menufile - self.root = None self._merged_files = set() self._directory_dirs = set() self.cache = MenuEntryCache() - self.parse_menu(tree.getroot(), filename, self.root) - self.handle_moves(self.root) - self.post_parse(self.root) + menu = self.parse_menu(tree.getroot(), filename) + menu.tree = tree + menu.filename = filename + + self.handle_moves(menu) + self.post_parse(menu) - self.root.tree = tree - self.root.filename = filename # generate the menu - self.generate_not_only_allocated(self.root) - self.generate_only_allocated(self.root) + self.generate_not_only_allocated(menu) + self.generate_only_allocated(menu) # and finally sort - self.sort(self.root) + menu.sort() - return self.root + return menu - def parse_menu(self, child, filename, parent=None): - m = Menu() - self.parse_node(child, filename, m) - if parent: - parent.addSubmenu(m) - else: - self.root = m + def parse_menu(self, node, filename): + menu = Menu() + self.parse_node(node, filename, menu) + return menu def parse_node(self, node, filename, parent=None): num_children = len(node) @@ -531,7 +649,8 @@ class Parser(object): tag, text = child.tag, child.text text = text.strip() if text else None if tag == 'Menu': - self.parse_menu(child, filename, parent) + menu = self.parse_menu(child, filename) + parent.addSubmenu(menu) elif tag == 'AppDir' and text: self.parse_app_dir(text, filename, parent) elif tag == 'DefaultAppDirs': @@ -564,7 +683,7 @@ class Parser(object): elif tag == 'DefaultMergeDirs': self.parse_default_merge_dirs(child, filename, parent) elif tag == 'Move': - self.parse_move(child, parent) + parent.Moves.append(self.parse_move(child)) elif tag == 'Layout': if num_children > 1: parent.Layout = self.parse_layout(child) @@ -608,7 +727,7 @@ class Parser(object): ]) return layout - def parse_move(self, node, parent): + def parse_move(self, node): old, new = "", "" for child in node: tag, text = child.tag, child.text @@ -617,7 +736,7 @@ class Parser(object): old = text elif tag == "New" and text: new = text - parent.Moves.append(Move(old, new)) + return Move(old, new) # ---------- <Rule> parsing @@ -821,26 +940,6 @@ class Parser(object): except IndexError: pass - # inline tags - def parse_inline(self, submenu, menu): - if submenu.Layout.inline: - if len(submenu.Entries) == 1 and submenu.Layout.inline_alias: - menuentry = submenu.Entries[0] - menuentry.DesktopEntry.set("Name", submenu.getName(), locale=True) - menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale=True) - menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale=True) - menu.Entries.append(menuentry) - elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0: - if submenu.Layout.inline_header: - header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment()) - menu.Entries.append(header) - for entry in submenu.Entries: - menu.Entries.append(entry) - else: - menu.Entries.append(submenu) - else: - menu.Entries.append(submenu) - def post_parse(self, menu): # unallocated / deleted if menu.Deleted == "notset": @@ -931,102 +1030,6 @@ class Parser(object): # menuentry.Allocated = True menu.MenuEntries.append(menuentry) - # And sorting ... - def sort(self, menu): - menu.Entries = [] - menu.Visible = 0 - - for submenu in menu.Submenus: - self.sort(submenu) - - tmp_s = [] - tmp_e = [] - - for order in menu.Layout.order: - if order[0] == "Filename": - tmp_e.append(order[1]) - elif order[0] == "Menuname": - tmp_s.append(order[1]) - - for order in menu.Layout.order: - if order[0] == "Separator": - separator = Separator(menu) - if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator): - separator.Show = False - menu.Entries.append(separator) - elif order[0] == "Filename": - menuentry = menu.getMenuEntry(order[1]) - if menuentry: - menu.Entries.append(menuentry) - elif order[0] == "Menuname": - submenu = menu.getMenu(order[1]) - if submenu: - self.parse_inline(submenu, menu) - elif order[0] == "Merge": - if order[1] == "files" or order[1] == "all": - menu.MenuEntries.sort() - for menuentry in menu.MenuEntries: - if menuentry not in tmp_e: - menu.Entries.append(menuentry) - elif order[1] == "menus" or order[1] == "all": - menu.Submenus.sort() - for submenu in menu.Submenus: - if submenu.Name not in tmp_s: - self.parse_inline(submenu, menu) - - # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec - for entry in menu.Entries: - entry.Show = True - menu.Visible += 1 - if isinstance(entry, Menu): - if entry.Deleted is True: - entry.Show = "Deleted" - menu.Visible -= 1 - elif isinstance(entry.Directory, MenuEntry): - if entry.Directory.DesktopEntry.nodisplay: - entry.Show = "NoDisplay" - menu.Visible -= 1 - elif entry.Directory.desktopEntry.hidden: - entry.Show = "Hidden" - menu.Visible -= 1 - elif isinstance(entry, MenuEntry): - if entry.DesktopEntry.getNoDisplay(): - entry.Show = "NoDisplay" - menu.Visible -= 1 - elif entry.DesktopEntry.getHidden(): - entry.Show = "Hidden" - menu.Visible -= 1 - elif entry.DesktopEntry.getTryExec() and not entry.DesktopEntry.findTryExec(): - entry.Show = "NoExec" - menu.Visible -= 1 - elif xdg.Config.windowmanager: - if (entry.DesktopEntry.OnlyShowIn != [] and ( - xdg.Config.windowmanager not in entry.DesktopEntry.OnlyShowIn - ) - ) or ( - xdg.Config.windowmanager in entry.DesktopEntry.NotShowIn - ): - entry.Show = "NotShowIn" - menu.Visible -= 1 - elif isinstance(entry, Separator): - menu.Visible -= 1 - - # remove separators at the beginning and at the end - if len(menu.Entries) > 0: - if isinstance(menu.Entries[0], Separator): - menu.Entries[0].Show = False - if len(menu.Entries) > 1: - if isinstance(menu.Entries[-1], Separator): - menu.Entries[-1].Show = False - - # show_empty tag - for entry in menu.Entries[:]: - if isinstance(entry, Menu) and not entry.Layout.show_empty and entry.Visible == 0: - entry.Show = "Empty" - menu.Visible -= 1 - if entry.NotInXml is True: - menu.Entries.remove(entry) - def handle_moves(self, menu): for submenu in menu.Submenus: self.handle_moves(submenu) @@ -1064,28 +1067,28 @@ class MenuEntryCache: self.cache = {} def add_menu_entries(self, dirs, prefix="", legacy=False): - for dir in dirs: - if not dir in self.cacheEntries: - self.cacheEntries[dir] = [] - self.__addFiles(dir, "", prefix, legacy) + for dir_ in dirs: + if not dir_ in self.cacheEntries: + self.cacheEntries[dir_] = [] + self.__addFiles(dir_, "", prefix, legacy) - def __addFiles(self, dir, subdir, prefix, legacy): - for item in os.listdir(os.path.join(dir, subdir)): + def __addFiles(self, dir_, subdir, prefix, legacy): + for item in os.listdir(os.path.join(dir_, subdir)): if item.endswith(".desktop"): try: - menuentry = MenuEntry(os.path.join(subdir, item), dir, prefix) + menuentry = MenuEntry(os.path.join(subdir, item), dir_, prefix) except ParsingError: continue - self.cacheEntries[dir].append(menuentry) + self.cacheEntries[dir_].append(menuentry) if legacy: self.cacheEntries['legacy'].append(menuentry) - elif os.path.isdir(os.path.join(dir, subdir, item)) and not legacy: - self.__addFiles(dir, os.path.join(subdir, item), prefix, legacy) + elif os.path.isdir(os.path.join(dir_, subdir, item)) and not legacy: + self.__addFiles(dir_, os.path.join(subdir, item), prefix, legacy) def get_menu_entries(self, dirs, legacy=True): - list = [] - ids = [] + entries = [] + ids = set() # handle legacy items appdirs = dirs[:] if legacy: @@ -1096,26 +1099,26 @@ class MenuEntryCache: return self.cache[key] except KeyError: pass - for dir in appdirs: - for menuentry in self.cacheEntries[dir]: + for dir_ in appdirs: + for menuentry in self.cacheEntries[dir_]: try: if menuentry.DesktopFileID not in ids: - ids.append(menuentry.DesktopFileID) - list.append(menuentry) - elif menuentry.getType() == "System": - # FIXME: This is only 99% correct, but still... - i = list.index(menuentry) - e = list[i] - if e.getType() == "User": - e.Original = menuentry + ids.add(menuentry.DesktopFileID) + entries.append(menuentry) + elif menuentry.getType() == MenuEntry.TYPE_SYSTEM: + # FIXME: This is only 99% correct, but still... + idx = entries.index(menuentry) + entry = entries[idx] + if entry.getType() == MenuEntry.TYPE_USER: + entry.Original = menuentry except UnicodeDecodeError: continue - self.cache[key] = list - return list + self.cache[key] = entries + return entries def parse(filename=None): """Helper function. - Equivalent to calling xdg.Menu.Parser().parse(filename) + Equivalent to calling xdg.Menu.XMLMenuBuilder().parse(filename) """ - return Parser().parse(filename) + return XMLMenuBuilder().parse(filename) diff --git a/xdg/MenuEditor.py b/xdg/MenuEditor.py index cc5ce54..25b8e83 100644 --- a/xdg/MenuEditor.py +++ b/xdg/MenuEditor.py @@ -1,14 +1,14 @@ """ CLass to edit XDG Menus """ - -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 +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree + +from xdg.Menu import Menu, MenuEntry, Layout, Separator, XMLMenuBuilder +from xdg.BaseDirectory import xdg_config_dirs, xdg_data_dirs +from xdg.Exceptions import ParsingError +from xdg.Config import setRootMode # XML-Cleanups: Move / Exclude # FIXME: proper reverte/delete @@ -20,28 +20,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 = XMLMenuBuilder() 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 +52,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 ParsingError: 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 +78,7 @@ class MenuEditor: self.__addEntry(parent, menuentry, after, before) - sort(self.menu) + self.menu.sort() return menuentry @@ -83,7 +94,7 @@ class MenuEditor: self.__addEntry(parent, menu, after, before) - sort(self.menu) + self.menu.sort() return menu @@ -92,7 +103,7 @@ class MenuEditor: self.__addEntry(parent, separator, after, before) - sort(self.menu) + self.menu.sort() return separator @@ -100,7 +111,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 +123,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 +131,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 +148,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 +206,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 +270,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 +278,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 +304,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 +333,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 +346,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 +357,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 +370,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 +389,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 +518,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 |