File indexing completed on 2025-01-05 04:47:12
0001 /* 0002 SPDX-FileCopyrightText: 2009 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "xmldocument.h" 0008 #include "format_p.h" 0009 #include "xmlreader.h" 0010 0011 #include <KLocalizedString> 0012 0013 #include <QFile> 0014 #include <qdom.h> 0015 0016 #ifdef HAVE_LIBXML2 0017 #include <QStandardPaths> 0018 #include <libxml/parser.h> 0019 #include <libxml/xmlIO.h> 0020 #include <libxml/xmlschemas.h> 0021 #endif 0022 0023 using namespace Akonadi; 0024 0025 // helper class for dealing with libxml resource management 0026 template<typename T, void FreeFunc(T)> 0027 class XmlPtr 0028 { 0029 public: 0030 explicit XmlPtr(const T &t) 0031 : p(t) 0032 { 0033 } 0034 0035 ~XmlPtr() 0036 { 0037 FreeFunc(p); 0038 } 0039 0040 operator T() const // NOLINT(google-explicit-constructor) 0041 { 0042 return p; 0043 } 0044 0045 explicit operator bool() const 0046 { 0047 return p != nullptr; 0048 } 0049 0050 private: 0051 Q_DISABLE_COPY(XmlPtr) 0052 T p; 0053 }; 0054 0055 static QDomElement findElementByRidHelper(const QDomElement &elem, const QString &rid, const QString &elemName) 0056 { 0057 if (elem.isNull()) { 0058 return QDomElement(); 0059 } 0060 if (elem.tagName() == elemName && elem.attribute(Format::Attr::remoteId()) == rid) { 0061 return elem; 0062 } 0063 const QDomNodeList children = elem.childNodes(); 0064 for (int i = 0; i < children.count(); ++i) { 0065 const QDomElement child = children.at(i).toElement(); 0066 if (child.isNull()) { 0067 continue; 0068 } 0069 const QDomElement rv = findElementByRidHelper(child, rid, elemName); 0070 if (!rv.isNull()) { 0071 return rv; 0072 } 0073 } 0074 return QDomElement(); 0075 } 0076 0077 namespace Akonadi 0078 { 0079 class XmlDocumentPrivate 0080 { 0081 public: 0082 XmlDocumentPrivate() 0083 : lastError(i18n("No data loaded.")) 0084 , valid(false) 0085 { 0086 } 0087 0088 QDomElement findElementByRid(const QString &rid, const QString &elemName) const 0089 { 0090 return findElementByRidHelper(document.documentElement(), rid, elemName); 0091 } 0092 0093 QDomDocument document; 0094 QString lastError; 0095 bool valid; 0096 }; 0097 0098 } // namespace Akonadi 0099 0100 XmlDocument::XmlDocument() 0101 : d(new XmlDocumentPrivate) 0102 { 0103 const QDomElement rootElem = d->document.createElement(Format::Tag::root()); 0104 d->document.appendChild(rootElem); 0105 } 0106 0107 XmlDocument::XmlDocument(const QString &fileName, const QString &xsdFile) 0108 : d(new XmlDocumentPrivate) 0109 { 0110 loadFile(fileName, xsdFile); 0111 } 0112 0113 XmlDocument::~XmlDocument() = default; 0114 0115 bool Akonadi::XmlDocument::loadFile(const QString &fileName, const QString &xsdFile) 0116 { 0117 d->valid = false; 0118 d->document = QDomDocument(); 0119 0120 if (fileName.isEmpty()) { 0121 d->lastError = i18n("No filename specified"); 0122 return false; 0123 } 0124 0125 QFile file(fileName); 0126 QByteArray data; 0127 if (file.exists()) { 0128 if (!file.open(QIODevice::ReadOnly)) { 0129 d->lastError = i18n("Unable to open data file '%1'.", fileName); 0130 return false; 0131 } 0132 data = file.readAll(); 0133 } else { 0134 d->lastError = i18n("File %1 does not exist.", fileName); 0135 return false; 0136 } 0137 0138 #ifdef HAVE_LIBXML2 0139 // schema validation 0140 XmlPtr<xmlDocPtr, xmlFreeDoc> sourceDoc(xmlParseMemory(data.constData(), data.length())); 0141 if (!sourceDoc) { 0142 d->lastError = i18n("Unable to parse data file '%1'.", fileName); 0143 return false; 0144 } 0145 0146 const QString &schemaFileName = 0147 xsdFile.isEmpty() ? QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf6/akonadi/akonadi-xml.xsd")) : xsdFile; 0148 const XmlPtr<xmlDocPtr, xmlFreeDoc> schemaDoc(xmlReadFile(schemaFileName.toLocal8Bit().constData(), nullptr, XML_PARSE_NONET)); 0149 if (!schemaDoc) { 0150 d->lastError = i18n("Schema definition could not be loaded and parsed."); 0151 return false; 0152 } 0153 XmlPtr<xmlSchemaParserCtxtPtr, xmlSchemaFreeParserCtxt> parserContext(xmlSchemaNewDocParserCtxt(schemaDoc)); 0154 if (!parserContext) { 0155 d->lastError = i18n("Unable to create schema parser context."); 0156 return false; 0157 } 0158 const XmlPtr<xmlSchemaPtr, xmlSchemaFree> schema(xmlSchemaParse(parserContext)); 0159 if (!schema) { 0160 d->lastError = i18n("Unable to create schema."); 0161 return false; 0162 } 0163 const XmlPtr<xmlSchemaValidCtxtPtr, xmlSchemaFreeValidCtxt> validationContext(xmlSchemaNewValidCtxt(schema)); 0164 if (!validationContext) { 0165 d->lastError = i18n("Unable to create schema validation context."); 0166 return false; 0167 } 0168 0169 if (xmlSchemaValidateDoc(validationContext, sourceDoc) != 0) { 0170 d->lastError = i18n("Invalid file format."); 0171 return false; 0172 } 0173 #endif 0174 0175 // DOM loading 0176 QString errMsg; 0177 if (!d->document.setContent(data, true, &errMsg)) { 0178 d->lastError = i18n("Unable to parse data file: %1", errMsg); 0179 return false; 0180 } 0181 0182 d->valid = true; 0183 d->lastError.clear(); 0184 return true; 0185 } 0186 0187 bool XmlDocument::writeToFile(const QString &fileName) const 0188 { 0189 QFile f(fileName); 0190 if (!f.open(QFile::WriteOnly)) { 0191 d->lastError = f.errorString(); 0192 return false; 0193 } 0194 0195 f.write(d->document.toByteArray(2)); 0196 0197 d->lastError.clear(); 0198 return true; 0199 } 0200 0201 bool XmlDocument::isValid() const 0202 { 0203 return d->valid; 0204 } 0205 0206 QString XmlDocument::lastError() const 0207 { 0208 return d->lastError; 0209 } 0210 0211 QDomDocument &XmlDocument::document() const 0212 { 0213 return d->document; 0214 } 0215 0216 QDomElement XmlDocument::collectionElement(const Collection &collection) const 0217 { 0218 if (collection == Collection::root()) { 0219 return d->document.documentElement(); 0220 } 0221 if (collection.remoteId().isEmpty()) { 0222 return QDomElement(); 0223 } 0224 if (collection.parentCollection().remoteId().isEmpty() && collection.parentCollection() != Collection::root()) { 0225 return d->findElementByRid(collection.remoteId(), Format::Tag::collection()); 0226 } 0227 QDomElement parent = collectionElement(collection.parentCollection()); 0228 if (parent.isNull()) { 0229 return QDomElement(); 0230 } 0231 const QDomNodeList children = parent.childNodes(); 0232 for (int i = 0; i < children.count(); ++i) { 0233 const QDomElement child = children.at(i).toElement(); 0234 if (child.isNull()) { 0235 continue; 0236 } 0237 if (child.tagName() == Format::Tag::collection() && child.attribute(Format::Attr::remoteId()) == collection.remoteId()) { 0238 return child; 0239 } 0240 } 0241 return QDomElement(); 0242 } 0243 0244 QDomElement XmlDocument::itemElementByRemoteId(const QString &rid) const 0245 { 0246 return d->findElementByRid(rid, Format::Tag::item()); 0247 } 0248 0249 QDomElement XmlDocument::collectionElementByRemoteId(const QString &rid) const 0250 { 0251 return d->findElementByRid(rid, Format::Tag::collection()); 0252 } 0253 0254 Collection XmlDocument::collectionByRemoteId(const QString &rid) const 0255 { 0256 const QDomElement elem = d->findElementByRid(rid, Format::Tag::collection()); 0257 return XmlReader::elementToCollection(elem); 0258 } 0259 0260 Item XmlDocument::itemByRemoteId(const QString &rid, bool includePayload) const 0261 { 0262 return XmlReader::elementToItem(itemElementByRemoteId(rid), includePayload); 0263 } 0264 0265 Collection::List XmlDocument::collections() const 0266 { 0267 return XmlReader::readCollections(d->document.documentElement()); 0268 } 0269 0270 Tag::List XmlDocument::tags() const 0271 { 0272 return XmlReader::readTags(d->document.documentElement()); 0273 } 0274 0275 Collection::List XmlDocument::childCollections(const Collection &parentCollection) const 0276 { 0277 QDomElement parentElem = collectionElement(parentCollection); 0278 0279 if (parentElem.isNull()) { 0280 d->lastError = QStringLiteral("Parent node not found."); 0281 return Collection::List(); 0282 } 0283 0284 Collection::List rv; 0285 const QDomNodeList children = parentElem.childNodes(); 0286 for (int i = 0; i < children.count(); ++i) { 0287 const QDomElement childElem = children.at(i).toElement(); 0288 if (childElem.isNull() || childElem.tagName() != Format::Tag::collection()) { 0289 continue; 0290 } 0291 Collection c = XmlReader::elementToCollection(childElem); 0292 c.setParentCollection(parentCollection); 0293 rv.append(c); 0294 } 0295 0296 return rv; 0297 } 0298 0299 Item::List XmlDocument::items(const Akonadi::Collection &collection, bool includePayload) const 0300 { 0301 const QDomElement colElem = collectionElement(collection); 0302 if (colElem.isNull()) { 0303 d->lastError = i18n("Unable to find collection %1", collection.name()); 0304 return Item::List(); 0305 } else { 0306 d->lastError.clear(); 0307 } 0308 0309 Item::List items; 0310 const QDomNodeList children = colElem.childNodes(); 0311 for (int i = 0; i < children.count(); ++i) { 0312 const QDomElement itemElem = children.at(i).toElement(); 0313 if (itemElem.isNull() || itemElem.tagName() != Format::Tag::item()) { 0314 continue; 0315 } 0316 items += XmlReader::elementToItem(itemElem, includePayload); 0317 } 0318 0319 return items; 0320 }