File indexing completed on 2024-05-12 15:34:13

0001 /*
0002     SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kconfigloader.h"
0008 #include "kconfigloader_p.h"
0009 #include "kconfigloaderhandler_p.h"
0010 
0011 #include <QColor>
0012 #include <QFont>
0013 #include <QHash>
0014 #include <QUrl>
0015 
0016 #include <QDebug>
0017 
0018 void ConfigLoaderPrivate::parse(KConfigLoader *loader, QIODevice *xml)
0019 {
0020     clearData();
0021     loader->clearItems();
0022 
0023     if (xml) {
0024         ConfigLoaderHandler handler(loader, this);
0025         handler.parse(xml);
0026     }
0027 }
0028 
0029 ConfigLoaderHandler::ConfigLoaderHandler(KConfigLoader *config, ConfigLoaderPrivate *d)
0030     : m_config(config)
0031     , d(d)
0032 {
0033     resetState();
0034 }
0035 
0036 bool ConfigLoaderHandler::parse(QIODevice *input)
0037 {
0038     if (!input->open(QIODevice::ReadOnly)) {
0039         qWarning() << "Impossible to open device";
0040         return false;
0041     }
0042     QXmlStreamReader reader(input);
0043 
0044     while (!reader.atEnd()) {
0045         reader.readNext();
0046         if (reader.hasError()) {
0047             return false;
0048         }
0049 
0050         switch (reader.tokenType()) {
0051         case QXmlStreamReader::StartElement:
0052             startElement(reader.name(), reader.attributes());
0053             break;
0054         case QXmlStreamReader::EndElement:
0055             endElement(reader.name());
0056             break;
0057         case QXmlStreamReader::Characters:
0058             if (!reader.isWhitespace() && !reader.text().trimmed().isEmpty()) {
0059                 m_cdata.append(reader.text());
0060             }
0061             break;
0062         default:
0063             break;
0064         }
0065     }
0066 
0067     if (!reader.isEndDocument()) {
0068         return false;
0069     }
0070 
0071     return true;
0072 }
0073 
0074 static bool caseInsensitiveCompare(const QStringView a, const QLatin1String b)
0075 {
0076     return a.compare(b, Qt::CaseInsensitive) == 0;
0077 }
0078 
0079 void ConfigLoaderHandler::startElement(const QStringView localName, const QXmlStreamAttributes &attrs)
0080 {
0081     // qDebug() << "ConfigLoaderHandler::startElement(" << localName << qName;
0082     if (caseInsensitiveCompare(localName, QLatin1String("group"))) {
0083         QString group;
0084         for (const auto &attr : attrs) {
0085             const auto attrName = attr.name();
0086             if (caseInsensitiveCompare(attrName, QLatin1String("name"))) {
0087                 // qDebug() << "set group to" << attrs.value(i);
0088                 group = attr.value().toString();
0089             }
0090         }
0091         if (group.isEmpty()) {
0092             group = d->baseGroup;
0093         } else {
0094             d->groups.append(group);
0095             if (!d->baseGroup.isEmpty()) {
0096                 group = d->baseGroup + QLatin1Char('\x1d') + group;
0097             }
0098         }
0099 
0100         if (m_config) {
0101             m_config->setCurrentGroup(group);
0102         }
0103     } else if (caseInsensitiveCompare(localName, QLatin1String("entry"))) {
0104         for (const auto &attr : attrs) {
0105             const auto attrName = attr.name();
0106             if (caseInsensitiveCompare(attrName, QLatin1String("name"))) {
0107                 m_name = attr.value().trimmed().toString();
0108             } else if (caseInsensitiveCompare(attrName, QLatin1String("type"))) {
0109                 m_type = attr.value().toString().toLower();
0110             } else if (caseInsensitiveCompare(attrName, QLatin1String("key"))) {
0111                 m_key = attr.value().trimmed().toString();
0112             }
0113         }
0114     } else if (caseInsensitiveCompare(localName, QLatin1String("choice"))) {
0115         m_choice.name.clear();
0116         m_choice.label.clear();
0117         m_choice.whatsThis.clear();
0118         for (const auto &attr : attrs) {
0119             const auto attrName = attr.name();
0120             if (caseInsensitiveCompare(attrName, QLatin1String("name"))) {
0121                 m_choice.name = attr.value().toString();
0122             }
0123         }
0124         m_inChoice = true;
0125     }
0126 }
0127 
0128 void ConfigLoaderHandler::endElement(const QStringView localName)
0129 {
0130     //     qDebug() << "ConfigLoaderHandler::endElement(" << localName << qName;
0131     if (caseInsensitiveCompare(localName, QLatin1String("entry"))) {
0132         addItem();
0133         resetState();
0134     } else if (caseInsensitiveCompare(localName, QLatin1String("label"))) {
0135         if (m_inChoice) {
0136             m_choice.label = m_cdata.trimmed();
0137         } else {
0138             m_label = m_cdata.trimmed();
0139         }
0140     } else if (caseInsensitiveCompare(localName, QLatin1String("whatsthis"))) {
0141         if (m_inChoice) {
0142             m_choice.whatsThis = m_cdata.trimmed();
0143         } else {
0144             m_whatsThis = m_cdata.trimmed();
0145         }
0146     } else if (caseInsensitiveCompare(localName, QLatin1String("default"))) {
0147         m_default = m_cdata.trimmed();
0148     } else if (caseInsensitiveCompare(localName, QLatin1String("min"))) {
0149         m_min = m_cdata.toInt(&m_haveMin);
0150     } else if (caseInsensitiveCompare(localName, QLatin1String("max"))) {
0151         m_max = m_cdata.toInt(&m_haveMax);
0152     } else if (caseInsensitiveCompare(localName, QLatin1String("choice"))) {
0153         m_enumChoices.append(m_choice);
0154         m_inChoice = false;
0155     }
0156 
0157     m_cdata.clear();
0158 }
0159 
0160 void ConfigLoaderHandler::addItem()
0161 {
0162     if (m_name.isEmpty()) {
0163         if (m_key.isEmpty()) {
0164             return;
0165         }
0166 
0167         m_name = m_key;
0168     }
0169 
0170     m_name.remove(QLatin1Char(' '));
0171 
0172     KConfigSkeletonItem *item = nullptr;
0173 
0174     if (m_type == QLatin1String("bool")) {
0175         const bool defaultValue = caseInsensitiveCompare(m_default, QLatin1String("true"));
0176         item = m_config->addItemBool(m_name, *d->newBool(), defaultValue, m_key);
0177     } else if (m_type == QLatin1String("color")) {
0178         item = m_config->addItemColor(m_name, *d->newColor(), QColor(m_default), m_key);
0179     } else if (m_type == QLatin1String("datetime")) {
0180         item = m_config->addItemDateTime(m_name, *d->newDateTime(), QDateTime::fromString(m_default), m_key);
0181     } else if (m_type == QLatin1String("enum")) {
0182         m_key = (m_key.isEmpty()) ? m_name : m_key;
0183         KConfigSkeleton::ItemEnum *enumItem = new KConfigSkeleton::ItemEnum(m_config->currentGroup(), m_key, *d->newInt(), m_enumChoices, m_default.toUInt());
0184         m_config->addItem(enumItem, m_name);
0185         item = enumItem;
0186     } else if (m_type == QLatin1String("font")) {
0187         item = m_config->addItemFont(m_name, *d->newFont(), QFont(m_default), m_key);
0188     } else if (m_type == QLatin1String("int")) {
0189         KConfigSkeleton::ItemInt *intItem = m_config->addItemInt(m_name, *d->newInt(), m_default.toInt(), m_key);
0190 
0191         if (m_haveMin) {
0192             intItem->setMinValue(m_min);
0193         }
0194 
0195         if (m_haveMax) {
0196             intItem->setMaxValue(m_max);
0197         }
0198 
0199         item = intItem;
0200     } else if (m_type == QLatin1String("password")) {
0201         item = m_config->addItemPassword(m_name, *d->newString(), m_default, m_key);
0202     } else if (m_type == QLatin1String("path")) {
0203         item = m_config->addItemPath(m_name, *d->newString(), m_default, m_key);
0204     } else if (m_type == QLatin1String("string")) {
0205         item = m_config->addItemString(m_name, *d->newString(), m_default, m_key);
0206     } else if (m_type == QLatin1String("stringlist")) {
0207         // FIXME: the split() is naive and will break on lists with ,'s in them
0208         // empty parts are not wanted in this case
0209         item = m_config->addItemStringList(m_name, *d->newStringList(), m_default.split(QLatin1Char(','), Qt::SkipEmptyParts), m_key);
0210     } else if (m_type == QLatin1String("uint")) {
0211         KConfigSkeleton::ItemUInt *uintItem = m_config->addItemUInt(m_name, *d->newUint(), m_default.toUInt(), m_key);
0212         if (m_haveMin) {
0213             uintItem->setMinValue(m_min);
0214         }
0215         if (m_haveMax) {
0216             uintItem->setMaxValue(m_max);
0217         }
0218         item = uintItem;
0219     } else if (m_type == QLatin1String("url")) {
0220         m_key = (m_key.isEmpty()) ? m_name : m_key;
0221         KConfigSkeleton::ItemUrl *urlItem = new KConfigSkeleton::ItemUrl(m_config->currentGroup(), m_key, *d->newUrl(), QUrl::fromUserInput(m_default));
0222         m_config->addItem(urlItem, m_name);
0223         item = urlItem;
0224     } else if (m_type == QLatin1String("double")) {
0225         KConfigSkeleton::ItemDouble *doubleItem = m_config->addItemDouble(m_name, *d->newDouble(), m_default.toDouble(), m_key);
0226         if (m_haveMin) {
0227             doubleItem->setMinValue(m_min);
0228         }
0229         if (m_haveMax) {
0230             doubleItem->setMaxValue(m_max);
0231         }
0232         item = doubleItem;
0233     } else if (m_type == QLatin1String("intlist")) {
0234         QList<int> defaultList;
0235         const QStringList tmpList = m_default.split(QLatin1Char(','), Qt::SkipEmptyParts);
0236         for (const QString &tmp : tmpList) {
0237             defaultList.append(tmp.toInt());
0238         }
0239         item = m_config->addItemIntList(m_name, *d->newIntList(), defaultList, m_key);
0240     } else if (m_type == QLatin1String("longlong")) {
0241         KConfigSkeleton::ItemLongLong *longlongItem = m_config->addItemLongLong(m_name, *d->newLongLong(), m_default.toLongLong(), m_key);
0242         if (m_haveMin) {
0243             longlongItem->setMinValue(m_min);
0244         }
0245         if (m_haveMax) {
0246             longlongItem->setMaxValue(m_max);
0247         }
0248         item = longlongItem;
0249         /* No addItemPathList in KConfigSkeleton ?
0250         } else if (m_type == "PathList") {
0251             //FIXME: the split() is naive and will break on lists with ,'s in them
0252             item = m_config->addItemPathList(m_name, *d->newStringList(), m_default.split(","), m_key);
0253         */
0254     } else if (m_type == QLatin1String("point")) {
0255         QPoint defaultPoint;
0256         const QStringList tmpList = m_default.split(QLatin1Char(','));
0257         if (tmpList.size() >= 2) {
0258             defaultPoint.setX(tmpList[0].toInt());
0259             defaultPoint.setY(tmpList[1].toInt());
0260         }
0261         item = m_config->addItemPoint(m_name, *d->newPoint(), defaultPoint, m_key);
0262     } else if (m_type == QLatin1String("rect")) {
0263         QRect defaultRect;
0264         const QStringList tmpList = m_default.split(QLatin1Char(','));
0265         if (tmpList.size() >= 4) {
0266             defaultRect.setCoords(tmpList[0].toInt(), tmpList[1].toInt(), tmpList[2].toInt(), tmpList[3].toInt());
0267         }
0268         item = m_config->addItemRect(m_name, *d->newRect(), defaultRect, m_key);
0269     } else if (m_type == QLatin1String("size")) {
0270         QSize defaultSize;
0271         const QStringList tmpList = m_default.split(QLatin1Char(','));
0272         if (tmpList.size() >= 2) {
0273             defaultSize.setWidth(tmpList[0].toInt());
0274             defaultSize.setHeight(tmpList[1].toInt());
0275         }
0276         item = m_config->addItemSize(m_name, *d->newSize(), defaultSize, m_key);
0277     } else if (m_type == QLatin1String("ulonglong")) {
0278         KConfigSkeleton::ItemULongLong *ulonglongItem = m_config->addItemULongLong(m_name, *d->newULongLong(), m_default.toULongLong(), m_key);
0279         if (m_haveMin) {
0280             ulonglongItem->setMinValue(m_min);
0281         }
0282         if (m_haveMax) {
0283             ulonglongItem->setMaxValue(m_max);
0284         }
0285         item = ulonglongItem;
0286         /* No addItemUrlList in KConfigSkeleton ?
0287         } else if (m_type == "urllist") {
0288             //FIXME: the split() is naive and will break on lists with ,'s in them
0289             QStringList tmpList = m_default.split(",");
0290             QList<QUrl> defaultList;
0291             foreach (const QString& tmp, tmpList) {
0292                 defaultList.append(QUrl(tmp));
0293             }
0294             item = m_config->addItemUrlList(m_name, *d->newUrlList(), defaultList, m_key);*/
0295     }
0296 
0297     if (item) {
0298         item->setLabel(m_label);
0299         item->setWhatsThis(m_whatsThis);
0300         d->keysToNames.insert(item->group() + item->key(), item->name());
0301     }
0302 }
0303 
0304 void ConfigLoaderHandler::resetState()
0305 {
0306     m_haveMin = false;
0307     m_min = 0;
0308     m_haveMax = false;
0309     m_max = 0;
0310     m_name.clear();
0311     m_type.clear();
0312     m_label.clear();
0313     m_default.clear();
0314     m_key.clear();
0315     m_whatsThis.clear();
0316     m_enumChoices.clear();
0317     m_inChoice = false;
0318 }
0319 
0320 KConfigLoader::KConfigLoader(const QString &configFile, QIODevice *xml, QObject *parent)
0321     : KConfigSkeleton(configFile, parent)
0322     , d(new ConfigLoaderPrivate)
0323 {
0324     d->parse(this, xml);
0325 }
0326 
0327 KConfigLoader::KConfigLoader(KSharedConfigPtr config, QIODevice *xml, QObject *parent)
0328     : KConfigSkeleton(std::move(config), parent)
0329     , d(new ConfigLoaderPrivate)
0330 {
0331     d->parse(this, xml);
0332 }
0333 
0334 // FIXME: obviously this is broken and should be using the group as the root,
0335 //       but KConfigSkeleton does not currently support this. it will eventually though,
0336 //       at which point this can be addressed properly
0337 KConfigLoader::KConfigLoader(const KConfigGroup &config, QIODevice *xml, QObject *parent)
0338     : KConfigSkeleton(KSharedConfig::openConfig(config.config()->name(), config.config()->openFlags(), config.config()->locationType()), parent)
0339     , d(new ConfigLoaderPrivate)
0340 {
0341     KConfigGroup group = config.parent();
0342     d->baseGroup = config.name();
0343     while (group.isValid() && group.name() != QLatin1String("<default>")) {
0344         d->baseGroup = group.name() + QLatin1Char('\x1d') + d->baseGroup;
0345         group = group.parent();
0346     }
0347     d->parse(this, xml);
0348 }
0349 
0350 KConfigLoader::~KConfigLoader()
0351 {
0352     delete d;
0353 }
0354 
0355 KConfigSkeletonItem *KConfigLoader::findItem(const QString &group, const QString &key) const
0356 {
0357     return KConfigSkeleton::findItem(d->keysToNames[group + key]);
0358 }
0359 
0360 KConfigSkeletonItem *KConfigLoader::findItemByName(const QString &name) const
0361 {
0362     return KConfigSkeleton::findItem(name);
0363 }
0364 
0365 QVariant KConfigLoader::property(const QString &name) const
0366 {
0367     KConfigSkeletonItem *item = KConfigSkeleton::findItem(name);
0368 
0369     if (item) {
0370         return item->property();
0371     }
0372 
0373     return QVariant();
0374 }
0375 
0376 bool KConfigLoader::hasGroup(const QString &group) const
0377 {
0378     return d->groups.contains(group);
0379 }
0380 
0381 QStringList KConfigLoader::groupList() const
0382 {
0383     return d->groups;
0384 }
0385 
0386 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 0)
0387 bool KConfigLoader::usrWriteConfig()
0388 #else
0389 bool KConfigLoader::usrSave()
0390 #endif
0391 {
0392     if (d->saveDefaults) {
0393         const auto listItems = items();
0394         for (const auto &item : listItems) {
0395             config()->group(item->group()).writeEntry(item->key(), "");
0396         }
0397     }
0398     return true;
0399 }