Akonadi

xmldocument.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Volker Krause <[email protected]>
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 
23 using namespace Akonadi;
24 
25 // helper class for dealing with libxml resource management
26 template<typename T, void FreeFunc(T)> class XmlPtr
27 {
28 public:
29  explicit XmlPtr(const T &t)
30  : p(t)
31  {
32  }
33 
34  ~XmlPtr()
35  {
36  FreeFunc(p);
37  }
38 
39  operator T() const // NOLINT(google-explicit-constructor)
40  {
41  return p;
42  }
43 
44  explicit operator bool() const
45  {
46  return p != nullptr;
47  }
48 
49 private:
50  Q_DISABLE_COPY(XmlPtr)
51  T p;
52 };
53 
54 static QDomElement findElementByRidHelper(const QDomElement &elem, const QString &rid, const QString &elemName)
55 {
56  if (elem.isNull()) {
57  return QDomElement();
58  }
59  if (elem.tagName() == elemName && elem.attribute(Format::Attr::remoteId()) == rid) {
60  return elem;
61  }
62  const QDomNodeList children = elem.childNodes();
63  for (int i = 0; i < children.count(); ++i) {
64  const QDomElement child = children.at(i).toElement();
65  if (child.isNull()) {
66  continue;
67  }
68  const QDomElement rv = findElementByRidHelper(child, rid, elemName);
69  if (!rv.isNull()) {
70  return rv;
71  }
72  }
73  return QDomElement();
74 }
75 
76 namespace Akonadi
77 {
78 class XmlDocumentPrivate
79 {
80 public:
81  XmlDocumentPrivate()
82  : valid(false)
83  {
84  lastError = i18n("No data loaded.");
85  }
86 
87  QDomElement findElementByRid(const QString &rid, const QString &elemName) const
88  {
89  return findElementByRidHelper(document.documentElement(), rid, elemName);
90  }
91 
92  QDomDocument document;
93  QString lastError;
94  bool valid;
95 };
96 
97 } // namespace Akonadi
98 
100  : d(new XmlDocumentPrivate)
101 {
102  const QDomElement rootElem = d->document.createElement(Format::Tag::root());
103  d->document.appendChild(rootElem);
104 }
105 
107  : d(new XmlDocumentPrivate)
108 {
109  loadFile(fileName);
110 }
111 
112 XmlDocument::~XmlDocument() = default;
113 
115 {
116  d->valid = false;
117  d->document = QDomDocument();
118 
119  if (fileName.isEmpty()) {
120  d->lastError = i18n("No filename specified");
121  return false;
122  }
123 
124  QFile file(fileName);
125  QByteArray data;
126  if (file.exists()) {
127  if (!file.open(QIODevice::ReadOnly)) {
128  d->lastError = i18n("Unable to open data file '%1'.", fileName);
129  return false;
130  }
131  data = file.readAll();
132  } else {
133  d->lastError = i18n("File %1 does not exist.", fileName);
134  return false;
135  }
136 
137 #ifdef HAVE_LIBXML2
138  // schema validation
139  XmlPtr<xmlDocPtr, xmlFreeDoc> sourceDoc(xmlParseMemory(data.constData(), data.length()));
140  if (!sourceDoc) {
141  d->lastError = i18n("Unable to parse data file '%1'.", fileName);
142  return false;
143  }
144 
145  const QString &schemaFileName =
146  QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/akonadi/akonadi-xml.xsd"));
147  XmlPtr<xmlDocPtr, xmlFreeDoc> schemaDoc(xmlReadFile(schemaFileName.toLocal8Bit().constData(), nullptr, XML_PARSE_NONET));
148  if (!schemaDoc) {
149  d->lastError = i18n("Schema definition could not be loaded and parsed.");
150  return false;
151  }
152  XmlPtr<xmlSchemaParserCtxtPtr, xmlSchemaFreeParserCtxt> parserContext(xmlSchemaNewDocParserCtxt(schemaDoc));
153  if (!parserContext) {
154  d->lastError = i18n("Unable to create schema parser context.");
155  return false;
156  }
157  XmlPtr<xmlSchemaPtr, xmlSchemaFree> schema(xmlSchemaParse(parserContext));
158  if (!schema) {
159  d->lastError = i18n("Unable to create schema.");
160  return false;
161  }
162  XmlPtr<xmlSchemaValidCtxtPtr, xmlSchemaFreeValidCtxt> validationContext(xmlSchemaNewValidCtxt(schema));
163  if (!validationContext) {
164  d->lastError = i18n("Unable to create schema validation context.");
165  return false;
166  }
167 
168  if (xmlSchemaValidateDoc(validationContext, sourceDoc) != 0) {
169  d->lastError = i18n("Invalid file format.");
170  return false;
171  }
172 #endif
173 
174  // DOM loading
175  QString errMsg;
176  if (!d->document.setContent(data, true, &errMsg)) {
177  d->lastError = i18n("Unable to parse data file: %1", errMsg);
178  return false;
179  }
180 
181  d->valid = true;
182  d->lastError.clear();
183  return true;
184 }
185 
186 bool 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());
256  return XmlReader::elementToCollection(elem);
257 }
258 
259 Item 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 
283  Collection::List rv;
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 
298 Item::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 }
bool writeToFile(const QString &fileName) const
Writes the current document into the given file.
QString errorString() const const
int count() const const
QDomElement toElement() const const
AKONADI_XML_EXPORT Item elementToItem(const QDomElement &elem, bool includePayload=true)
Converts an item element.
Definition: xmlreader.cpp:135
bool loadFile(const QString &fileName)
Parses the given XML file and validates it.
QString tagName() const const
virtual bool open(QIODevice::OpenMode mode) override
bool isNull() const const
AKONADI_XML_EXPORT Collection elementToCollection(const QDomElement &elem)
Converts a collection element.
Definition: xmlreader.cpp:52
void append(const T &value)
QVector< Collection > List
Describes a list of collections.
Definition: collection.h:84
Represents a collection of PIM items.
Definition: collection.h:61
QDomElement collectionElement(const Collection &collection) const
Returns the DOM element representing collection.
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
XmlDocument()
Creates an empty document.
Definition: xmldocument.cpp:99
bool exists() const const
QDomNode at(int index) const const
QVector< Item > List
Describes a list of items.
Definition: item.h:115
Item::List items(const Collection &collection, bool includePayload=true) const
Returns the items in the given collection.
QString i18n(const char *text, const TYPE &arg...)
AKONADI_XML_EXPORT Tag::List readTags(const QDomElement &elem)
Reads recursively all tags starting from the given DOM element.
Definition: xmlreader.cpp:115
bool isEmpty() const const
QDomNodeList childNodes() const const
Tag::List tags() const
Returns the tags defined in this document.
QString lastError() const
Returns the last error occurred during file loading/parsing.
static Collection root()
Returns the root collection.
Definition: collection.cpp:287
Collection::List collections() const
Returns the collections defined in this document.
Item itemByRemoteId(const QString &rid, bool includePayload=true) const
Returns the item with the given remote id.
QDomElement collectionElementByRemoteId(const QString &rid) const
Returns the DOM element representing the collection with the given remote id.
QString remoteId() const
Returns the remote id of the collection.
Definition: collection.cpp:106
Collection parentCollection() const
Returns the parent collection of this object.
Definition: collection.cpp:187
const char * constData() const const
bool isValid() const
Returns true if the document could be parsed successfully.
QDomDocument & document() const
Returns the DOM document for this XML document.
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
Definition: collection.cpp:204
QByteArray readAll()
int length() const const
QString attribute(const QString &name, const QString &defValue) const const
QByteArray toLocal8Bit() const const
QDomElement itemElementByRemoteId(const QString &rid) const
Returns the DOM element representing the item with the given remote id.
AKONADI_XML_EXPORT Collection::List readCollections(const QDomElement &elem)
Reads recursively all collections starting from the given DOM element.
Definition: xmlreader.cpp:72
Collection::List childCollections(const Collection &parentCollection) const
Returns the immediate child collections of parentCollection.
Collection collectionByRemoteId(const QString &rid) const
Returns the collection with the given remote id.
Represents a PIM item stored in Akonadi storage.
Definition: item.h:104
qint64 write(const char *data, qint64 maxSize)
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:01:08 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.