File indexing completed on 2024-04-28 15:29:56

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kbuildservicefactory_p.h"
0009 #include "kbuildsycocainterface_p.h"
0010 #include "sycocadebug.h"
0011 #include "vfolder_menu_p.h"
0012 
0013 #include <kservice.h>
0014 
0015 #include <QDebug>
0016 #include <QDir>
0017 #include <QDirIterator>
0018 #include <QFile>
0019 #include <QMap>
0020 #include <QStandardPaths>
0021 
0022 static void foldNode(QDomElement &docElem, QDomElement &e, QMap<QString, QDomElement> &dupeList, QString s = QString()) // krazy:exclude=passbyvalue
0023 {
0024     if (s.isEmpty()) {
0025         s = e.text();
0026     }
0027     auto it = dupeList.find(s);
0028     if (it != dupeList.end()) {
0029         // qCDebug(SYCOCA) << e.tagName() << "and" << s << "requires combining!";
0030 
0031         docElem.removeChild(*it);
0032         dupeList.erase(it);
0033     }
0034     dupeList.insert(s, e);
0035 }
0036 
0037 static void replaceNode(QDomElement &docElem, QDomNode &node, const QStringList &list, const QString &tag)
0038 {
0039     for (const QString &str : list) {
0040         QDomElement element = docElem.ownerDocument().createElement(tag);
0041         const QDomText txt = docElem.ownerDocument().createTextNode(str);
0042         element.appendChild(txt);
0043         docElem.insertAfter(element, node);
0044     }
0045 
0046     QDomNode next = node.nextSibling();
0047     docElem.removeChild(node);
0048     node = next;
0049     //   qCDebug(SYCOCA) << "Next tag = " << n.toElement().tagName();
0050 }
0051 
0052 void VFolderMenu::registerFile(const QString &file)
0053 {
0054     int i = file.lastIndexOf(QLatin1Char('/'));
0055     if (i < 0) {
0056         return;
0057     }
0058 
0059     QString dir = file.left(i + 1); // Include trailing '/'
0060     registerDirectory(dir);
0061 }
0062 
0063 void VFolderMenu::registerDirectory(const QString &directory)
0064 {
0065     m_allDirectories.append(directory);
0066 }
0067 
0068 QStringList VFolderMenu::allDirectories()
0069 {
0070     if (m_allDirectories.isEmpty()) {
0071         return m_allDirectories;
0072     }
0073     m_allDirectories.sort();
0074 
0075     QStringList::Iterator it = m_allDirectories.begin();
0076     QString previous = *it++;
0077     for (; it != m_allDirectories.end();) {
0078 #ifndef Q_OS_WIN
0079         if ((*it).startsWith(previous))
0080 #else
0081         if ((*it).startsWith(previous, Qt::CaseInsensitive))
0082 #endif
0083         {
0084             it = m_allDirectories.erase(it);
0085         } else {
0086             previous = *it;
0087             ++it;
0088         }
0089     }
0090     return m_allDirectories;
0091 }
0092 
0093 static void track(const QString &menuId,
0094                   const QString &menuName,
0095                   const QHash<QString, KService::Ptr> &includeList,
0096                   const QHash<QString, KService::Ptr> &excludeList,
0097                   const QHash<QString, KService::Ptr> &itemList,
0098                   const QString &comment)
0099 {
0100     if (itemList.contains(menuId)) {
0101         printf("%s: %s INCL %d EXCL %d\n",
0102                qPrintable(menuName),
0103                qPrintable(comment),
0104                includeList.contains(menuId) ? 1 : 0,
0105                excludeList.contains(menuId) ? 1 : 0);
0106     }
0107 }
0108 
0109 void VFolderMenu::includeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
0110 {
0111     for (const KService::Ptr &p : items2) {
0112         items1.insert(p->menuId(), p);
0113     }
0114 }
0115 
0116 void VFolderMenu::matchItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
0117 {
0118     const QHash<QString, KService::Ptr> tmpItems1 = items1;
0119     for (const KService::Ptr &p : tmpItems1) {
0120         QString id = p->menuId();
0121         if (!items2.contains(id)) {
0122             items1.remove(id);
0123         }
0124     }
0125 }
0126 
0127 void VFolderMenu::excludeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
0128 {
0129     for (const KService::Ptr &p : items2) {
0130         items1.remove(p->menuId());
0131     }
0132 }
0133 
0134 VFolderMenu::SubMenu *VFolderMenu::takeSubMenu(SubMenu *parentMenu, const QString &menuName)
0135 {
0136     const int i = menuName.indexOf(QLatin1Char('/'));
0137     const QString s1 = i > 0 ? menuName.left(i) : menuName;
0138     const QString s2 = menuName.mid(i + 1);
0139 
0140     // Look up menu
0141     for (QList<SubMenu *>::Iterator it = parentMenu->subMenus.begin(); it != parentMenu->subMenus.end(); ++it) {
0142         SubMenu *menu = *it;
0143         if (menu->name == s1) {
0144             if (i == -1) {
0145                 // Take it out
0146                 parentMenu->subMenus.erase(it);
0147                 return menu;
0148             } else {
0149                 return takeSubMenu(menu, s2);
0150             }
0151         }
0152     }
0153     return nullptr; // Not found
0154 }
0155 
0156 void VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority)
0157 {
0158     if (m_track) {
0159         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("Before MenuMerge w. %1 (incl)").arg(menu2->name));
0160         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("Before MenuMerge w. %1 (excl)").arg(menu2->name));
0161     }
0162     if (reversePriority) {
0163         // Merge menu1 with menu2, menu1 takes precedent
0164         excludeItems(menu2->items, menu1->excludeItems);
0165         includeItems(menu1->items, menu2->items);
0166         excludeItems(menu2->excludeItems, menu1->items);
0167         includeItems(menu1->excludeItems, menu2->excludeItems);
0168     } else {
0169         // Merge menu1 with menu2, menu2 takes precedent
0170         excludeItems(menu1->items, menu2->excludeItems);
0171         includeItems(menu1->items, menu2->items);
0172         includeItems(menu1->excludeItems, menu2->excludeItems);
0173         menu1->isDeleted = menu2->isDeleted;
0174     }
0175     while (!menu2->subMenus.isEmpty()) {
0176         SubMenu *subMenu = menu2->subMenus.takeFirst();
0177         insertSubMenu(menu1, subMenu->name, subMenu, reversePriority);
0178     }
0179 
0180     if (reversePriority) {
0181         // Merge menu1 with menu2, menu1 takes precedent
0182         if (menu1->directoryFile.isEmpty()) {
0183             menu1->directoryFile = menu2->directoryFile;
0184         }
0185         if (menu1->defaultLayoutNode.isNull()) {
0186             menu1->defaultLayoutNode = menu2->defaultLayoutNode;
0187         }
0188         if (menu1->layoutNode.isNull()) {
0189             menu1->layoutNode = menu2->layoutNode;
0190         }
0191     } else {
0192         // Merge menu1 with menu2, menu2 takes precedent
0193         if (!menu2->directoryFile.isEmpty()) {
0194             menu1->directoryFile = menu2->directoryFile;
0195         }
0196         if (!menu2->defaultLayoutNode.isNull()) {
0197             menu1->defaultLayoutNode = menu2->defaultLayoutNode;
0198         }
0199         if (!menu2->layoutNode.isNull()) {
0200             menu1->layoutNode = menu2->layoutNode;
0201         }
0202     }
0203 
0204     if (m_track) {
0205         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("After MenuMerge w. %1 (incl)").arg(menu2->name));
0206         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("After MenuMerge w. %1 (excl)").arg(menu2->name));
0207     }
0208 
0209     delete menu2;
0210 }
0211 
0212 void VFolderMenu::insertSubMenu(SubMenu *parentMenu, const QString &menuName, SubMenu *newMenu, bool reversePriority)
0213 {
0214     const int i = menuName.indexOf(QLatin1Char('/'));
0215     const QString s1 = menuName.left(i);
0216     const QString s2 = menuName.mid(i + 1);
0217 
0218     // Look up menu
0219     for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
0220         if (menu->name == s1) {
0221             if (i == -1) {
0222                 mergeMenu(menu, newMenu, reversePriority);
0223                 return;
0224             } else {
0225                 insertSubMenu(menu, s2, newMenu, reversePriority);
0226                 return;
0227             }
0228         }
0229     }
0230     if (i == -1) {
0231         // Add it here
0232         newMenu->name = menuName;
0233         parentMenu->subMenus.append(newMenu);
0234     } else {
0235         SubMenu *menu = new SubMenu;
0236         menu->name = s1;
0237         parentMenu->subMenus.append(menu);
0238         insertSubMenu(menu, s2, newMenu);
0239     }
0240 }
0241 
0242 void VFolderMenu::insertService(SubMenu *parentMenu, const QString &name, KService::Ptr newService)
0243 {
0244     const int i = name.indexOf(QLatin1Char('/'));
0245 
0246     if (i == -1) {
0247         // Add it here
0248         parentMenu->items.insert(newService->menuId(), newService);
0249         return;
0250     }
0251 
0252     QString s1 = name.left(i);
0253     QString s2 = name.mid(i + 1);
0254 
0255     // Look up menu
0256     for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
0257         if (menu->name == s1) {
0258             insertService(menu, s2, newService);
0259             return;
0260         }
0261     }
0262 
0263     SubMenu *menu = new SubMenu;
0264     menu->name = s1;
0265     parentMenu->subMenus.append(menu);
0266     insertService(menu, s2, newService);
0267 }
0268 
0269 VFolderMenu::VFolderMenu(KServiceFactory *serviceFactory, KBuildSycocaInterface *kbuildsycocaInterface)
0270     : m_appsInfo(nullptr)
0271     , m_rootMenu(nullptr)
0272     , m_currentMenu(nullptr)
0273     , m_track(false)
0274     , m_serviceFactory(serviceFactory)
0275     , m_kbuildsycocaInterface(kbuildsycocaInterface)
0276 {
0277     m_usedAppsDict.reserve(797);
0278     initDirs();
0279 }
0280 
0281 VFolderMenu::~VFolderMenu()
0282 {
0283     qDeleteAll(m_appsInfoList);
0284     delete m_rootMenu;
0285 }
0286 // clang-format off
0287 #define FOR_ALL_APPLICATIONS(it) \
0288     for (AppsInfo *info : std::as_const(m_appsInfoStack)) \
0289     { \
0290         QHashIterator<QString,KService::Ptr> it = info->applications; \
0291         while (it.hasNext()) \
0292         { \
0293             it.next();
0294 #define FOR_ALL_APPLICATIONS_END } }
0295 
0296 #define FOR_CATEGORY(category, it) \
0297     for (AppsInfo *info : std::as_const(m_appsInfoStack)) \
0298     { \
0299         const KService::List list = info->dictCategories.value(category); \
0300         for(KService::List::ConstIterator it = list.constBegin(); \
0301                 it != list.constEnd(); ++it) \
0302         {
0303 #define FOR_CATEGORY_END } }
0304 // clang-format on
0305 
0306 KService::Ptr VFolderMenu::findApplication(const QString &relPath)
0307 {
0308     for (AppsInfo *info : std::as_const(m_appsInfoStack)) {
0309         if (info->applications.contains(relPath)) {
0310             KService::Ptr s = info->applications[relPath];
0311             if (s) {
0312                 return s;
0313             }
0314         }
0315     }
0316     return KService::Ptr();
0317 }
0318 
0319 void VFolderMenu::addApplication(const QString &id, KService::Ptr service)
0320 {
0321     service->setMenuId(id);
0322     m_appsInfo->applications.insert(id, service); // replaces, if already there
0323     m_serviceFactory->addEntry(KSycocaEntry::Ptr(service));
0324 }
0325 
0326 void VFolderMenu::buildApplicationIndex(bool unusedOnly)
0327 {
0328     for (AppsInfo *info : std::as_const(m_appsInfoList)) {
0329         info->dictCategories.clear();
0330         QMutableHashIterator<QString, KService::Ptr> it = info->applications;
0331         while (it.hasNext()) {
0332             KService::Ptr s = it.next().value();
0333             if (unusedOnly && m_usedAppsDict.contains(s->menuId())) {
0334                 // Remove and skip this one
0335                 it.remove();
0336                 continue;
0337             }
0338 
0339             const auto categories = s->categories();
0340             for (const QString &cat : categories) {
0341                 info->dictCategories[cat].append(s); // find or insert entry in hash
0342             }
0343         }
0344     }
0345 }
0346 
0347 void VFolderMenu::createAppsInfo()
0348 {
0349     if (m_appsInfo) {
0350         return;
0351     }
0352 
0353     m_appsInfo = new AppsInfo;
0354     m_appsInfoStack.prepend(m_appsInfo);
0355     m_appsInfoList.append(m_appsInfo);
0356     m_currentMenu->apps_info = m_appsInfo;
0357 }
0358 
0359 void VFolderMenu::loadAppsInfo()
0360 {
0361     m_appsInfo = m_currentMenu->apps_info;
0362     if (!m_appsInfo) {
0363         return; // No appsInfo for this menu
0364     }
0365 
0366     if (!m_appsInfoStack.isEmpty() && m_appsInfoStack.first() == m_appsInfo) {
0367         return; // Already added (By createAppsInfo?)
0368     }
0369 
0370     m_appsInfoStack.prepend(m_appsInfo); // Add
0371 }
0372 
0373 void VFolderMenu::unloadAppsInfo()
0374 {
0375     m_appsInfo = m_currentMenu->apps_info;
0376     if (!m_appsInfo) {
0377         return; // No appsInfo for this menu
0378     }
0379 
0380     if (m_appsInfoStack.first() != m_appsInfo) {
0381         return; // Already removed (huh?)
0382     }
0383 
0384     m_appsInfoStack.removeAll(m_appsInfo); // Remove
0385     m_appsInfo = nullptr;
0386 }
0387 
0388 QString VFolderMenu::absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg)
0389 {
0390     QString dir = _dir;
0391     if (QDir::isRelativePath(dir)) {
0392         dir = baseDir + dir;
0393     }
0394 
0395     bool relative = QDir::isRelativePath(dir);
0396     if (relative && !keepRelativeToCfg) {
0397         relative = false;
0398         dir = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + dir, QStandardPaths::LocateDirectory);
0399     }
0400 
0401     if (!relative) {
0402         QString resolved = QDir(dir).canonicalPath();
0403         if (!resolved.isEmpty()) {
0404             dir = resolved;
0405         }
0406     }
0407 
0408     if (!dir.endsWith(QLatin1Char('/'))) {
0409         dir += QLatin1Char('/');
0410     }
0411 
0412     return dir;
0413 }
0414 
0415 static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir)
0416 {
0417     QDomNodeList mergeFileList = doc.elementsByTagName(tag);
0418     for (int i = 0; i < mergeFileList.count(); i++) {
0419         QDomAttr attr = doc.createAttribute(QStringLiteral("__BaseDir"));
0420         attr.setValue(dir);
0421         mergeFileList.item(i).toElement().setAttributeNode(attr);
0422     }
0423 }
0424 
0425 static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path)
0426 {
0427     QDomNodeList mergeFileList = doc.elementsByTagName(tag);
0428     for (int i = 0; i < mergeFileList.count(); i++) {
0429         QDomAttr attr = doc.createAttribute(QStringLiteral("__BasePath"));
0430         attr.setValue(path);
0431         mergeFileList.item(i).toElement().setAttributeNode(attr);
0432     }
0433 }
0434 
0435 QDomDocument VFolderMenu::loadDoc()
0436 {
0437     QDomDocument doc;
0438     if (m_docInfo.path.isEmpty()) {
0439         return doc;
0440     }
0441     QFile file(m_docInfo.path);
0442     if (!file.open(QIODevice::ReadOnly)) {
0443         qCWarning(SYCOCA) << "Could not open " << m_docInfo.path;
0444         return doc;
0445     }
0446     if (file.size() == 0) {
0447         return doc;
0448     }
0449     QString errorMsg;
0450     int errorRow;
0451     int errorCol;
0452     if (!doc.setContent(&file, &errorMsg, &errorRow, &errorCol)) {
0453         qCWarning(SYCOCA) << "Parse error in " << m_docInfo.path << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg;
0454         file.close();
0455         return doc;
0456     }
0457     file.close();
0458 
0459     tagBaseDir(doc, QStringLiteral("MergeFile"), m_docInfo.baseDir);
0460     tagBasePath(doc, QStringLiteral("MergeFile"), m_docInfo.path);
0461     tagBaseDir(doc, QStringLiteral("MergeDir"), m_docInfo.baseDir);
0462     tagBaseDir(doc, QStringLiteral("DirectoryDir"), m_docInfo.baseDir);
0463     tagBaseDir(doc, QStringLiteral("AppDir"), m_docInfo.baseDir);
0464     tagBaseDir(doc, QStringLiteral("LegacyDir"), m_docInfo.baseDir);
0465 
0466     return doc;
0467 }
0468 
0469 void VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere)
0470 {
0471     // qCDebug(SYCOCA) << m_docInfo.path;
0472     QDomDocument doc = loadDoc();
0473 
0474     QDomElement docElem = doc.documentElement();
0475     QDomNode n = docElem.firstChild();
0476     QDomNode last = mergeHere;
0477     while (!n.isNull()) {
0478         QDomElement e = n.toElement(); // try to convert the node to an element.
0479         QDomNode next = n.nextSibling();
0480 
0481         if (e.isNull()) {
0482             // Skip
0483         }
0484         // The spec says we must ignore any Name nodes
0485         else if (e.tagName() != QLatin1String("Name")) {
0486             parent.insertAfter(n, last);
0487             last = n;
0488         }
0489 
0490         docElem.removeChild(n);
0491         n = next;
0492     }
0493 }
0494 
0495 void VFolderMenu::mergeMenus(QDomElement &docElem, QString &name)
0496 {
0497     QMap<QString, QDomElement> menuNodes;
0498     QMap<QString, QDomElement> directoryNodes;
0499     QMap<QString, QDomElement> appDirNodes;
0500     QMap<QString, QDomElement> directoryDirNodes;
0501     QMap<QString, QDomElement> legacyDirNodes;
0502     QDomElement defaultLayoutNode;
0503     QDomElement layoutNode;
0504 
0505     QDomNode n = docElem.firstChild();
0506     while (!n.isNull()) {
0507         QDomElement e = n.toElement(); // try to convert the node to an element.
0508         if (e.isNull()) {
0509             // qCDebug(SYCOCA) << "Empty node";
0510         } else if (e.tagName() == QLatin1String("DefaultAppDirs")) {
0511             // Replace with m_defaultAppDirs
0512             replaceNode(docElem, n, m_defaultAppDirs, QStringLiteral("AppDir"));
0513             continue;
0514         } else if (e.tagName() == QLatin1String("DefaultDirectoryDirs")) {
0515             // Replace with m_defaultDirectoryDirs
0516             replaceNode(docElem, n, m_defaultDirectoryDirs, QStringLiteral("DirectoryDir"));
0517             continue;
0518         } else if (e.tagName() == QLatin1String("DefaultMergeDirs")) {
0519             // Replace with m_defaultMergeDirs
0520             replaceNode(docElem, n, m_defaultMergeDirs, QStringLiteral("MergeDir"));
0521             continue;
0522         } else if (e.tagName() == QLatin1String("AppDir")) {
0523             // Filter out dupes
0524             foldNode(docElem, e, appDirNodes);
0525         } else if (e.tagName() == QLatin1String("DirectoryDir")) {
0526             // Filter out dupes
0527             foldNode(docElem, e, directoryDirNodes);
0528         } else if (e.tagName() == QLatin1String("LegacyDir")) {
0529             // Filter out dupes
0530             foldNode(docElem, e, legacyDirNodes);
0531         } else if (e.tagName() == QLatin1String("Directory")) {
0532             // Filter out dupes
0533             foldNode(docElem, e, directoryNodes);
0534         } else if (e.tagName() == QLatin1String("Move")) {
0535             // Filter out dupes
0536             QString orig;
0537             QDomNode n2 = e.firstChild();
0538             while (!n2.isNull()) {
0539                 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
0540                 if (e2.tagName() == QLatin1String("Old")) {
0541                     orig = e2.text();
0542                     break;
0543                 }
0544                 n2 = n2.nextSibling();
0545             }
0546             foldNode(docElem, e, appDirNodes, orig);
0547         } else if (e.tagName() == QLatin1String("Menu")) {
0548             QString name;
0549             mergeMenus(e, name);
0550             QMap<QString, QDomElement>::iterator it = menuNodes.find(name);
0551             if (it != menuNodes.end()) {
0552                 QDomElement docElem2 = *it;
0553                 QDomNode n2 = docElem2.firstChild();
0554                 QDomNode first = e.firstChild();
0555                 while (!n2.isNull()) {
0556                     QDomElement e2 = n2.toElement(); // try to convert the node to an element.
0557                     QDomNode n3 = n2.nextSibling();
0558                     e.insertBefore(n2, first);
0559                     docElem2.removeChild(n2);
0560                     n2 = n3;
0561                 }
0562                 // We still have duplicated Name entries
0563                 // but we don't care about that
0564 
0565                 docElem.removeChild(docElem2);
0566                 menuNodes.erase(it);
0567             }
0568             menuNodes.insert(name, e);
0569         } else if (e.tagName() == QLatin1String("MergeFile")) {
0570             if ((e.attribute(QStringLiteral("type")) == QLatin1String("parent"))) {
0571                 // Ignore e.text(), as per the standard. We'll just look up the parent (more global) xml file.
0572                 pushDocInfoParent(e.attribute(QStringLiteral("__BasePath")), e.attribute(QStringLiteral("__BaseDir")));
0573             } else {
0574                 pushDocInfo(e.text(), e.attribute(QStringLiteral("__BaseDir")));
0575             }
0576 
0577             if (!m_docInfo.path.isEmpty()) {
0578                 mergeFile(docElem, n);
0579             }
0580             popDocInfo();
0581 
0582             QDomNode last = n;
0583             n = n.nextSibling();
0584             docElem.removeChild(last); // Remove the MergeFile node
0585             continue;
0586         } else if (e.tagName() == QLatin1String("MergeDir")) {
0587             QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")), true);
0588             Q_ASSERT(dir.endsWith(QLatin1Char('/')));
0589 
0590             const bool relative = QDir::isRelativePath(dir);
0591             const QStringList dirs =
0592                 QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + dir, QStandardPaths::LocateDirectory);
0593             for (const QString &menuDir : dirs) {
0594                 registerDirectory(menuDir);
0595             }
0596 
0597             QStringList fileList;
0598             for (const QString &menuDir : dirs) {
0599                 const QStringList fileNames = QDir(menuDir).entryList(QStringList() << QStringLiteral("*.menu"));
0600                 for (const QString &file : fileNames) {
0601                     const QString fileToAdd = relative ? dir + file : menuDir + file;
0602                     if (!fileList.contains(fileToAdd)) {
0603                         fileList.append(fileToAdd);
0604                     }
0605                 }
0606             }
0607 
0608             for (const QString &file : std::as_const(fileList)) {
0609                 pushDocInfo(file);
0610                 mergeFile(docElem, n);
0611                 popDocInfo();
0612             }
0613 
0614             QDomNode last = n;
0615             n = n.nextSibling();
0616             docElem.removeChild(last); // Remove the MergeDir node
0617 
0618             continue;
0619         } else if (e.tagName() == QLatin1String("Name")) {
0620             name = e.text();
0621         } else if (e.tagName() == QLatin1String("DefaultLayout")) {
0622             if (!defaultLayoutNode.isNull()) {
0623                 docElem.removeChild(defaultLayoutNode);
0624             }
0625             defaultLayoutNode = e;
0626         } else if (e.tagName() == QLatin1String("Layout")) {
0627             if (!layoutNode.isNull()) {
0628                 docElem.removeChild(layoutNode);
0629             }
0630             layoutNode = e;
0631         }
0632         n = n.nextSibling();
0633     }
0634 }
0635 
0636 static QString makeRelative(const QString &dir)
0637 {
0638     const QString canonical = QDir(dir).canonicalPath();
0639     const auto list = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory);
0640     for (const QString &base : list) {
0641         if (canonical.startsWith(base)) {
0642             return canonical.mid(base.length() + 1);
0643         }
0644     }
0645     return dir;
0646 }
0647 
0648 void VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir)
0649 {
0650     m_docInfoStack.push(m_docInfo);
0651     if (!baseDir.isEmpty()) {
0652         if (!QDir::isRelativePath(baseDir)) {
0653             m_docInfo.baseDir = makeRelative(baseDir);
0654         } else {
0655             m_docInfo.baseDir = baseDir;
0656         }
0657     }
0658 
0659     QString baseName = fileName;
0660     if (!QDir::isRelativePath(baseName)) {
0661         registerFile(baseName);
0662     } else {
0663         baseName = m_docInfo.baseDir + baseName;
0664     }
0665 
0666     m_docInfo.path = locateMenuFile(fileName);
0667     if (m_docInfo.path.isEmpty()) {
0668         m_docInfo.baseDir.clear();
0669         m_docInfo.baseName.clear();
0670         qCDebug(SYCOCA) << "Menu" << fileName << "not found.";
0671         return;
0672     }
0673     qCDebug(SYCOCA) << "Found menu file" << m_docInfo.path;
0674     int i;
0675     i = baseName.lastIndexOf(QLatin1Char('/'));
0676     if (i > 0) {
0677         m_docInfo.baseDir = baseName.left(i + 1);
0678         m_docInfo.baseName = baseName.mid(i + 1, baseName.length() - i - 6);
0679     } else {
0680         m_docInfo.baseDir.clear();
0681         m_docInfo.baseName = baseName.left(baseName.length() - 5);
0682     }
0683 }
0684 
0685 void VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir)
0686 {
0687     m_docInfoStack.push(m_docInfo);
0688 
0689     m_docInfo.baseDir = baseDir;
0690 
0691     QString fileName = basePath.mid(basePath.lastIndexOf(QLatin1Char('/')) + 1);
0692     m_docInfo.baseName = fileName.left(fileName.length() - 5); // without ".menu"
0693     QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
0694 
0695     QStringList result = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName);
0696 
0697     // Remove anything "more local" than basePath.
0698     while (!result.isEmpty() && (result.at(0) != basePath)) {
0699         result.removeFirst();
0700     }
0701 
0702     if (result.count() <= 1) {
0703         m_docInfo.path.clear(); // No parent found
0704         return;
0705     }
0706     // Now result.at(0) == basePath, take the next one, i.e. the one in the parent dir.
0707     m_docInfo.path = result.at(1);
0708 }
0709 
0710 void VFolderMenu::popDocInfo()
0711 {
0712     m_docInfo = m_docInfoStack.pop();
0713 }
0714 
0715 QString VFolderMenu::locateMenuFile(const QString &fileName)
0716 {
0717     if (!QDir::isRelativePath(fileName)) {
0718         if (QFile::exists(fileName)) {
0719             return fileName;
0720         }
0721         return QString();
0722     }
0723 
0724     QString result;
0725 
0726     QString xdgMenuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX"));
0727     if (!xdgMenuPrefix.isEmpty()) {
0728         QFileInfo fileInfo(fileName);
0729 
0730         QString fileNameOnly = fileInfo.fileName();
0731         if (!fileNameOnly.startsWith(xdgMenuPrefix)) {
0732             fileNameOnly = xdgMenuPrefix + fileNameOnly;
0733         }
0734 
0735         QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileInfo.path() + QLatin1Char('/') + fileNameOnly);
0736         result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName);
0737     }
0738 
0739     if (result.isEmpty()) {
0740         QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
0741         result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName);
0742     }
0743 
0744     return result;
0745 }
0746 
0747 QString VFolderMenu::locateDirectoryFile(const QString &fileName)
0748 {
0749     if (fileName.isEmpty()) {
0750         return QString();
0751     }
0752 
0753     if (!QDir::isRelativePath(fileName)) {
0754         if (QFile::exists(fileName)) {
0755             return fileName;
0756         }
0757         return QString();
0758     }
0759 
0760     // First location in the list wins
0761     for (QStringList::ConstIterator it = m_directoryDirs.constBegin(); it != m_directoryDirs.constEnd(); ++it) {
0762         QString tmp = (*it) + fileName;
0763         if (QFile::exists(tmp)) {
0764             return tmp;
0765         }
0766     }
0767 
0768     return QString();
0769 }
0770 
0771 void VFolderMenu::initDirs()
0772 {
0773     m_defaultAppDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
0774     m_defaultDirectoryDirs =
0775         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories"), QStandardPaths::LocateDirectory);
0776 }
0777 
0778 void VFolderMenu::loadMenu(const QString &fileName)
0779 {
0780     m_defaultMergeDirs.clear();
0781 
0782     if (!fileName.endsWith(QLatin1String(".menu"))) {
0783         return;
0784     }
0785 
0786     pushDocInfo(fileName);
0787     m_defaultMergeDirs << QStringLiteral("applications-merged/");
0788     m_doc = loadDoc();
0789     popDocInfo();
0790 
0791     if (m_doc.isNull()) {
0792         if (m_docInfo.path.isEmpty()) {
0793             qCritical() << fileName << " not found in " << m_allDirectories;
0794         } else {
0795             qCWarning(SYCOCA) << "Load error (" << m_docInfo.path << ")";
0796         }
0797         return;
0798     }
0799 
0800     QDomElement e = m_doc.documentElement();
0801     QString name;
0802     mergeMenus(e, name);
0803 }
0804 
0805 void VFolderMenu::processCondition(QDomElement &domElem, QHash<QString, KService::Ptr> &items)
0806 {
0807     if (domElem.tagName() == QLatin1String("And")) {
0808         QDomNode n = domElem.firstChild();
0809         // Look for the first child element
0810         while (!n.isNull()) { // loop in case of comments
0811             QDomElement e = n.toElement();
0812             n = n.nextSibling();
0813             if (!e.isNull()) {
0814                 processCondition(e, items);
0815                 break; // we only want the first one
0816             }
0817         }
0818 
0819         QHash<QString, KService::Ptr> andItems;
0820         while (!n.isNull()) {
0821             QDomElement e = n.toElement();
0822             if (e.tagName() == QLatin1String("Not")) {
0823                 // Special handling for "and not"
0824                 QDomNode n2 = e.firstChild();
0825                 while (!n2.isNull()) {
0826                     QDomElement e2 = n2.toElement();
0827                     andItems.clear();
0828                     processCondition(e2, andItems);
0829                     excludeItems(items, andItems);
0830                     n2 = n2.nextSibling();
0831                 }
0832             } else {
0833                 andItems.clear();
0834                 processCondition(e, andItems);
0835                 matchItems(items, andItems);
0836             }
0837             n = n.nextSibling();
0838         }
0839     } else if (domElem.tagName() == QLatin1String("Or")) {
0840         QDomNode n = domElem.firstChild();
0841         // Look for the first child element
0842         while (!n.isNull()) { // loop in case of comments
0843             QDomElement e = n.toElement();
0844             n = n.nextSibling();
0845             if (!e.isNull()) {
0846                 processCondition(e, items);
0847                 break; // we only want the first one
0848             }
0849         }
0850 
0851         QHash<QString, KService::Ptr> orItems;
0852         while (!n.isNull()) {
0853             QDomElement e = n.toElement();
0854             if (!e.isNull()) {
0855                 orItems.clear();
0856                 processCondition(e, orItems);
0857                 includeItems(items, orItems);
0858             }
0859             n = n.nextSibling();
0860         }
0861     } else if (domElem.tagName() == QLatin1String("Not")) {
0862         FOR_ALL_APPLICATIONS(it)
0863         {
0864             KService::Ptr s = it.value();
0865             items.insert(s->menuId(), s);
0866         }
0867         FOR_ALL_APPLICATIONS_END
0868 
0869         QHash<QString, KService::Ptr> notItems;
0870         QDomNode n = domElem.firstChild();
0871         while (!n.isNull()) {
0872             QDomElement e = n.toElement();
0873             if (!e.isNull()) {
0874                 notItems.clear();
0875                 processCondition(e, notItems);
0876                 excludeItems(items, notItems);
0877             }
0878             n = n.nextSibling();
0879         }
0880     } else if (domElem.tagName() == QLatin1String("Category")) {
0881         FOR_CATEGORY(domElem.text(), it)
0882         {
0883             KService::Ptr s = *it;
0884             items.insert(s->menuId(), s);
0885         }
0886         FOR_CATEGORY_END
0887     } else if (domElem.tagName() == QLatin1String("All")) {
0888         FOR_ALL_APPLICATIONS(it)
0889         {
0890             KService::Ptr s = it.value();
0891             items.insert(s->menuId(), s);
0892         }
0893         FOR_ALL_APPLICATIONS_END
0894     } else if (domElem.tagName() == QLatin1String("Filename")) {
0895         const QString filename = domElem.text();
0896         // qCDebug(SYCOCA) << "Adding file" << filename;
0897         KService::Ptr s = findApplication(filename);
0898         if (s) {
0899             items.insert(filename, s);
0900         }
0901     }
0902 }
0903 
0904 void VFolderMenu::loadApplications(const QString &dir, const QString &prefix)
0905 {
0906     qCDebug(SYCOCA) << "Looking up applications under" << dir;
0907 
0908     QDirIterator it(dir);
0909     while (it.hasNext()) {
0910         it.next();
0911         const QFileInfo fi = it.fileInfo();
0912         const QString fn = fi.fileName();
0913         if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in ksycocautils_p.h
0914             if (fn == QLatin1String(".") || fn == QLatin1String("..")) {
0915                 continue;
0916             }
0917             loadApplications(fi.filePath(), prefix + fn + QLatin1Char('-'));
0918             continue;
0919         }
0920         if (fi.isFile()) {
0921             if (!fn.endsWith(QLatin1String(".desktop"))) {
0922                 continue;
0923             }
0924             KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
0925 #ifndef NDEBUG
0926             if (fn.contains(QLatin1String("fake"))) {
0927                 qCDebug(SYCOCA) << "createService" << fi.absoluteFilePath() << "returned" << (service ? "valid service" : "NULL SERVICE");
0928             }
0929 #endif
0930             if (service) {
0931                 addApplication(prefix + fn, service);
0932             }
0933         }
0934     }
0935 }
0936 
0937 void VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix)
0938 {
0939     // qCDebug(SYCOCA).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")";
0940 
0941     QHash<QString, KService::Ptr> items;
0942     QDirIterator it(dir);
0943     while (it.hasNext()) {
0944         it.next();
0945         const QFileInfo fi = it.fileInfo();
0946         const QString fn = fi.fileName();
0947         if (fi.isDir()) {
0948             if (fn == QLatin1String(".") || fn == QLatin1String("..")) {
0949                 continue;
0950             }
0951             SubMenu *parentMenu = m_currentMenu;
0952 
0953             m_currentMenu = new SubMenu;
0954             m_currentMenu->name = fn;
0955             m_currentMenu->directoryFile = fi.absoluteFilePath() + QLatin1String("/.directory");
0956 
0957             parentMenu->subMenus.append(m_currentMenu);
0958 
0959             processLegacyDir(fi.filePath(), relDir + fn + QLatin1Char('/'), prefix);
0960             m_currentMenu = parentMenu;
0961             continue;
0962         }
0963         if (fi.isFile() /*&& !fi.isSymLink() ?? */) {
0964             if (!fn.endsWith(QLatin1String(".desktop"))) {
0965                 continue;
0966             }
0967             KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
0968             if (service) {
0969                 const QString id = prefix + fn;
0970 
0971                 // TODO: Add legacy category
0972                 addApplication(id, service);
0973                 items.insert(service->menuId(), service);
0974 
0975                 if (service->categories().isEmpty()) {
0976                     m_currentMenu->items.insert(id, service);
0977                 }
0978             }
0979         }
0980     }
0981     markUsedApplications(items);
0982 }
0983 
0984 void VFolderMenu::processMenu(QDomElement &docElem, int pass)
0985 {
0986     SubMenu *parentMenu = m_currentMenu;
0987     int oldDirectoryDirsCount = m_directoryDirs.count();
0988 
0989     QString name;
0990     QString directoryFile;
0991     bool onlyUnallocated = false;
0992     bool isDeleted = false;
0993     QDomElement defaultLayoutNode;
0994     QDomElement layoutNode;
0995 
0996     QDomElement query;
0997     QDomNode n = docElem.firstChild();
0998     while (!n.isNull()) {
0999         QDomElement e = n.toElement(); // try to convert the node to an element.
1000         if (e.tagName() == QLatin1String("Name")) {
1001             name = e.text();
1002         } else if (e.tagName() == QLatin1String("Directory")) {
1003             directoryFile = e.text();
1004         } else if (e.tagName() == QLatin1String("DirectoryDir")) {
1005             QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1006 
1007             m_directoryDirs.prepend(dir);
1008         } else if (e.tagName() == QLatin1String("OnlyUnallocated")) {
1009             onlyUnallocated = true;
1010         } else if (e.tagName() == QLatin1String("NotOnlyUnallocated")) {
1011             onlyUnallocated = false;
1012         } else if (e.tagName() == QLatin1String("Deleted")) {
1013             isDeleted = true;
1014         } else if (e.tagName() == QLatin1String("NotDeleted")) {
1015             isDeleted = false;
1016         } else if (e.tagName() == QLatin1String("DefaultLayout")) {
1017             defaultLayoutNode = e;
1018         } else if (e.tagName() == QLatin1String("Layout")) {
1019             layoutNode = e;
1020         }
1021         n = n.nextSibling();
1022     }
1023 
1024     // Setup current menu entry
1025     if (pass == 0) {
1026         m_currentMenu = nullptr;
1027         // Look up menu
1028         if (parentMenu) {
1029             for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
1030                 if (menu->name == name) {
1031                     m_currentMenu = menu;
1032                     break;
1033                 }
1034             }
1035         }
1036 
1037         if (!m_currentMenu) { // Not found?
1038             // Create menu
1039             m_currentMenu = new SubMenu;
1040             m_currentMenu->name = name;
1041 
1042             if (parentMenu) {
1043                 parentMenu->subMenus.append(m_currentMenu);
1044             } else {
1045                 m_rootMenu = m_currentMenu;
1046             }
1047         }
1048         if (directoryFile.isEmpty()) {
1049             // qCDebug(SYCOCA) << "Menu" << name << "does not specify a directory file.";
1050         }
1051 
1052         // Override previous directoryFile iff available
1053         QString tmp = locateDirectoryFile(directoryFile);
1054         if (!tmp.isEmpty()) {
1055             m_currentMenu->directoryFile = tmp;
1056         }
1057         m_currentMenu->isDeleted = isDeleted;
1058 
1059         m_currentMenu->defaultLayoutNode = defaultLayoutNode;
1060         m_currentMenu->layoutNode = layoutNode;
1061     } else {
1062         // Look up menu
1063         if (parentMenu) {
1064             for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
1065                 if (menu->name == name) {
1066                     m_currentMenu = menu;
1067                     break;
1068                 }
1069             }
1070         } else {
1071             m_currentMenu = m_rootMenu;
1072         }
1073     }
1074 
1075     // Process AppDir and LegacyDir
1076     if (pass == 0) {
1077         QDomElement query;
1078         QDomNode n = docElem.firstChild();
1079         while (!n.isNull()) {
1080             QDomElement e = n.toElement(); // try to convert the node to an element.
1081             if (e.tagName() == QLatin1String("AppDir")) {
1082                 createAppsInfo();
1083                 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1084 
1085                 registerDirectory(dir);
1086 
1087                 loadApplications(dir, QString());
1088             } else if (e.tagName() == QLatin1String("LegacyDir")) {
1089                 createAppsInfo();
1090                 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1091 
1092                 QString prefix = e.attributes().namedItem(QStringLiteral("prefix")).toAttr().value();
1093 
1094                 SubMenu *oldMenu = m_currentMenu;
1095                 m_currentMenu = new SubMenu;
1096 
1097                 registerDirectory(dir);
1098 
1099                 processLegacyDir(dir, QString(), prefix);
1100 
1101                 m_legacyNodes.insert(dir, m_currentMenu);
1102                 m_currentMenu = oldMenu;
1103             }
1104             n = n.nextSibling();
1105         }
1106     }
1107 
1108     loadAppsInfo(); // Update the scope wrt the list of applications
1109 
1110     if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) {
1111         n = docElem.firstChild();
1112 
1113         while (!n.isNull()) {
1114             QDomElement e = n.toElement(); // try to convert the node to an element.
1115             if (e.tagName() == QLatin1String("Include")) {
1116                 QHash<QString, KService::Ptr> items;
1117 
1118                 QDomNode n2 = e.firstChild();
1119                 while (!n2.isNull()) {
1120                     QDomElement e2 = n2.toElement();
1121                     items.clear();
1122                     processCondition(e2, items);
1123                     if (m_track) {
1124                         track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before <Include>"));
1125                     }
1126                     includeItems(m_currentMenu->items, items);
1127                     excludeItems(m_currentMenu->excludeItems, items);
1128                     markUsedApplications(items);
1129 
1130                     if (m_track) {
1131                         track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After <Include>"));
1132                     }
1133 
1134                     n2 = n2.nextSibling();
1135                 }
1136             }
1137 
1138             else if (e.tagName() == QLatin1String("Exclude")) {
1139                 QHash<QString, KService::Ptr> items;
1140 
1141                 QDomNode n2 = e.firstChild();
1142                 while (!n2.isNull()) {
1143                     QDomElement e2 = n2.toElement();
1144                     items.clear();
1145                     processCondition(e2, items);
1146                     if (m_track) {
1147                         track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before <Exclude>"));
1148                     }
1149                     excludeItems(m_currentMenu->items, items);
1150                     includeItems(m_currentMenu->excludeItems, items);
1151                     if (m_track) {
1152                         track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After <Exclude>"));
1153                     }
1154                     n2 = n2.nextSibling();
1155                 }
1156             }
1157 
1158             n = n.nextSibling();
1159         }
1160     }
1161 
1162     n = docElem.firstChild();
1163     while (!n.isNull()) {
1164         QDomElement e = n.toElement(); // try to convert the node to an element.
1165         if (e.tagName() == QLatin1String("Menu")) {
1166             processMenu(e, pass);
1167         }
1168         // We insert legacy dir in pass 0, this way the order in the .menu-file determines
1169         // which .directory file gets used, but the menu-entries of legacy-menus will always
1170         // have the lowest priority.
1171         //      else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated))
1172         else if (pass == 0) {
1173             if (e.tagName() == QLatin1String("LegacyDir")) {
1174                 // Add legacy nodes to Menu structure
1175                 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1176                 SubMenu *legacyMenu = m_legacyNodes[dir];
1177                 if (legacyMenu) {
1178                     mergeMenu(m_currentMenu, legacyMenu);
1179                 }
1180             }
1181         }
1182         n = n.nextSibling();
1183     }
1184 
1185     if (pass == 2) {
1186         n = docElem.firstChild();
1187         while (!n.isNull()) {
1188             QDomElement e = n.toElement(); // try to convert the node to an element.
1189             if (e.tagName() == QLatin1String("Move")) {
1190                 QString orig;
1191                 QString dest;
1192                 QDomNode n2 = e.firstChild();
1193                 while (!n2.isNull()) {
1194                     QDomElement e2 = n2.toElement(); // try to convert the node to an element.
1195                     if (e2.tagName() == QLatin1String("Old")) {
1196                         orig = e2.text();
1197                     }
1198                     if (e2.tagName() == QLatin1String("New")) {
1199                         dest = e2.text();
1200                     }
1201                     n2 = n2.nextSibling();
1202                 }
1203                 // qCDebug(SYCOCA) << "Moving" << orig << "to" << dest;
1204                 if (!orig.isEmpty() && !dest.isEmpty()) {
1205                     SubMenu *menu = takeSubMenu(m_currentMenu, orig);
1206                     if (menu) {
1207                         insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority
1208                     }
1209                 }
1210             }
1211             n = n.nextSibling();
1212         }
1213     }
1214 
1215     unloadAppsInfo(); // Update the scope wrt the list of applications
1216 
1217     while (m_directoryDirs.count() > oldDirectoryDirsCount) {
1218         m_directoryDirs.pop_front();
1219     }
1220 
1221     m_currentMenu = parentMenu;
1222 }
1223 
1224 static QString parseAttribute(const QDomElement &e)
1225 {
1226     QString option;
1227 
1228     const QString SHOW_EMPTY = QStringLiteral("show_empty");
1229     if (e.hasAttribute(SHOW_EMPTY)) {
1230         QString str = e.attribute(SHOW_EMPTY);
1231         if (str == QLatin1String("true")) {
1232             option = QStringLiteral("ME ");
1233         } else if (str == QLatin1String("false")) {
1234             option = QStringLiteral("NME ");
1235         } else {
1236             // qCDebug(SYCOCA)<<" Error in parsing show_empty attribute :"<<str;
1237         }
1238     }
1239     const QString INLINE = QStringLiteral("inline");
1240     if (e.hasAttribute(INLINE)) {
1241         QString str = e.attribute(INLINE);
1242         if (str == QLatin1String("true")) {
1243             option += QLatin1String("I ");
1244         } else if (str == QLatin1String("false")) {
1245             option += QLatin1String("NI ");
1246         } else {
1247             qCDebug(SYCOCA) << " Error in parsing inline attribute :" << str;
1248         }
1249     }
1250     if (e.hasAttribute(QStringLiteral("inline_limit"))) {
1251         bool ok;
1252         int value = e.attribute(QStringLiteral("inline_limit")).toInt(&ok);
1253         if (ok) {
1254             option += QStringLiteral("IL[%1] ").arg(value);
1255         }
1256     }
1257     if (e.hasAttribute(QStringLiteral("inline_header"))) {
1258         QString str = e.attribute(QStringLiteral("inline_header"));
1259         if (str == QLatin1String("true")) {
1260             option += QLatin1String("IH ");
1261         } else if (str == QLatin1String("false")) {
1262             option += QLatin1String("NIH ");
1263         } else {
1264             qCDebug(SYCOCA) << " Error in parsing of inline_header attribute :" << str;
1265         }
1266     }
1267     if (e.hasAttribute(QStringLiteral("inline_alias")) && e.attribute(QStringLiteral("inline_alias")) == QLatin1String("true")) {
1268         QString str = e.attribute(QStringLiteral("inline_alias"));
1269         if (str == QLatin1String("true")) {
1270             option += QLatin1String("IA");
1271         } else if (str == QLatin1String("false")) {
1272             option += QLatin1String("NIA");
1273         } else {
1274             qCDebug(SYCOCA) << " Error in parsing inline_alias attribute :" << str;
1275         }
1276     }
1277     if (!option.isEmpty()) {
1278         option.prepend(QStringLiteral(":O"));
1279     }
1280     return option;
1281 }
1282 
1283 QStringList VFolderMenu::parseLayoutNode(const QDomElement &docElem) const
1284 {
1285     QStringList layout;
1286 
1287     QString optionDefaultLayout;
1288     if (docElem.tagName() == QLatin1String("DefaultLayout")) {
1289         optionDefaultLayout = parseAttribute(docElem);
1290     }
1291     if (!optionDefaultLayout.isEmpty()) {
1292         layout.append(optionDefaultLayout);
1293     }
1294 
1295     bool mergeTagExists = false;
1296     QDomNode n = docElem.firstChild();
1297     while (!n.isNull()) {
1298         QDomElement e = n.toElement(); // try to convert the node to an element.
1299         if (e.tagName() == QLatin1String("Separator")) {
1300             layout.append(QStringLiteral(":S"));
1301         } else if (e.tagName() == QLatin1String("Filename")) {
1302             layout.append(e.text());
1303         } else if (e.tagName() == QLatin1String("Menuname")) {
1304             layout.append(QLatin1Char('/') + e.text());
1305             QString option = parseAttribute(e);
1306             if (!option.isEmpty()) {
1307                 layout.append(option);
1308             }
1309         } else if (e.tagName() == QLatin1String("Merge")) {
1310             QString type = e.attributeNode(QStringLiteral("type")).value();
1311             if (type == QLatin1String("files")) {
1312                 layout.append(QStringLiteral(":F"));
1313             } else if (type == QLatin1String("menus")) {
1314                 layout.append(QStringLiteral(":M"));
1315             } else if (type == QLatin1String("all")) {
1316                 layout.append(QStringLiteral(":A"));
1317             }
1318             mergeTagExists = true;
1319         }
1320 
1321         n = n.nextSibling();
1322     }
1323 
1324     if (!mergeTagExists) {
1325         layout.append(QStringLiteral(":M"));
1326         layout.append(QStringLiteral(":F"));
1327         qCWarning(SYCOCA) << "The menu spec file (" << m_docInfo.path
1328                           << ") contains a Layout or DefaultLayout tag without the mandatory Merge tag inside. Please fix it.";
1329     }
1330     return layout;
1331 }
1332 
1333 void VFolderMenu::layoutMenu(VFolderMenu::SubMenu *menu, QStringList defaultLayout) // krazy:exclude=passbyvalue
1334 {
1335     if (!menu->defaultLayoutNode.isNull()) {
1336         defaultLayout = parseLayoutNode(menu->defaultLayoutNode);
1337     }
1338 
1339     if (menu->layoutNode.isNull()) {
1340         menu->layoutList = defaultLayout;
1341     } else {
1342         menu->layoutList = parseLayoutNode(menu->layoutNode);
1343         if (menu->layoutList.isEmpty()) {
1344             menu->layoutList = defaultLayout;
1345         }
1346     }
1347 
1348     for (VFolderMenu::SubMenu *subMenu : std::as_const(menu->subMenus)) {
1349         layoutMenu(subMenu, defaultLayout);
1350     }
1351 }
1352 
1353 void VFolderMenu::markUsedApplications(const QHash<QString, KService::Ptr> &items)
1354 {
1355     for (const KService::Ptr &p : items) {
1356         m_usedAppsDict.insert(p->menuId());
1357     }
1358 }
1359 
1360 VFolderMenu::SubMenu *VFolderMenu::parseMenu(const QString &file)
1361 {
1362     m_appsInfo = nullptr;
1363 
1364     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory);
1365     for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
1366         registerDirectory(*it);
1367     }
1368 
1369     loadMenu(file);
1370 
1371     delete m_rootMenu;
1372     m_rootMenu = m_currentMenu = nullptr;
1373 
1374     QDomElement docElem = m_doc.documentElement();
1375 
1376     for (int pass = 0; pass <= 2; pass++) {
1377         // pass 0: load application desktop files
1378         // pass 1: the normal processing
1379         // pass 2: process <OnlyUnallocated> to put unused files into "Lost & Found".
1380         processMenu(docElem, pass);
1381 
1382         switch (pass) {
1383         case 0:
1384             // Fill the dictCategories for each AppsInfo in m_appsInfoList,
1385             // in preparation for processMenu pass 1.
1386             buildApplicationIndex(false);
1387             break;
1388         case 1:
1389             // Fill the dictCategories for each AppsInfo in m_appsInfoList,
1390             // with only the unused apps, in preparation for processMenu pass 2.
1391             buildApplicationIndex(true /* unusedOnly */);
1392             break;
1393         case 2: {
1394             QStringList defaultLayout;
1395             defaultLayout << QStringLiteral(":M"); // Sub-Menus
1396             defaultLayout << QStringLiteral(":F"); // Individual entries
1397             layoutMenu(m_rootMenu, defaultLayout);
1398             break;
1399         }
1400         default:
1401             break;
1402         }
1403     }
1404 
1405     return m_rootMenu;
1406 }
1407 
1408 void VFolderMenu::setTrackId(const QString &id)
1409 {
1410     m_track = !id.isEmpty();
1411     m_trackId = id;
1412 }
1413 
1414 #include "moc_vfolder_menu_p.cpp"