6.6.1 original

This commit is contained in:
kleuter
2023-12-04 18:42:35 +01:00
parent 9bf343ceed
commit 8490fae44c
607 changed files with 15608 additions and 14647 deletions

View File

@ -20,13 +20,14 @@ qt_standard_project_setup()
qt_add_executable(convert
cborconverter.cpp cborconverter.h
converter.h
converter.cpp converter.h
datastreamconverter.cpp datastreamconverter.h
debugtextdumper.cpp debugtextdumper.h
jsonconverter.cpp jsonconverter.h
main.cpp
nullconverter.cpp nullconverter.h
textconverter.cpp textconverter.h
variantorderedmap.h
xmlconverter.cpp xmlconverter.h
)

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "cborconverter.h"
#include "variantorderedmap.h"
#include <QCborArray>
#include <QCborMap>
@ -9,6 +10,7 @@
#include <QCborStreamWriter>
#include <QCborValue>
#include <QDataStream>
#include <QDebug>
#include <QFile>
#include <QFloat16>
#include <QMetaType>
@ -57,9 +59,9 @@ QT_END_NAMESPACE
// 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.
//! [0]
static QVariant convertCborValue(const QCborValue &value);
//! [0]
static QVariant convertCborMap(const QCborMap &map)
{
VariantOrderedMap result;
@ -87,8 +89,9 @@ static QVariant convertCborValue(const QCborValue &value)
return value.toVariant();
}
//! [0]
enum TrimFloatingPoint { Double, Float, Float16 };
//! [1]
enum TrimFloatingPoint { Double, Float, Float16 };
static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming)
{
if (v.userType() == QMetaType::QVariantList) {
@ -140,20 +143,6 @@ const char *CborDiagnosticDumper::optionsHelp() const
return diagnosticHelp;
}
bool CborDiagnosticDumper::probeFile(QIODevice *f) const
{
Q_UNUSED(f);
return false;
}
QVariant CborDiagnosticDumper::loadFile(QIODevice *f, const Converter *&outputConverter) const
{
Q_UNREACHABLE();
Q_UNUSED(f);
Q_UNUSED(outputConverter);
return QVariant();
}
void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const
{
@ -178,9 +167,8 @@ void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents,
}
}
fprintf(stderr, "Unknown CBOR diagnostic option '%s'. Available options are:\n%s",
qPrintable(s), diagnosticHelp);
exit(EXIT_FAILURE);
qFatal("Unknown CBOR diagnostic option '%s'. Available options are:\n%s",
qPrintable(s), diagnosticHelp);
}
QTextStream out(f);
@ -221,7 +209,6 @@ bool CborConverter::probeFile(QIODevice *f) const
return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3);
}
//! [2]
QVariant CborConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
{
const char *ptr = nullptr;
@ -239,28 +226,25 @@ QVariant CborConverter::loadFile(QIODevice *f, const Converter *&outputConverter
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);
qFatal().nospace()
<< "Error loading CBOR contents (byte " << offset
<< "): " << reader.lastError().toString()
<< "\n bytes: " << (ptr ? mapped.mid(offset, 9) : f->read(9));
} else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) {
fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n");
qWarning("Warning: bytes remaining at the end of the CBOR stream");
}
if (outputConverter == nullptr)
outputConverter = &cborDiagnosticDumper;
else if (outputConverter == null)
else if (isNull(outputConverter))
return QVariant();
else if (!outputConverter->outputOptions().testFlag(SupportsArbitraryMapKeys))
return contents.toVariant();
return convertCborValue(contents);
}
//! [2]
//! [3]
void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) const
{
//! [3]
bool useSignature = true;
bool useIntegers = true;
enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes;
@ -315,11 +299,10 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri
}
}
fprintf(stderr, "Unknown CBOR format option '%s'. Valid options are:\n%s",
qPrintable(s), cborOptionHelp);
exit(EXIT_FAILURE);
qFatal("Unknown CBOR format option '%s'. Valid options are:\n%s",
qPrintable(s), cborOptionHelp);
}
//! [4]
QCborValue v =
convertFromVariant(contents,
useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double);
@ -336,4 +319,3 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri
opts |= QCborValue::UseFloat16;
v.toCbor(writer, opts);
}
//! [4]

View File

@ -14,8 +14,6 @@ public:
Directions directions() const override;
Options outputOptions() const override;
const char *optionsHelp() const override;
bool probeFile(QIODevice *f) const override;
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
void saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const override;
};

