1 | /* |
2 | SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include "kconfigloader.h" |
8 | #include "kconfigloader_p.h" |
9 | #include "kconfigloaderhandler_p.h" |
10 | |
11 | #include <QColor> |
12 | #include <QFont> |
13 | #include <QHash> |
14 | #include <QUrl> |
15 | |
16 | #include <QDebug> |
17 | |
18 | void ConfigLoaderPrivate::parse(KConfigLoader *loader, QIODevice *xml) |
19 | { |
20 | clearData(); |
21 | loader->clearItems(); |
22 | |
23 | if (xml) { |
24 | ConfigLoaderHandler handler(loader, this); |
25 | handler.parse(input: xml); |
26 | } |
27 | } |
28 | |
29 | ConfigLoaderHandler::ConfigLoaderHandler(KConfigLoader *config, ConfigLoaderPrivate *d) |
30 | : m_config(config) |
31 | , d(d) |
32 | { |
33 | resetState(); |
34 | } |
35 | |
36 | bool ConfigLoaderHandler::parse(QIODevice *input) |
37 | { |
38 | if (!input->open(mode: QIODevice::ReadOnly)) { |
39 | qWarning() << "Impossible to open device" ; |
40 | return false; |
41 | } |
42 | QXmlStreamReader reader(input); |
43 | |
44 | while (!reader.atEnd()) { |
45 | reader.readNext(); |
46 | if (reader.hasError()) { |
47 | return false; |
48 | } |
49 | |
50 | switch (reader.tokenType()) { |
51 | case QXmlStreamReader::StartElement: |
52 | startElement(localName: reader.name(), attrs: reader.attributes()); |
53 | break; |
54 | case QXmlStreamReader::EndElement: |
55 | endElement(localName: reader.name()); |
56 | break; |
57 | case QXmlStreamReader::Characters: |
58 | if (!reader.isWhitespace() && !reader.text().trimmed().isEmpty()) { |
59 | m_cdata.append(v: reader.text()); |
60 | } |
61 | break; |
62 | default: |
63 | break; |
64 | } |
65 | } |
66 | |
67 | if (!reader.isEndDocument()) { |
68 | return false; |
69 | } |
70 | |
71 | return true; |
72 | } |
73 | |
74 | static bool caseInsensitiveCompare(const QStringView a, const QLatin1String b) |
75 | { |
76 | return a.compare(s: b, cs: Qt::CaseInsensitive) == 0; |
77 | } |
78 | |
79 | void ConfigLoaderHandler::startElement(const QStringView localName, const QXmlStreamAttributes &attrs) |
80 | { |
81 | // qDebug() << "ConfigLoaderHandler::startElement(" << localName << qName; |
82 | if (caseInsensitiveCompare(a: localName, b: QLatin1String("group" ))) { |
83 | QString group; |
84 | for (const auto &attr : attrs) { |
85 | const auto attrName = attr.name(); |
86 | if (caseInsensitiveCompare(a: attrName, b: QLatin1String("name" ))) { |
87 | // qDebug() << "set group to" << attrs.value(i); |
88 | group = attr.value().toString(); |
89 | } |
90 | } |
91 | if (group.isEmpty()) { |
92 | group = d->baseGroup; |
93 | } else { |
94 | d->groups.append(t: group); |
95 | if (!d->baseGroup.isEmpty()) { |
96 | group = d->baseGroup + QLatin1Char('\x1d') + group; |
97 | } |
98 | } |
99 | |
100 | if (m_config) { |
101 | m_config->setCurrentGroup(group); |
102 | } |
103 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("entry" ))) { |
104 | for (const auto &attr : attrs) { |
105 | const auto attrName = attr.name(); |
106 | if (caseInsensitiveCompare(a: attrName, b: QLatin1String("name" ))) { |
107 | m_name = attr.value().trimmed().toString(); |
108 | } else if (caseInsensitiveCompare(a: attrName, b: QLatin1String("type" ))) { |
109 | m_type = attr.value().toString().toLower(); |
110 | } else if (caseInsensitiveCompare(a: attrName, b: QLatin1String("key" ))) { |
111 | m_key = attr.value().trimmed().toString(); |
112 | } |
113 | } |
114 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("choice" ))) { |
115 | m_choice.name.clear(); |
116 | m_choice.label.clear(); |
117 | m_choice.whatsThis.clear(); |
118 | for (const auto &attr : attrs) { |
119 | const auto attrName = attr.name(); |
120 | if (caseInsensitiveCompare(a: attrName, b: QLatin1String("name" ))) { |
121 | m_choice.name = attr.value().toString(); |
122 | } |
123 | } |
124 | m_inChoice = true; |
125 | } |
126 | } |
127 | |
128 | void ConfigLoaderHandler::endElement(const QStringView localName) |
129 | { |
130 | // qDebug() << "ConfigLoaderHandler::endElement(" << localName << qName; |
131 | if (caseInsensitiveCompare(a: localName, b: QLatin1String("entry" ))) { |
132 | addItem(); |
133 | resetState(); |
134 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("label" ))) { |
135 | if (m_inChoice) { |
136 | m_choice.label = std::move(m_cdata).trimmed(); |
137 | } else { |
138 | m_label = std::move(m_cdata).trimmed(); |
139 | } |
140 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("whatsthis" ))) { |
141 | if (m_inChoice) { |
142 | m_choice.whatsThis = std::move(m_cdata).trimmed(); |
143 | } else { |
144 | m_whatsThis = std::move(m_cdata).trimmed(); |
145 | } |
146 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("default" ))) { |
147 | m_default = std::move(m_cdata).trimmed(); |
148 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("min" ))) { |
149 | m_min = m_cdata.toInt(ok: &m_haveMin); |
150 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("max" ))) { |
151 | m_max = m_cdata.toInt(ok: &m_haveMax); |
152 | } else if (caseInsensitiveCompare(a: localName, b: QLatin1String("choice" ))) { |
153 | m_enumChoices.append(t: m_choice); |
154 | m_inChoice = false; |
155 | } |
156 | |
157 | m_cdata.clear(); |
158 | } |
159 | |
160 | void ConfigLoaderHandler::addItem() |
161 | { |
162 | if (m_name.isEmpty()) { |
163 | if (m_key.isEmpty()) { |
164 | return; |
165 | } |
166 | |
167 | m_name = m_key; |
168 | } |
169 | |
170 | m_name.remove(c: QLatin1Char(' ')); |
171 | |
172 | KConfigSkeletonItem *item = nullptr; |
173 | |
174 | if (m_type == QLatin1String("bool" )) { |
175 | const bool defaultValue = caseInsensitiveCompare(a: m_default, b: QLatin1String("true" )); |
176 | item = m_config->addItemBool(name: m_name, reference&: *d->newBool(), defaultValue, key: m_key); |
177 | } else if (m_type == QLatin1String("color" )) { |
178 | item = m_config->addItemColor(name: m_name, reference&: *d->newColor(), defaultValue: QColor(m_default), key: m_key); |
179 | } else if (m_type == QLatin1String("datetime" )) { |
180 | item = m_config->addItemDateTime(name: m_name, reference&: *d->newDateTime(), defaultValue: QDateTime::fromString(string: m_default), key: m_key); |
181 | } else if (m_type == QLatin1String("enum" )) { |
182 | m_key = (m_key.isEmpty()) ? m_name : m_key; |
183 | |
184 | bool ok = false; |
185 | int defaultValue = m_default.toInt(ok: &ok); |
186 | if (!ok) { |
187 | for (int i = 0; i < m_enumChoices.size(); i++) { |
188 | if (m_default == m_enumChoices[i].name) { |
189 | defaultValue = i; |
190 | break; |
191 | } |
192 | } |
193 | } |
194 | |
195 | KConfigSkeleton::ItemEnum *enumItem = new KConfigSkeleton::ItemEnum(m_config->currentGroup(), m_key, *d->newInt(), m_enumChoices, defaultValue); |
196 | m_config->addItem(item: enumItem, name: m_name); |
197 | item = enumItem; |
198 | } else if (m_type == QLatin1String("font" )) { |
199 | item = m_config->addItemFont(name: m_name, reference&: *d->newFont(), defaultValue: QFont(m_default), key: m_key); |
200 | } else if (m_type == QLatin1String("int" )) { |
201 | KConfigSkeleton::ItemInt *intItem = m_config->addItemInt(name: m_name, reference&: *d->newInt(), defaultValue: m_default.toInt(), key: m_key); |
202 | |
203 | if (m_haveMin) { |
204 | intItem->setMinValue(m_min); |
205 | } |
206 | |
207 | if (m_haveMax) { |
208 | intItem->setMaxValue(m_max); |
209 | } |
210 | |
211 | item = intItem; |
212 | } else if (m_type == QLatin1String("password" )) { |
213 | item = m_config->addItemPassword(name: m_name, reference&: *d->newString(), defaultValue: m_default, key: m_key); |
214 | } else if (m_type == QLatin1String("path" )) { |
215 | item = m_config->addItemPath(name: m_name, reference&: *d->newString(), defaultValue: m_default, key: m_key); |
216 | } else if (m_type == QLatin1String("string" )) { |
217 | item = m_config->addItemString(name: m_name, reference&: *d->newString(), defaultValue: m_default, key: m_key); |
218 | } else if (m_type == QLatin1String("stringlist" )) { |
219 | // FIXME: the split() is naive and will break on lists with ,'s in them |
220 | // empty parts are not wanted in this case |
221 | item = m_config->addItemStringList(name: m_name, reference&: *d->newStringList(), defaultValue: m_default.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts), key: m_key); |
222 | } else if (m_type == QLatin1String("uint" )) { |
223 | KConfigSkeleton::ItemUInt *uintItem = m_config->addItemUInt(name: m_name, reference&: *d->newUint(), defaultValue: m_default.toUInt(), key: m_key); |
224 | if (m_haveMin) { |
225 | uintItem->setMinValue(m_min); |
226 | } |
227 | if (m_haveMax) { |
228 | uintItem->setMaxValue(m_max); |
229 | } |
230 | item = uintItem; |
231 | } else if (m_type == QLatin1String("url" )) { |
232 | m_key = (m_key.isEmpty()) ? m_name : m_key; |
233 | KConfigSkeleton::ItemUrl *urlItem = new KConfigSkeleton::ItemUrl(m_config->currentGroup(), m_key, *d->newUrl(), QUrl::fromUserInput(userInput: m_default)); |
234 | m_config->addItem(item: urlItem, name: m_name); |
235 | item = urlItem; |
236 | } else if (m_type == QLatin1String("double" )) { |
237 | KConfigSkeleton::ItemDouble *doubleItem = m_config->addItemDouble(name: m_name, reference&: *d->newDouble(), defaultValue: m_default.toDouble(), key: m_key); |
238 | if (m_haveMin) { |
239 | doubleItem->setMinValue(m_min); |
240 | } |
241 | if (m_haveMax) { |
242 | doubleItem->setMaxValue(m_max); |
243 | } |
244 | item = doubleItem; |
245 | } else if (m_type == QLatin1String("intlist" )) { |
246 | QList<int> defaultList; |
247 | const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts); |
248 | for (const QStringView tmp : tmpList) { |
249 | defaultList.append(t: tmp.toInt()); |
250 | } |
251 | item = m_config->addItemIntList(name: m_name, reference&: *d->newIntList(), defaultValue: defaultList, key: m_key); |
252 | } else if (m_type == QLatin1String("longlong" )) { |
253 | KConfigSkeleton::ItemLongLong *longlongItem = m_config->addItemLongLong(name: m_name, reference&: *d->newLongLong(), defaultValue: m_default.toLongLong(), key: m_key); |
254 | if (m_haveMin) { |
255 | longlongItem->setMinValue(m_min); |
256 | } |
257 | if (m_haveMax) { |
258 | longlongItem->setMaxValue(m_max); |
259 | } |
260 | item = longlongItem; |
261 | /* No addItemPathList in KConfigSkeleton ? |
262 | } else if (m_type == "PathList") { |
263 | //FIXME: the split() is naive and will break on lists with ,'s in them |
264 | item = m_config->addItemPathList(m_name, *d->newStringList(), m_default.split(","), m_key); |
265 | */ |
266 | } else if (m_type == QLatin1String("point" )) { |
267 | QPoint defaultPoint; |
268 | const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(',')); |
269 | if (tmpList.size() >= 2) { |
270 | defaultPoint.setX(tmpList[0].toInt()); |
271 | defaultPoint.setY(tmpList[1].toInt()); |
272 | } |
273 | item = m_config->addItemPoint(name: m_name, reference&: *d->newPoint(), defaultValue: defaultPoint, key: m_key); |
274 | } else if (m_type == QLatin1String("pointf" )) { |
275 | QPointF defaultPointF; |
276 | const auto tmpList = QStringView(m_default).split(sep: u','); |
277 | if (tmpList.size() >= 2) { |
278 | defaultPointF.setX(tmpList[0].toDouble()); |
279 | defaultPointF.setY(tmpList[1].toDouble()); |
280 | } |
281 | item = m_config->addItemPointF(name: m_name, reference&: *d->newPointF(), defaultValue: defaultPointF, key: m_key); |
282 | } else if (m_type == QLatin1String("rect" )) { |
283 | QRect defaultRect; |
284 | const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(',')); |
285 | if (tmpList.size() >= 4) { |
286 | defaultRect.setCoords(xp1: tmpList[0].toInt(), yp1: tmpList[1].toInt(), xp2: tmpList[2].toInt(), yp2: tmpList[3].toInt()); |
287 | } |
288 | item = m_config->addItemRect(name: m_name, reference&: *d->newRect(), defaultValue: defaultRect, key: m_key); |
289 | } else if (m_type == QLatin1String("rectf" )) { |
290 | QRectF defaultRectF; |
291 | const auto tmpList = QStringView(m_default).split(sep: u','); |
292 | if (tmpList.size() >= 4) { |
293 | defaultRectF.setCoords(xp1: tmpList[0].toDouble(), yp1: tmpList[1].toDouble(), xp2: tmpList[2].toDouble(), yp2: tmpList[3].toDouble()); |
294 | } |
295 | item = m_config->addItemRectF(name: m_name, reference&: *d->newRectF(), defaultValue: defaultRectF, key: m_key); |
296 | } else if (m_type == QLatin1String("size" )) { |
297 | QSize defaultSize; |
298 | const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(',')); |
299 | if (tmpList.size() >= 2) { |
300 | defaultSize.setWidth(tmpList[0].toInt()); |
301 | defaultSize.setHeight(tmpList[1].toInt()); |
302 | } |
303 | item = m_config->addItemSize(name: m_name, reference&: *d->newSize(), defaultValue: defaultSize, key: m_key); |
304 | } else if (m_type == QLatin1String("sizef" )) { |
305 | QSizeF defaultSizeF; |
306 | const auto tmpList = QStringView(m_default).split(sep: u','); |
307 | if (tmpList.size() >= 2) { |
308 | defaultSizeF.setWidth(tmpList[0].toDouble()); |
309 | defaultSizeF.setHeight(tmpList[1].toDouble()); |
310 | } |
311 | item = m_config->addItemSizeF(name: m_name, reference&: *d->newSizeF(), defaultValue: defaultSizeF, key: m_key); |
312 | } else if (m_type == QLatin1String("ulonglong" )) { |
313 | KConfigSkeleton::ItemULongLong *ulonglongItem = m_config->addItemULongLong(name: m_name, reference&: *d->newULongLong(), defaultValue: m_default.toULongLong(), key: m_key); |
314 | if (m_haveMin) { |
315 | ulonglongItem->setMinValue(m_min); |
316 | } |
317 | if (m_haveMax) { |
318 | ulonglongItem->setMaxValue(m_max); |
319 | } |
320 | item = ulonglongItem; |
321 | /* No addItemUrlList in KConfigSkeleton ? |
322 | } else if (m_type == "urllist") { |
323 | //FIXME: the split() is naive and will break on lists with ,'s in them |
324 | QStringList tmpList = m_default.split(","); |
325 | QList<QUrl> defaultList; |
326 | foreach (const QString& tmp, tmpList) { |
327 | defaultList.append(QUrl(tmp)); |
328 | } |
329 | item = m_config->addItemUrlList(m_name, *d->newUrlList(), defaultList, m_key);*/ |
330 | } |
331 | |
332 | if (item) { |
333 | item->setLabel(m_label); |
334 | item->setWhatsThis(m_whatsThis); |
335 | d->keysToNames.insert(key: item->group() + item->key(), value: item->name()); |
336 | } |
337 | } |
338 | |
339 | void ConfigLoaderHandler::resetState() |
340 | { |
341 | m_haveMin = false; |
342 | m_min = 0; |
343 | m_haveMax = false; |
344 | m_max = 0; |
345 | m_name.clear(); |
346 | m_type.clear(); |
347 | m_label.clear(); |
348 | m_default.clear(); |
349 | m_key.clear(); |
350 | m_whatsThis.clear(); |
351 | m_enumChoices.clear(); |
352 | m_inChoice = false; |
353 | } |
354 | |
355 | KConfigLoader::KConfigLoader(const QString &configFile, QIODevice *xml, QObject *parent) |
356 | : KConfigSkeleton(configFile, parent) |
357 | , d(new ConfigLoaderPrivate) |
358 | { |
359 | d->parse(loader: this, xml); |
360 | } |
361 | |
362 | KConfigLoader::KConfigLoader(KSharedConfigPtr config, QIODevice *xml, QObject *parent) |
363 | : KConfigSkeleton(std::move(config), parent) |
364 | , d(new ConfigLoaderPrivate) |
365 | { |
366 | d->parse(loader: this, xml); |
367 | } |
368 | |
369 | // FIXME: obviously this is broken and should be using the group as the root, |
370 | // but KConfigSkeleton does not currently support this. it will eventually though, |
371 | // at which point this can be addressed properly |
372 | KConfigLoader::KConfigLoader(const KConfigGroup &config, QIODevice *xml, QObject *parent) |
373 | : KConfigSkeleton(KSharedConfig::openConfig(fileName: config.config()->name(), mode: config.config()->openFlags(), type: config.config()->locationType()), parent) |
374 | , d(new ConfigLoaderPrivate) |
375 | { |
376 | KConfigGroup group = config.parent(); |
377 | d->baseGroup = config.name(); |
378 | while (group.isValid() && group.name() != QLatin1String("<default>" )) { |
379 | d->baseGroup = group.name() + QLatin1Char('\x1d') + d->baseGroup; |
380 | group = group.parent(); |
381 | } |
382 | d->parse(loader: this, xml); |
383 | } |
384 | |
385 | KConfigLoader::~KConfigLoader() |
386 | { |
387 | delete d; |
388 | } |
389 | |
390 | KConfigSkeletonItem *KConfigLoader::findItem(const QString &group, const QString &key) const |
391 | { |
392 | return KConfigSkeleton::findItem(name: d->keysToNames[group + key]); |
393 | } |
394 | |
395 | KConfigSkeletonItem *KConfigLoader::findItemByName(const QString &name) const |
396 | { |
397 | return KConfigSkeleton::findItem(name); |
398 | } |
399 | |
400 | QVariant KConfigLoader::property(const QString &name) const |
401 | { |
402 | KConfigSkeletonItem *item = KConfigSkeleton::findItem(name); |
403 | |
404 | if (item) { |
405 | return item->property(); |
406 | } |
407 | |
408 | return QVariant(); |
409 | } |
410 | |
411 | bool KConfigLoader::hasGroup(const QString &group) const |
412 | { |
413 | return d->groups.contains(str: group); |
414 | } |
415 | |
416 | QStringList KConfigLoader::groupList() const |
417 | { |
418 | return d->groups; |
419 | } |
420 | |
421 | bool KConfigLoader::usrSave() |
422 | { |
423 | if (d->saveDefaults) { |
424 | const auto listItems = items(); |
425 | for (const auto &item : listItems) { |
426 | config()->group(group: item->group()).writeEntry(key: item->key(), value: "" ); |
427 | } |
428 | } |
429 | return true; |
430 | } |
431 | |