| 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 | |