summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorju1ius <jules.bernable@laposte.net>2013-12-05 07:31:57 +0100
committerju1ius <jules.bernable@laposte.net>2013-12-05 07:31:57 +0100
commit9cdc7e022bda32a87fe06772b4b85d146e62b109 (patch)
tree81a161724201e42d3f7014892bf7582227f79057
parent0603c32c3342cf328a32ffc5421f25215641a94a (diff)
parent2475cb23b0538f42c5f348b9cd0e9b20a0063a49 (diff)
Merge remote-tracking branch 'upstream/master'
-rw-r--r--test/test-menu-rules.py4
-rw-r--r--xdg/Menu.py349
-rw-r--r--xdg/MenuEditor.py264
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