summaryrefslogtreecommitdiffstats
path: root/examples/corelib/serialization/convert/xmlconverter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/corelib/serialization/convert/xmlconverter.cpp')
-rw-r--r--examples/corelib/serialization/convert/xmlconverter.cpp514
1 files changed, 514 insertions, 0 deletions
diff --git a/examples/corelib/serialization/convert/xmlconverter.cpp b/examples/corelib/serialization/convert/xmlconverter.cpp
new file mode 100644
index 0000000000..62908273ce
--- /dev/null
+++ b/examples/corelib/serialization/convert/xmlconverter.cpp
@@ -0,0 +1,514 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Intel Corporation.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "xmlconverter.h"
+
+#include <QBitArray>
+#include <QtCborCommon>
+#include <QFile>
+#include <QFloat16>
+#include <QMetaType>
+#include <QRegularExpression>
+#include <QUrl>
+#include <QXmlStreamReader>
+#include <QXmlStreamWriter>
+
+static const char optionHelp[] =
+ "compact=no|yes Use compact XML form.\n";
+
+static XmlConverter xmlConverter;
+
+static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options);
+
+static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options options)
+{
+ QVariantList list;
+ while (!xml.atEnd() && !xml.isEndElement()) {
+ xml.readNext();
+ switch (xml.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ list << variantFromXml(xml, options);
+ continue;
+
+ case QXmlStreamReader::EndElement:
+ continue;
+
+ case QXmlStreamReader::Comment:
+ // ignore comments
+ continue;
+
+ case QXmlStreamReader::Characters:
+ // ignore whitespace
+ if (xml.isWhitespace())
+ continue;
+ Q_FALLTHROUGH();
+
+ default:
+ break;
+ }
+
+ fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(),
+ qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ xml.readNext();
+ return list;
+}
+
+static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Converter::Options options)
+{
+ QVariant key, value;
+ while (!xml.atEnd() && !xml.isEndElement()) {
+ xml.readNext();
+ switch (xml.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ if (value.isValid())
+ break;
+ if (key.isValid())
+ value = variantFromXml(xml, options);
+ else
+ key = variantFromXml(xml, options);
+ continue;
+
+ case QXmlStreamReader::EndElement:
+ continue;
+
+ case QXmlStreamReader::Comment:
+ // ignore comments
+ continue;
+
+ case QXmlStreamReader::Characters:
+ // ignore whitespace
+ if (xml.isWhitespace())
+ continue;
+ Q_FALLTHROUGH();
+
+ default:
+ break;
+ }
+
+ fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(),
+ qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ return { key, value };
+}
+
+static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options)
+{
+ QVariantMap map1;
+ VariantOrderedMap map2;
+
+ while (!xml.atEnd() && !xml.isEndElement()) {
+ xml.readNext();
+ switch (xml.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ if (xml.name() == QLatin1String("entry")) {
+ auto pair = mapEntryFromXml(xml, options);
+ if (options & Converter::SupportsArbitraryMapKeys)
+ map2.append(pair);
+ else
+ map1.insert(pair.first.toString(), pair.second);
+ continue;
+ }
+ break;
+
+ case QXmlStreamReader::EndElement:
+ continue;
+
+ case QXmlStreamReader::Comment:
+ // ignore comments
+ continue;
+
+ case QXmlStreamReader::Characters:
+ // ignore whitespace
+ if (xml.isWhitespace())
+ continue;
+ Q_FALLTHROUGH();
+
+ default:
+ break;
+ }
+
+ fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(),
+ qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ xml.readNext();
+ if (options & Converter::SupportsArbitraryMapKeys)
+ return QVariant::fromValue(map2);
+ return map1;
+}
+
+static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options)
+{
+ QStringRef name = xml.name();
+ if (name == QLatin1String("list"))
+ return listFromXml(xml, options);
+ if (name == QLatin1String("map"))
+ return mapFromXml(xml, options);
+ if (name != QLatin1String("value")) {
+ fprintf(stderr, "%lld:%lld: Invalid XML key '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ QXmlStreamAttributes attrs = xml.attributes();
+ QStringRef type = attrs.value(QLatin1String("type"));
+
+ forever {
+ xml.readNext();
+ if (xml.isComment())
+ continue;
+ if (xml.isCDATA() || xml.isCharacters() || xml.isEndElement())
+ break;
+
+ fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(),
+ qPrintable(xml.tokenString()), qPrintable(name.toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ QStringRef text = xml.text();
+ if (!xml.isCDATA())
+ text = text.trimmed();
+
+ QVariant result;
+ bool ok;
+ if (type.isEmpty()) {
+ // ok
+ } else if (type == QLatin1String("number")) {
+ // try integer first
+ qint64 v = text.toLongLong(&ok);
+ if (ok) {
+ result = v;
+ } else {
+ // let's see floating point
+ double d = text.toDouble(&ok);
+ result = d;
+ if (!ok) {
+ fprintf(stderr, "%lld:%lld: Invalid XML: could not interpret '%s' as a number.\n",
+ xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString()));
+ exit(EXIT_FAILURE);
+ }
+ }
+ } else if (type == QLatin1String("bytes")) {
+ QByteArray data = text.toLatin1();
+ QStringRef encoding = attrs.value("encoding");
+ if (encoding == QLatin1String("base64url")) {
+ result = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding);
+ } else if (encoding == QLatin1String("hex")) {
+ result = QByteArray::fromHex(data);
+ } else if (encoding.isEmpty() || encoding == QLatin1String("base64")) {
+ result = QByteArray::fromBase64(data);
+ } else {
+ fprintf(stderr, "%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.\n",
+ xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString()));
+ exit(EXIT_FAILURE);
+ }
+ } else if (type == QLatin1String("string")) {
+ result = text.toString();
+ } else if (type == QLatin1String("null")) {
+ result = QVariant::fromValue(nullptr);
+ } else if (type == QLatin1String("CBOR simple type")) {
+ result = QVariant::fromValue(QCborSimpleType(text.toShort()));
+ } else if (type == QLatin1String("bits")) {
+ QBitArray ba;
+ ba.resize(text.size());
+ qsizetype n = 0;
+ for (qsizetype i = 0; i < text.size(); ++i) {
+ QChar c = text.at(i);
+ if (c == '1') {
+ ba.setBit(n++);
+ } else if (c == '0') {
+ ++n;
+ } else if (!c.isSpace()) {
+ fprintf(stderr, "%lld:%lld: Invalid XML: invalid bit string '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString()));
+ exit(EXIT_FAILURE);
+ }
+ }
+ ba.resize(n);
+ result = ba;
+ } else {
+ int id = QVariant::Invalid;
+ if (type == QLatin1String("datetime"))
+ id = QVariant::DateTime;
+ else if (type == QLatin1String("url"))
+ id = QVariant::Url;
+ else if (type == QLatin1String("uuid"))
+ id = QVariant::Uuid;
+ else if (type == QLatin1String("regex"))
+ id = QVariant::RegularExpression;
+ else
+ id = QMetaType::type(type.toLatin1());
+ if (id == QVariant::Invalid) {
+ fprintf(stderr, "%lld:%lld: Invalid XML: unknown type '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ result = text.toString();
+ if (!result.convert(id)) {
+ fprintf(stderr, "%lld:%lld: Invalid XML: could not parse content as type '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString()));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ do {
+ xml.readNext();
+ } while (xml.isComment() || xml.isWhitespace());
+
+ if (!xml.isEndElement()) {
+ fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
+ xml.lineNumber(), xml.columnNumber(),
+ qPrintable(xml.tokenString()), qPrintable(name.toString()));
+ exit(EXIT_FAILURE);
+ }
+
+ xml.readNext();
+ return result;
+}
+
+static void variantToXml(QXmlStreamWriter &xml, const QVariant &v)
+{
+ int type = v.userType();
+ if (type == QVariant::List) {
+ QVariantList list = v.toList();
+ xml.writeStartElement("list");
+ for (const QVariant &v : list)
+ variantToXml(xml, v);
+ xml.writeEndElement();
+ } else if (type == QVariant::Map || type == qMetaTypeId<VariantOrderedMap>()) {
+ const VariantOrderedMap map = (type == QVariant::Map) ?
+ VariantOrderedMap(v.toMap()) :
+ v.value<VariantOrderedMap>();
+
+ xml.writeStartElement("map");
+ for (const auto &pair : map) {
+ xml.writeStartElement("entry");
+ variantToXml(xml, pair.first);
+ variantToXml(xml, pair.second);
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ } else {
+ xml.writeStartElement("value");
+ QString typeString = QStringLiteral("type");
+ switch (type) {
+ case QMetaType::Short:
+ case QMetaType::UShort:
+ case QMetaType::Int:
+ case QMetaType::UInt:
+ case QMetaType::Long:
+ case QMetaType::ULong:
+ case QMetaType::LongLong:
+ case QMetaType::ULongLong:
+ case QMetaType::Float:
+ case QMetaType::Double:
+ xml.writeAttribute(typeString, "number");
+ xml.writeCharacters(v.toString());
+ break;
+
+ case QMetaType::QByteArray:
+ xml.writeAttribute(typeString, "bytes");
+ xml.writeAttribute("encoding", "base64");
+ xml.writeCharacters(QString::fromLatin1(v.toByteArray().toBase64()));
+ break;
+
+ case QMetaType::QString:
+ xml.writeAttribute(typeString, "string");
+ xml.writeCDATA(v.toString());
+ break;
+
+ case QMetaType::Bool:
+ xml.writeAttribute(typeString, "bool");
+ xml.writeCharacters(v.toString());
+ break;
+
+ case QMetaType::Nullptr:
+ xml.writeAttribute(typeString, "null");
+ break;
+
+ case QMetaType::UnknownType:
+ break;
+
+ case QMetaType::QDate:
+ case QMetaType::QTime:
+ case QMetaType::QDateTime:
+ xml.writeAttribute(typeString, "dateime");
+ xml.writeCharacters(v.toString());
+ break;
+
+ case QMetaType::QUrl:
+ xml.writeAttribute(typeString, "url");
+ xml.writeCharacters(v.toUrl().toString(QUrl::FullyEncoded));
+ break;
+
+ case QMetaType::QUuid:
+ xml.writeAttribute(typeString, "uuid");
+ xml.writeCharacters(v.toString());
+ break;
+
+ case QMetaType::QBitArray:
+ xml.writeAttribute(typeString, "bits");
+ xml.writeCharacters([](const QBitArray &ba) {
+ QString result;
+ for (qsizetype i = 0; i < ba.size(); ++i) {
+ if (i && i % 72 == 0)
+ result += '\n';
+ result += QLatin1Char(ba.testBit(i) ? '1' : '0');
+ }
+ return result;
+ }(v.toBitArray()));
+ break;
+
+ case QMetaType::QRegularExpression:
+ xml.writeAttribute(typeString, "regex");
+ xml.writeCharacters(v.toRegularExpression().pattern());
+ break;
+
+ default:
+ if (type == qMetaTypeId<qfloat16>()) {
+ xml.writeAttribute(typeString, "number");
+ xml.writeCharacters(QString::number(float(v.value<qfloat16>())));
+ } else if (type == qMetaTypeId<QCborSimpleType>()) {
+ xml.writeAttribute(typeString, "CBOR simple type");
+ xml.writeCharacters(QString::number(int(v.value<QCborSimpleType>())));
+ } else {
+ // does this convert to string?
+ const char *typeName = v.typeName();
+ QVariant copy = v;
+ if (copy.convert(QVariant::String)) {
+ xml.writeAttribute(typeString, QString::fromLatin1(typeName));
+ xml.writeCharacters(copy.toString());
+ } else {
+ fprintf(stderr, "XML: don't know how to serialize type '%s'.\n", typeName);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ xml.writeEndElement();
+ }
+}
+
+QString XmlConverter::name()
+{
+ return QStringLiteral("xml");
+}
+
+Converter::Direction XmlConverter::directions()
+{
+ return InOut;
+}
+
+Converter::Options XmlConverter::outputOptions()
+{
+ return SupportsArbitraryMapKeys;
+}
+
+const char *XmlConverter::optionsHelp()
+{
+ return optionHelp;
+}
+
+bool XmlConverter::probeFile(QIODevice *f)
+{
+ if (QFile *file = qobject_cast<QFile *>(f)) {
+ if (file->fileName().endsWith(QLatin1String(".xml")))
+ return true;
+ }
+
+ return f->isReadable() && f->peek(5) == "<?xml";
+}
+
+QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter)
+{
+ if (!outputConverter)
+ outputConverter = this;
+
+ QXmlStreamReader xml(f);
+ xml.readNextStartElement();
+ QVariant v = variantFromXml(xml, outputConverter->outputOptions());
+ if (xml.hasError()) {
+ fprintf(stderr, "XML error: %s", qPrintable(xml.errorString()));
+ exit(EXIT_FAILURE);
+ }
+
+ return v;
+}
+
+void XmlConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
+{
+ bool compact = false;
+ for (const QString &s : options) {
+ if (s == QLatin1String("compact=no")) {
+ compact = false;
+ } else if (s == QLatin1String("compact=yes")) {
+ compact = true;
+ } else {
+ fprintf(stderr, "Unknown option '%s' to XML output. Valid options are:\n%s", qPrintable(s), optionHelp);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ QXmlStreamWriter xml(f);
+ xml.setAutoFormatting(!compact);
+ xml.writeStartDocument();
+ variantToXml(xml, contents);
+ xml.writeEndDocument();
+}