View File

@ -11,6 +11,7 @@ target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/convert
INSTALLS += target
SOURCES += main.cpp \
converter.cpp \
cborconverter.cpp \
datastreamconverter.cpp \
debugtextdumper.cpp \
@ -27,4 +28,5 @@ HEADERS += \
jsonconverter.h \
nullconverter.h \
textconverter.h \
variantorderedmap.h \
xmlconverter.h

View File

@ -0,0 +1,44 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "converter.h"
//! [0]
Converter::Converter()
{
converters().append(this);
}
Converter::~Converter()
{
converters().removeAll(this);
}
QList<const Converter *> &Converter::converters()
{
Q_CONSTINIT static QList<const Converter *> store;
return store;
}
const QList<const Converter *> &Converter::allConverters()
{
return converters();
}
//! [0]
// Some virtual methods that Converter classes needn't override, when not relevant:
Converter::Options Converter::outputOptions() const { return {}; }
const char *Converter::optionsHelp() const { return nullptr; }
bool Converter::probeFile(QIODevice *) const { return false; }
// The virtual method they should override if they claim to support In:
QVariant Converter::loadFile(QIODevice *, const Converter *&outputConverter) const
{
Q_ASSERT(!directions().testFlag(Converter::Direction::In));
// For those that don't, this should never be called.
Q_UNIMPLEMENTED();
// But every implementation should at least do this:
if (!outputConverter)
outputConverter = this;
return QVariant();
}

View File

@ -6,31 +6,19 @@
#include <QIODevice>
#include <QList>
#include <QPair>
#include <QStringList>
#include <QVariant>
#include <QVariantMap>
class VariantOrderedMap : public QList<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)
//! [0]
class Converter
{
static QList<const Converter *> &converters();
protected:
Converter();
static bool isNull(const Converter *converter); // in nullconverter.cpp
public:
static Converter *null;
static const QList<const Converter *> &allConverters();
enum class Direction { In = 1, Out = 2, InOut = In | Out };
Q_DECLARE_FLAGS(Directions, Direction)
@ -42,15 +30,16 @@ public:
virtual QString name() const = 0;
virtual Directions directions() const = 0;
virtual Options outputOptions() const = 0;
virtual const char *optionsHelp() const = 0;
virtual bool probeFile(QIODevice *f) const = 0;
virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const = 0;
virtual Options outputOptions() const;
virtual const char *optionsHelp() const;
virtual bool probeFile(QIODevice *f) const;
virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const;
virtual void saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const = 0;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Directions)
Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Options)
//! [0]
#endif // CONVERTER_H

View File

