Merged in f5soh/librepilot/LP-575_fedora_package (pull request #491)
[librepilot.git] / ground / gcs / src / libs / utils / xmlconfig.cpp
blobe0afff1759171db573794893c9f572a29d9129c3
1 /**
2 ******************************************************************************
4 * @file xmlconfig.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
8 * @see The GNU Public License (GPL) Version 3
9 * @brief Widget for Import/Export Plugin
10 * @addtogroup GCSPlugins GCS Plugins
11 * @{
12 * @addtogroup utils
13 * @{
15 *****************************************************************************/
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 3 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
25 * for more details.
27 * You should have received a copy of the GNU General Public License along
28 * with this program; if not, write to the Free Software Foundation, Inc.,
29 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 /* Nokia Corporation */
32 #include "xmlconfig.h"
34 #include <QDebug>
35 #include <QStringList>
36 #include <QRegExp>
37 #include <QDataStream>
38 #include <QVariant>
39 #include <QRect>
40 #include <QSize>
41 #include <QPoint>
42 #include <QUrl>
43 #include <QDomElement>
45 #define NUM_PREFIX "arr_"
47 const QString XmlConfig::RootName = "gcs";
49 const QSettings::Format XmlConfig::XmlFormat =
50 QSettings::registerFormat("xml", XmlConfig::readXmlFile, XmlConfig::writeXmlFile);
52 bool XmlConfig::readXmlFile(QIODevice &device, QSettings::SettingsMap &map)
54 QDomDocument domDoc;
55 QDomElement root;
56 QString errorStr;
57 int errorLine;
58 int errorColumn;
60 if (!domDoc.setContent(&device, true, &errorStr, &errorLine,
61 &errorColumn)) {
62 QString err = QString(tr("GCS config")) +
63 tr("Parse error at line %1, column %2:\n%3")
64 .arg(errorLine)
65 .arg(errorColumn)
66 .arg(errorStr);
67 qFatal("%s", err.toLatin1().data());
68 return false;
70 root = domDoc.documentElement();
71 handleNode(&root, map);
73 return true;
76 void XmlConfig::handleNode(QDomElement *node, QSettings::SettingsMap &map, QString path)
78 if (!node) {
79 return;
81 // qDebug() << "XmlConfig::handleNode start";
83 QString nodeName = node->nodeName();
84 // For arrays, QT will use simple numbers as keys, which is not a valid element in XML.
85 // Therefore we prefixed these.
86 if (nodeName.startsWith(NUM_PREFIX)) {
87 nodeName.replace(NUM_PREFIX, "");
89 // Xml tags are restrictive with allowed characters,
90 // so we urlencode and replace % with __PCT__ on file
91 nodeName = nodeName.replace("__PCT__", "%");
92 nodeName = QUrl::fromPercentEncoding(nodeName.toLatin1());
94 if (nodeName == XmlConfig::RootName) {
96 } else if (path == "") {
97 path = nodeName;
98 } else {
99 path += "/" + nodeName;
102 // qDebug() << "Node: " << ": " << path << " Children: " << node->childNodes().length();
103 for (int i = 0; i < node->childNodes().length(); ++i) {
104 QDomNode child = node->childNodes().item(i);
105 if (child.isElement()) {
106 handleNode(static_cast<QDomElement *>(&child), map, path);
107 } else if (child.isText()) {
108 // qDebug() << "Key: " << path << " Value:" << node->text();
109 map.insert(path, stringToVariant(node->text()));
110 } else {
111 qDebug() << "Child not Element or text!" << child.nodeType();
114 // qDebug() << "XmlConfig::handleNode end";
117 bool XmlConfig::writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map)
119 QDomDocument outDocument;
121 // qDebug() << "writeXmlFile start";
122 outDocument.appendChild(outDocument.createElement(XmlConfig::RootName));
123 QMapIterator<QString, QVariant> iter(map);
124 while (iter.hasNext()) {
125 iter.next();
126 // qDebug() << "Entry: " << iter.key() << ": " << iter.value().toString() << endl;
127 QDomNode node = outDocument.firstChild();
128 foreach(QString elem, iter.key().split('/')) {
129 if (elem == "") {
130 continue;
132 // Xml tags are restrictive with allowed characters,
133 // so we urlencode and replace % with __PCT__ on file
134 elem = QString(QUrl::toPercentEncoding(elem));
135 elem = elem.replace("%", "__PCT__");
136 // For arrays, QT will use simple numbers as keys, which is not a valid element in XML.
137 // Therefore we prefixed these.
138 if (elem.startsWith(NUM_PREFIX)) {
139 qWarning() << "ERROR: Settings must not start with " << NUM_PREFIX
140 << " in: " + iter.key();
142 if (QRegExp("[0-9]").exactMatch(elem.left(1))) {
143 elem.prepend(NUM_PREFIX);
145 if (node.firstChildElement(elem).isNull()) {
146 node.appendChild(outDocument.createElement(elem));
148 node = node.firstChildElement(elem);
150 node.appendChild(outDocument.createTextNode(variantToString(iter.value())));
152 device.write(outDocument.toByteArray(2).constData());
153 // qDebug() << "Document:\n" << outDocument.toByteArray(2).constData();
154 // qDebug() << "writeXmlFile end";
155 return true;
158 QString XmlConfig::variantToString(const QVariant &v)
160 QString result;
162 switch (v.type()) {
163 case QVariant::Invalid:
164 result = QLatin1String("@Invalid()");
165 break;
167 case QVariant::ByteArray:
169 QByteArray a = v.toByteArray().toBase64();
170 result = QLatin1String("@ByteArray(");
171 result += QString::fromLatin1(a.constData(), a.size());
172 result += QLatin1Char(')');
173 break;
176 case QVariant::String:
177 case QVariant::LongLong:
178 case QVariant::ULongLong:
179 case QVariant::Int:
180 case QVariant::UInt:
181 case QVariant::Bool:
182 case QVariant::Double:
183 case QVariant::KeySequence:
184 case QVariant::Color:
186 result = v.toString();
187 if (result.startsWith(QLatin1Char('@'))) {
188 result.prepend(QLatin1Char('@'));
190 break;
192 #ifndef QT_NO_GEOM_VARIANT
193 case QVariant::Rect:
195 QRect r = qvariant_cast<QRect>(v);
196 result += QLatin1String("@Rect(");
197 result += QString::number(r.x());
198 result += QLatin1Char(' ');
199 result += QString::number(r.y());
200 result += QLatin1Char(' ');
201 result += QString::number(r.width());
202 result += QLatin1Char(' ');
203 result += QString::number(r.height());
204 result += QLatin1Char(')');
205 break;
207 case QVariant::Size:
209 QSize s = qvariant_cast<QSize>(v);
210 result += QLatin1String("@Size(");
211 result += QString::number(s.width());
212 result += QLatin1Char(' ');
213 result += QString::number(s.height());
214 result += QLatin1Char(')');
215 break;
217 case QVariant::Point:
219 QPoint p = qvariant_cast<QPoint>(v);
220 result += QLatin1String("@Point(");
221 result += QString::number(p.x());
222 result += QLatin1Char(' ');
223 result += QString::number(p.y());
224 result += QLatin1Char(')');
225 break;
227 #endif // !QT_NO_GEOM_VARIANT
229 default:
231 #ifndef QT_NO_DATASTREAM
232 QByteArray a;
234 QDataStream s(&a, QIODevice::WriteOnly);
235 s.setVersion(QDataStream::Qt_4_0);
236 s << v;
239 result = QLatin1String("@Variant(");
240 result += QString::fromLatin1(a.toBase64().constData());
241 result += QLatin1Char(')');
242 // These were being much too noisy!!
243 // qDebug() << "Variant Type: " << v.type();
244 // qDebug()<< "Variant: " << result;
245 #else
246 Q_ASSERT(!"QSettings: Cannot save custom types without QDataStream support");
247 #endif
248 break;
252 return result;
255 QVariant XmlConfig::stringToVariant(const QString &s)
257 if (s.startsWith(QLatin1Char('@'))) {
258 if (s.endsWith(QLatin1Char(')'))) {
259 if (s.startsWith(QLatin1String("@ByteArray("))) {
260 return QVariant(QByteArray::fromBase64(s.toLatin1().mid(11, s.size() - 12)));
261 } else if (s.startsWith(QLatin1String("@Variant("))) {
262 #ifndef QT_NO_DATASTREAM
263 QByteArray a(QByteArray::fromBase64(s.toLatin1().mid(9)));
264 QDataStream stream(&a, QIODevice::ReadOnly);
265 stream.setVersion(QDataStream::Qt_4_0);
266 QVariant result;
267 stream >> result;
268 return result;
270 #else
271 Q_ASSERT(!"QSettings: Cannot load custom types without QDataStream support");
272 #endif
273 #ifndef QT_NO_GEOM_VARIANT
274 } else if (s.startsWith(QLatin1String("@Rect("))) {
275 QStringList args = splitArgs(s, 5);
276 if (args.size() == 4) {
277 return QVariant(QRect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt()));
279 } else if (s.startsWith(QLatin1String("@Size("))) {
280 QStringList args = splitArgs(s, 5);
281 if (args.size() == 2) {
282 return QVariant(QSize(args[0].toInt(), args[1].toInt()));
284 } else if (s.startsWith(QLatin1String("@Point("))) {
285 QStringList args = splitArgs(s, 6);
286 if (args.size() == 2) {
287 return QVariant(QPoint(args[0].toInt(), args[1].toInt()));
289 #endif
290 } else if (s == QLatin1String("@Invalid()")) {
291 return QVariant();
294 if (s.startsWith(QLatin1String("@@"))) {
295 return QVariant(s.mid(1));
299 return QVariant(s);
302 QStringList XmlConfig::splitArgs(const QString &s, int idx)
304 int l = s.length();
306 Q_ASSERT(l > 0);
307 Q_ASSERT(s.at(idx) == QLatin1Char('('));
308 Q_ASSERT(s.at(l - 1) == QLatin1Char(')'));
310 QStringList result;
311 QString item;
313 for (++idx; idx < l; ++idx) {
314 QChar c = s.at(idx);
315 if (c == QLatin1Char(')')) {
316 Q_ASSERT(idx == l - 1);
317 result.append(item);
318 } else if (c == QLatin1Char(' ')) {
319 result.append(item);
320 item.clear();
321 } else {
322 item.append(c);
326 return result;
330 * @}
331 * @}