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("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 | |
341 | void 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 | |
357 | KConfigLoader::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 | |
364 | KConfigLoader::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 |
374 | KConfigLoader::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 | |
387 | KConfigLoader::~KConfigLoader() |
388 | { |
389 | delete d; |
390 | } |
391 | |
392 | KConfigSkeletonItem *KConfigLoader::findItem(const QString &group, const QString &key) const |
393 | { |
394 | return KConfigSkeleton::findItem(name: d->keysToNames[group + key]); |
395 | } |
396 | |
397 | KConfigSkeletonItem *KConfigLoader::findItemByName(const QString &name) const |
398 | { |
399 | return KConfigSkeleton::findItem(name); |
400 | } |
401 | |
402 | QVariant 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 | |
413 | bool KConfigLoader::hasGroup(const QString &group) const |
414 | { |
415 | return d->groups.contains(str: group); |
416 | } |
417 | |
418 | QStringList KConfigLoader::groupList() const |
419 | { |
420 | return d->groups; |
421 | } |
422 | |
423 | bool 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 | |