@ -3,6 +3,7 @@
#include "datastreamconverter.h"
#include "debugtextdumper.h"
#include "variantorderedmap.h"
#include <QDataStream>
@ -79,10 +80,8 @@ QVariant DataStreamConverter::loadFile(QIODevice *f, const Converter *&outputCon
outputConverter = &debugTextDumper;
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);
}
if (f->read(sizeof(signature) - 1) != signature || !f->getChar(&c) || (c != 'l' && c != 'B'))
qFatal("Could not load QDataStream file: invalid signature.");
QDataStream ds(f);
ds.setByteOrder(c == 'l' ? QDataStream::LittleEndian : QDataStream::BigEndian);
@ -124,15 +123,13 @@ void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents,
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);
qFatal("Invalid version number '%s': must be a number from 1 to %d.",
qPrintable(pair.last()), QDataStream::Qt_DefaultCompiledVersion);
}
}
fprintf(stderr, "Unknown QDataStream formatting option '%s'. Available options are:\n%s",
qFatal("Unknown QDataStream formatting option '%s'. Available options are:\n%s",
qPrintable(option), dataStreamOptionHelp);
exit(EXIT_FAILURE);
}
char c = order == QDataStream::LittleEndian ? 'l' : 'B';

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "debugtextdumper.h"
#include "variantorderedmap.h"
#include <QDebug>
#include <QTextStream>
@ -58,29 +59,13 @@ Converter::Options DebugTextDumper::outputOptions() const
return SupportsArbitraryMapKeys;
}
const char *DebugTextDumper::optionsHelp() const
{
return nullptr;
}
bool DebugTextDumper::probeFile(QIODevice *f) const
{
Q_UNUSED(f);
return false;
}
QVariant DebugTextDumper::loadFile(QIODevice *f, const Converter *&outputConverter) const
{
Q_UNREACHABLE();
Q_UNUSED(f);
Q_UNUSED(outputConverter);
return QVariant();
}
void DebugTextDumper::saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const
{
Q_UNUSED(options);
if (!options.isEmpty()) {
qFatal("Unknown option '%s' to debug text output. This format has no options.",
qPrintable(options.first()));
}
QString s = dumpVariant(contents);
s[s.size() - 1] = u'\n'; // replace the comma with newline

View File

@ -13,9 +13,6 @@ public:
QString name() const override;
Directions directions() const override;
Options outputOptions() const override;
const char *optionsHelp() const override;
bool probeFile(QIODevice *f) const override;
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
void saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const override;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -5,78 +5,152 @@
\example serialization/convert
\examplecategory {Data Processing & I/O}
\meta tag {network}
\title Convert Example
\title Serialization Converter
\brief The Convert example demonstrates how to convert between different
serialization formats.
\brief How to convert between different serialization formats.
The Convert example converts between the serialization formats JSON, CBOR,
XML, QDataStream and text. It can also auto detect the format being used.
Not all formats support both input and output, and they have different
sets of which types they support. QDataStream and XML are the richest,
followed by CBOR, then JSON, and then the plain text one.
This example converts between JSON, CBOR, XML, QDataStream and some simple
text formats. It can auto-detect the format being used, or be told which
format to use. Not all formats support both input and output, and they have
different sets of which content datatypes they support. QDataStream and XML
are the richest, followed by CBOR, then JSON, and then the plain text
formats. Conversion via the less capable formats is apt to lose structure
from the data.
\image convert.png
\sa {Parsing and displaying CBOR data}, {JSON Save Game}
\section1 The Converter Class
The Converter class is the abstract superclass for all the converters to
and from all the formats. They all convert to and from the QVariant class,
which is used to represent all the datastructures internally.
The Converter class is the abstract superclass for all the converters to and
from all the formats. They all convert from or to the QVariant class, which
is used to represent all the datastructures internally.
\snippet serialization/convert/converter.h 0
The Converter constructor and destructor manage a list of available
converters used by the main program so that it knows what converters are
available. Each converter type defines a static instance that ensures it is
constructed and thus available to the main program via this list. The \c
allConverters() method provides \c main()'s code with access to the list.
\snippet serialization/convert/converter.cpp 0
The name() function returns the name of the converter. The directions()
function is used to determine if a converter can be used for input, output,
or both. The outputOptions() and optionsHelp() functions are used to get
and query which options are used by the different converters. The
probeFile() function is used to determine if a file has the same file
format as the converter. The loadFile() function deserializes the given
file, while the saveFile() serializes to the given file.
or both. These enable the main program to report what converters are
available in its help text for the command-line options to select input and
output formats.
\section1 The CborConverter Class
\snippet serialization/convert/main.cpp 0
The optionsHelp() function is used to report the various command-line
options supported by the available formats, when queried using its \c
{--format-options <format>} command-line option.
\snippet serialization/convert/main.cpp 1
The outputOptions() function reports the output capabilities of a converter.
At present the only optional feature is support for arbitrary keys in
mappings from keys to values. An input converter's loadFile() can use this
information to tailor the form in which it presents the data it has read, to
be as faithfully represented by the output converter as its capabilities
permit.
The probeFile() function is used to determine if a file matches the format
of the converter. The main program uses this to determine what format to use
when reading or writing a file, based on its name and potentially content,
when the user has not specified the format to use on the command-line.
The loadFile() function deserializes data. The caller tells loadFile() which
serializer it intends to use, so that loadFile() can query its
outputOptions() to determine the form in which to represent the loaded data.
If the caller hasn't settled on a choice of output converter, loadFile()
supplies it with a default output converter suitable to the data it is
returning.
The saveFile() function serializes data. It is passed options from the
command-line, as described by loadHelp(), that can tune the details of how
it represents the data when saving to file.
Both loadFile() and saveFile() can be used with an arbitrary \l QIODevice.
This means that a Converter could also be used with a network socket or
other source of data, to read from or write to. In the present program, the
main program always passes a \l QFile, accessing either a file on disk or
one of the standard streams of the process.
\section2 The Available Converters
Several converters are supported, illustrating how the converter program
could be adapted to other formats, should the need arise. See the source
code for each for its details. The CBOR converters serve as a relatively
full-featured illustration of the ways converters can work, that we'll look
into in more detail below. This table summarizes the available converters:
\table
\header \li Class \li mode \li format
\row \li CborConverter \li In/Out \li CBOR
\row \li CborDiagnosticDumper \li Out \li CBOR diagnostic
\row \li DataStreamConverter \li In/Out \li QDataStream
\row \li DebugTextDumper \li Out \li Lossless, non-standard, human-readable
\row \li JsonConverter \li In/Out \li JSON
\row \li NullConverter \li Out \li No output
\row \li TextConverter \li In/Out \li Structured plain text
\row \li XmlConverter \li In/Out \li XML
\endtable
Those that support input use themselves as loadFile()'s fallback converter,
except for the CBOR and QDataStream converters, which use their respective
output-only dumper companion classes. The null converter can be used as
output converter when running the program for the sake of any validation or
verification that an input converter may perform.
\section2 The CborConverter and CborDiagnosticDumper Classes
The CborConverter class supports serializing to and from the CBOR format.
It supports various options to configure the output of floating point values
and a \c{signature} option to determine whether to start its output with a
CBOR tag that serves as a file header, identifying the file as containing
CBOR data.
The CborConverter class shows how to serialize to and from the CBOR-format.
There is also a CborDiagnosticDumper class to output in CBOR diagnostic
notation. That is similar to JSON, but not exactly, because it allows
displaying the contents of a CBOR stream losslessly, while a conversion
to JSON is lossy.
notation. It does not support loading data. The form of its output can be
configured using two options. One selects whether to use the (more verbose)
extended CBOR diagnostic format. The other control whether each CBOR value
appears on a separate line.
The plain diagnostic notation is similar to JSON, but not exactly, because
it supports displaying the contents of a CBOR stream losslessly, while a
conversion to JSON can be lossy. CborConverter's loadFile() uses
CborDiagnosticDumper for the fallback output converter, if its caller hasn't
determined the output format for itself.
The convertCborValue(), convertCborMap() and convertCborArray() helper
functions are used to convert a QCborValue to a QVariant, for the benefit of
CborConverter::loadFile().
The convertCborValue() function is used to convert a QCborValue to a
QVariant. It uses the helper functions convertCborMap() and
convertCborArray().
\snippet serialization/convert/cborconverter.cpp 0
A CBOR-file is read using loadFile() function.
\snippet serialization/convert/cborconverter.cpp 2
The convertFromVariant() function is used to convert a QVariant to a
QCborValue.
\snippet serialization/convert/cborconverter.cpp 1
QCborValue for output by the \c saveFile() of either class.
A CBOR-file is written using the saveFile() function.
\snippet serialization/convert/cborconverter.cpp 3
\snippet serialization/convert/cborconverter.cpp 4
\snippet serialization/convert/cborconverter.cpp 1
\sa {CBOR Support in Qt}
\section1 The DataStreamConverter Class
\section1 The convert program
The DataStreamConverter class is used to serialize to and from the
QDataStream format. There is also the DebugTextDumper class for outputting
the data lossless in a non-standardized human readable format.
The \c main() function sets up a \l QApplication and a \l QCommandLineParser
to make sense of the options the user has specified and provide help if the
user asks for it. It uses the values obtained for the various \l
QCommandLineOption instances describing the user's choices, plus the
positional arguments for file names, to prepare the converters it will use.
\section1 The JsonConverter Class
It then uses its input converter to load data (and possibly resolve its
choice of output converter, if it hasn't selected one yet) and its output
converter to serialize that data, taking account of any output options the
user has supplied on the command-line.
The JsonConverter class is used to serialize to and from the JSON-format.
\sa {JSON Support in Qt}
\section1 The XmlConverter Class
The XmlConverter class is used to serialize to and from the XML-format.
\section1 The TextConverter Class
The TextConverter class is used to serialize to and from a text format.
\section1 The NullConverter Class
The NullConverter class is an output serializer that does nothing.
\snippet serialization/convert/main.cpp 2
*/

View File

@ -18,10 +18,8 @@ static const char jsonOptionHelp[] = "compact=no|yes Use compact JS
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);
}
if (!doc.isObject() && !doc.isArray())
qFatal("Could not convert contents to JSON.");
return doc;
}
@ -35,11 +33,6 @@ Converter::Directions JsonConverter::directions() const
return Direction::InOut;
}
Converter::Options JsonConverter::outputOptions() const
{
return {};
}
const char *JsonConverter::optionsHelp() const
{
return jsonOptionHelp;
@ -75,11 +68,10 @@ QVariant JsonConverter::loadFile(QIODevice *f, const Converter *&outputConverter
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);
qFatal("Could not parse JSON content: offset %d: %s",
error.offset, qPrintable(error.errorString()));
}
if (outputConverter == null)
if (isNull(outputConverter))
return QVariant();
return doc.toVariant();
}
@ -94,9 +86,8 @@ void JsonConverter::saveFile(QIODevice *f, const QVariant &contents,
} else if (s == "compact=yes"_L1) {
format = QJsonDocument::Compact;
} else {
fprintf(stderr, "Unknown option '%s' to JSON output. Valid options are:\n%s",
qPrintable(s), jsonOptionHelp);
exit(EXIT_FAILURE);
qFatal("Unknown option '%s' to JSON output. Valid options are:\n%s",
qPrintable(s), jsonOptionHelp);
}
}

