diff options
Diffstat (limited to 'examples/corelib/serialization')
19 files changed, 3288 insertions, 1 deletions
diff --git a/examples/corelib/serialization/cbordump/cbordump.pro b/examples/corelib/serialization/cbordump/cbordump.pro new file mode 100644 index 0000000000..7fb2ef69f0 --- /dev/null +++ b/examples/corelib/serialization/cbordump/cbordump.pro @@ -0,0 +1,14 @@ +QT += core +QT -= gui + +TARGET = cbordump +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/cbordump +INSTALLS += target + +SOURCES += main.cpp diff --git a/examples/corelib/serialization/cbordump/main.cpp b/examples/corelib/serialization/cbordump/main.cpp new file mode 100644 index 0000000000..222bd43645 --- /dev/null +++ b/examples/corelib/serialization/cbordump/main.cpp @@ -0,0 +1,765 @@ +/**************************************************************************** +** +** 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 <QCborStreamReader> +#include <QCommandLineParser> +#include <QCommandLineOption> +#include <QCoreApplication> +#include <QFile> +#include <QLocale> +#include <QStack> + +#include <locale.h> +#include <math.h> +#include <stdarg.h> +#include <stdio.h> + +/* + * To regenerate: + * curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml + * xsltproc tag-transform.xslt cbor-tags.xml + */ + +// GENERATED CODE +struct CborTagDescription +{ + QCborTag tag; + const char *description; // with space and parentheses +}; + +// CBOR Tags +static const CborTagDescription tagDescriptions[] = { + // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + { QCborTag(0), " (Standard date/time string; see Section 2.4.1 [RFC7049])" }, + { QCborTag(1), " (Epoch-based date/time; see Section 2.4.1 [RFC7049])" }, + { QCborTag(2), " (Positive bignum; see Section 2.4.2 [RFC7049])" }, + { QCborTag(3), " (Negative bignum; see Section 2.4.2 [RFC7049])" }, + { QCborTag(4), " (Decimal fraction; see Section 2.4.3 [RFC7049])" }, + { QCborTag(5), " (Bigfloat; see Section 2.4.3 [RFC7049])" }, + { QCborTag(16), " (COSE Single Recipient Encrypted Data Object [RFC8152])" }, + { QCborTag(17), " (COSE Mac w/o Recipients Object [RFC8152])" }, + { QCborTag(18), " (COSE Single Signer Data Object [RFC8152])" }, + { QCborTag(21), " (Expected conversion to base64url encoding; see Section 2.4.4.2 [RFC7049])" }, + { QCborTag(22), " (Expected conversion to base64 encoding; see Section 2.4.4.2 [RFC7049])" }, + { QCborTag(23), " (Expected conversion to base16 encoding; see Section 2.4.4.2 [RFC7049])" }, + { QCborTag(24), " (Encoded CBOR data item; see Section 2.4.4.1 [RFC7049])" }, + { QCborTag(25), " (reference the nth previously seen string)" }, + { QCborTag(26), " (Serialised Perl object with classname and constructor arguments)" }, + { QCborTag(27), " (Serialised language-independent object with type name and constructor arguments)" }, + { QCborTag(28), " (mark value as (potentially) shared)" }, + { QCborTag(29), " (reference nth marked value)" }, + { QCborTag(30), " (Rational number)" }, + { QCborTag(32), " (URI; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(33), " (base64url; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(34), " (base64; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(35), " (Regular expression; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(36), " (MIME message; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(37), " (Binary UUID ( section 4.1.2))" }, + { QCborTag(38), " (Language-tagged string)" }, + { QCborTag(39), " (Identifier)" }, + { QCborTag(61), " (CBOR Web Token (CWT))" }, + { QCborTag(96), " (COSE Encrypted Data Object [RFC8152])" }, + { QCborTag(97), " (COSE MACed Data Object [RFC8152])" }, + { QCborTag(98), " (COSE Signed Data Object [RFC8152])" }, + { QCborTag(256), " (mark value as having string references)" }, + { QCborTag(257), " (Binary MIME message)" }, + { QCborTag(258), " (Mathematical finite set)" }, + { QCborTag(260), " (Network Address (IPv4 or IPv6 or MAC Address))" }, + { QCborTag(264), " (Decimal fraction with arbitrary exponent)" }, + { QCborTag(265), " (Bigfloat with arbitrary exponent)" }, + { QCborTag(1001), " (extended time)" }, + { QCborTag(1002), " (duration)" }, + { QCborTag(1003), " (period)" }, + { QCborTag(22098), " (hint that indicates an additional level of indirection)" }, + { QCborTag(55799), " (Self-describe CBOR; see Section 2.4.5 [RFC7049])" }, + { QCborTag(15309736), " (RAINS Message)" }, + { QCborTag(-1), nullptr } +}; +// END GENERATED CODE + +enum { + // See RFC 7049 section 2. + SmallValueBitLength = 5, + SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */ + Value8Bit = 24, + Value16Bit = 25, + Value32Bit = 26, + Value64Bit = 27 +}; + +struct CborDumper +{ + enum DumpOption { + ShowCompact = 0x01, + ShowWidthIndicators = 0x02, + ShowAnnotated = 0x04 + }; + Q_DECLARE_FLAGS(DumpOptions, DumpOption) + + CborDumper(QFile *f, DumpOptions opts_); + QCborError dump(); + +private: + void dumpOne(int nestingLevel); + void dumpOneDetailed(int nestingLevel); + + void printByteArray(const QByteArray &ba); + void printWidthIndicator(quint64 value, char space = '\0'); + void printStringWidthIndicator(quint64 value); + + QCborStreamReader reader; + QByteArray data; + QStack<quint8> byteArrayEncoding; + qint64 offset = 0; + DumpOptions opts; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(CborDumper::DumpOptions) + +static int cborNumberSize(quint64 value) +{ + int normalSize = 1; + if (value > std::numeric_limits<quint32>::max()) + normalSize += 8; + else if (value > std::numeric_limits<quint16>::max()) + normalSize += 4; + else if (value > std::numeric_limits<quint8>::max()) + normalSize += 2; + else if (value >= Value8Bit) + normalSize += 1; + return normalSize; +} + +CborDumper::CborDumper(QFile *f, DumpOptions opts_) + : opts(opts_) +{ + // try to mmap the file, this is faster + char *ptr = reinterpret_cast<char *>(f->map(0, f->size(), QFile::MapPrivateOption)); + if (ptr) { + // worked + data = QByteArray::fromRawData(ptr, f->size()); + reader.addData(data); + } else if ((opts & ShowAnnotated) || f->isSequential()) { + // details requires full contents, so allocate memory + data = f->readAll(); + reader.addData(data); + } else { + // just use the QIODevice + reader.setDevice(f); + } +} + +QCborError CborDumper::dump() +{ + byteArrayEncoding << quint8(QCborKnownTags::ExpectedBase16); + if (!reader.lastError()) { + if (opts & ShowAnnotated) + dumpOneDetailed(0); + else + dumpOne(0); + } + + QCborError err = reader.lastError(); + offset = reader.currentOffset(); + if (err) { + fflush(stdout); + fprintf(stderr, "cbordump: decoding failed at %lld: %s\n", + offset, qPrintable(err.toString())); + if (!data.isEmpty()) + fprintf(stderr, " bytes at %lld: %s\n", offset, + data.mid(offset, 9).toHex(' ').constData()); + } else { + if (!opts.testFlag(ShowAnnotated)) + printf("\n"); + if (offset < data.size() || (reader.device() && reader.device()->bytesAvailable())) + fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n"); + } + + return err; +} + +template <typename T> static inline bool canConvertTo(double v) +{ + // The [conv.fpint] (7.10 Floating-integral conversions) section of the + // standard says only exact conversions are guaranteed. Converting + // integrals to floating-point with loss of precision has implementation- + // defined behavior whether the next higher or next lower is returned; + // converting FP to integral is UB if it can't be represented.; + Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer); + + double supremum = ldexp(1, std::numeric_limits<T>::digits); + if (v >= supremum) + return false; + + if (v < std::numeric_limits<T>::min()) // either zero or a power of two, so it's exact + return false; + + // we're in range + return v == floor(v); +} + +static QString fpToString(double v, const char *suffix) +{ + if (qIsInf(v)) + return v < 0 ? QStringLiteral("-inf") : QStringLiteral("inf"); + if (qIsNaN(v)) + return QStringLiteral("nan"); + if (canConvertTo<qint64>(v)) + return QString::number(qint64(v)) + ".0" + suffix; + if (canConvertTo<quint64>(v)) + return QString::number(quint64(v)) + ".0" + suffix; + + QString s = QString::number(v, 'g', QLocale::FloatingPointShortest); + if (!s.contains('.') && !s.contains('e')) + s += '.'; + s += suffix; + return s; +}; + +void CborDumper::dumpOne(int nestingLevel) +{ + QString indent(1, QLatin1Char(' ')); + QString indented = indent; + if (!opts.testFlag(ShowCompact)) { + indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' ')); + indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' ')); + } + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: { + quint64 u = reader.toUnsignedInteger(); + printf("%llu", u); + reader.next(); + printWidthIndicator(u); + return; + } + + case QCborStreamReader::NegativeInteger: { + quint64 n = quint64(reader.toNegativeInteger()); + if (n == 0) // -2^64 (wrapped around) + printf("-18446744073709551616"); + else + printf("-%llu", n); + reader.next(); + printWidthIndicator(n); + return; + } + + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: { + bool isLengthKnown = reader.isLengthKnown(); + if (!isLengthKnown) { + printf("(_ "); + ++offset; + } + + QString comma; + if (reader.isByteArray()) { + auto r = reader.readByteArray(); + while (r.status == QCborStreamReader::Ok) { + printf("%s", qPrintable(comma)); + printByteArray(r.data); + printStringWidthIndicator(r.data.size()); + + r = reader.readByteArray(); + comma = QLatin1Char(',') + indented; + } + } else { + auto r = reader.readString(); + while (r.status == QCborStreamReader::Ok) { + printf("%s\"%s\"", qPrintable(comma), qPrintable(r.data)); + printStringWidthIndicator(r.data.toUtf8().size()); + + r = reader.readString(); + comma = QLatin1Char(',') + indented; + } + } + + if (!isLengthKnown && !reader.lastError()) + printf(")"); + break; + } + + case QCborStreamReader::Array: + case QCborStreamReader::Map: { + const char *delimiters = (reader.isArray() ? "[]" : "{}"); + printf("%c", delimiters[0]); + + if (reader.isLengthKnown()) { + quint64 len = reader.length(); + reader.enterContainer(); + printWidthIndicator(len, ' '); + } else { + reader.enterContainer(); + offset = reader.currentOffset(); + printf("_ "); + } + + const char *comma = ""; + while (!reader.lastError() && reader.hasNext()) { + printf("%s%s", comma, qPrintable(indented)); + comma = ","; + dumpOne(nestingLevel + 1); + + if (reader.parentContainerType() != QCborStreamReader::Map) + continue; + if (reader.lastError()) + break; + printf(": "); + dumpOne(nestingLevel + 1); + } + + if (!reader.lastError()) { + reader.leaveContainer(); + printf("%s%c", qPrintable(indent), delimiters[1]); + } + break; + } + + case QCborStreamReader::Tag: { + QCborTag tag = reader.toTag(); + printf("%llu", quint64(tag)); + + if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64 + || tag == QCborKnownTags::ExpectedBase64url) + byteArrayEncoding.push(quint8(tag)); + + if (reader.next()) { + printWidthIndicator(quint64(tag)); + printf("("); + dumpOne(nestingLevel); // same level! + printf(")"); + } + + if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64 + || tag == QCborKnownTags::ExpectedBase64url) + byteArrayEncoding.pop(); + break; + } + + case QCborStreamReader::SimpleType: + switch (reader.toSimpleType()) { + case QCborSimpleType::False: + printf("false"); + break; + case QCborSimpleType::True: + printf("true"); + break; + case QCborSimpleType::Null: + printf("null"); + break; + case QCborSimpleType::Undefined: + printf("undefined"); + break; + default: + printf("simple(%u)", quint8(reader.toSimpleType())); + break; + } + reader.next(); + break; + + case QCborStreamReader::Float16: + printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"))); + reader.next(); + break; + case QCborStreamReader::Float: + printf("%s", qPrintable(fpToString(reader.toFloat(), "f"))); + reader.next(); + break; + case QCborStreamReader::Double: + printf("%s", qPrintable(fpToString(reader.toDouble(), ""))); + reader.next(); + break; + case QCborStreamReader::Invalid: + return; + } + + offset = reader.currentOffset(); +} + +void CborDumper::dumpOneDetailed(int nestingLevel) +{ + auto tagDescription = [](QCborTag tag) { + for (auto entry : tagDescriptions) { + if (entry.tag == tag) + return entry.description; + if (entry.tag > tag) + break; + } + return ""; + }; + auto printOverlong = [](int actualSize, quint64 value) { + if (cborNumberSize(value) != actualSize) + printf(" (overlong)"); + }; + auto print = [=](const char *descr, const char *fmt, ...) { + qint64 prevOffset = offset; + offset = reader.currentOffset(); + if (prevOffset == offset) + return; + + QByteArray bytes = data.mid(prevOffset, offset - prevOffset); + QByteArray indent(nestingLevel * 2, ' '); + printf("%-50s # %s ", (indent + bytes.toHex(' ')).constData(), descr); + + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + + if (strstr(fmt, "%ll")) { + // Only works because all callers below that use %ll, use it as the + // first arg + va_start(va, fmt); + quint64 value = va_arg(va, quint64); + va_end(va); + printOverlong(bytes.size(), value); + } + + puts(""); + }; + + auto printFp = [=](const char *descr, double d) { + QString s = fpToString(d, ""); + if (s.size() <= 6) + return print(descr, "%s", qPrintable(s)); + return print(descr, "%a", d); + }; + + auto printString = [=](const char *descr) { + QByteArray indent(nestingLevel * 2, ' '); + const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk "); + int width = 48 - indent.size(); + int bytesPerLine = qMax(width / 3, 5); + + qsizetype size = reader.currentStringChunkSize(); + if (size < 0) + return; // error + if (size >= std::numeric_limits<int>::max()) { + fprintf(stderr, "String length too big, %lli\n", qint64(size)); + exit(EXIT_FAILURE); + } + + // if asking for the current string chunk changes the offset, then it + // was chunked + print(descr, "(indeterminate length)"); + + QByteArray bytes(size, Qt::Uninitialized); + auto r = reader.readStringChunk(bytes.data(), bytes.size()); + while (r.status == QCborStreamReader::Ok) { + // We'll have to decode the length's width directly from CBOR... + const char *lenstart = data.constData() + offset; + const char *lenend = lenstart + 1; + quint8 additionalInformation = (*lenstart & SmallValueMask); + + // Decode this number directly from CBOR (see RFC 7049 section 2) + if (additionalInformation >= Value8Bit) { + if (additionalInformation == Value8Bit) + lenend += 1; + else if (additionalInformation == Value16Bit) + lenend += 2; + else if (additionalInformation == Value32Bit) + lenend += 4; + else + lenend += 8; + } + + { + QByteArray lenbytes = QByteArray::fromRawData(lenstart, lenend - lenstart); + printf("%-50s # %s %slength %llu", + (indent + lenbytes.toHex(' ')).constData(), descr, chunkStr, quint64(size)); + printOverlong(lenbytes.size(), size); + puts(""); + } + + offset = reader.currentOffset(); + + for (int i = 0; i < r.data; i += bytesPerLine) { + QByteArray section = bytes.mid(i, bytesPerLine); + printf(" %s%s", indent.constData(), section.toHex(' ').constData()); + + // print the decode + QByteArray spaces(width > 0 ? width - section.size() * 3 + 1: 0, ' '); + printf("%s # \"", spaces.constData()); + auto ptr = reinterpret_cast<const uchar *>(section.constData()); + for (int j = 0; j < section.size(); ++j) + printf("%c", ptr[j] >= 0x80 || ptr[j] < 0x20 ? '.' : ptr[j]); + + puts("\""); + } + + // get the next chunk + size = reader.currentStringChunkSize(); + if (size < 0) + return; // error + if (size >= std::numeric_limits<int>::max()) { + fprintf(stderr, "String length too big, %lli\n", qint64(size)); + exit(EXIT_FAILURE); + } + bytes.resize(size); + r = reader.readStringChunk(bytes.data(), bytes.size()); + } + }; + + if (reader.lastError()) + return; + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: { + quint64 u = reader.toUnsignedInteger(); + reader.next(); + if (u < 65536 || (u % 100000) == 0) + print("Unsigned integer", "%llu", u); + else + print("Unsigned integer", "0x%llx", u); + return; + } + + case QCborStreamReader::NegativeInteger: { + quint64 n = quint64(reader.toNegativeInteger()); + reader.next(); + print("Negative integer", n == 0 ? "-18446744073709551616" : "-%llu", n); + return; + } + + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: { + bool isLengthKnown = reader.isLengthKnown(); + const char *descr = (reader.isString() ? "Text string" : "Byte string"); + if (!isLengthKnown) + ++nestingLevel; + + printString(descr); + if (reader.lastError()) + return; + + if (!isLengthKnown) { + --nestingLevel; + print("Break", ""); + } + break; + } + + case QCborStreamReader::Array: + case QCborStreamReader::Map: { + const char *descr = (reader.isArray() ? "Array" : "Map"); + if (reader.isLengthKnown()) { + quint64 len = reader.length(); + reader.enterContainer(); + print(descr, "length %llu", len); + } else { + reader.enterContainer(); + print(descr, "(indeterminate length)"); + } + + while (!reader.lastError() && reader.hasNext()) + dumpOneDetailed(nestingLevel + 1); + + if (!reader.lastError()) { + reader.leaveContainer(); + print("Break", ""); + } + break; + } + + case QCborStreamReader::Tag: { + QCborTag tag = reader.toTag(); + reader.next(); + print("Tag", "%llu%s", quint64(tag), tagDescription(tag)); + dumpOneDetailed(nestingLevel + 1); + break; + } + + case QCborStreamReader::SimpleType: { + QCborSimpleType st = reader.toSimpleType(); + reader.next(); + switch (st) { + case QCborSimpleType::False: + print("Simple Type", "false"); + break; + case QCborSimpleType::True: + print("Simple Type", "true"); + break; + case QCborSimpleType::Null: + print("Simple Type", "null"); + break; + case QCborSimpleType::Undefined: + print("Simple Type", "undefined"); + break; + default: + print("Simple Type", "%u", quint8(st)); + break; + } + break; + } + + case QCborStreamReader::Float16: { + double d = reader.toFloat16(); + reader.next(); + printFp("Float16", d); + break; + } + case QCborStreamReader::Float: { + double d = reader.toFloat(); + reader.next(); + printFp("Float", d); + break; + } + case QCborStreamReader::Double: { + double d = reader.toDouble(); + reader.next(); + printFp("Double", d); + break; + } + case QCborStreamReader::Invalid: + return; + } + + offset = reader.currentOffset(); +} + +void CborDumper::printByteArray(const QByteArray &ba) +{ + switch (byteArrayEncoding.top()) { + default: + printf("h'%s'", ba.toHex(' ').constData()); + break; + + case quint8(QCborKnownTags::ExpectedBase64): + printf("b64'%s'", ba.toBase64().constData()); + break; + + case quint8(QCborKnownTags::ExpectedBase64url): + printf("b64'%s'", ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData()); + break; + } +} + +void printIndicator(quint64 value, qint64 previousOffset, qint64 offset, char space) +{ + int normalSize = cborNumberSize(value); + int actualSize = offset - previousOffset; + + if (actualSize != normalSize) { + Q_ASSERT(actualSize > 1); + actualSize -= 2; + printf("_%d", qPopulationCount(uint(actualSize))); + if (space) + printf("%c", space); + } +} + +void CborDumper::printWidthIndicator(quint64 value, char space) +{ + qint64 previousOffset = offset; + offset = reader.currentOffset(); + if (opts & ShowWidthIndicators) + printIndicator(value, previousOffset, offset, space); +} + +void CborDumper::printStringWidthIndicator(quint64 value) +{ + qint64 previousOffset = offset; + offset = reader.currentOffset(); + if (opts & ShowWidthIndicators) + printIndicator(value, previousOffset, offset - uint(value), '\0'); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + setlocale(LC_ALL, "C"); + + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool")); + parser.addHelpOption(); + + QCommandLineOption compact({QStringLiteral("c"), QStringLiteral("compact")}, + QStringLiteral("Use compact form (no line breaks)")); + parser.addOption(compact); + + QCommandLineOption showIndicators({QStringLiteral("i"), QStringLiteral("indicators")}, + QStringLiteral("Show indicators for width of lengths and integrals")); + parser.addOption(showIndicators); + + QCommandLineOption verbose({QStringLiteral("a"), QStringLiteral("annotated")}, + QStringLiteral("Show bytes and annotated decoding")); + parser.addOption(verbose); + + parser.addPositionalArgument(QStringLiteral("[source]"), + QStringLiteral("CBOR file to read from")); + + parser.process(app); + + CborDumper::DumpOptions opts; + if (parser.isSet(compact)) + opts |= CborDumper::ShowCompact; + if (parser.isSet(showIndicators)) + opts |= CborDumper::ShowWidthIndicators; + if (parser.isSet(verbose)) + opts |= CborDumper::ShowAnnotated; + + QStringList files = parser.positionalArguments(); + if (files.isEmpty()) + files << "-"; + for (const QString &file : qAsConst(files)) { + QFile f(file); + if (file == "-" ? f.open(stdin, QIODevice::ReadOnly) : f.open(QIODevice::ReadOnly)) { + if (files.size() > 1) + printf("/ From \"%s\" /\n", qPrintable(file)); + + CborDumper dumper(&f, opts); + QCborError err = dumper.dump(); + if (err) + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/corelib/serialization/cbordump/tag-transform.xslt b/examples/corelib/serialization/cbordump/tag-transform.xslt new file mode 100644 index 0000000000..3cc1b9b293 --- /dev/null +++ b/examples/corelib/serialization/cbordump/tag-transform.xslt @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://www.iana.org/assignments" xmlns="http://www.iana.org/assignments" xmlns:_="http://www.iana.org/assignments" xmlns:DEFAULT="http://www.iana.org/assignments" version="1.0"> +<xsl:output omit-xml-declaration="yes" indent="no" method="text"/> +<xsl:template match="/a:registry[@id='cbor-tags']">struct CborTagDescription +{ + QCborTag tag; + const char *description; // with space and parentheses +}; + +// <xsl:value-of select="a:registry/a:title"/> +static const CborTagDescription tagDescriptions[] = { + // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml +<xsl:for-each select="a:registry/a:record"> + <xsl:sort select="a:value" data-type="number"/> + <xsl:if test="a:semantics != ''"> + <xsl:call-template name="row"/> + </xsl:if> + </xsl:for-each> { QCborTag(-1), nullptr } +}; +</xsl:template> +<xsl:template name="row"> { QCborTag(<xsl:value-of select="a:value"/>), " (<xsl:value-of select="a:semantics"/> <xsl:call-template name="xref"/>)" }, +</xsl:template> +<xsl:template name="xref"><xsl:if test="a:xref/@type = 'rfc'"> [<xsl:value-of select="translate(a:xref/@data,'rfc','RFC')"/>]</xsl:if> +</xsl:template> +</xsl:stylesheet> diff --git a/examples/corelib/serialization/convert/cborconverter.cpp b/examples/corelib/serialization/convert/cborconverter.cpp new file mode 100644 index 0000000000..41724c935e --- /dev/null +++ b/examples/corelib/serialization/convert/cborconverter.cpp @@ -0,0 +1,393 @@ +/**************************************************************************** +** +** 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 "cborconverter.h" + +#include <QCborStreamReader> +#include <QCborStreamWriter> +#include <QCborMap> +#include <QCborArray> +#include <QCborValue> +#include <QDataStream> +#include <QDebug> +#include <QFloat16> +#include <QFile> +#include <QMetaType> +#include <QTextStream> + +#include <stdio.h> + +static CborConverter cborConverter; +static CborDiagnosticDumper cborDiagnosticDumper; + +static const char optionHelp[] = + "convert-float-to-int=yes|no Write integers instead of floating point, if no\n" + " loss of precision occurs on conversion.\n" + "float16=yes|always|no Write using half-precision floating point.\n" + " If 'always', won't check for loss of precision.\n" + "float32=yes|always|no Write using single-precision floating point.\n" + " If 'always', won't check for loss of precision.\n" + "signature=yes|no Prepend the CBOR signature to the file output.\n" + ; + +static const char diagnosticHelp[] = + "extended=no|yes Use extended CBOR diagnostic format.\n" + "line-wrap=yes|no Split output into multiple lines.\n" + ; + +QT_BEGIN_NAMESPACE + +QDataStream &operator<<(QDataStream &ds, QCborSimpleType st) +{ + return ds << quint8(st); +} + +QDataStream &operator>>(QDataStream &ds, QCborSimpleType &st) +{ + quint8 v; + ds >> v; + st = QCborSimpleType(v); + return ds; +} + +QDataStream &operator<<(QDataStream &ds, QCborTag tag) +{ + return ds << quint64(tag); +} + +QDataStream &operator>>(QDataStream &ds, QCborTag &tag) +{ + quint64 v; + ds >> v; + tag = QCborTag(v); + return ds; +} + +QT_END_NAMESPACE + +// We can't use QCborValue::toVariant directly because that would destroy +// non-string keys in CBOR maps (QVariantMap can't handle those). Instead, we +// have our own set of converter functions so we can keep the keys properly. + +static QVariant convertCborValue(const QCborValue &value); + +static QVariant convertCborMap(const QCborMap &map) +{ + VariantOrderedMap result; + result.reserve(map.size()); + for (auto pair : map) + result.append({ convertCborValue(pair.first), convertCborValue(pair.second) }); + return QVariant::fromValue(result); +} + +static QVariant convertCborArray(const QCborArray &array) +{ + QVariantList result; + result.reserve(array.size()); + for (auto value : array) + result.append(convertCborValue(value)); + return result; +} + +static QVariant convertCborValue(const QCborValue &value) +{ + if (value.isArray()) + return convertCborArray(value.toArray()); + if (value.isMap()) + return convertCborMap(value.toMap()); + return value.toVariant(); +} + +enum TrimFloatingPoint { Double, Float, Float16 }; +static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming) +{ + if (v.userType() == QVariant::List) { + const QVariantList list = v.toList(); + QCborArray array; + for (const QVariant &v : list) + array.append(convertFromVariant(v, fpTrimming)); + + return array; + } + + if (v.userType() == qMetaTypeId<VariantOrderedMap>()) { + const auto m = v.value<VariantOrderedMap>(); + QCborMap map; + for (const auto &pair : m) + map.insert(convertFromVariant(pair.first, fpTrimming), + convertFromVariant(pair.second, fpTrimming)); + return map; + } + + if (v.userType() == QVariant::Double && fpTrimming != Double) { + float f = float(v.toDouble()); + if (fpTrimming == Float16) + return float(qfloat16(f)); + return f; + } + + return QCborValue::fromVariant(v); +} + +QString CborDiagnosticDumper::name() +{ + return QStringLiteral("cbor-dump"); +} + +Converter::Direction CborDiagnosticDumper::directions() +{ + return Out; +} + +Converter::Options CborDiagnosticDumper::outputOptions() +{ + return SupportsArbitraryMapKeys; +} + +const char *CborDiagnosticDumper::optionsHelp() +{ + return diagnosticHelp; +} + +bool CborDiagnosticDumper::probeFile(QIODevice *f) +{ + Q_UNUSED(f); + return false; +} + +QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverter) +{ + Q_UNREACHABLE(); + Q_UNUSED(f); + Q_UNUSED(outputConverter); + return QVariant(); +} + +void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + QCborValue::DiagnosticNotationOptions opts = QCborValue::LineWrapped; + for (const QString &s : options) { + QStringList pair = s.split('='); + if (pair.size() == 2) { + if (pair.first() == "line-wrap") { + opts &= ~QCborValue::LineWrapped; + if (pair.last() == "yes") { + opts |= QCborValue::LineWrapped; + continue; + } else if (pair.last() == "no") { + continue; + } + } + if (pair.first() == "extended") { + opts &= ~QCborValue::ExtendedFormat; + if (pair.last() == "yes") + opts |= QCborValue::ExtendedFormat; + continue; + } + } + + fprintf(stderr, "Unknown CBOR diagnostic option '%s'. Available options are:\n%s", + qPrintable(s), diagnosticHelp); + exit(EXIT_FAILURE); + } + + QTextStream out(f); + out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) + << endl; +} + +CborConverter::CborConverter() +{ + qRegisterMetaType<QCborTag>(); + qRegisterMetaTypeStreamOperators<QCborTag>(); + QMetaType::registerDebugStreamOperator<QCborTag>(); +} + +QString CborConverter::name() +{ + return "cbor"; +} + +Converter::Direction CborConverter::directions() +{ + return InOut; +} + +Converter::Options CborConverter::outputOptions() +{ + return SupportsArbitraryMapKeys; +} + +const char *CborConverter::optionsHelp() +{ + return optionHelp; +} + +bool CborConverter::probeFile(QIODevice *f) +{ + if (QFile *file = qobject_cast<QFile *>(f)) { + if (file->fileName().endsWith(QLatin1String(".cbor"))) + return true; + } + return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3); +} + +QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) +{ + const char *ptr = nullptr; + if (auto file = qobject_cast<QFile *>(f)) + ptr = reinterpret_cast<char *>(file->map(0, file->size())); + + QByteArray mapped = QByteArray::fromRawData(ptr, ptr ? f->size() : 0); + QCborStreamReader reader(mapped); + if (!ptr) + reader.setDevice(f); + + if (reader.isTag() && reader.toTag() == QCborKnownTags::Signature) + reader.next(); + + QCborValue contents = QCborValue::fromCbor(reader); + qint64 offset = reader.currentOffset(); + if (reader.lastError()) { + fprintf(stderr, "Error loading CBOR contents (byte %lld): %s\n", offset, + qPrintable(reader.lastError().toString())); + fprintf(stderr, " bytes: %s\n", + (ptr ? mapped.mid(offset, 9) : f->read(9)).toHex(' ').constData()); + exit(EXIT_FAILURE); + } else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) { + fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n"); + } + + if (outputConverter == nullptr) + outputConverter = &cborDiagnosticDumper; + else if (outputConverter == null) + return QVariant(); + else if (!outputConverter->outputOptions().testFlag(SupportsArbitraryMapKeys)) + return contents.toVariant(); + return convertCborValue(contents); +} + +void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + bool useSignature = true; + bool useIntegers = true; + enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes; + + for (const QString &s : options) { + QStringList pair = s.split('='); + if (pair.size() == 2) { + if (pair.first() == "convert-float-to-int") { + if (pair.last() == "yes") { + useIntegers = true; + continue; + } else if (pair.last() == "no") { + useIntegers = false; + continue; + } + } + + if (pair.first() == "float16") { + if (pair.last() == "no") { + useFloat16 = No; + continue; + } else if (pair.last() == "yes") { + useFloat16 = Yes; + continue; + } else if (pair.last() == "always") { + useFloat16 = Always; + continue; + } + } + + if (pair.first() == "float32") { + if (pair.last() == "no") { + useFloat = No; + continue; + } else if (pair.last() == "yes") { + useFloat = Yes; + continue; + } else if (pair.last() == "always") { + useFloat = Always; + continue; + } + } + + if (pair.first() == "signature") { + if (pair.last() == "yes") { + useSignature = true; + continue; + } else if (pair.last() == "no") { + useSignature = false; + continue; + } + } + } + + fprintf(stderr, "Unknown CBOR format option '%s'. Valid options are:\n%s", + qPrintable(s), optionHelp); + exit(EXIT_FAILURE); + } + + QCborValue v = convertFromVariant(contents, + useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); + QCborStreamWriter writer(f); + if (useSignature) + writer.append(QCborKnownTags::Signature); + + QCborValue::EncodingOptions opts; + if (useIntegers) + opts |= QCborValue::UseIntegers; + if (useFloat != No) + opts |= QCborValue::UseFloat; + if (useFloat16 != No) + opts |= QCborValue::UseFloat16; + v.toCbor(writer, opts); +} + diff --git a/examples/corelib/serialization/convert/cborconverter.h b/examples/corelib/serialization/convert/cborconverter.h new file mode 100644 index 0000000000..f0a89cb141 --- /dev/null +++ b/examples/corelib/serialization/convert/cborconverter.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef CBORCONVERTER_H +#define CBORCONVERTER_H + +#include "converter.h" + +class CborDiagnosticDumper : public Converter +{ + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +class CborConverter : public Converter +{ +public: + CborConverter(); + + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +#endif // CBORCONVERTER_H diff --git a/examples/corelib/serialization/convert/convert.pro b/examples/corelib/serialization/convert/convert.pro new file mode 100644 index 0000000000..d9b1de41e3 --- /dev/null +++ b/examples/corelib/serialization/convert/convert.pro @@ -0,0 +1,29 @@ +QT += core +QT -= gui + +TARGET = convert +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/convert +INSTALLS += target + +SOURCES += main.cpp \ + cborconverter.cpp \ + jsonconverter.cpp \ + datastreamconverter.cpp \ + textconverter.cpp \ + xmlconverter.cpp \ + nullconverter.cpp + +HEADERS += \ + converter.h \ + cborconverter.h \ + jsonconverter.h \ + datastreamconverter.h \ + textconverter.h \ + xmlconverter.h \ + nullconverter.h diff --git a/examples/corelib/serialization/convert/converter.h b/examples/corelib/serialization/convert/converter.h new file mode 100644 index 0000000000..82e1fa1cb3 --- /dev/null +++ b/examples/corelib/serialization/convert/converter.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef CONVERTER_H +#define CONVERTER_H + +#include <QIODevice> +#include <QPair> +#include <QVariant> +#include <QVector> + +class VariantOrderedMap : public QVector<QPair<QVariant, QVariant>> +{ +public: + VariantOrderedMap() = default; + VariantOrderedMap(const QVariantMap &map) + { + reserve(map.size()); + for (auto it = map.begin(); it != map.end(); ++it) + append({it.key(), it.value()}); + } +}; +using Map = VariantOrderedMap; +Q_DECLARE_METATYPE(Map) + +class Converter +{ +protected: + Converter(); + +public: + static Converter *null; + + enum Direction { + In = 1, Out = 2, InOut = 3 + }; + + enum Option { + SupportsArbitraryMapKeys = 0x01 + }; + Q_DECLARE_FLAGS(Options, Option) + + virtual ~Converter() = 0; + + virtual QString name() = 0; + virtual Direction directions() = 0; + virtual Options outputOptions() = 0; + virtual const char *optionsHelp() = 0; + virtual bool probeFile(QIODevice *f) = 0; + virtual QVariant loadFile(QIODevice *f, Converter *&outputConverter) = 0; + virtual void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) = 0; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Options) + +#endif // CONVERTER_H diff --git a/examples/corelib/serialization/convert/datastreamconverter.cpp b/examples/corelib/serialization/convert/datastreamconverter.cpp new file mode 100644 index 0000000000..7cdb844141 --- /dev/null +++ b/examples/corelib/serialization/convert/datastreamconverter.cpp @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** 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 "datastreamconverter.h" + +#include <QDataStream> +#include <QDebug> +#include <QTextStream> + +static const char optionHelp[] = + "byteorder=host|big|little Byte order to use.\n" + "version=<n> QDataStream version (default: Qt 5.0).\n" + ; + +static const char signature[] = "qds"; + +static DataStreamDumper dataStreamDumper; +static DataStreamConverter DataStreamConverter; + +QDataStream &operator<<(QDataStream &ds, const VariantOrderedMap &map) +{ + ds << qint64(map.size()); + for (const auto &pair : map) + ds << pair.first << pair.second; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, VariantOrderedMap &map) +{ + map.clear(); + + qint64 size; + ds >> size; + map.reserve(size); + + while (size-- > 0) { + VariantOrderedMap::value_type pair; + ds >> pair.first >> pair.second; + map.append(pair); + } + + return ds; +} + + +static QString dumpVariant(const QVariant &v, const QString &indent = QLatin1String("\n")) +{ + QString result; + QString indented = indent + QLatin1String(" "); + + int type = v.userType(); + if (type == qMetaTypeId<VariantOrderedMap>() || type == QVariant::Map) { + const auto map = (type == QVariant::Map) ? + VariantOrderedMap(v.toMap()) : v.value<VariantOrderedMap>(); + + result = QLatin1String("Map {"); + for (const auto &pair : map) { + result += indented + dumpVariant(pair.first, indented); + result.chop(1); // remove comma + result += QLatin1String(" => ") + dumpVariant(pair.second, indented); + + } + result.chop(1); // remove comma + result += indent + QLatin1String("},"); + } else if (type == QVariant::List) { + const QVariantList list = v.toList(); + + result = QLatin1String("List ["); + for (const auto &item : list) + result += indented + dumpVariant(item, indented); + result.chop(1); // remove comma + result += indent + QLatin1String("],"); + } else { + QDebug debug(&result); + debug.nospace() << v << ','; + } + return result; +} + +QString DataStreamDumper::name() +{ + return QStringLiteral("datastream-dump"); +} + +Converter::Direction DataStreamDumper::directions() +{ + return Out; +} + +Converter::Options DataStreamDumper::outputOptions() +{ + return SupportsArbitraryMapKeys; +} + +const char *DataStreamDumper::optionsHelp() +{ + return nullptr; +} + +bool DataStreamDumper::probeFile(QIODevice *f) +{ + Q_UNUSED(f); + return false; +} + +QVariant DataStreamDumper::loadFile(QIODevice *f, Converter *&outputConverter) +{ + Q_UNREACHABLE(); + Q_UNUSED(f); + Q_UNUSED(outputConverter); + return QVariant(); +} + +void DataStreamDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + Q_UNUSED(options); + QString s = dumpVariant(contents); + s[s.size() - 1] = QLatin1Char('\n'); // replace the comma with newline + + QTextStream out(f); + out << s; +} + +DataStreamConverter::DataStreamConverter() +{ + qRegisterMetaType<VariantOrderedMap>(); + qRegisterMetaTypeStreamOperators<VariantOrderedMap>(); +} + +QString DataStreamConverter::name() +{ + return QStringLiteral("datastream"); +} + +Converter::Direction DataStreamConverter::directions() +{ + return InOut; +} + +Converter::Options DataStreamConverter::outputOptions() +{ + return SupportsArbitraryMapKeys; +} + +const char *DataStreamConverter::optionsHelp() +{ + return optionHelp; +} + +bool DataStreamConverter::probeFile(QIODevice *f) +{ + return f->isReadable() && f->peek(sizeof(signature) - 1) == signature; +} + +QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter) +{ + if (!outputConverter) + outputConverter = &dataStreamDumper; + + char c; + if (f->read(sizeof(signature) -1) != signature || + !f->getChar(&c) || (c != 'l' && c != 'B')) { + fprintf(stderr, "Could not load QDataStream file: invalid signature.\n"); + exit(EXIT_FAILURE); + } + + QDataStream ds(f); + ds.setByteOrder(c == 'l' ? QDataStream::LittleEndian : QDataStream::BigEndian); + + std::underlying_type<QDataStream::Version>::type version; + ds >> version; + ds.setVersion(QDataStream::Version(version)); + + QVariant result; + ds >> result; + return result; +} + +void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + QDataStream::Version version = QDataStream::Qt_5_0; + auto order = QDataStream::ByteOrder(QSysInfo::ByteOrder); + for (const QString &option : options) { + const QStringList pair = option.split('='); + if (pair.size() == 2) { + if (pair.first() == "byteorder") { + if (pair.last() == "little") { + order = QDataStream::LittleEndian; + continue; + } else if (pair.last() == "big") { + order = QDataStream::BigEndian; + continue; + } else if (pair.last() == "host") { + order = QDataStream::ByteOrder(QSysInfo::ByteOrder); + continue; + } + } + if (pair.first() == "version") { + bool ok; + int n = pair.last().toInt(&ok); + if (ok) { + version = QDataStream::Version(n); + continue; + } + + fprintf(stderr, "Invalid version number '%s': must be a number from 1 to %d.\n", + qPrintable(pair.last()), QDataStream::Qt_DefaultCompiledVersion); + exit(EXIT_FAILURE); + } + } + + fprintf(stderr, "Unknown QDataStream formatting option '%s'. Available options are:\n%s", + qPrintable(option), optionHelp); + exit(EXIT_FAILURE); + } + + char c = order == QDataStream::LittleEndian ? 'l' : 'B'; + f->write(signature); + f->write(&c, 1); + + QDataStream ds(f); + ds.setVersion(version); + ds.setByteOrder(order); + ds << std::underlying_type<decltype(version)>::type(version); + ds << contents; +} diff --git a/examples/corelib/serialization/convert/datastreamconverter.h b/examples/corelib/serialization/convert/datastreamconverter.h new file mode 100644 index 0000000000..1b74abc54f --- /dev/null +++ b/examples/corelib/serialization/convert/datastreamconverter.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef DATASTREAMCONVERTER_H +#define DATASTREAMCONVERTER_H + +#include "converter.h" + +class DataStreamDumper : public Converter +{ + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +class DataStreamConverter : public Converter +{ +public: + DataStreamConverter(); + + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +#endif // DATASTREAMCONVERTER_H diff --git a/examples/corelib/serialization/convert/jsonconverter.cpp b/examples/corelib/serialization/convert/jsonconverter.cpp new file mode 100644 index 0000000000..80d1cc6827 --- /dev/null +++ b/examples/corelib/serialization/convert/jsonconverter.cpp @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** 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 "jsonconverter.h" + +#include <QFile> +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> + +static JsonConverter jsonConverter; +static BinaryJsonConverter BinaryJsonConverter; + +static const char optionHelp[] = + "compact=no|yes Use compact JSON form.\n"; + +static QJsonDocument convertFromVariant(const QVariant &v) +{ + QJsonDocument doc = QJsonDocument::fromVariant(v); + if (!doc.isObject() && !doc.isArray()) { + fprintf(stderr, "Could not convert contents to JSON.\n"); + exit(EXIT_FAILURE); + } + return doc; +} + +JsonConverter::JsonConverter() +{ +} + +QString JsonConverter::name() +{ + return "json"; +} + +Converter::Direction JsonConverter::directions() +{ + return InOut; +} + +Converter::Options JsonConverter::outputOptions() +{ + return {}; +} + +const char *JsonConverter::optionsHelp() +{ + return optionHelp; +} + +bool JsonConverter::probeFile(QIODevice *f) +{ + if (QFile *file = qobject_cast<QFile *>(f)) { + if (file->fileName().endsWith(QLatin1String(".json"))) + return true; + } + + if (f->isReadable()) { + QByteArray ba = f->peek(1); + return ba == "{" || ba == "["; + } + return false; +} + +QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) +{ + if (!outputConverter) + outputConverter = this; + + QJsonParseError error; + QJsonDocument doc; + if (auto file = qobject_cast<QFile *>(f)) { + const char *ptr = reinterpret_cast<char *>(file->map(0, file->size())); + if (ptr) + doc = QJsonDocument::fromJson(QByteArray::fromRawData(ptr, file->size()), &error); + } + + if (doc.isNull()) + doc = QJsonDocument::fromJson(f->readAll(), &error); + if (error.error) { + fprintf(stderr, "Could not parse JSON content: offset %d: %s", + error.offset, qPrintable(error.errorString())); + exit(EXIT_FAILURE); + } + if (outputConverter == null) + return QVariant(); + return doc.toVariant(); +} + +void JsonConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + QJsonDocument::JsonFormat format = QJsonDocument::Indented; + for (const QString &s : options) { + if (s == QLatin1String("compact=no")) { + format = QJsonDocument::Indented; + } else if (s == QLatin1String("compact=yes")) { + format = QJsonDocument::Compact; + } else { + fprintf(stderr, "Unknown option '%s' to JSON output. Valid options are:\n%s", qPrintable(s), optionHelp); + exit(EXIT_FAILURE); + } + } + + f->write(convertFromVariant(contents).toJson(format)); +} + +QString BinaryJsonConverter::name() +{ + return "binary-json"; +} + +Converter::Direction BinaryJsonConverter::directions() +{ + return InOut; +} + +Converter::Options BinaryJsonConverter::outputOptions() +{ + return {}; +} + +const char *BinaryJsonConverter::optionsHelp() +{ + return nullptr; +} + +bool BinaryJsonConverter::probeFile(QIODevice *f) +{ + return f->isReadable() && f->peek(4) == "qbjs"; +} + +QVariant BinaryJsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) +{ + if (!outputConverter) + outputConverter = &jsonConverter; + + QJsonDocument doc; + if (auto file = qobject_cast<QFile *>(f)) { + uchar *ptr = file->map(0, file->size()); + if (ptr) + doc = QJsonDocument::fromRawData(reinterpret_cast<char *>(ptr), file->size()); + } + + if (doc.isNull()) + doc = QJsonDocument::fromBinaryData(f->readAll()); + + if (!doc.isObject() && !doc.isArray()) { + fprintf(stderr, "Failed to load Binary JSON.\n"); + exit(EXIT_FAILURE); + } + if (outputConverter == null) + return QVariant(); + return doc.toVariant(); +} + +void BinaryJsonConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + if (!options.isEmpty()) { + fprintf(stderr, "Unknown option '%s' to JSON output. This format has no options.\n", qPrintable(options.first())); + exit(EXIT_FAILURE); + } + + f->write(convertFromVariant(contents).toBinaryData()); +} diff --git a/examples/corelib/serialization/convert/jsonconverter.h b/examples/corelib/serialization/convert/jsonconverter.h new file mode 100644 index 0000000000..17170603c7 --- /dev/null +++ b/examples/corelib/serialization/convert/jsonconverter.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONCONVERTER_H +#define JSONCONVERTER_H + +#include "converter.h" + +class JsonConverter : public Converter +{ +public: + JsonConverter(); + + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +class BinaryJsonConverter : public Converter +{ + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +#endif // JSONCONVERTER_H diff --git a/examples/corelib/serialization/convert/main.cpp b/examples/corelib/serialization/convert/main.cpp new file mode 100644 index 0000000000..e9d14792b0 --- /dev/null +++ b/examples/corelib/serialization/convert/main.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** 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 "converter.h" + +#include <QCommandLineParser> +#include <QCommandLineOption> +#include <QCoreApplication> +#include <QFile> +#include <QFileInfo> + +#include <stdio.h> + +static QVector<Converter *> *availableConverters; + +Converter::Converter() +{ + if (!availableConverters) + availableConverters = new QVector<Converter *>; + availableConverters->append(this); +} + +Converter::~Converter() +{ + availableConverters->removeAll(this); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QStringList inputFormats; + QStringList outputFormats; + for (Converter *conv : qAsConst(*availableConverters)) { + auto direction = conv->directions(); + QString name = conv->name(); + if (direction & Converter::In) + inputFormats << name; + if (direction & Converter::Out) + outputFormats << name; + } + inputFormats.sort(); + outputFormats.sort(); + inputFormats.prepend("auto"); + outputFormats.prepend("auto"); + + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("Qt file format conversion tool")); + parser.addHelpOption(); + + QCommandLineOption inputFormatOption(QStringList{"I", "input-format"}); + inputFormatOption.setDescription(QLatin1String("Select the input format for the input file. Available formats: ") + + inputFormats.join(", ")); + inputFormatOption.setValueName("format"); + inputFormatOption.setDefaultValue(inputFormats.constFirst()); + parser.addOption(inputFormatOption); + + QCommandLineOption outputFormatOption(QStringList{"O", "output-format"}); + outputFormatOption.setDescription(QLatin1String("Select the output format for the output file. Available formats: ") + + outputFormats.join(", ")); + outputFormatOption.setValueName("format"); + outputFormatOption.setDefaultValue(outputFormats.constFirst()); + parser.addOption(outputFormatOption); + + QCommandLineOption optionOption(QStringList{"o", "option"}); + optionOption.setDescription(QStringLiteral("Format-specific options. Use --format-options to find out what options are available.")); + optionOption.setValueName("options..."); + optionOption.setDefaultValues({}); + parser.addOption(optionOption); + + QCommandLineOption formatOptionsOption("format-options"); + formatOptionsOption.setDescription(QStringLiteral("Prints the list of valid options for --option for the converter format <format>.")); + formatOptionsOption.setValueName("format"); + parser.addOption(formatOptionsOption); + + parser.addPositionalArgument(QStringLiteral("[source]"), + QStringLiteral("File to read from (stdin if none)")); + parser.addPositionalArgument(QStringLiteral("[destination]"), + QStringLiteral("File to write to (stdout if none)")); + + parser.process(app); + + if (parser.isSet(formatOptionsOption)) { + QString format = parser.value(formatOptionsOption); + for (Converter *conv : qAsConst(*availableConverters)) { + if (conv->name() == format) { + const char *help = conv->optionsHelp(); + if (help) + printf("The following options are available for format '%s':\n\n%s", qPrintable(format), help); + else + printf("Format '%s' supports no options.\n", qPrintable(format)); + return EXIT_SUCCESS; + } + } + + fprintf(stderr, "Unknown file format '%s'\n", qPrintable(format)); + return EXIT_FAILURE; + } + + Converter *inconv = nullptr; + QString format = parser.value(inputFormatOption); + if (format != "auto") { + for (Converter *conv : qAsConst(*availableConverters)) { + if (conv->name() == format) { + inconv = conv; + break; + } + } + + if (!inconv) { + fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format)); + return EXIT_FAILURE; + } + } + + Converter *outconv = nullptr; + format = parser.value(outputFormatOption); + if (format != "auto") { + for (Converter *conv : qAsConst(*availableConverters)) { + if (conv->name() == format) { + outconv = conv; + break; + } + } + + if (!outconv) { + fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format)); + return EXIT_FAILURE; + } + } + + QStringList files = parser.positionalArguments(); + QFile input(files.value(0)); + QFile output(files.value(1)); + + if (input.fileName().isEmpty()) + input.open(stdin, QIODevice::ReadOnly); + else + input.open(QIODevice::ReadOnly); + if (!input.isOpen()) { + fprintf(stderr, "Could not open \"%s\" for reading: %s\n", + qPrintable(input.fileName()), qPrintable(input.errorString())); + return EXIT_FAILURE; + } + + if (output.fileName().isEmpty()) + output.open(stdout, QIODevice::WriteOnly | QIODevice::Truncate); + else + output.open(QIODevice::WriteOnly | QIODevice::Truncate); + if (!output.isOpen()) { + fprintf(stderr, "Could not open \"%s\" for writing: %s\n", + qPrintable(output.fileName()), qPrintable(output.errorString())); + return EXIT_FAILURE; + } + + if (!inconv) { + // probe the input to find a file format + for (Converter *conv : qAsConst(*availableConverters)) { + if (conv->directions() & Converter::In && conv->probeFile(&input)) { + inconv = conv; + break; + } + } + + if (!inconv) { + fprintf(stderr, "Could not determine input format. pass -I option.\n"); + return EXIT_FAILURE; + } + } + + if (!outconv) { + // probe the output to find a file format + for (Converter *conv : qAsConst(*availableConverters)) { + if (conv->directions() & Converter::Out && conv->probeFile(&output)) { + outconv = conv; + break; + } + } + } + + // now finally perform the conversion + QVariant data = inconv->loadFile(&input, outconv); + Q_ASSERT_X(outconv, "Converter Tool", + "Internal error: converter format did not provide default"); + outconv->saveFile(&output, data, parser.values(optionOption)); + return EXIT_SUCCESS; +} diff --git a/examples/corelib/serialization/convert/nullconverter.cpp b/examples/corelib/serialization/convert/nullconverter.cpp new file mode 100644 index 0000000000..2de492e64e --- /dev/null +++ b/examples/corelib/serialization/convert/nullconverter.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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 "nullconverter.h" + +static NullConverter nullConverter; +Converter* Converter::null = &nullConverter; + +QString NullConverter::name() +{ + return QLatin1String("null"); +} + +Converter::Direction NullConverter::directions() +{ + return Out; +} + +Converter::Options NullConverter::outputOptions() +{ + return SupportsArbitraryMapKeys; +} + +const char *NullConverter::optionsHelp() +{ + return nullptr; +} + +bool NullConverter::probeFile(QIODevice *f) +{ + Q_UNUSED(f); + return false; +} + +QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter) +{ + Q_UNUSED(f); + Q_UNUSED(outputConverter); + outputConverter = this; + return QVariant(); +} + +void NullConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + if (!options.isEmpty()) { + fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n", qPrintable(options.first())); + exit(EXIT_FAILURE); + } + + Q_UNUSED(f); + Q_UNUSED(contents); +} diff --git a/examples/corelib/serialization/convert/nullconverter.h b/examples/corelib/serialization/convert/nullconverter.h new file mode 100644 index 0000000000..af7339f092 --- /dev/null +++ b/examples/corelib/serialization/convert/nullconverter.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef NULLCONVERTER_H +#define NULLCONVERTER_H + +#include "converter.h" + +class NullConverter : public Converter +{ + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +#endif // NULLCONVERTER_H diff --git a/examples/corelib/serialization/convert/textconverter.cpp b/examples/corelib/serialization/convert/textconverter.cpp new file mode 100644 index 0000000000..e80e69a0b5 --- /dev/null +++ b/examples/corelib/serialization/convert/textconverter.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** 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 "textconverter.h" + +#include <QFile> +#include <QTextStream> + +static void dumpVariant(QTextStream &out, const QVariant &v) +{ + switch (v.userType()) { + case QVariant::List: { + const QVariantList list = v.toList(); + for (const QVariant &item : list) + dumpVariant(out, item); + break; + } + + case QVariant::String: { + const QStringList list = v.toStringList(); + for (const QString &s : list) + out << s << endl; + break; + } + + case QVariant::Map: { + const QVariantMap map = v.toMap(); + for (auto it = map.begin(); it != map.end(); ++it) { + out << it.key() << " => "; + dumpVariant(out, it.value()); + } + break; + } + + case QMetaType::Nullptr: + out << "(null)" << endl; + break; + + default: + out << v.toString() << endl; + break; + } +} + +QString TextConverter::name() +{ + return QStringLiteral("text"); +} + +Converter::Direction TextConverter::directions() +{ + return InOut; +} + +Converter::Options TextConverter::outputOptions() +{ + return {}; +} + +const char *TextConverter::optionsHelp() +{ + return nullptr; +} + +bool TextConverter::probeFile(QIODevice *f) +{ + if (QFile *file = qobject_cast<QFile *>(f)) + return file->fileName().endsWith(QLatin1String(".txt")); + return false; +} + +QVariant TextConverter::loadFile(QIODevice *f, Converter *&outputConverter) +{ + if (!outputConverter) + outputConverter = this; + + QVariantList list; + QTextStream in(f); + QString line ; + while (!in.atEnd()) { + in.readLineInto(&line); + + bool ok; + qint64 v = line.toLongLong(&ok); + if (ok) { + list.append(v); + continue; + } + + double d = line.toDouble(&ok); + if (ok) { + list.append(d); + continue; + } + + list.append(line); + } + + return list; +} + +void TextConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +{ + if (!options.isEmpty()) { + fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n", qPrintable(options.first())); + exit(EXIT_FAILURE); + } + + QTextStream out(f); + dumpVariant(out, contents); +} + +static TextConverter textConverter; diff --git a/examples/corelib/serialization/convert/textconverter.h b/examples/corelib/serialization/convert/textconverter.h new file mode 100644 index 0000000000..66f5136c02 --- /dev/null +++ b/examples/corelib/serialization/convert/textconverter.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TEXTCONVERTER_H +#define TEXTCONVERTER_H + +#include "converter.h" + +class TextConverter : public Converter +{ + + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +#endif // TEXTCONVERTER_H 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(); +} diff --git a/examples/corelib/serialization/convert/xmlconverter.h b/examples/corelib/serialization/convert/xmlconverter.h new file mode 100644 index 0000000000..8fc0fea592 --- /dev/null +++ b/examples/corelib/serialization/convert/xmlconverter.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef XMLCONVERTER_H +#define XMLCONVERTER_H + +#include "converter.h" + +class XmlConverter : public Converter +{ + // Converter interface +public: + QString name() override; + Direction directions() override; + Options outputOptions() override; + const char *optionsHelp() override; + bool probeFile(QIODevice *f) override; + QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; + void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; +}; + +#endif // XMLCONVERTER_H diff --git a/examples/corelib/serialization/serialization.pro b/examples/corelib/serialization/serialization.pro index af4d3e6f0f..7651444f19 100644 --- a/examples/corelib/serialization/serialization.pro +++ b/examples/corelib/serialization/serialization.pro @@ -1,2 +1,5 @@ TEMPLATE = subdirs -SUBDIRS = savegame +SUBDIRS = \ + cbordump \ + convert \ + savegame |