KXmlGui

kxmlguiversionhandler.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
4 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
5 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kxmlguiversionhandler_p.h"
11
12#include "kxmlguiclient.h"
13#include "kxmlguifactory.h"
14
15#include <QDomDocument>
16#include <QDomElement>
17#include <QFile>
18#include <QMap>
19#include <QStandardPaths>
20
21struct DocStruct {
22 QString file;
23 QString data;
24};
25
26static QList<QDomElement> extractToolBars(const QDomDocument &doc)
27{
28 QList<QDomElement> toolbars;
29 QDomElement parent = doc.documentElement();
30 for (QDomElement e = parent.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
31 if (e.tagName().compare(QStringLiteral("ToolBar"), Qt::CaseInsensitive) == 0) {
32 toolbars.append(e);
33 }
34 }
35 return toolbars;
36}
37
38static QStringList toolBarNames(const QList<QDomElement> &toolBars)
39{
40 QStringList names;
41 names.reserve(toolBars.count());
42 for (const QDomElement &e : toolBars) {
43 names.append(e.attribute(QStringLiteral("name")));
44 }
45 return names;
46}
47
48static void removeToolBars(QDomDocument &doc, const QStringList &toolBarNames)
49{
50 QDomElement parent = doc.documentElement();
51 const QList<QDomElement> toolBars = extractToolBars(doc);
52 for (const QDomElement &e : toolBars) {
53 if (toolBarNames.contains(e.attribute(QStringLiteral("name")))) {
54 parent.removeChild(e);
55 }
56 }
57}
58
59static void insertToolBars(QDomDocument &doc, const QList<QDomElement> &toolBars)
60{
61 QDomElement parent = doc.documentElement();
62 QDomElement menuBar = parent.namedItem(QStringLiteral("MenuBar")).toElement();
63 QDomElement insertAfter = menuBar;
64 if (menuBar.isNull()) {
65 insertAfter = parent.firstChildElement(); // if null, insertAfter will do an append
66 }
67 for (const QDomElement &e : toolBars) {
68 QDomNode result = parent.insertAfter(e, insertAfter);
69 Q_ASSERT(!result.isNull());
70 }
71}
72
73//
74
76
77static ActionPropertiesMap extractActionProperties(const QDomDocument &doc)
78{
80
81 QDomElement actionPropElement = doc.documentElement().namedItem(QStringLiteral("ActionProperties")).toElement();
82
83 if (actionPropElement.isNull()) {
84 return properties;
85 }
86
87 QDomNode n = actionPropElement.firstChild();
88 while (!n.isNull()) {
89 QDomElement e = n.toElement();
90 n = n.nextSibling(); // Advance now so that we can safely delete e
91 if (e.isNull()) {
92 continue;
93 }
94
95 if (e.tagName().compare(QStringLiteral("action"), Qt::CaseInsensitive) != 0) {
96 continue;
97 }
98
99 const QString actionName = e.attribute(QStringLiteral("name"));
100 if (actionName.isEmpty()) {
101 continue;
102 }
103
104 QMap<QString, QMap<QString, QString>>::Iterator propIt = properties.find(actionName);
105 if (propIt == properties.end()) {
106 propIt = properties.insert(actionName, QMap<QString, QString>());
107 }
108
109 const QDomNamedNodeMap attributes = e.attributes();
110 const int attributeslength = attributes.length();
111
112 for (int i = 0; i < attributeslength; ++i) {
113 const QDomAttr attr = attributes.item(i).toAttr();
114
115 if (attr.isNull()) {
116 continue;
117 }
118
119 const QString name = attr.name();
120
121 if (name == QLatin1String("name") || name.isEmpty()) {
122 continue;
123 }
124
125 (*propIt)[name] = attr.value();
126 }
127 }
128
129 return properties;
130}
131
132static void storeActionProperties(QDomDocument &doc, const ActionPropertiesMap &properties)
133{
134 QDomElement actionPropElement = doc.documentElement().namedItem(QStringLiteral("ActionProperties")).toElement();
135
136 if (actionPropElement.isNull()) {
137 actionPropElement = doc.createElement(QStringLiteral("ActionProperties"));
138 doc.documentElement().appendChild(actionPropElement);
139 }
140
141 // Remove only those ActionProperties entries from the document, that are present
142 // in the properties argument. In real life this means that local ActionProperties
143 // takes precedence over global ones, if they exists (think local override of shortcuts).
144 QDomNode actionNode = actionPropElement.firstChild();
145 while (!actionNode.isNull()) {
146 if (properties.contains(actionNode.toElement().attribute(QStringLiteral("name")))) {
147 QDomNode nextNode = actionNode.nextSibling();
148 actionPropElement.removeChild(actionNode);
149 actionNode = nextNode;
150 } else {
151 actionNode = actionNode.nextSibling();
152 }
153 }
154
157 for (; it != end; ++it) {
158 QDomElement action = doc.createElement(QStringLiteral("Action"));
159 action.setAttribute(QStringLiteral("name"), it.key());
160 actionPropElement.appendChild(action);
161
162 const QMap<QString, QString> attributes = (*it);
163 QMap<QString, QString>::ConstIterator attrIt = attributes.begin();
164 const QMap<QString, QString>::ConstIterator attrEnd = attributes.end();
165 for (; attrIt != attrEnd; ++attrIt) {
166 action.setAttribute(attrIt.key(), attrIt.value());
167 }
168 }
169}
170
171KXmlGuiVersionHandler::KXmlGuiVersionHandler(const QStringList &files)
172{
173 Q_ASSERT(!files.isEmpty());
174
175 if (files.count() == 1) {
176 // No need to parse version numbers if there's only one file anyway
177 m_file = files.first();
178 m_doc = KXMLGUIFactory::readConfigFile(m_file);
179 return;
180 }
181
182 std::vector<DocStruct> allDocuments;
183 allDocuments.reserve(files.size());
184
185 for (const QString &file : files) {
186 allDocuments.push_back({file, KXMLGUIFactory::readConfigFile(file)});
187 }
188
189 auto best = allDocuments.end();
190 uint bestVersion = 0;
191
192 auto docIt = allDocuments.begin();
193 const auto docEnd = allDocuments.end();
194 for (; docIt != docEnd; ++docIt) {
195 const QString versionStr = KXMLGUIClient::findVersionNumber((*docIt).data);
196 if (versionStr.isEmpty()) {
197 // qCDebug(DEBUG_KXMLGUI) << "found no version in" << (*docIt).file;
198 continue;
199 }
200
201 bool ok = false;
202 uint version = versionStr.toUInt(&ok);
203 if (!ok) {
204 continue;
205 }
206 // qCDebug(DEBUG_KXMLGUI) << "found version" << version << "for" << (*docIt).file;
207
208 if (version > bestVersion) {
209 best = docIt;
210 // qCDebug(DEBUG_KXMLGUI) << "best version is now " << version;
211 bestVersion = version;
212 }
213 }
214
215 if (best != docEnd) {
216 if (best != allDocuments.begin()) {
217 auto local = allDocuments.begin();
218
220 // load the local document and extract the action properties
221 QDomDocument localDocument;
222 localDocument.setContent((*local).data);
223
224 const ActionPropertiesMap properties = extractActionProperties(localDocument);
225 const QList<QDomElement> toolbars = extractToolBars(localDocument);
226
227 // in case the document has a ActionProperties section
228 // we must not delete it but copy over the global doc
229 // to the local and insert the ActionProperties section
230
231 // TODO: kedittoolbar should mark toolbars as modified so that
232 // we don't keep old toolbars just because the user defined a shortcut
233
234 if (!properties.isEmpty() || !toolbars.isEmpty()) {
235 // now load the global one with the higher version number
236 // into memory
237 QDomDocument document;
238 document.setContent((*best).data);
239 // and store the properties in there
240 storeActionProperties(document, properties);
241 if (!toolbars.isEmpty()) {
242 // remove application toolbars present in the user file
243 // (not others, that the app might have added since)
244 removeToolBars(document, toolBarNames(toolbars));
245 // add user toolbars
246 insertToolBars(document, toolbars);
247 }
248
249 (*local).data = document.toString();
250 // make sure we pick up the new local doc, when we return later
251 best = local;
252
253 // write out the new version of the local document
254 QFile f((*local).file);
255 if (f.open(QIODevice::WriteOnly)) {
256 const QByteArray utf8data = (*local).data.toUtf8();
257 f.write(utf8data.constData(), utf8data.length());
258 f.close();
259 }
260 } else {
261 // Move away the outdated local file, to speed things up next time
262 const QString f = (*local).file;
263 const QString backup = f + QLatin1String(".backup");
264 QFile::rename(f, backup);
265 }
266 }
267 }
268 m_doc = (*best).data;
269 m_file = (*best).file;
270 } else {
271 // qCDebug(DEBUG_KXMLGUI) << "returning first one...";
272 const auto &[file, data] = allDocuments.at(0);
273 m_file = file;
274 m_doc = data;
275 }
276}
static QString findVersionNumber(const QString &xml)
Returns the version number of the given xml data (belonging to an xml rc file)
static QString readConfigFile(const QString &filename, const QString &componentName=QString())
KDB_EXPORT KDbVersionInfo version()
KGuiItem properties()
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
const char * constData() const const
char * data()
qsizetype length() const const
QString name() const const
QString value() const const
QDomElement createElement(const QString &tagName)
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QString toString(int indent) const const
QString attribute(const QString &name, const QString &defValue) const const
QDomNamedNodeMap attributes() const const
void setAttribute(const QString &name, const QString &value)
QString tagName() const const
QDomNode item(int index) const const
int length() const const
QDomNode appendChild(const QDomNode &newChild)
QDomNode firstChild() const const
QDomElement firstChildElement(const QString &tagName, const QString &namespaceURI) const const
QDomNode insertAfter(const QDomNode &newChild, const QDomNode &refChild)
bool isNull() const const
QDomNode namedItem(const QString &name) const const
QDomNode nextSibling() const const
QDomNode removeChild(const QDomNode &oldChild)
QDomAttr toAttr() const const
QDomElement toElement() const const
bool rename(const QString &newName)
void append(QList< T > &&value)
qsizetype count() const const
T & first()
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
typedef ConstIterator
iterator begin()
iterator end()
Key key(const T &value, const Key &defaultKey) const const
T value(const Key &key, const T &defaultValue) const const
QString writableLocation(StandardLocation type)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool isEmpty() const const
uint toUInt(bool *ok, int base) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:21:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.