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 QString errMsg;
177 if (!d->document.setContent(data, true, &errMsg)) {
178 d->lastError = i18n("Unable to parse data file: %1", errMsg);
179 return false;
180 }
181
182 d->valid = true;
183 d->lastError.clear();
184 return true;
185}
186
187bool XmlDocument::writeToFile(const QString &fileName) const
188{
189 QFile f(fileName);
190 if (!f.open(QFile::WriteOnly)) {
191 d->lastError = f.errorString();
192 return false;
193 }
194
195 f.write(d->document.toByteArray(2));
196
197 d->lastError.clear();
198 return true;
199}
200
202{
203 return d->valid;
204}
205
207{
208 return d->lastError;
209}
210
212{
213 return d->document;
214}
215
217{
218 if (collection == Collection::root()) {
219 return d->document.documentElement();
220 }
221 if (collection.remoteId().isEmpty()) {
222 return QDomElement();
223 }
224 if (collection.parentCollection().remoteId().isEmpty() && collection.parentCollection() != Collection::root()) {
225 return d->findElementByRid(collection.remoteId(), Format::Tag::collection());
226 }
227 QDomElement parent = collectionElement(collection.parentCollection());
228 if (parent.isNull()) {
229 return QDomElement();
230 }
231 const QDomNodeList children = parent.childNodes();
232 for (int i = 0; i < children.count(); ++i) {
233 const QDomElement child = children.at(i).toElement();
234 if (child.isNull()) {
235 continue;
236 }
237 if (child.tagName() == Format::Tag::collection() && child.attribute(Format::Attr::remoteId()) == collection.remoteId()) {
238 return child;
239 }
240 }
241 return QDomElement();
242}
243
245{
246 return d->findElementByRid(rid, Format::Tag::item());
247}
248
250{
251 return d->findElementByRid(rid, Format::Tag::collection());
252}
253
255{
256 const QDomElement elem = d->findElementByRid(rid, Format::Tag::collection());
258}
259
260Item XmlDocument::itemByRemoteId(const QString &rid, bool includePayload) const
261{
262 return XmlReader::elementToItem(itemElementByRemoteId(rid), includePayload);
263}
264
266{
267 return XmlReader::readCollections(d->document.documentElement());
268}
269
271{
272 return XmlReader::readTags(d->document.documentElement());
273}
274
276{
277 QDomElement parentElem = collectionElement(parentCollection);
278
279 if (parentElem.isNull()) {
280 d->lastError = QStringLiteral("Parent node not found.");
281 return Collection::List();
282 }
283
285 const QDomNodeList children = parentElem.childNodes();
286 for (int i = 0; i < children.count(); ++i) {
287 const QDomElement childElem = children.at(i).toElement();
288 if (childElem.isNull() || childElem.tagName() != Format::Tag::collection()) {
289 continue;
290 }
292 c.setParentCollection(parentCollection);
293 rv.append(c);
294 }
295
296 return rv;
297}
298
299Item::List XmlDocument::items(const Akonadi::Collection &collection, bool includePayload) const
300{
301 const QDomElement colElem = collectionElement(collection);
302 if (colElem.isNull()) {
303 d->lastError = i18n("Unable to find collection %1", collection.name());
304 return Item::List();
305 } else {
306 d->lastError.clear();
307 }
308
310 const QDomNodeList children = colElem.childNodes();
311 for (int i = 0; i < children.count(); ++i) {
312 const QDomElement itemElem = children.at(i).toElement();
313 if (itemElem.isNull() || itemElem.tagName() != Format::Tag::item()) {
314 continue;
315 }
316 items += XmlReader::elementToItem(itemElem, includePayload);
317 }
318
319 return items;
320}
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:101
QList< Item > List
Describes a list of items.
Definition item.h:111
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 Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.