View File

@ -12,7 +12,6 @@ class JsonConverter : public Converter
public:
QString name() const override;
Directions directions() const override;
Options outputOptions() const override;
const char *optionsHelp() const override;
bool probeFile(QIODevice *f) const override;
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;

View File

@ -1,4 +1,5 @@
// Copyright (C) 2018 Intel Corporation.
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "converter.h"
@ -13,27 +14,57 @@
using namespace Qt::StringLiterals;
static QList<const Converter *> *availableConverters;
Converter::Converter()
static const Converter *prepareConverter(QString format, Converter::Direction direction,
QFile *stream)
{
if (!availableConverters)
availableConverters = new QList<const Converter *>;
availableConverters->append(this);
}
const bool out = direction == Converter::Direction::Out;
const QIODevice::OpenMode mode = out
? QIODevice::WriteOnly | QIODevice::Truncate
: QIODevice::ReadOnly;
const char *dirn = out ? "output" : "input";
Converter::~Converter()
{
availableConverters->removeAll(this);
if (stream->fileName().isEmpty())
stream->open(out ? stdout : stdin, mode);
else
stream->open(mode);
if (!stream->isOpen()) {
qFatal("Could not open \"%s\" for %s: %s",
qPrintable(stream->fileName()), dirn, qPrintable(stream->errorString()));
} else if (format == "auto"_L1) {
for (const Converter *conv : Converter::allConverters()) {
if (conv->directions().testFlag(direction) && conv->probeFile(stream))
return conv;
}
if (out) // Failure to identify output format can be remedied by loadFile().
return nullptr;
// Input format, however, we must know before we can call that:
qFatal("Could not determine input format. Specify it with the -I option.");
} else {
for (const Converter *conv : Converter::allConverters()) {
if (conv->name() == format) {
if (!conv->directions().testFlag(direction)) {
qWarning("File format \"%s\" cannot be used for %s",
qPrintable(format), dirn);
continue; // on the off chance there's another with the same name
}
return conv;
}
}
qFatal("Unknown %s file format \"%s\"", dirn, qPrintable(format));
}
Q_UNREACHABLE_RETURN(nullptr);
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
//! [0]
QStringList inputFormats;
QStringList outputFormats;
for (const Converter *conv : std::as_const(*availableConverters)) {
for (const Converter *conv : Converter::allConverters()) {
auto direction = conv->directions();
QString name = conv->name();
if (direction.testFlag(Converter::Direction::In))
@ -41,13 +72,14 @@ int main(int argc, char *argv[])
if (direction.testFlag(Converter::Direction::Out))
outputFormats << name;
}
//! [0]
inputFormats.sort();
outputFormats.sort();
inputFormats.prepend("auto"_L1);
outputFormats.prepend("auto"_L1);
QCommandLineParser parser;
parser.setApplicationDescription("Qt file format conversion tool"_L1);
parser.setApplicationDescription("Qt serialization format conversion tool"_L1);
parser.addHelpOption();
QCommandLineOption inputFormatOption(QStringList{ "I"_L1, "input-format"_L1 });
@ -86,110 +118,38 @@ int main(int argc, char *argv[])
if (parser.isSet(formatOptionsOption)) {
QString format = parser.value(formatOptionsOption);
for (const Converter *conv : std::as_const(*availableConverters)) {
//! [1]
for (const Converter *conv : Converter::allConverters()) {
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);
qInfo("The following options are available for format '%s':\n\n%s",
qPrintable(format), help);
} else {
printf("Format '%s' supports no options.\n", qPrintable(format));
qInfo("Format '%s' supports no options.", qPrintable(format));
}
return EXIT_SUCCESS;
}
}
//! [1]
fprintf(stderr, "Unknown file format '%s'\n", qPrintable(format));
return EXIT_FAILURE;
}
const Converter *inconv = nullptr;
QString format = parser.value(inputFormatOption);
if (format != "auto"_L1) {
for (const Converter *conv : std::as_const(*availableConverters)) {
if (conv->name() == format) {
inconv = conv;
break;
}
}
if (!inconv) {
fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format));
return EXIT_FAILURE;
}
}
const Converter *outconv = nullptr;
format = parser.value(outputFormatOption);
if (format != "auto"_L1) {
for (const Converter *conv : std::as_const(*availableConverters)) {
if (conv->name() == format) {
outconv = conv;
break;
}
}
if (!outconv) {
fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format));
return EXIT_FAILURE;
}
qFatal("Unknown file format '%s'", qPrintable(format));
}
//! [2]
QStringList files = parser.positionalArguments();
QFile input(files.value(0));
QFile output(files.value(1));
const Converter *inconv = prepareConverter(parser.value(inputFormatOption),
Converter::Direction::In, &input);
const Converter *outconv = prepareConverter(parser.value(outputFormatOption),
Converter::Direction::Out, &output);
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 (const Converter *conv : std::as_const(*availableConverters)) {
if (conv->directions().testFlag(Converter::Direction::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 (const Converter *conv : std::as_const(*availableConverters)) {
if (conv->directions().testFlag(Converter::Direction::Out)
&& conv->probeFile(&output)) {
outconv = conv;
break;
}
}
}
// now finally perform the conversion
// Now finally perform the conversion:
QVariant data = inconv->loadFile(&input, outconv);
Q_ASSERT_X(outconv, "Converter Tool",
Q_ASSERT_X(outconv, "Serialization Converter",
"Internal error: converter format did not provide default");
outconv->saveFile(&output, data, parser.values(optionOption));
return EXIT_SUCCESS;
//! [2]
}

View File

@ -6,7 +6,10 @@
using namespace Qt::StringLiterals;
static NullConverter nullConverter;
Converter *Converter::null = &nullConverter;
bool Converter::isNull(const Converter *converter)
{
return converter == &nullConverter;
}
QString NullConverter::name() const
{
@ -23,32 +26,12 @@ Converter::Options NullConverter::outputOptions() const
return SupportsArbitraryMapKeys;
}
const char *NullConverter::optionsHelp() const
{
return nullptr;
}
bool NullConverter::probeFile(QIODevice *f) const
{
Q_UNUSED(f);
return false;
}
QVariant NullConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
{
Q_UNUSED(f);
Q_UNUSED(outputConverter);
outputConverter = this;
return QVariant();
}
void NullConverter::saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const
{
if (!options.isEmpty()) {
fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n",
qPrintable(options.first()));
exit(EXIT_FAILURE);
qFatal("Unknown option '%s' to null output. This format has no options.",
qPrintable(options.first()));
}
Q_UNUSED(f);

View File

@ -13,9 +13,6 @@ public:
QString name() const override;
Directions directions() const override;
Options outputOptions() const override;
const char *optionsHelp() const override;
bool probeFile(QIODevice *f) const override;
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
void saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const override;
};

View File

@ -54,16 +54,6 @@ Converter::Directions TextConverter::directions() const
return Direction::InOut;
}
Converter::Options TextConverter::outputOptions() const
{
return {};
}
const char *TextConverter::optionsHelp() const
{
return nullptr;
}
bool TextConverter::probeFile(QIODevice *f) const
{
if (QFile *file = qobject_cast<QFile *>(f))
@ -98,9 +88,8 @@ void TextConverter::saveFile(QIODevice *f, const QVariant &contents,
const QStringList &options) const
{
if (!options.isEmpty()) {
fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n",
qPrintable(options.first()));
exit(EXIT_FAILURE);
qFatal("Unknown option '%s' to text output. This format has no options.",
qPrintable(options.first()));
}
QTextStream out(f);

View File

@ -12,8 +12,6 @@ class TextConverter : public Converter
public:
QString name() const override;
Directions directions() const override;
Options outputOptions() const override;
const char *optionsHelp() const override;
bool probeFile(QIODevice *f) const override;
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
void saveFile(QIODevice *f, const QVariant &contents,

View File

@ -0,0 +1,24 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef VARIANTORDEREDMAP_H
#define VARIANTORDEREDMAP_H
#include <QList>
#include <QPair>
#include <QVariant>
#include <QVariantMap>
class VariantOrderedMap : public QList<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()});
}
};
#endif // VARIANTORDEREDMAP_H

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "xmlconverter.h"
#include "variantorderedmap.h"
#include <QBitArray>
#include <QtCborCommon>
@ -48,9 +49,8 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option
break;
}
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
exit(EXIT_FAILURE);
qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
}
xml.readNext();
@ -90,9 +90,8 @@ static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml,
break;
}
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
exit(EXIT_FAILURE);
qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
}
return { key, value };
@ -134,9 +133,8 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options)
break;
}
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
exit(EXIT_FAILURE);
qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
}
xml.readNext();
@ -153,9 +151,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
if (name == "map"_L1)
return mapFromXml(xml, options);
if (name != "value"_L1) {
fprintf(stderr, "%lld:%lld: Invalid XML key '%s'.\n",
xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString()));
exit(EXIT_FAILURE);
qFatal("%lld:%lld: Invalid XML key '%s'.",
xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString()));
}
QXmlStreamAttributes attrs = xml.attributes();
@ -168,9 +165,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
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);
qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(name.toString()));
}
QStringView text = xml.text();
@ -190,9 +186,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
// let's see floating point
double d = text.toDouble(&ok);
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);
qFatal("%lld:%lld: Invalid XML: could not interpret '%s' as a number.",
xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString()));
}
result = d;
}
@ -206,9 +201,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
} else if (encoding.isEmpty() || encoding == "base64"_L1) {
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);
qFatal("%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.",
xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString()));
}
} else if (type == "string"_L1) {
result = text.toString();
@ -227,9 +221,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
} 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);
qFatal("%lld:%lld: Invalid XML: invalid bit string '%s'.",
xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString()));
}
}
ba.resize(n);
@ -247,16 +240,14 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
else
id = QMetaType::fromName(type.toLatin1()).id();
if (id == QMetaType::UnknownType) {
fprintf(stderr, "%lld:%lld: Invalid XML: unknown type '%s'.\n",
xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString()));
exit(EXIT_FAILURE);
qFatal("%lld:%lld: Invalid XML: unknown type '%s'.",
xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString()));
}
result = text.toString();
if (!result.convert(QMetaType(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);
qFatal("%lld:%lld: Invalid XML: could not parse content as type '%s'.",
xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString()));
}
}
@ -265,9 +256,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
} 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);
qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.tokenString()), qPrintable(name.toString()));
}
xml.readNext();
@ -387,8 +377,7 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v)
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);
qFatal("XML: don't know how to serialize type '%s'.", typeName);
}
}
}
@ -434,10 +423,8 @@ QVariant XmlConverter::loadFile(QIODevice *f, const Converter *&outputConverter)
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);
}
if (xml.hasError())
qFatal("XML error: %s", qPrintable(xml.errorString()));
return v;
}
@ -452,9 +439,8 @@ void XmlConverter::saveFile(QIODevice *f, const QVariant &contents,
} else if (s == "compact=yes"_L1) {
compact = true;
} else {
fprintf(stderr, "Unknown option '%s' to XML output. Valid options are:\n%s",
qPrintable(s), xmlOptionHelp);
exit(EXIT_FAILURE);
qFatal("Unknown option '%s' to XML output. Valid options are:\n%s",
qPrintable(s), xmlOptionHelp);
}
}

View File

@ -4,10 +4,9 @@
/*!
\example serialization/savegame
\examplecategory {Data Processing & I/O}
\title JSON Save Game Example
\title Saving and Loading a Game
\brief The JSON Save Game example demonstrates how to save and load a
small game using QJsonDocument, QJsonObject and QJsonArray.
\brief How to save and load a game using Qt's JSON or CBOR classes.
Many games provide save functionality, so that the player's progress through
the game can be saved and loaded at a later time. The process of saving a