move drlaunch in drlaunch
[drlaunch] / drlaunch / src / xdg / Menu.py
diff --git a/drlaunch/src/xdg/Menu.py b/drlaunch/src/xdg/Menu.py
new file mode 100644 (file)
index 0000000..d437ee4
--- /dev/null
@@ -0,0 +1,1074 @@
+"""
+Implementation of the XDG Menu Specification Version 1.0.draft-1
+http://standards.freedesktop.org/menu-spec/
+"""
+
+from __future__ import generators
+import locale, os, xml.dom.minidom
+
+from xdg.BaseDirectory import *
+from xdg.DesktopEntry import *
+from xdg.Exceptions import *
+
+import xdg.Locale
+import xdg.Config
+
+ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE
+
+# for python <= 2.3
+try:
+    reversed = reversed
+except NameError:
+    def reversed(x):
+        return x[::-1]
+
+class Menu:
+    def __init__(self):
+        # Public stuff
+        self.Name = ""
+        self.Directory = None
+        self.Entries = []
+        self.Doc = ""
+        self.Filename = ""
+        self.Depth = 0
+        self.Parent = None
+        self.NotInXml = False
+
+        # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True
+        self.Show = True
+        self.Visible = 0
+
+        # Private stuff, only needed for parsing
+        self.AppDirs = []
+        self.DefaultLayout = None
+        self.Deleted = "notset"
+        self.Directories = []
+        self.DirectoryDirs = []
+        self.Layout = None
+        self.MenuEntries = []
+        self.Moves = []
+        self.OnlyUnallocated = "notset"
+        self.Rules = []
+        self.Submenus = []
+
+    def __str__(self):
+        return self.Name
+
+    def __add__(self, other):
+        for dir in other.AppDirs:
+            self.AppDirs.append(dir)
+
+        for dir in other.DirectoryDirs:
+            self.DirectoryDirs.append(dir)
+
+        for directory in other.Directories:
+            self.Directories.append(directory)
+
+        if other.Deleted != "notset":
+            self.Deleted = other.Deleted
+
+        if other.OnlyUnallocated != "notset":
+            self.OnlyUnallocated = other.OnlyUnallocated
+
+        if other.Layout:
+            self.Layout = other.Layout
+
+        if other.DefaultLayout:
+            self.DefaultLayout = other.DefaultLayout
+
+        for rule in other.Rules:
+            self.Rules.append(rule)
+
+        for move in other.Moves:
+            self.Moves.append(move)
+
+        for submenu in other.Submenus:
+            self.addSubmenu(submenu)
+
+        return self
+
+    # FIXME: Performance: cache getName()
+    def __cmp__(self, other):
+        return locale.strcoll(self.getName(), other.getName())
+
+    def __eq__(self, other):
+        if self.Name == str(other):
+            return True
+        else:
+            return False
+
+    """ PUBLIC STUFF """
+    def getEntries(self, hidden=False):
+        for entry in self.Entries:
+            if hidden == True:
+                yield entry
+            elif entry.Show == True:
+                yield entry
+
+    # FIXME: Add searchEntry/seaqrchMenu function
+    # search for name/comment/genericname/desktopfileide
+    # return multiple items
+
+    def getMenuEntry(self, desktopfileid, deep = False):
+        for menuentry in self.MenuEntries:
+            if menuentry.DesktopFileID == desktopfileid:
+                return menuentry
+        if deep == True:
+            for submenu in self.Submenus:
+                submenu.getMenuEntry(desktopfileid, deep)
+
+    def getMenu(self, path):
+        array = path.split("/", 1)
+        for submenu in self.Submenus:
+            if submenu.Name == array[0]:
+                if len(array) > 1:
+                    return submenu.getMenu(array[1])
+                else:
+                    return submenu
+
+    def getPath(self, org=False, toplevel=False):
+        parent = self
+        names=[]
+        while 1:
+            if org:
+                names.append(parent.Name)
+            else:
+                names.append(parent.getName())
+            if parent.Depth > 0:
+                parent = parent.Parent
+            else:
+                break
+        names.reverse()
+        path = ""
+        if toplevel == False:
+            names.pop(0)
+        for name in names:
+            path = os.path.join(path, name)
+        return path
+
+    def getName(self):
+        try:
+            return self.Directory.DesktopEntry.getName()
+        except AttributeError:
+            return self.Name
+
+    def getGenericName(self):
+        try:
+            return self.Directory.DesktopEntry.getGenericName()
+        except AttributeError:
+            return ""
+
+    def getComment(self):
+        try:
+            return self.Directory.DesktopEntry.getComment()
+        except AttributeError:
+            return ""
+
+    def getIcon(self):
+        try:
+            return self.Directory.DesktopEntry.getIcon()
+        except AttributeError:
+            return ""
+
+    """ PRIVATE STUFF """
+    def addSubmenu(self, newmenu):
+        for submenu in self.Submenus:
+            if submenu == newmenu:
+                submenu += newmenu
+                break
+        else:
+            self.Submenus.append(newmenu)
+            newmenu.Parent = self
+            newmenu.Depth = self.Depth + 1
+
+class Move:
+    "A move operation"
+    def __init__(self, node=None):
+        if node:
+            self.parseNode(node)
+        else:
+            self.Old = ""
+            self.New = ""
+
+    def __cmp__(self, other):
+        return cmp(self.Old, other.Old)
+
+    def parseNode(self, node):
+        for child in node.childNodes:
+            if child.nodeType == ELEMENT_NODE:
+                if child.tagName == "Old":
+                    try:
+                        self.parseOld(child.childNodes[0].nodeValue)
+                    except IndexError:
+                        raise ValidationError('Old cannot be empty', '??')                                            
+                elif child.tagName == "New":
+                    try:
+                        self.parseNew(child.childNodes[0].nodeValue)
+                    except IndexError:
+                        raise ValidationError('New cannot be empty', '??')                                            
+
+    def parseOld(self, value):
+        self.Old = value
+    def parseNew(self, value):
+        self.New = value
+
+
+class Layout:
+    "Menu Layout class"
+    def __init__(self, node=None):
+        self.order = []
+        if node:
+            self.show_empty = node.getAttribute("show_empty") or "false"
+            self.inline = node.getAttribute("inline") or "false"
+            self.inline_limit = node.getAttribute("inline_limit") or 4
+            self.inline_header = node.getAttribute("inline_header") or "true"
+            self.inline_alias = node.getAttribute("inline_alias") or "false"
+            self.inline_limit = int(self.inline_limit)
+            self.parseNode(node)
+        else:
+            self.show_empty = "false"
+            self.inline = "false"
+            self.inline_limit = 4
+            self.inline_header = "true"
+            self.inline_alias = "false"
+            self.order.append(["Merge", "menus"])
+            self.order.append(["Merge", "files"])
+
+    def parseNode(self, node):
+        for child in node.childNodes:
+            if child.nodeType == ELEMENT_NODE:
+                if child.tagName == "Menuname":
+                    try:
+                        self.parseMenuname(
+                            child.childNodes[0].nodeValue,
+                            child.getAttribute("show_empty") or "false",
+                            child.getAttribute("inline") or "false",
+                            child.getAttribute("inline_limit") or 4,
+                            child.getAttribute("inline_header") or "true",
+                            child.getAttribute("inline_alias") or "false" )
+                    except IndexError:
+                        raise ValidationError('Menuname cannot be empty', "")
+                elif child.tagName == "Separator":
+                    self.parseSeparator()
+                elif child.tagName == "Filename":
+                    try:
+                        self.parseFilename(child.childNodes[0].nodeValue)
+                    except IndexError:
+                        raise ValidationError('Filename cannot be empty', "")
+                elif child.tagName == "Merge":
+                    self.parseMerge(child.getAttribute("type") or "all")
+
+    def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"):
+        self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias])
+        self.order[-1][4] = int(self.order[-1][4])
+
+    def parseSeparator(self):
+        self.order.append(["Separator"])
+
+    def parseFilename(self, value):
+        self.order.append(["Filename", value])
+
+    def parseMerge(self, type="all"):
+        self.order.append(["Merge", type])
+
+
+class Rule:
+    "Inlcude / Exclude Rules Class"
+    def __init__(self, type, node=None):
+        # Type is Include or Exclude
+        self.Type = type
+        # Rule is a python expression
+        self.Rule = ""
+
+        # Private attributes, only needed for parsing
+        self.Depth = 0
+        self.Expr = [ "or" ]
+        self.New = True
+
+        # Begin parsing
+        if node:
+            self.parseNode(node)
+            self.compile()
+
+    def __str__(self):
+        return self.Rule
+
+    def compile(self):
+        exec("""
+def do(menuentries, type, run):
+    for menuentry in menuentries:
+        if run == 2 and ( menuentry.MatchedInclude == True \
+        or menuentry.Allocated == True ):
+            continue
+        elif %s:
+            if type == "Include":
+                menuentry.Add = True
+                menuentry.MatchedInclude = True
+            else:
+                menuentry.Add = False
+    return menuentries
+""" % self.Rule) in self.__dict__
+
+    def parseNode(self, node):
+        for child in node.childNodes:
+            if child.nodeType == ELEMENT_NODE:
+                if child.tagName == 'Filename':
+                    try:
+                        self.parseFilename(child.childNodes[0].nodeValue)
+                    except IndexError:
+                        raise ValidationError('Filename cannot be empty', "???")
+                elif child.tagName == 'Category':
+                    try:
+                        self.parseCategory(child.childNodes[0].nodeValue)
+                    except IndexError:
+                        raise ValidationError('Category cannot be empty', "???")
+                elif child.tagName == 'All':
+                    self.parseAll()
+                elif child.tagName == 'And':
+                    self.parseAnd(child)
+                elif child.tagName == 'Or':
+                    self.parseOr(child)
+                elif child.tagName == 'Not':
+                    self.parseNot(child)
+
+    def parseNew(self, set=True):
+        if not self.New:
+            self.Rule += " " + self.Expr[self.Depth] + " "
+        if not set:
+            self.New = True
+        elif set:
+            self.New = False
+
+    def parseFilename(self, value):
+        self.parseNew()
+        self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'")
+
+    def parseCategory(self, value):
+        self.parseNew()
+        self.Rule += "'%s' in menuentry.Categories" % value.strip()
+
+    def parseAll(self):
+        self.parseNew()
+        self.Rule += "True"
+
+    def parseAnd(self, node):
+        self.parseNew(False)
+        self.Rule += "("
+        self.Depth += 1
+        self.Expr.append("and")
+        self.parseNode(node)
+        self.Depth -= 1
+        self.Expr.pop()
+        self.Rule += ")"
+
+    def parseOr(self, node):
+        self.parseNew(False)
+        self.Rule += "("
+        self.Depth += 1
+        self.Expr.append("or")
+        self.parseNode(node)
+        self.Depth -= 1
+        self.Expr.pop()
+        self.Rule += ")"
+
+    def parseNot(self, node):
+        self.parseNew(False)
+        self.Rule += "not ("
+        self.Depth += 1
+        self.Expr.append("or")
+        self.parseNode(node)
+        self.Depth -= 1
+        self.Expr.pop()
+        self.Rule += ")"
+
+
+class MenuEntry:
+    "Wrapper for 'Menu Style' Desktop Entries"
+    def __init__(self, filename, dir="", prefix=""):
+        # Create entry
+        self.DesktopEntry = DesktopEntry(os.path.join(dir,filename))
+        self.setAttributes(filename, dir, prefix)
+
+        # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True
+        self.Show = True
+
+        # Semi-Private
+        self.Original = None
+        self.Parents = []
+
+        # Private Stuff
+        self.Allocated = False
+        self.Add = False
+        self.MatchedInclude = False
+
+        # Caching
+        self.Categories = self.DesktopEntry.getCategories()
+
+    def save(self):
+        if self.DesktopEntry.tainted == True:
+            self.DesktopEntry.write()
+
+    def getDir(self):
+        return self.DesktopEntry.filename.replace(self.Filename, '')
+
+    def getType(self):
+        # Can be one of System/User/Both
+        if xdg.Config.root_mode == False:
+            if self.Original:
+                return "Both"
+            elif xdg_data_dirs[0] in self.DesktopEntry.filename:
+                return "User"
+            else:
+                return "System"
+        else:
+            return "User"
+
+    def setAttributes(self, filename, dir="", prefix=""):
+        self.Filename = filename
+        self.Prefix = prefix
+        self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-")
+
+        if not os.path.isabs(self.DesktopEntry.filename):
+            self.__setFilename()
+
+    def updateAttributes(self):
+        if self.getType() == "System":
+            self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
+            self.__setFilename()
+
+    def __setFilename(self):
+        if xdg.Config.root_mode == False:
+            path = xdg_data_dirs[0]
+        else:
+            path= xdg_data_dirs[1]
+
+        if self.DesktopEntry.getType() == "Application":
+            dir = os.path.join(path, "applications")
+        else:
+            dir = os.path.join(path, "desktop-directories")
+
+        self.DesktopEntry.filename = os.path.join(dir, self.Filename)
+
+    def __cmp__(self, other):
+        return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
+
+    def __eq__(self, other):
+        if self.DesktopFileID == str(other):
+            return True
+        else:
+            return False
+
+    def __repr__(self):
+        return self.DesktopFileID
+
+
+class Separator:
+    "Just a dummy class for Separators"
+    def __init__(self, parent):
+        self.Parent = parent
+        self.Show = True
+
+
+class Header:
+    "Class for Inline Headers"
+    def __init__(self, name, generic_name, comment):
+        self.Name = name
+        self.GenericName = generic_name
+        self.Comment = comment
+
+    def __str__(self):
+        return self.Name
+
+
+tmp = {}
+
+def __getFileName(filename):
+    dirs = xdg_config_dirs[:]
+    if xdg.Config.root_mode == True:
+        dirs.pop(0)
+
+    for dir in dirs:
+        menuname = os.path.join (dir, "menus" , filename)
+        if os.path.isdir(dir) and os.path.isfile(menuname):
+            return menuname
+
+def parse(filename=None):
+    # conver to absolute path
+    if filename and not os.path.isabs(filename):
+        filename = __getFileName(filename)
+
+    # use default if no filename given
+    if not filename: 
+        candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu"
+        filename = __getFileName(candidate)
+        
+    if not filename:
+        raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate)
+
+    # check if it is a .menu file
+    if not os.path.splitext(filename)[1] == ".menu":
+        raise ParsingError('Not a .menu file', filename)
+
+    # create xml parser
+    try:
+        doc = xml.dom.minidom.parse(filename)
+    except xml.parsers.expat.ExpatError:
+        raise ParsingError('Not a valid .menu file', filename)
+
+    # parse menufile
+    tmp["Root"] = ""
+    tmp["mergeFiles"] = []
+    tmp["DirectoryDirs"] = []
+    tmp["cache"] = MenuEntryCache()
+
+    __parse(doc, filename, tmp["Root"])
+    __parsemove(tmp["Root"])
+    __postparse(tmp["Root"])
+
+    tmp["Root"].Doc = doc
+    tmp["Root"].Filename = filename
+
+    # generate the menu
+    __genmenuNotOnlyAllocated(tmp["Root"])
+    __genmenuOnlyAllocated(tmp["Root"])
+
+    # and finally sort
+    sort(tmp["Root"])
+
+    return tmp["Root"]
+
+
+def __parse(node, filename, parent=None):
+    for child in node.childNodes:
+        if child.nodeType == ELEMENT_NODE:
+            if child.tagName == 'Menu':
+                __parseMenu(child, filename, parent)
+            elif child.tagName == 'AppDir':
+                try:
+                    __parseAppDir(child.childNodes[0].nodeValue, filename, parent)
+                except IndexError:
+                    raise ValidationError('AppDir cannot be empty', filename)
+            elif child.tagName == 'DefaultAppDirs':
+                __parseDefaultAppDir(filename, parent)
+            elif child.tagName == 'DirectoryDir':
+                try:
+                    __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent)
+                except IndexError:
+                    raise ValidationError('DirectoryDir cannot be empty', filename)
+            elif child.tagName == 'DefaultDirectoryDirs':
+                __parseDefaultDirectoryDir(filename, parent)
+            elif child.tagName == 'Name' :
+                try:
+                    parent.Name = child.childNodes[0].nodeValue
+                except IndexError:
+                    raise ValidationError('Name cannot be empty', filename)
+            elif child.tagName == 'Directory' :
+                try:
+                    parent.Directories.append(child.childNodes[0].nodeValue)
+                except IndexError:
+                    raise ValidationError('Directory cannot be empty', filename)
+            elif child.tagName == 'OnlyUnallocated':
+                parent.OnlyUnallocated = True
+            elif child.tagName == 'NotOnlyUnallocated':
+                parent.OnlyUnallocated = False
+            elif child.tagName == 'Deleted':
+                parent.Deleted = True
+            elif child.tagName == 'NotDeleted':
+                parent.Deleted = False
+            elif child.tagName == 'Include' or child.tagName == 'Exclude':
+                parent.Rules.append(Rule(child.tagName, child))
+            elif child.tagName == 'MergeFile':
+                try:
+                    if child.getAttribute("type") == "parent":
+                        __parseMergeFile("applications.menu", child, filename, parent)
+                    else:
+                        __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent)
+                except IndexError:
+                    raise ValidationError('MergeFile cannot be empty', filename)
+            elif child.tagName == 'MergeDir':
+                try:
+                    __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent)
+                except IndexError:
+                    raise ValidationError('MergeDir cannot be empty', filename)
+            elif child.tagName == 'DefaultMergeDirs':
+                __parseDefaultMergeDirs(child, filename, parent)
+            elif child.tagName == 'Move':
+                parent.Moves.append(Move(child))
+            elif child.tagName == 'Layout':
+                if len(child.childNodes) > 1:
+                    parent.Layout = Layout(child)
+            elif child.tagName == 'DefaultLayout':
+                if len(child.childNodes) > 1:
+                    parent.DefaultLayout = Layout(child)
+            elif child.tagName == 'LegacyDir':
+                try:
+                    __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent)
+                except IndexError:
+                    raise ValidationError('LegacyDir cannot be empty', filename)
+            elif child.tagName == 'KDELegacyDirs':
+                __parseKDELegacyDirs(filename, parent)
+
+def __parsemove(menu):
+    for submenu in menu.Submenus:
+        __parsemove(submenu)
+
+    # parse move operations
+    for move in menu.Moves:
+        move_from_menu = menu.getMenu(move.Old)
+        if move_from_menu:
+            move_to_menu = menu.getMenu(move.New)
+
+            menus = move.New.split("/")
+            oldparent = None
+            while len(menus) > 0:
+                if not oldparent:
+                    oldparent = menu
+                newmenu = oldparent.getMenu(menus[0])
+                if not newmenu:
+                    newmenu = Menu()
+                    newmenu.Name = menus[0]
+                    if len(menus) > 1:
+                        newmenu.NotInXml = True
+                    oldparent.addSubmenu(newmenu)
+                oldparent = newmenu
+                menus.pop(0)
+
+            newmenu += move_from_menu
+            move_from_menu.Parent.Submenus.remove(move_from_menu)
+
+def __postparse(menu):
+    # unallocated / deleted
+    if menu.Deleted == "notset":
+        menu.Deleted = False
+    if menu.OnlyUnallocated == "notset":
+        menu.OnlyUnallocated = False
+
+    # Layout Tags
+    if not menu.Layout or not menu.DefaultLayout:
+        if menu.DefaultLayout:
+            menu.Layout = menu.DefaultLayout
+        elif menu.Layout:
+            if menu.Depth > 0:
+                menu.DefaultLayout = menu.Parent.DefaultLayout
+            else:
+                menu.DefaultLayout = Layout()
+        else:
+            if menu.Depth > 0:
+                menu.Layout = menu.Parent.DefaultLayout
+                menu.DefaultLayout = menu.Parent.DefaultLayout
+            else:
+                menu.Layout = Layout()
+                menu.DefaultLayout = Layout()
+
+    # add parent's app/directory dirs
+    if menu.Depth > 0:
+        menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
+        menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
+
+    # remove duplicates
+    menu.Directories = __removeDuplicates(menu.Directories)
+    menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs)
+    menu.AppDirs = __removeDuplicates(menu.AppDirs)
+
+    # go recursive through all menus
+    for submenu in menu.Submenus:
+        __postparse(submenu)
+
+    # reverse so handling is easier
+    menu.Directories.reverse()
+    menu.DirectoryDirs.reverse()
+    menu.AppDirs.reverse()
+
+    # get the valid .directory file out of the list
+    for directory in menu.Directories:
+        for dir in menu.DirectoryDirs:
+            if os.path.isfile(os.path.join(dir, directory)):
+                menuentry = MenuEntry(directory, dir)
+                if not menu.Directory:
+                    menu.Directory = menuentry
+                elif menuentry.getType() == "System":
+                    if menu.Directory.getType() == "User":
+                        menu.Directory.Original = menuentry
+        if menu.Directory:
+            break
+
+
+# Menu parsing stuff
+def __parseMenu(child, filename, parent):
+    m = Menu()
+    __parse(child, filename, m)
+    if parent:
+        parent.addSubmenu(m)
+    else:
+        tmp["Root"] = m
+
+# helper function
+def __check(value, filename, type):
+    path = os.path.dirname(filename)
+
+    if not os.path.isabs(value):
+        value = os.path.join(path, value)
+
+    value = os.path.abspath(value)
+
+    if type == "dir" and os.path.exists(value) and os.path.isdir(value):
+        return value
+    elif type == "file" and os.path.exists(value) and os.path.isfile(value):
+        return value
+    else:
+        return False
+
+# App/Directory Dir Stuff
+def __parseAppDir(value, filename, parent):
+    value = __check(value, filename, "dir")
+    if value:
+        parent.AppDirs.append(value)
+
+def __parseDefaultAppDir(filename, parent):
+    for dir in reversed(xdg_data_dirs):
+        __parseAppDir(os.path.join(dir, "applications"), filename, parent)
+
+def __parseDirectoryDir(value, filename, parent):
+    value = __check(value, filename, "dir")
+    if value:
+        parent.DirectoryDirs.append(value)
+
+def __parseDefaultDirectoryDir(filename, parent):
+    for dir in reversed(xdg_data_dirs):
+        __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent)
+
+# Merge Stuff
+def __parseMergeFile(value, child, filename, parent):
+    if child.getAttribute("type") == "parent":
+        for dir in xdg_config_dirs:
+            rel_file = filename.replace(dir, "").strip("/")
+            if rel_file != filename:
+                for p in xdg_config_dirs:
+                    if dir == p:
+                        continue
+                    if os.path.isfile(os.path.join(p,rel_file)):
+                        __mergeFile(os.path.join(p,rel_file),child,parent)
+                        break
+    else:
+        value = __check(value, filename, "file")
+        if value:
+            __mergeFile(value, child, parent)
+
+def __parseMergeDir(value, child, filename, parent):
+    value = __check(value, filename, "dir")
+    if value:
+        for item in os.listdir(value):
+            try:
+                if os.path.splitext(item)[1] == ".menu":
+                    __mergeFile(os.path.join(value, item), child, parent)
+            except UnicodeDecodeError:
+                continue
+
+def __parseDefaultMergeDirs(child, filename, parent):
+    basename = os.path.splitext(os.path.basename(filename))[0]
+    for dir in reversed(xdg_config_dirs):
+        __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent)
+
+def __mergeFile(filename, child, parent):
+    # check for infinite loops
+    if filename in tmp["mergeFiles"]:
+        if debug:
+            raise ParsingError('Infinite MergeFile loop detected', filename)
+        else:
+            return
+
+    tmp["mergeFiles"].append(filename)
+
+    # load file
+    try:
+        doc = xml.dom.minidom.parse(filename)
+    except IOError:
+        if debug:
+            raise ParsingError('File not found', filename)
+        else:
+            return
+    except xml.parsers.expat.ExpatError:
+        if debug:
+            raise ParsingError('Not a valid .menu file', filename)
+        else:
+            return
+
+    # append file
+    for child in doc.childNodes:
+        if child.nodeType == ELEMENT_NODE:
+            __parse(child,filename,parent)
+            break
+
+# Legacy Dir Stuff
+def __parseLegacyDir(dir, prefix, filename, parent):
+    m = __mergeLegacyDir(dir,prefix,filename,parent)
+    if m:
+        parent += m
+
+def __mergeLegacyDir(dir, prefix, filename, parent):
+    dir = __check(dir,filename,"dir")
+    if dir and dir not in tmp["DirectoryDirs"]:
+        tmp["DirectoryDirs"].append(dir)
+
+        m = Menu()
+        m.AppDirs.append(dir)
+        m.DirectoryDirs.append(dir)
+        m.Name = os.path.basename(dir)
+        m.NotInXml = True
+
+        for item in os.listdir(dir):
+            try:
+                if item == ".directory":
+                    m.Directories.append(item)
+                elif os.path.isdir(os.path.join(dir,item)):
+                    m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent))
+            except UnicodeDecodeError:
+                continue
+
+        tmp["cache"].addMenuEntries([dir],prefix, True)
+        menuentries = tmp["cache"].getMenuEntries([dir], False)
+
+        for menuentry in menuentries:
+            categories = menuentry.Categories
+            if len(categories) == 0:
+                r = Rule("Include")
+                r.parseFilename(menuentry.DesktopFileID)
+                r.compile()
+                m.Rules.append(r)
+            if not dir in parent.AppDirs:
+                categories.append("Legacy")
+                menuentry.Categories = categories
+
+        return m
+
+def __parseKDELegacyDirs(filename, parent):
+    f=os.popen3("kde-config --path apps")
+    output = f[1].readlines()
+    try:
+        for dir in output[0].split(":"):
+            __parseLegacyDir(dir,"kde", filename, parent)
+    except IndexError:
+        pass
+
+# remove duplicate entries from a list
+def __removeDuplicates(list):
+    set = {}
+    list.reverse()
+    list = [set.setdefault(e,e) for e in list if e not in set]
+    list.reverse()
+    return list
+
+# Finally generate the menu
+def __genmenuNotOnlyAllocated(menu):
+    for submenu in menu.Submenus:
+        __genmenuNotOnlyAllocated(submenu)
+
+    if menu.OnlyUnallocated == False:
+        tmp["cache"].addMenuEntries(menu.AppDirs)
+        menuentries = []
+        for rule in menu.Rules:
+            menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1)
+        for menuentry in menuentries:
+            if menuentry.Add == True:
+                menuentry.Parents.append(menu)
+                menuentry.Add = False
+                menuentry.Allocated = True
+                menu.MenuEntries.append(menuentry)
+
+def __genmenuOnlyAllocated(menu):
+    for submenu in menu.Submenus:
+        __genmenuOnlyAllocated(submenu)
+
+    if menu.OnlyUnallocated == True:
+        tmp["cache"].addMenuEntries(menu.AppDirs)
+        menuentries = []
+        for rule in menu.Rules:
+            menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2)
+        for menuentry in menuentries:
+            if menuentry.Add == True:
+                menuentry.Parents.append(menu)
+            #   menuentry.Add = False
+            #   menuentry.Allocated = True
+                menu.MenuEntries.append(menuentry)
+
+# And sorting ...
+def sort(menu):
+    menu.Entries = []
+    menu.Visible = 0
+
+    for submenu in menu.Submenus:
+        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:
+                __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:
+                        __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 == True:
+                entry.Show = "Deleted"
+                menu.Visible -= 1
+            elif isinstance(entry.Directory, MenuEntry):
+                if entry.Directory.DesktopEntry.getNoDisplay() == True:
+                    entry.Show = "NoDisplay"
+                    menu.Visible -= 1
+                elif entry.Directory.DesktopEntry.getHidden() == True:
+                    entry.Show = "Hidden"
+                    menu.Visible -= 1
+        elif isinstance(entry, MenuEntry):
+            if entry.DesktopEntry.getNoDisplay() == True:
+                entry.Show = "NoDisplay"
+                menu.Visible -= 1
+            elif entry.DesktopEntry.getHidden() == True:
+                entry.Show = "Hidden"
+                menu.Visible -= 1
+            elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()):
+                entry.Show = "NoExec"
+                menu.Visible -= 1
+            elif xdg.Config.windowmanager:
+                if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \
+                or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn():
+                    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 entry.Layout.show_empty == "false" and entry.Visible == 0:
+            entry.Show = "Empty"
+            menu.Visible -= 1
+            if entry.NotInXml == True:
+                menu.Entries.remove(entry)
+
+def __try_exec(executable):
+    paths = os.environ['PATH'].split(os.pathsep)
+    if not os.path.isfile(executable):
+        for p in paths:
+            f = os.path.join(p, executable)
+            if os.path.isfile(f):
+                if os.access(f, os.X_OK):
+                    return True
+    else:
+        if os.access(executable, os.X_OK):
+            return True
+    return False
+
+# inline tags
+def __parse_inline(submenu, menu):
+    if submenu.Layout.inline == "true":
+        if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true":
+            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 == "true":
+                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)
+
+class MenuEntryCache:
+    "Class to cache Desktop Entries"
+    def __init__(self):
+        self.cacheEntries = {}
+        self.cacheEntries['legacy'] = []
+        self.cache = {}
+
+    def addMenuEntries(self, dirs, prefix="", legacy=False):
+        for dir in dirs:
+            if not self.cacheEntries.has_key(dir):
+                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)):
+            if os.path.splitext(item)[1] == ".desktop":
+                try:
+                    menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix)
+                except ParsingError:
+                    continue
+
+                self.cacheEntries[dir].append(menuentry)
+                if legacy == True:
+                    self.cacheEntries['legacy'].append(menuentry)
+            elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False:
+                self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy)
+
+    def getMenuEntries(self, dirs, legacy=True):
+        list = []
+        ids = []
+        # handle legacy items
+        appdirs = dirs[:]
+        if legacy == True:
+            appdirs.append("legacy")
+        # cache the results again
+        key = "".join(appdirs)
+        try:
+            return self.cache[key]
+        except KeyError:
+            pass
+        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
+                except UnicodeDecodeError:
+                    continue
+        self.cache[key] = list
+        return list