diff options
Diffstat (limited to 'examples/corelib/serialization/convert/xmlconverter.cpp')
-rw-r--r-- | examples/corelib/serialization/convert/xmlconverter.cpp | 514 |
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(); +} |