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
18void 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
29ConfigLoaderHandler::ConfigLoaderHandler(KConfigLoader *config, ConfigLoaderPrivate *d)
30 : m_config(config)
31 , d(d)
32{
33 resetState();
34}
35
36bool 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
74static bool caseInsensitiveCompare(const QStringView a, const QLatin1String b)
75{
76 return a.compare(s: b, cs: Qt::CaseInsensitive) == 0;
77}
78
79void 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
128void 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
160void 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("time")) {
182 item = m_config->addItemTime(name: m_name, reference&: *d->newTime(), defaultValue: QTime::fromString(string: m_default), key: m_key);
183 } else if (m_type == QLatin1String("enum")) {
184 m_key = (m_key.isEmpty()) ? m_name : m_key;
185
186 bool ok = false;
187 int defaultValue = m_default.toInt(ok: &ok);
188 if (!ok) {
189 for (int i = 0; i < m_enumChoices.size(); i++) {
190 if (m_default == m_enumChoices[i].name) {
191 defaultValue = i;
192 break;
193 }
194 }
195 }
196
197 KConfigSkeleton::ItemEnum *enumItem = new KConfigSkeleton::ItemEnum(m_config->currentGroup(), m_key, *d->newInt(), m_enumChoices, defaultValue);
198 m_config->addItem(item: enumItem, name: m_name);
199 item = enumItem;
200 } else if (m_type == QLatin1String("font")) {
201 item = m_config->addItemFont(name: m_name, reference&: *d->newFont(), defaultValue: QFont(m_default), key: m_key);
202 } else if (m_type == QLatin1String("int")) {
203 KConfigSkeleton::ItemInt *intItem = m_config->addItemInt(name: m_name, reference&: *d->newInt(), defaultValue: m_default.toInt(), key: m_key);
204
205 if (m_haveMin) {
206 intItem->setMinValue(m_min);
207 }
208
209 if (m_haveMax) {
210 intItem->setMaxValue(m_max);
211 }
212
213 item = intItem;
214 } else if (m_type == QLatin1String("password")) {
215 item = m_config->addItemPassword(name: m_name, reference&: *d->newString(), defaultValue: m_default, key: m_key);
216 } else if (m_type == QLatin1String("path")) {
217 item = m_config->addItemPath(name: m_name, reference&: *d->newString(), defaultValue: m_default, key: m_key);
218 } else if (m_type == QLatin1String("string")) {
219 item = m_config->addItemString(name: m_name, reference&: *d->newString(), defaultValue: m_default, key: m_key);
220 } else if (m_type == QLatin1String("stringlist")) {
221 // FIXME: the split() is naive and will break on lists with ,'s in them
222 // empty parts are not wanted in this case
223 item = m_config->addItemStringList(name: m_name, reference&: *d->newStringList(), defaultValue: m_default.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts), key: m_key);
224 } else if (m_type == QLatin1String("uint")) {
225 KConfigSkeleton::ItemUInt *uintItem = m_config->addItemUInt(name: m_name, reference&: *d->newUint(), defaultValue: m_default.toUInt(), key: m_key);
226 if (m_haveMin) {
227 uintItem->setMinValue(m_min);
228 }
229 if (m_haveMax) {
230 uintItem->setMaxValue(m_max);
231 }
232 item = uintItem;
233 } else if (m_type == QLatin1String("url")) {
234 m_key = (m_key.isEmpty()) ? m_name : m_key;
235 KConfigSkeleton::ItemUrl *urlItem = new KConfigSkeleton::ItemUrl(m_config->currentGroup(), m_key, *d->newUrl(), QUrl::fromUserInput(userInput: m_default));
236 m_config->addItem(item: urlItem, name: m_name);
237 item = urlItem;
238 } else if (m_type == QLatin1String("double")) {
239 KConfigSkeleton::ItemDouble *doubleItem = m_config->addItemDouble(name: m_name, reference&: *d->newDouble(), defaultValue: m_default.toDouble(), key: m_key);
240 if (m_haveMin) {
241 doubleItem->setMinValue(m_min);
242 }
243 if (m_haveMax) {
244 doubleItem->setMaxValue(m_max);
245 }
246 item = doubleItem;
247 } else if (m_type == QLatin1String("intlist")) {
248 QList<int> defaultList;
249 const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
250 for (const QStringView tmp : tmpList) {
251 defaultList.append(t: tmp.toInt());
252 }
253 item = m_config->addItemIntList(name: m_name, reference&: *d->newIntList(), defaultValue: defaultList, key: m_key);
254 } else if (m_type == QLatin1String("longlong")) {
255 KConfigSkeleton::ItemLongLong *longlongItem = m_config->addItemLongLong(name: m_name, reference&: *d->newLongLong(), defaultValue: m_default.toLongLong(), key: m_key);
256 if (m_haveMin) {
257 longlongItem->setMinValue(m_min);
258 }
259 if (m_haveMax) {
260 longlongItem->setMaxValue(m_max);
261 }
262 item = longlongItem;
263 /* No addItemPathList in KConfigSkeleton ?
264 } else if (m_type == "PathList") {
265 //FIXME: the split() is naive and will break on lists with ,'s in them
266 item = m_config->addItemPathList(m_name, *d->newStringList(), m_default.split(","), m_key);
267 */
268 } else if (m_type == QLatin1String("point")) {
269 QPoint defaultPoint;
270 const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(','));
271 if (tmpList.size() >= 2) {
272 defaultPoint.setX(tmpList[0].toInt());
273 defaultPoint.setY(tmpList[1].toInt());
274 }
275 item = m_config->addItemPoint(name: m_name, reference&: *d->newPoint(), defaultValue: defaultPoint, key: m_key);
276 } else if (m_type == QLatin1String("pointf")) {
277 QPointF defaultPointF;
278 const auto tmpList = QStringView(m_default).split(sep: u',');
279 if (tmpList.size() >= 2) {
280 defaultPointF.setX(tmpList[0].toDouble());
281 defaultPointF.setY(tmpList[1].toDouble());
282 }
283 item = m_config->addItemPointF(name: m_name, reference&: *d->newPointF(), defaultValue: defaultPointF, key: m_key);
284 } else if (m_type == QLatin1String("rect")) {
285 QRect defaultRect;
286 const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(','));
287 if (tmpList.size() >= 4) {
288 defaultRect.setCoords(xp1: tmpList[0].toInt(), yp1: tmpList[1].toInt(), xp2: tmpList[2].toInt(), yp2: tmpList[3].toInt());
289 }
290 item = m_config->addItemRect(name: m_name, reference&: *d->newRect(), defaultValue: defaultRect, key: m_key);
291 } else if (m_type == QLatin1String("rectf")) {
292 QRectF defaultRectF;
293 const auto tmpList = QStringView(m_default).split(sep: u',');
294 if (tmpList.size() >= 4) {
295 defaultRectF.setCoords(xp1: tmpList[0].toDouble(), yp1: tmpList[1].toDouble(), xp2: tmpList[2].toDouble(), yp2: tmpList[3].toDouble());
296 }
297 item = m_config->addItemRectF(name: m_name, reference&: *d->newRectF(), defaultValue: defaultRectF, key: m_key);
298 } else if (m_type == QLatin1String("size")) {
299 QSize defaultSize;
300 const QList<QStringView> tmpList = QStringView(m_default).split(sep: QLatin1Char(','));
301 if (tmpList.size() >= 2) {
302 defaultSize.setWidth(tmpList[0].toInt());
303 defaultSize.setHeight(tmpList[1].toInt());
304 }
305 item = m_config->addItemSize(name: m_name, reference&: *d->newSize(), defaultValue: defaultSize, key: m_key);
306 } else if (m_type == QLatin1String("sizef")) {
307 QSizeF defaultSizeF;
308 const auto tmpList = QStringView(m_default).split(sep: u',');
309 if (tmpList.size() >= 2) {
310 defaultSizeF.setWidth(tmpList[0].toDouble());
311 defaultSizeF.setHeight(tmpList[1].toDouble());
312 }
313 item = m_config->addItemSizeF(name: m_name, reference&: *d->newSizeF(), defaultValue: defaultSizeF, key: m_key);
314 } else if (m_type == QLatin1String("ulonglong")) {
315 KConfigSkeleton::ItemULongLong *ulonglongItem = m_config->addItemULongLong(name: m_name, reference&: *d->newULongLong(), defaultValue: m_default.toULongLong(), key: m_key);
316 if (m_haveMin) {
317 ulonglongItem->setMinValue(m_min);
318 }
319 if (m_haveMax) {
320 ulonglongItem->setMaxValue(m_max);
321 }
322 item = ulonglongItem;
323 /* No addItemUrlList in KConfigSkeleton ?
324 } else if (m_type == "urllist") {
325 //FIXME: the split() is naive and will break on lists with ,'s in them
326 QStringList tmpList = m_default.split(",");
327 QList<QUrl> defaultList;
328 foreach (const QString& tmp, tmpList) {
329 defaultList.append(QUrl(tmp));
330 }
331 item = m_config->addItemUrlList(m_name, *d->newUrlList(), defaultList, m_key);*/
332 }
333
334 if (item) {
335 item->setLabel(m_label);
336 item->setWhatsThis(m_whatsThis);
337 d->keysToNames.insert(key: item->group() + item->key(), value: item->name());
338 }
339}
340
341void ConfigLoaderHandler::resetState()
342{
343 m_haveMin = false;
344 m_min = 0;
345 m_haveMax = false;
346 m_max = 0;
347 m_name.clear();
348 m_type.clear();
349 m_label.clear();
350 m_default.clear();
351 m_key.clear();
352 m_whatsThis.clear();
353 m_enumChoices.clear();
354 m_inChoice = false;
355}
356
357KConfigLoader::KConfigLoader(const QString &configFile, QIODevice *xml, QObject *parent)
358 : KConfigSkeleton(configFile, parent)
359 , d(new ConfigLoaderPrivate)
360{
361 d->parse(loader: this, xml);
362}
363
364KConfigLoader::KConfigLoader(KSharedConfigPtr config, QIODevice *xml, QObject *parent)
365 : KConfigSkeleton(std::move(config), parent)
366 , d(new ConfigLoaderPrivate)
367{
368 d->parse(loader: this, xml);
369}
370
371// FIXME: obviously this is broken and should be using the group as the root,
372// but KConfigSkeleton does not currently support this. it will eventually though,
373// at which point this can be addressed properly
374KConfigLoader::KConfigLoader(const KConfigGroup &config, QIODevice *xml, QObject *parent)
375 : KConfigSkeleton(KSharedConfig::openConfig(fileName: config.config()->name(), mode: config.config()->openFlags(), type: config.config()->locationType()), parent)
376 , d(new ConfigLoaderPrivate)
377{
378 KConfigGroup group = config.parent();
379 d->baseGroup = config.name();
380 while (group.isValid() && group.name() != QLatin1String("<default>")) {
381 d->baseGroup = group.name() + QLatin1Char('\x1d') + d->baseGroup;
382 group = group.parent();
383 }
384 d->parse(loader: this, xml);
385}
386
387KConfigLoader::~KConfigLoader()
388{
389 delete d;
390}
391
392KConfigSkeletonItem *KConfigLoader::findItem(const QString &group, const QString &key) const
393{
394 return KConfigSkeleton::findItem(name: d->keysToNames[group + key]);
395}
396
397KConfigSkeletonItem *KConfigLoader::findItemByName(const QString &name) const
398{
399 return KConfigSkeleton::findItem(name);
400}
401
402QVariant KConfigLoader::property(const QString &name) const
403{
404 KConfigSkeletonItem *item = KConfigSkeleton::findItem(name);
405
406 if (item) {
407 return item->property();
408 }
409
410 return QVariant();
411}
412
413bool KConfigLoader::hasGroup(const QString &group) const
414{
415 return d->groups.contains(str: group);
416}
417
418QStringList KConfigLoader::groupList() const
419{
420 return d->groups;
421}
422
423bool KConfigLoader::usrSave()
424{
425 if (d->saveDefaults) {
426 const auto listItems = items();
427 for (const auto &item : listItems) {
428 config()->group(group: item->group()).writeEntry(key: item->key(), value: "");
429 }
430 }
431 return true;
432}
433

source code of kconfig/src/gui/kconfigloader.cpp