Akonadi

xmldocument.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "xmldocument.h"
8#include "format_p.h"
9#include "xmlreader.h"
10
11#include <KLocalizedString>
12
13#include <QFile>
14#include <qdom.h>
15
16#ifdef HAVE_LIBXML2
17#include <QStandardPaths>
18#include <libxml/parser.h>
19#include <libxml/xmlIO.h>
20#include <libxml/xmlschemas.h>
21#endif
22
23using namespace Akonadi;
24
25// helper class for dealing with libxml resource management
26template<typename T, void FreeFunc(T)>
27class XmlPtr
28{
29public:
30 explicit XmlPtr(const T &t)
31 : p(t)
32 {
33 }
34
35 ~XmlPtr()
36 {
37 FreeFunc(p);
38 }
39
40 operator T() const // NOLINT(google-explicit-constructor)
41 {
42 return p;
43 }
44
45 explicit operator bool() const
46 {
47 return p != nullptr;
48 }
49
50private:
51 Q_DISABLE_COPY(XmlPtr)
52 T p;
53};
54
55static QDomElement findElementByRidHelper(const QDomElement &elem, const QString &rid, const QString &elemName)
56{
57 if (elem.isNull()) {
58 return QDomElement();
59 }
60 if (elem.tagName() == elemName && elem.attribute(Format::Attr::remoteId()) == rid) {
61 return elem;
62 }
63 const QDomNodeList children = elem.childNodes();
64 for (int i = 0; i < children.count(); ++i) {
65 const QDomElement child = children.at(i).toElement();
66 if (child.isNull()) {
67 continue;
68 }
69 const QDomElement rv = findElementByRidHelper(child, rid, elemName);
70 if (!rv.isNull()) {
71 return rv;
72 }
73 }
74 return QDomElement();
75}
76
77namespace Akonadi
78{
79class XmlDocumentPrivate
80{
81public:
82 XmlDocumentPrivate()
83 : lastError(i18n("No data loaded."))
84 , valid(false)
85 {
86 }
87
88 QDomElement findElementByRid(const QString &rid, const QString &elemName) const
89 {
90 return findElementByRidHelper(document.documentElement(), rid, elemName);
91 }
92
93 QDomDocument document;
94 QString lastError;
95 bool valid;
96};
97
98} // namespace Akonadi
99
101 : d(new XmlDocumentPrivate)
102{
103 const QDomElement rootElem = d->document.createElement(Format::Tag::root());
104 d->document.appendChild(rootElem);
105}
106
107XmlDocument::XmlDocument(const QString &fileName, const QString &xsdFile)
108 : d(new XmlDocumentPrivate)
109{
110 loadFile(fileName, xsdFile);
111}
112
113XmlDocument::~XmlDocument() = default;
114
115bool Akonadi::XmlDocument::loadFile(const QString &fileName, const QString &xsdFile)
116{
117 d->valid = false;
118 d->document = QDomDocument();
119
120 if (fileName.isEmpty()) {
121 d->lastError = i18n("No filename specified");
122 return false;
123 }
124
125 QFile file(fileName);
126 QByteArray data;
127 if (file.exists()) {
128 if (!file.open(QIODevice::ReadOnly)) {
129 d->lastError = i18n("Unable to open data file '%1'.", fileName);
130 return false;
131 }
132 data = file.readAll();
133 } else {
134 d->lastError = i18n("File %1 does not exist.", fileName);
135 return false;
136 }
137
138#ifdef HAVE_LIBXML2
139 // schema validation
140 XmlPtr<xmlDocPtr, xmlFreeDoc> sourceDoc(xmlParseMemory(data.constData(), data.length()));
141 if (!sourceDoc) {
142 d->lastError = i18n("Unable to parse data file '%1'.", fileName);
143 return false;
144 }
145
146 const QString &schemaFileName =
147 xsdFile.isEmpty() ? QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf6/akonadi/akonadi-xml.xsd")) : xsdFile;
148 const XmlPtr<xmlDocPtr, xmlFreeDoc> schemaDoc(xmlReadFile(schemaFileName.toLocal8Bit().constData(), nullptr, XML_PARSE_NONET));
149 if (!schemaDoc) {
150 d->lastError = i18n("Schema definition could not be loaded and parsed.");
151 return false;
152 }
153 XmlPtr<xmlSchemaParserCtxtPtr, xmlSchemaFreeParserCtxt> parserContext(xmlSchemaNewDocParserCtxt(schemaDoc));
154 if (!parserContext) {
155 d->lastError = i18n("Unable to create schema parser context.");
156 return false;
157 }
158 const XmlPtr<xmlSchemaPtr, xmlSchemaFree> schema(xmlSchemaParse(parserContext));
159 if (!schema) {
160 d->lastError = i18n("Unable to create schema.");
161 return false;
162 }
163 const XmlPtr<xmlSchemaValidCtxtPtr, xmlSchemaFreeValidCtxt> validationContext(xmlSchemaNewValidCtxt(schema));
164 if (!validationContext) {
165 d->lastError = i18n("Unable to create schema validation context.");
166 return false;
167 }
168
169 if (xmlSchemaValidateDoc(validationContext, sourceDoc) != 0) {
170 d->lastError = i18n("Invalid file format.");
171 return false;
172 }
173#endif
174
175 // DOM loading
176 if (const auto result = d->document.setContent(data, QDomDocument::ParseOption::UseNamespaceProcessing); !result) {
177 d->lastError = i18n("Unable to parse data file: %1", result.errorMessage);
178 return false;
179 }
180
181 d->valid = true;
182 d->lastError.clear();
183 return true;
184}
185
186bool XmlDocument::writeToFile(const QString &fileName) const
187{
188 QFile f(fileName);
189 if (!f.open(QFile::WriteOnly)) {
190 d->lastError = f.errorString();
191 return false;
192 }
193
194 f.write(d->document.toByteArray(2));
195
196 d->lastError.clear();
197 return true;
198}
199
201{
202 return d->valid;
203}
204
206{
207 return d->lastError;
208}
209
211{
212 return d->document;
213}
214
216{
217 if (collection == Collection::root()) {
218 return d->document.documentElement();
219 }
220 if (collection.remoteId().isEmpty()) {
221 return QDomElement();
222 }
223 if (collection.parentCollection().remoteId().isEmpty() && collection.parentCollection() != Collection::root()) {
224 return d->findElementByRid(collection.remoteId(), Format::Tag::collection());
225 }
226 QDomElement parent = collectionElement(collection.parentCollection());
227 if (parent.isNull()) {
228 return QDomElement();
229 }
230 const QDomNodeList children = parent.childNodes();
231 for (int i = 0; i < children.count(); ++i) {
232 const QDomElement child = children.at(i).toElement();
233 if (child.isNull()) {
234 continue;
235 }
236 if (child.tagName() == Format::Tag::collection() && child.attribute(Format::Attr::remoteId()) == collection.remoteId()) {
237 return child;
238 }
239 }
240 return QDomElement();
241}
242
244{
245 return d->findElementByRid(rid, Format::Tag::item());
246}
247
249{
250 return d->findElementByRid(rid, Format::Tag::collection());
251}
252
254{
255 const QDomElement elem = d->findElementByRid(rid, Format::Tag::collection());
257}
258
259Item XmlDocument::itemByRemoteId(const QString &rid, bool includePayload) const
260{
261 return XmlReader::elementToItem(itemElementByRemoteId(rid), includePayload);
262}
263
265{
266 return XmlReader::readCollections(d->document.documentElement());
267}
268
270{
271 return XmlReader::readTags(d->document.documentElement());
272}
273
275{
276 QDomElement parentElem = collectionElement(parentCollection);
277
278 if (parentElem.isNull()) {
279 d->lastError = QStringLiteral("Parent node not found.");
280 return Collection::List();
281 }
282
284 const QDomNodeList children = parentElem.childNodes();
285 for (int i = 0; i < children.count(); ++i) {
286 const QDomElement childElem = children.at(i).toElement();
287 if (childElem.isNull() || childElem.tagName() != Format::Tag::collection()) {
288 continue;
289 }
291 c.setParentCollection(parentCollection);
292 rv.append(c);
293 }
294
295 return rv;
296}
297
298Item::List XmlDocument::items(const Akonadi::Collection &collection, bool includePayload) const
299{
300 const QDomElement colElem = collectionElement(collection);
301 if (colElem.isNull()) {
302 d->lastError = i18n("Unable to find collection %1", collection.name());
303 return Item::List();
304 } else {
305 d->lastError.clear();
306 }
307
309 const QDomNodeList children = colElem.childNodes();
310 for (int i = 0; i < children.count(); ++i) {
311 const QDomElement itemElem = children.at(i).toElement();
312 if (itemElem.isNull() || itemElem.tagName() != Format::Tag::item()) {
313 continue;
314 }
315 items += XmlReader::elementToItem(itemElem, includePayload);
316 }
317
318 return items;
319}
Represents a collection of PIM items.
Definition collection.h:62
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
static Collection root()
Returns the root collection.
Collection parentCollection() const
Returns the parent collection of this object.
QList< Collection > List
Describes a list of collections.
Definition collection.h:84
QString remoteId() const
Returns the remote id of the collection.
Represents a PIM item stored in Akonadi storage.
Definition item.h:100
QList< Item > List
Describes a list of items.
Definition item.h:110
bool loadFile(const QString &fileName, const QString &xsdFile={})
Parses the given XML file and validates it.
QDomDocument & document() const
Returns the DOM document for this XML document.
Collection collectionByRemoteId(const QString &rid) const
Returns the collection with the given remote id.
Item::List items(const Collection &collection, bool includePayload=true) const
Returns the items in the given collection.
Tag::List tags() const
Returns the tags defined in this document.
bool isValid() const
Returns true if the document could be parsed successfully.
XmlDocument()
Creates an empty document.
QDomElement itemElementByRemoteId(const QString &rid) const
Returns the DOM element representing the item with the given remote id.
Item itemByRemoteId(const QString &rid, bool includePayload=true) const
Returns the item with the given remote id.
Collection::List childCollections(const Collection &parentCollection) const
Returns the immediate child collections of parentCollection.
QDomElement collectionElementByRemoteId(const QString &rid) const
Returns the DOM element representing the collection with the given remote id.
Collection::List collections() const
Returns the collections defined in this document.
bool writeToFile(const QString &fileName) const
Writes the current document into the given file.
QDomElement collectionElement(const Collection &collection) const
Returns the DOM element representing collection.
QString lastError() const
Returns the last error occurred during file loading/parsing.
QString i18n(const char *text, const TYPE &arg...)
AKONADI_XML_EXPORT Item elementToItem(const QDomElement &elem, bool includePayload=true)
Converts an item element.
AKONADI_XML_EXPORT Collection elementToCollection(const QDomElement &elem)
Converts a collection element.
Definition xmlreader.cpp:53
AKONADI_XML_EXPORT Collection::List readCollections(const QDomElement &elem)
Reads recursively all collections starting from the given DOM element.
Definition xmlreader.cpp:73
AKONADI_XML_EXPORT Tag::List readTags(const QDomElement &elem)
Reads recursively all tags starting from the given DOM element.
Helper integration between Akonadi and Qt.
const char * constData() const const
qsizetype length() const const
QDomElement documentElement() const const
QString attribute(const QString &name, const QString &defValue) const const
QString tagName() const const
QDomNodeList childNodes() const const
bool isNull() const const
QDomElement toElement() const const
QDomNode at(int index) const const
int count() const const
bool exists(const QString &fileName)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
QString errorString() const const
QByteArray readAll()
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
bool isEmpty() const const
QByteArray toLocal8Bit() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:52:53 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.