qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_example(cbordump)
qt_internal_add_example(convert)
qt_internal_add_example(savegame)
if(TARGET Qt6::Network AND TARGET Qt6::Widgets)
qt_internal_add_example(rsslisting)
endif()
if(TARGET Qt6::Widgets)
qt_internal_add_example(streambookmarks)
endif()

View File

@ -0,0 +1,29 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(cbordump LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/cbordump")
find_package(Qt6 REQUIRED COMPONENTS Core)
qt_standard_project_setup()
qt_add_executable(cbordump
main.cpp
)
target_link_libraries(cbordump PRIVATE
Qt6::Core
)
install(TARGETS cbordump
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -0,0 +1,13 @@
QT += core
QT -= gui
TARGET = cbordump
CONFIG += cmdline
TEMPLATE = app
# install
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/cbordump
INSTALLS += target
SOURCES += main.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,54 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example serialization/cbordump
\examplecategory {Input/Output}
\title Parsing and displaying CBOR data
\brief A demonstration of how to parse files in CBOR format.
This example shows how to use the QCborStreamReader class directly to parse
CBOR content. The \c cbordump program reads content in CBOR format from
files or standard input and dumps the decoded content to stdout in a
human-readable format. It can output in CBOR diagnostic notation (which is
similar to JSON), or it can produce a verbose output where each byte input
is displayed with its encoding beside it.
\sa QCborStreamReader
\image cbordump.png
\section1 The CborDumper Class
The CborDumper class contains a QCborStreamReader object that is initialized
using the QFile object argument passed to the CborDumper constructor. Based
on the arguments the dump function calls either dumpOne() or
dumpOneDetailed() to dump the contents to standard output,
\snippet serialization/cbordump/main.cpp 0
\section2 The dumpOne() Function
Switching on QCborStreamReader::type() enables printing appropriate to the
type of the current value in the stream. If the type is an array or map, the
value's content is iterated over, and for each entry the dumpOne() function
is called recursively with a higher indentation argument. If the type is a
tag, it is printed out and dumpOne() is called once without increasing the
indentation argument.
\section2 The dumpOneDetailed() Function
This function dumps out both the incoming bytes and the decoded contents
on the same line. It uses lambda functions to print out the bytes and
decoded content, but otherwise has a similar structure as dumpOne().
\section1 CborTagDescription
The \c tagDescriptions table, describing the CBOR tags available, is
automatically generated from an XML file available from the iana.org
website. When \c dumpOneDetailed() reports a tag, it uses its description
from this table.
\sa {CBOR Support in Qt}
*/

View File

@ -0,0 +1,859 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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
*
* The XHTML URL mentioned in the comment below is a human-readable version of
* the same resource.
*/
/* TODO (if possible): fix XSLT to replace each newline and surrounding space in
a semantics entry with a single space, instead of using a raw string to wrap
each, propagating the spacing from the XML to the output of cbordump. Also
auto-purge dangling spaces from the ends of generated lines.
*/
// 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),
R"r( (Standard date/time string; see Section 3.4.1 [RFC8949]))r" },
{ QCborTag(1),
R"r( (Epoch-based date/time; see Section 3.4.2 [RFC8949]))r" },
{ QCborTag(2),
R"r( (Positive bignum; see Section 3.4.3 [RFC8949]))r" },
{ QCborTag(3),
R"r( (Negative bignum; see Section 3.4.3 [RFC8949]))r" },
{ QCborTag(4),
R"r( (Decimal fraction; see Section 3.4.4 [RFC8949]))r" },
{ QCborTag(5),
R"r( (Bigfloat; see Section 3.4.4 [RFC8949]))r" },
{ QCborTag(16),
R"r( (COSE Single Recipient Encrypted Data Object [RFC9052]))r" },
{ QCborTag(17),
R"r( (COSE Mac w/o Recipients Object [RFC9052]))r" },
{ QCborTag(18),
R"r( (COSE Single Signer Data Object [RFC9052]))r" },
{ QCborTag(19),
R"r( (COSE standalone V2 countersignature [RFC9338]))r" },
{ QCborTag(21),
R"r( (Expected conversion to base64url encoding; see Section 3.4.5.2 [RFC8949]))r" },
{ QCborTag(22),
R"r( (Expected conversion to base64 encoding; see Section 3.4.5.2 [RFC8949]))r" },
{ QCborTag(23),
R"r( (Expected conversion to base16 encoding; see Section 3.4.5.2 [RFC8949]))r" },
{ QCborTag(24),
R"r( (Encoded CBOR data item; see Section 3.4.5.1 [RFC8949]))r" },
{ QCborTag(25),
R"r( (reference the nth previously seen string))r" },
{ QCborTag(26),
R"r( (Serialised Perl object with classname and constructor arguments))r" },
{ QCborTag(27),
R"r( (Serialised language-independent object with type name and constructor arguments))r" },
{ QCborTag(28),
R"r( (mark value as (potentially) shared))r" },
{ QCborTag(29),
R"r( (reference nth marked value))r" },
{ QCborTag(30),
R"r( (Rational number))r" },
{ QCborTag(31),
R"r( (Absent value in a CBOR Array))r" },
{ QCborTag(32),
R"r( (URI; see Section 3.4.5.3 [RFC8949]))r" },
{ QCborTag(33),
R"r( (base64url; see Section 3.4.5.3 [RFC8949]))r" },
{ QCborTag(34),
R"r( (base64; see Section 3.4.5.3 [RFC8949]))r" },
{ QCborTag(35),
R"r( (Regular expression; see Section 2.4.4.3 [RFC7049]))r" },
{ QCborTag(36),
R"r( (MIME message; see Section 3.4.5.3 [RFC8949]))r" },
{ QCborTag(37),
R"r( (Binary UUID (RFC4122, Section 4.1.2)))r" },
{ QCborTag(38),
R"r( (Language-tagged string [RFC9290]))r" },
{ QCborTag(39),
R"r( (Identifier))r" },
{ QCborTag(40),
R"r( (Multi-dimensional Array, row-major order [RFC8746]))r" },
{ QCborTag(41),
R"r( (Homogeneous Array [RFC8746]))r" },
{ QCborTag(42),
R"r( (IPLD content identifier))r" },
{ QCborTag(43),
R"r( (YANG bits datatype; see Section 6.7. [RFC9254]))r" },
{ QCborTag(44),
R"r( (YANG enumeration datatype; see Section 6.6. [RFC9254]))r" },
{ QCborTag(45),
R"r( (YANG identityref datatype; see Section 6.10. [RFC9254]))r" },
{ QCborTag(46),
R"r( (YANG instance-identifier datatype; see Section 6.13. [RFC9254]))r" },
{ QCborTag(47),
R"r( (YANG Schema Item iDentifier (sid); see Section 3.2. [RFC9254]))r" },
{ QCborTag(52),
R"r( (IPv4, [prefixlen,IPv4], [IPv4,prefixpart] [RFC9164]))r" },
{ QCborTag(54),
R"r( (IPv6, [prefixlen,IPv6], [IPv6,prefixpart] [RFC9164]))r" },
{ QCborTag(61),
R"r( (CBOR Web Token (CWT) [RFC8392]))r" },
{ QCborTag(63),
R"r( (Encoded CBOR Sequence ))r" },
{ QCborTag(64),
R"r( (uint8 Typed Array [RFC8746]))r" },
{ QCborTag(65),
R"r( (uint16, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(66),
R"r( (uint32, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(67),
R"r( (uint64, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(68),
R"r( (uint8 Typed Array, clamped arithmetic [RFC8746]))r" },
{ QCborTag(69),
R"r( (uint16, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(70),
R"r( (uint32, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(71),
R"r( (uint64, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(72),
R"r( (sint8 Typed Array [RFC8746]))r" },
{ QCborTag(73),
R"r( (sint16, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(74),
R"r( (sint32, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(75),
R"r( (sint64, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(76),
R"r( ((reserved) [RFC8746]))r" },
{ QCborTag(77),
R"r( (sint16, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(78),
R"r( (sint32, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(79),
R"r( (sint64, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(80),
R"r( (IEEE 754 binary16, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(81),
R"r( (IEEE 754 binary32, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(82),
R"r( (IEEE 754 binary64, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(83),
R"r( (IEEE 754 binary128, big endian, Typed Array [RFC8746]))r" },
{ QCborTag(84),
R"r( (IEEE 754 binary16, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(85),
R"r( (IEEE 754 binary32, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(86),
R"r( (IEEE 754 binary64, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(87),
R"r( (IEEE 754 binary128, little endian, Typed Array [RFC8746]))r" },
{ QCborTag(96),
R"r( (COSE Encrypted Data Object [RFC9052]))r" },
{ QCborTag(97),
R"r( (COSE MACed Data Object [RFC9052]))r" },
{ QCborTag(98),
R"r( (COSE Signed Data Object [RFC9052]))r" },
{ QCborTag(100),
R"r( (Number of days since the epoch date 1970-01-01 [RFC8943]))r" },
{ QCborTag(101),
R"r( (alternatives as given by the uint + 128; see Section 9.1))r" },
{ QCborTag(103),
R"r( (Geographic Coordinates))r" },
{ QCborTag(104),
R"r( (Geographic Coordinate Reference System WKT or EPSG number))r" },
{ QCborTag(110),
R"r( (relative object identifier (BER encoding); SDNV sequence [RFC9090]))r" },
{ QCborTag(111),
R"r( (object identifier (BER encoding) [RFC9090]))r" },
{ QCborTag(112),
R"r( (object identifier (BER encoding), relative to 1.3.6.1.4.1 [RFC9090]))r" },
{ QCborTag(120),
R"r( (Internet of Things Data Point))r" },
{ QCborTag(260),
R"r( (Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 and 54
for IP addresses) [http://www.employees.oRg/~RaviR/CboR-netwoRk.txt]))r" },
{ QCborTag(261),
R"r( (Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED in favor of 52 and 54
for IP addresses) [https://github.Com/toRaviR/CBOR-Tag-SpeCs/blob/masteR/netwoRkPReFix.md]))r" },
{ QCborTag(271),
R"r( (DDoS Open Threat Signaling (DOTS) signal channel object,
as defined in [RFC9132]))r" },
{ QCborTag(1004),
R"r( ( full-date string [RFC8943]))r" },
{ QCborTag(1040),
R"r( (Multi-dimensional Array, column-major order [RFC8746]))r" },
{ QCborTag(55799),
R"r( (Self-described CBOR; see Section 3.4.6 [RFC8949]))r" },
{ QCborTag(55800),
R"r( (indicates that the file contains CBOR Sequences [RFC9277]))r" },
{ QCborTag(55801),
R"r( (indicates that the file starts with a CBOR-Labeled Non-CBOR Data label. [RFC9277]))r" },
{ 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
};
//! [0]
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;
};
//! [0]
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)
{
using TypeInfo = std::numeric_limits<T>;
// 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.;
static_assert(TypeInfo::is_integer);
double supremum = ldexp(1, TypeInfo::digits);
if (v >= supremum)
return false;
if (v < TypeInfo::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) {
constexpr qsizetype ChunkSizeLimit = std::numeric_limits<int>::max();
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 >= ChunkSizeLimit) {
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 >= ChunkSizeLimit) {
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 : std::as_const(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;
}

View File

@ -0,0 +1,27 @@
<?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"/>),
R"r( (<xsl:value-of select="a:semantics"/> <xsl:call-template name="xref"/>))r" },
</xsl:template><!-- fn:replace(a:semantics, '\s+', ' ') -->
<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>

View File

@ -0,0 +1,36 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(convert LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/convert")
find_package(Qt6 REQUIRED COMPONENTS Core)
qt_standard_project_setup()
qt_add_executable(convert
cborconverter.cpp cborconverter.h
converter.h
datastreamconverter.cpp datastreamconverter.h
jsonconverter.cpp jsonconverter.h
main.cpp
nullconverter.cpp nullconverter.h
textconverter.cpp textconverter.h
xmlconverter.cpp xmlconverter.h
)
target_link_libraries(convert PRIVATE
Qt6::Core
)
install(TARGETS convert
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -0,0 +1,336 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "cborconverter.h"
#include <QCborStreamReader>
#include <QCborStreamWriter>
#include <QCborMap>
#include <QCborArray>
#include <QCborValue>
#include <QDataStream>
#include <QFloat16>
#include <QFile>
#include <QMetaType>
#include <QTextStream>
#include <stdio.h>
static CborConverter cborConverter;
static CborDiagnosticDumper cborDiagnosticDumper;
static const char cborOptionHelp[] =
"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, 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);
//! [0]
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();
}
//! [0]
enum TrimFloatingPoint { Double, Float, Float16 };
//! [1]
static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming)
{
if (v.userType() == QMetaType::QVariantList) {
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 = qvariant_cast<VariantOrderedMap>(v);
QCborMap map;
for (const auto &pair : m)
map.insert(convertFromVariant(pair.first, fpTrimming),
convertFromVariant(pair.second, fpTrimming));
return map;
}
if (v.userType() == QMetaType::Double && fpTrimming != Double) {
float f = float(v.toDouble());
if (fpTrimming == Float16)
return float(qfloat16(f));
return f;
}
return QCborValue::fromVariant(v);
}
//! [1]
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)
<< Qt::endl;
}
CborConverter::CborConverter()
{
qRegisterMetaType<QCborTag>();
}
QString CborConverter::name()
{
return "cbor";
}
Converter::Direction CborConverter::directions()
{
return InOut;
}
Converter::Options CborConverter::outputOptions()
{
return SupportsArbitraryMapKeys;
}
const char *CborConverter::optionsHelp()
{
return cborOptionHelp;
}
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);
}
//! [2]
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);
}
//! [2]
//! [3]
void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
{
//! [3]
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), cborOptionHelp);
exit(EXIT_FAILURE);
}
//! [4]
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);
}
//! [4]

View File

@ -0,0 +1,38 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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

View File

@ -0,0 +1,28 @@
QT += core
QT -= gui
TARGET = convert
CONFIG += cmdline
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

View File

@ -0,0 +1,57 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef CONVERTER_H
#define CONVERTER_H
#include <QIODevice>
#include <QPair>
#include <QVariant>
#include <QVariantMap>
#include <QList>
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)
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

View File

@ -0,0 +1,225 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "datastreamconverter.h"
#include <QDataStream>
#include <QDebug>
#include <QTextStream>
static const char dataStreamOptionHelp[] =
"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 == QMetaType::QVariantMap) {
const auto map = (type == QMetaType::QVariantMap) ?
VariantOrderedMap(v.toMap()) : qvariant_cast<VariantOrderedMap>(v);
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 == QMetaType::QVariantList) {
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>();
}
QString DataStreamConverter::name()
{
return QStringLiteral("datastream");
}
Converter::Direction DataStreamConverter::directions()
{
return InOut;
}
Converter::Options DataStreamConverter::outputOptions()
{
return SupportsArbitraryMapKeys;
}
const char *DataStreamConverter::optionsHelp()
{
return dataStreamOptionHelp;
}
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), dataStreamOptionHelp);
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;
}

View File

@ -0,0 +1,38 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,81 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example serialization/convert
\examplecategory {Input/Output}
\title Convert Example
\brief The Convert example demonstrates 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.
\image convert.png
\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 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.
\section1 The CborConverter Class
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.
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
A CBOR-file is written using the saveFile() function.
\snippet serialization/convert/cborconverter.cpp 3
\snippet serialization/convert/cborconverter.cpp 4
\sa {CBOR Support in Qt}
\section1 The DataStreamConverter Class
The DataStreamConverter class is used to serialize to and from the
QDataStream format. There is also the DataStreamDumper class for outputting
the data lossless in a non-standardized human readable format.
\section1 The JsonConverter Class
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.
*/

View File

@ -0,0 +1,106 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "jsonconverter.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
static JsonConverter jsonConverter;
static const char jsonOptionHelp[] =
"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 jsonOptionHelp;
}
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), jsonOptionHelp);
exit(EXIT_FAILURE);
}
}
f->write(convertFromVariant(contents).toJson(format));
}

View File

@ -0,0 +1,25 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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;
};
#endif // JSONCONVERTER_H

View File

@ -0,0 +1,187 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "converter.h"
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QCoreApplication>
#include <QFile>
#include <QFileInfo>
#include <stdio.h>
static QList<Converter *> *availableConverters;
Converter::Converter()
{
if (!availableConverters)
availableConverters = new QList<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 : std::as_const(*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 : std::as_const(*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 : 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;
}
}
Converter *outconv = nullptr;
format = parser.value(outputFormatOption);
if (format != "auto") {
for (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;
}
}
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 : std::as_const(*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 : std::as_const(*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;
}

View File

@ -0,0 +1,52 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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);
}

View File

@ -0,0 +1,22 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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

View File

@ -0,0 +1,113 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "textconverter.h"
#include <QFile>
#include <QTextStream>
static void dumpVariant(QTextStream &out, const QVariant &v)
{
switch (v.userType()) {
case QMetaType::QVariantList: {
const QVariantList list = v.toList();
for (const QVariant &item : list)
dumpVariant(out, item);
break;
}
case QMetaType::QString: {
const QStringList list = v.toStringList();
for (const QString &s : list)
out << s << Qt::endl;
break;
}
case QMetaType::QVariantMap: {
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)" << Qt::endl;
break;
default:
out << v.toString() << Qt::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;

View File

@ -0,0 +1,23 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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

View File

@ -0,0 +1,468 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "xmlconverter.h"
#include <QBitArray>
#include <QtCborCommon>
#include <QFile>
#include <QFloat16>
#include <QMetaType>
#include <QRegularExpression>
#include <QUrl>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
static const char xmlOptionHelp[] =
"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.name() == QLatin1String("list"))) {
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.name() == QLatin1String("entry"))) {
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.name() == QLatin1String("map"))) {
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)
{
QStringView 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();
QStringView 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);
}
QStringView 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();
QStringView 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 = QMetaType::UnknownType;
if (type == QLatin1String("datetime"))
id = QMetaType::QDateTime;
else if (type == QLatin1String("url"))
id = QMetaType::QUrl;
else if (type == QLatin1String("uuid"))
id = QMetaType::QUuid;
else if (type == QLatin1String("regex"))
id = QMetaType::QRegularExpression;
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);
}
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);
}
}
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 == QMetaType::QVariantList) {
QVariantList list = v.toList();
xml.writeStartElement("list");
for (const QVariant &v : list)
variantToXml(xml, v);
xml.writeEndElement();
} else if (type == QMetaType::QVariantMap || type == qMetaTypeId<VariantOrderedMap>()) {
const VariantOrderedMap map = (type == QMetaType::QVariantMap) ?
VariantOrderedMap(v.toMap()) :
qvariant_cast<VariantOrderedMap>(v);
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(qvariant_cast<qfloat16>(v))));
} else if (type == qMetaTypeId<QCborSimpleType>()) {
xml.writeAttribute(typeString, "CBOR simple type");
xml.writeCharacters(QString::number(int(qvariant_cast<QCborSimpleType>(v))));
} else {
// does this convert to string?
const char *typeName = v.typeName();
QVariant copy = v;
if (copy.convert(QMetaType(QMetaType::QString))) {
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 xmlOptionHelp;
}
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), xmlOptionHelp);
exit(EXIT_FAILURE);
}
}
QXmlStreamWriter xml(f);
xml.setAutoFormatting(!compact);
xml.writeStartDocument();
variantToXml(xml, contents);
xml.writeEndDocument();
}

View File

@ -0,0 +1,22 @@
// Copyright (C) 2018 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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

View File

@ -0,0 +1,38 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(rsslisting LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/rsslisting")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
qt_standard_project_setup()
qt_add_executable(rsslisting
main.cpp
rsslisting.cpp rsslisting.h
)
set_target_properties(rsslisting PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(rsslisting PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Network
Qt6::Widgets
)
install(TARGETS rsslisting
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -0,0 +1,26 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
/*
main.cpp
Provides the main function for the RSS news reader example.
*/
#include <QtWidgets>
#include "rsslisting.h"
/*!
Create an application and a main widget. Open the main widget for
user input, and exit with an appropriate return value when it is
closed.
*/
int main(int argc, char **argv)
{
QApplication app(argc, argv);
RSSListing *rsslisting = new RSSListing;
rsslisting->show();
return app.exec();
}

View File

@ -0,0 +1,211 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
/*
rsslisting.cpp
Provides a widget for displaying news items from RDF news sources.
RDF is an XML-based format for storing items of information (see
http://www.w3.org/RDF/ for details).
The widget itself provides a simple user interface for specifying
the URL of a news source, and controlling the downloading of news.
The widget downloads and parses the XML asynchronously, feeding the
data to an XML reader in pieces. This allows the user to interrupt
its operation, and also allows very large data sources to be read.
*/
#include <QtCore>
#include <QtWidgets>
#include <QtNetwork>
#include "rsslisting.h"
/*
Constructs an RSSListing widget with a simple user interface, and sets
up the XML reader to use a custom handler class.
The user interface consists of a line edit, a push button, and a
list view widget. The line edit is used for entering the URLs of news
sources; the push button starts the process of reading the
news.
*/
RSSListing::RSSListing(QWidget *parent)
: QWidget(parent), currentReply(0)
{
lineEdit = new QLineEdit(this);
lineEdit->setText("http://blog.qt.io/feed/");
fetchButton = new QPushButton(tr("Fetch"), this);
treeWidget = new QTreeWidget(this);
connect(treeWidget, &QTreeWidget::itemActivated,
this, &RSSListing::itemActivated);
QStringList headerLabels;
headerLabels << tr("Title") << tr("Link");
treeWidget->setHeaderLabels(headerLabels);
treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
connect(&manager, &QNetworkAccessManager::finished,
this, &RSSListing::finished);
connect(lineEdit, &QLineEdit::returnPressed, this, &RSSListing::fetch);
connect(fetchButton, &QPushButton::clicked, this, &RSSListing::fetch);
QVBoxLayout *layout = new QVBoxLayout(this);
QHBoxLayout *hboxLayout = new QHBoxLayout;
hboxLayout->addWidget(lineEdit);
hboxLayout->addWidget(fetchButton);
layout->addLayout(hboxLayout);
layout->addWidget(treeWidget);
setWindowTitle(tr("RSS listing example"));
resize(640,480);
}
/*
Starts the network request and connects the needed signals
*/
void RSSListing::get(const QUrl &url)
{
QNetworkRequest request(url);
if (currentReply) {
currentReply->disconnect(this);
currentReply->deleteLater();
}
currentReply = manager.get(request);
connect(currentReply, &QNetworkReply::readyRead, this, &RSSListing::readyRead);
connect(currentReply, &QNetworkReply::metaDataChanged, this, &RSSListing::metaDataChanged);
connect(currentReply, &QNetworkReply::errorOccurred, this, &RSSListing::error);
}
/*
Starts fetching data from a news source specified in the line
edit widget.
The line edit is made read only to prevent the user from modifying its
contents during the fetch; this is only for cosmetic purposes.
The fetch button is disabled, the list view is cleared, and we
define the last list view item to be 0, meaning that there are no
existing items in the list.
A URL is created with the raw contents of the line edit and
a get is initiated.
*/
void RSSListing::fetch()
{
lineEdit->setReadOnly(true);
fetchButton->setEnabled(false);
treeWidget->clear();
xml.clear();
QUrl url(lineEdit->text());
get(url);
}
void RSSListing::metaDataChanged()
{
QUrl redirectionTarget = currentReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (redirectionTarget.isValid()) {
get(redirectionTarget);
}
}
/*
Reads data received from the RDF source.
We read all the available data, and pass it to the XML
stream reader. Then we call the XML parsing function.
*/
void RSSListing::readyRead()
{
int statusCode = currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (statusCode >= 200 && statusCode < 300) {
QByteArray data = currentReply->readAll();
xml.addData(data);
parseXml();
}
}
/*
Finishes processing an HTTP request.
The default behavior is to keep the text edit read only.
If an error has occurred, the user interface is made available
to the user for further input, allowing a new fetch to be
started.
If the HTTP get request has finished, we make the
user interface available to the user for further input.
*/
void RSSListing::finished(QNetworkReply *reply)
{
Q_UNUSED(reply);
lineEdit->setReadOnly(false);
fetchButton->setEnabled(true);
}
/*
Parses the XML data and creates treeWidget items accordingly.
*/
void RSSListing::parseXml()
{
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == u"item")
linkString = xml.attributes().value("rss:about").toString();
currentTag = xml.name().toString();
} else if (xml.isEndElement()) {
if (xml.name() == u"item") {
QTreeWidgetItem *item = new QTreeWidgetItem;
item->setText(0, titleString);
item->setText(1, linkString);
treeWidget->addTopLevelItem(item);
titleString.clear();
linkString.clear();
}
} else if (xml.isCharacters() && !xml.isWhitespace()) {
if (currentTag == "title")
titleString += xml.text();
else if (currentTag == "link")
linkString += xml.text();
}
}
if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) {
qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString();
}
}
/*
Open the link in the browser
*/
void RSSListing::itemActivated(QTreeWidgetItem * item)
{
QDesktopServices::openUrl(QUrl(item->text(1)));
}
void RSSListing::error(QNetworkReply::NetworkError)
{
qWarning("error retrieving RSS feed");
currentReply->disconnect(this);
currentReply->deleteLater();
currentReply = 0;
}

View File

@ -0,0 +1,55 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef RSSLISTING_H
#define RSSLISTING_H
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QWidget>
#include <QBuffer>
#include <QXmlStreamReader>
#include <QUrl>
QT_BEGIN_NAMESPACE
class QLineEdit;
class QTreeWidget;
class QTreeWidgetItem;
class QPushButton;
QT_END_NAMESPACE
class RSSListing : public QWidget
{
Q_OBJECT
public:
RSSListing(QWidget *widget = nullptr);
public slots:
void fetch();
void finished(QNetworkReply *reply);
void readyRead();
void metaDataChanged();
void itemActivated(QTreeWidgetItem * item);
void error(QNetworkReply::NetworkError);
private:
void parseXml();
void get(const QUrl &url);
QXmlStreamReader xml;
QString currentTag;
QString linkString;
QString titleString;
QNetworkAccessManager manager;
QNetworkReply *currentReply;
QLineEdit *lineEdit;
QTreeWidget *treeWidget;
QPushButton *fetchButton;
};
#endif

View File

@ -0,0 +1,8 @@
HEADERS += rsslisting.h
SOURCES += main.cpp rsslisting.cpp
QT += network widgets
requires(qtConfig(treewidget))
# install
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/rsslisting
INSTALLS += target

View File

@ -0,0 +1,32 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(savegame LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/savegame")
find_package(Qt6 REQUIRED COMPONENTS Core)
qt_standard_project_setup()
qt_add_executable(savegame
character.cpp character.h
game.cpp game.h
level.cpp level.h
main.cpp
)
target_link_libraries(savegame PRIVATE
Qt6::Core
)
install(TARGETS savegame
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -0,0 +1,82 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "character.h"
#include <QMetaEnum>
#include <QTextStream>
Character::Character()
= default;
Character::Character(const QString &name,
int level,
Character::ClassType classType) :
mName(name),
mLevel(level),
mClassType(classType)
{
}
QString Character::name() const
{
return mName;
}
void Character::setName(const QString &name)
{
mName = name;
}
int Character::level() const
{
return mLevel;
}
void Character::setLevel(int level)
{
mLevel = level;
}
Character::ClassType Character::classType() const
{
return mClassType;
}
void Character::setClassType(Character::ClassType classType)
{
mClassType = classType;
}
//! [0]
void Character::read(const QJsonObject &json)
{
if (json.contains("name") && json["name"].isString())
mName = json["name"].toString();
if (json.contains("level") && json["level"].isDouble())
mLevel = json["level"].toInt();
if (json.contains("classType") && json["classType"].isDouble())
mClassType = ClassType(json["classType"].toInt());
}
//! [0]
//! [1]
void Character::write(QJsonObject &json) const
{
json["name"] = mName;
json["level"] = mLevel;
json["classType"] = mClassType;
}
//! [1]
void Character::print(int indentation) const
{
const QString indent(indentation * 2, ' ');
QTextStream(stdout) << indent << "Name:\t" << mName << "\n";
QTextStream(stdout) << indent << "Level:\t" << mLevel << "\n";
QString className = QMetaEnum::fromType<ClassType>().valueToKey(mClassType);
QTextStream(stdout) << indent << "Class:\t" << className << "\n";
}

View File

@ -0,0 +1,45 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef CHARACTER_H
#define CHARACTER_H
#include <QJsonObject>
#include <QObject>
#include <QString>
//! [0]
class Character
{
Q_GADGET
public:
enum ClassType {
Warrior, Mage, Archer
};
Q_ENUM(ClassType)
Character();
Character(const QString &name, int level, ClassType classType);
QString name() const;
void setName(const QString &name);
int level() const;
void setLevel(int level);
ClassType classType() const;
void setClassType(ClassType classType);
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private:
QString mName;
int mLevel = 0;
ClassType mClassType = Warrior;
};
//! [0]
#endif // CHARACTER_H

View File

@ -0,0 +1,163 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example serialization/savegame
\examplecategory {Input/Output}
\title JSON Save Game Example
\brief The JSON Save Game example demonstrates how to save and load a
small game using QJsonDocument, QJsonObject and QJsonArray.
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
game generally involves serializing each game object's member variables
to a file. Many formats can be used for this purpose, one of which is JSON.
With QJsonDocument, you also have the ability to serialize a document in a
\l {RFC 7049} {CBOR} format, which is great if you
don't want the save file to be readable, or if you need to keep the file size down.
In this example, we'll demonstrate how to save and load a simple game to
and from JSON and binary formats.
\section1 The Character Class
The Character class represents a non-player character (NPC) in our game, and
stores the player's name, level, and class type.
It provides read() and write() functions to serialise its member variables.
\snippet serialization/savegame/character.h 0
Of particular interest to us are the read and write function
implementations:
\snippet serialization/savegame/character.cpp 0
In the read() function, we assign Character's members values from the
QJsonObject argument. You can use either \l QJsonObject::operator[]() or
QJsonObject::value() to access values within the JSON object; both are
const functions and return QJsonValue::Undefined if the key is invalid. We
check if the keys are valid before attempting to read them with
QJsonObject::contains().
\snippet serialization/savegame/character.cpp 1
In the write() function, we do the reverse of the read() function; assign
values from the Character object to the JSON object. As with accessing
values, there are two ways to set values on a QJsonObject:
\l QJsonObject::operator[]() and QJsonObject::insert(). Both will override
any existing value at the given key.
Next up is the Level class:
\snippet serialization/savegame/level.h 0
We want to have several levels in our game, each with several NPCs, so we
keep a QList of Character objects. We also provide the familiar read() and
write() functions.
\snippet serialization/savegame/level.cpp 0
Containers can be written and read to and from JSON using QJsonArray. In our
case, we construct a QJsonArray from the value associated with the key
\c "npcs". Then, for each QJsonValue element in the array, we call
toObject() to get the Character's JSON object. The Character object can then
read their JSON and be appended to our NPC array.
\note \l{Container Classes}{Associate containers} can be written by storing
the key in each value object (if it's not already). With this approach, the
container is stored as a regular array of objects, but the index of each
element is used as the key to construct the container when reading it back
in.
\snippet serialization/savegame/level.cpp 1
Again, the write() function is similar to the read() function, except
reversed.
Having established the Character and Level classes, we can move on to
the Game class:
\snippet serialization/savegame/game.h 0
First of all, we define the \c SaveFormat enum. This will allow us to
specify the format in which the game should be saved: \c Json or \c Binary.
Next, we provide accessors for the player and levels. We then expose three
functions: newGame(), saveGame() and loadGame().
The read() and write() functions are used by saveGame() and loadGame().
\snippet serialization/savegame/game.cpp 0
To setup a new game, we create the player and populate the levels and their
NPCs.
\snippet serialization/savegame/game.cpp 1
The first thing we do in the read() function is tell the player to read
itself. We then clear the level array so that calling loadGame() on the
same Game object twice doesn't result in old levels hanging around.
We then populate the level array by reading each Level from a QJsonArray.
\snippet serialization/savegame/game.cpp 2
We write the game to JSON similarly to how we write Level.
\snippet serialization/savegame/game.cpp 3
When loading a saved game in loadGame(), the first thing we do is open the
save file based on which format it was saved to; \c "save.json" for JSON,
and \c "save.dat" for CBOR. We print a warning and return \c false if the
file couldn't be opened.
Since \l QJsonDocument::fromJson() and \l QCborValue::fromCbor() both take
a QByteArray, we can read the entire contents of the save file into one,
regardless of the save format.
After constructing the QJsonDocument, we instruct the Game object to read
itself and then return \c true to indicate success.
\snippet serialization/savegame/game.cpp 4
Not surprisingly, saveGame() looks very much like loadGame(). We determine
the file extension based on the format, print a warning and return \c false
if the opening of the file fails. We then write the Game object to a
QJsonDocument, and call either QJsonDocument::toJson() or to
QJsonDocument::toBinaryData() to save the game, depending on which format
was specified.
We are now ready to enter main():
\snippet serialization/savegame/main.cpp 0
Since we're only interested in demonstrating \e serialization of a game with
JSON, our game is not actually playable. Therefore, we only need
QCoreApplication and have no event loop. On application start-up we parse
the command-line arguments to decide how to start the game. For the first
argument the options "new" (default) and "load" are available. When "new"
is specified a new game will be generated, and when "load" is specified a
previously saved game will be loaded in. For the second argument
"json" (default) and "binary" are available as options. This argument will
decide which file is saved to and/or loaded from. We then move ahead and
assume that the player had a great time and made lots of progress, altering
the internal state of our Character, Level and Game objects.
\snippet serialization/savegame/main.cpp 1
When the player has finished, we save their game. For demonstration
purposes, we can serialize to either JSON or CBOR. You can examine the
contents of the files in the same directory as the executable (or re-run
the example, making sure to also specify the "load" option), although the
binary save file will contain some garbage characters (which is normal).
That concludes our example. As you can see, serialization with Qt's JSON
classes is very simple and convenient. The advantages of using QJsonDocument
and friends over QDataStream, for example, is that you not only get
human-readable JSON files, but you also have the option to use a binary
format if it's required, \e without rewriting any code.
\sa {JSON Support in Qt}, {CBOR Support in Qt}, {Data Input Output}
*/

View File

@ -0,0 +1,160 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "game.h"
#include <QCborMap>
#include <QCborValue>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QRandomGenerator>
#include <QTextStream>
Character Game::player() const
{
return mPlayer;
}
QList<Level> Game::levels() const
{
return mLevels;
}
//! [0]
void Game::newGame()
{
mPlayer = Character();
mPlayer.setName(QStringLiteral("Hero"));
mPlayer.setClassType(Character::Archer);
mPlayer.setLevel(QRandomGenerator::global()->bounded(15, 21));
mLevels.clear();
mLevels.reserve(2);
Level village(QStringLiteral("Village"));
QList<Character> villageNpcs;
villageNpcs.reserve(2);
villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"),
QRandomGenerator::global()->bounded(8, 11),
Character::Warrior));
villageNpcs.append(Character(QStringLiteral("Terry the Trader"),
QRandomGenerator::global()->bounded(6, 8),
Character::Warrior));
village.setNpcs(villageNpcs);
mLevels.append(village);
Level dungeon(QStringLiteral("Dungeon"));
QList<Character> dungeonNpcs;
dungeonNpcs.reserve(3);
dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"),
QRandomGenerator::global()->bounded(18, 26),
Character::Mage));
dungeonNpcs.append(Character(QStringLiteral("Eric's Left Minion"),
QRandomGenerator::global()->bounded(5, 7),
Character::Warrior));
dungeonNpcs.append(Character(QStringLiteral("Eric's Right Minion"),
QRandomGenerator::global()->bounded(4, 9),
Character::Warrior));
dungeon.setNpcs(dungeonNpcs);
mLevels.append(dungeon);
}
//! [0]
//! [3]
bool Game::loadGame(Game::SaveFormat saveFormat)
{
QFile loadFile(saveFormat == Json
? QStringLiteral("save.json")
: QStringLiteral("save.dat"));
if (!loadFile.open(QIODevice::ReadOnly)) {
qWarning("Couldn't open save file.");
return false;
}
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(saveFormat == Json
? QJsonDocument::fromJson(saveData)
: QJsonDocument(QCborValue::fromCbor(saveData).toMap().toJsonObject()));
read(loadDoc.object());
QTextStream(stdout) << "Loaded save for "
<< loadDoc["player"]["name"].toString()
<< " using "
<< (saveFormat != Json ? "CBOR" : "JSON") << "...\n";
return true;
}
//! [3]
//! [4]
bool Game::saveGame(Game::SaveFormat saveFormat) const
{
QFile saveFile(saveFormat == Json
? QStringLiteral("save.json")
: QStringLiteral("save.dat"));
if (!saveFile.open(QIODevice::WriteOnly)) {
qWarning("Couldn't open save file.");
return false;
}
QJsonObject gameObject;
write(gameObject);
saveFile.write(saveFormat == Json
? QJsonDocument(gameObject).toJson()
: QCborValue::fromJsonValue(gameObject).toCbor());
return true;
}
//! [4]
//! [1]
void Game::read(const QJsonObject &json)
{
if (json.contains("player") && json["player"].isObject())
mPlayer.read(json["player"].toObject());
if (json.contains("levels") && json["levels"].isArray()) {
QJsonArray levelArray = json["levels"].toArray();
mLevels.clear();
mLevels.reserve(levelArray.size());
for (const QJsonValue &v : levelArray) {
QJsonObject levelObject = v.toObject();
Level level;
level.read(levelObject);
mLevels.append(level);
}
}
}
//! [1]
//! [2]
void Game::write(QJsonObject &json) const
{
QJsonObject playerObject;
mPlayer.write(playerObject);
json["player"] = playerObject;
QJsonArray levelArray;
for (const Level &level : mLevels) {
QJsonObject levelObject;
level.write(levelObject);
levelArray.append(levelObject);
}
json["levels"] = levelArray;
}
//! [2]
void Game::print(int indentation) const
{
const QString indent(indentation * 2, ' ');
QTextStream(stdout) << indent << "Player\n";
mPlayer.print(indentation + 1);
QTextStream(stdout) << indent << "Levels\n";
for (const Level &level : mLevels)
level.print(indentation + 1);
}

View File

@ -0,0 +1,38 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef GAME_H
#define GAME_H
#include "character.h"
#include "level.h"
#include <QJsonObject>
#include <QList>
//! [0]
class Game
{
public:
enum SaveFormat {
Json, Binary
};
Character player() const;
QList<Level> levels() const;
void newGame();
bool loadGame(SaveFormat saveFormat);
bool saveGame(SaveFormat saveFormat) const;
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private:
Character mPlayer;
QList<Level> mLevels;
};
//! [0]
#endif // GAME_H

View File

@ -0,0 +1,70 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "level.h"
#include <QJsonArray>
#include <QTextStream>
Level::Level(const QString &name) : mName(name)
{
}
QString Level::name() const
{
return mName;
}
QList<Character> Level::npcs() const
{
return mNpcs;
}
void Level::setNpcs(const QList<Character> &npcs)
{
mNpcs = npcs;
}
//! [0]
void Level::read(const QJsonObject &json)
{
if (json.contains("name") && json["name"].isString())
mName = json["name"].toString();
if (json.contains("npcs") && json["npcs"].isArray()) {
QJsonArray npcArray = json["npcs"].toArray();
mNpcs.clear();
mNpcs.reserve(npcArray.size());
for (const QJsonValue &v : npcArray) {
QJsonObject npcObject = v.toObject();
Character npc;
npc.read(npcObject);
mNpcs.append(npc);
}
}
}
//! [0]
//! [1]
void Level::write(QJsonObject &json) const
{
json["name"] = mName;
QJsonArray npcArray;
for (const Character &npc : mNpcs) {
QJsonObject npcObject;
npc.write(npcObject);
npcArray.append(npcObject);
}
json["npcs"] = npcArray;
}
//! [1]
void Level::print(int indentation) const
{
const QString indent(indentation * 2, ' ');
QTextStream(stdout) << indent << "Name:\t" << mName << "\n";
QTextStream(stdout) << indent << "NPCs:\n";
for (const Character &character : mNpcs)
character.print(2);
}

View File

@ -0,0 +1,34 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef LEVEL_H
#define LEVEL_H
#include "character.h"
#include <QJsonObject>
#include <QList>
//! [0]
class Level
{
public:
Level() = default;
explicit Level(const QString &name);
QString name() const;
QList<Character> npcs() const;
void setNpcs(const QList<Character> &npcs);
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private:
QString mName;
QList<Character> mNpcs;
};
//! [0]
#endif // LEVEL_H

View File

@ -0,0 +1,39 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "game.h"
#include <QCoreApplication>
#include <QStringList>
#include <QString>
#include <QTextStream>
using namespace Qt::StringLiterals; // for _L1
//! [0]
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
const QStringList args = QCoreApplication::arguments();
const bool newGame
= args.size() <= 1 || QString::compare(args[1], "load"_L1, Qt::CaseInsensitive) == 0;
const bool json
= args.size() <= 2 || QString::compare(args[2], "binary"_L1, Qt::CaseInsensitive) == 0;
Game game;
if (newGame)
game.newGame();
else if (!game.loadGame(json ? Game::Json : Game::Binary))
return 1;
// Game is played; changes are made...
//! [0]
//! [1]
QTextStream(stdout) << "Game ended in the following state:\n";
game.print();
if (!game.saveGame(json ? Game::Json : Game::Binary))
return 1;
return 0;
}
//! [1]

View File

@ -0,0 +1,21 @@
QT += core
QT -= gui
TARGET = savegame
CONFIG += cmdline
TEMPLATE = app
# install
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/savegame
INSTALLS += target
SOURCES += main.cpp \
character.cpp \
game.cpp \
level.cpp
HEADERS += \
character.h \
game.h \
level.h

View File

@ -0,0 +1,11 @@
TEMPLATE = subdirs
SUBDIRS = \
cbordump \
convert \
savegame
qtHaveModule(widgets) {
SUBDIRS += streambookmarks
qtHaveModule(network): SUBDIRS += \
rsslisting
}

View File

@ -0,0 +1,39 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(streambookmarks LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/streambookmarks")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(streambookmarks
main.cpp
mainwindow.cpp mainwindow.h
xbelreader.cpp xbelreader.h
xbelwriter.cpp xbelwriter.h
)
set_target_properties(streambookmarks PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(streambookmarks PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
install(TARGETS streambookmarks
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,171 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example serialization/streambookmarks
\title QXmlStream Bookmarks Example
\examplecategory {Input/Output}
\brief Demonstrates how to read and write to XBEL files.
\ingroup xml-examples
The QXmlStream Bookmarks example provides a reader for XML Bookmark
Exchange Language (XBEL) files using Qt's QXmlStreamReader class
for reading, and QXmlStreamWriter class for writing the files.
\image xmlstreamexample-screenshot.png
\section1 XbelWriter Class Definition
The \c XbelWriter class contains a private instance of QXmlStreamWriter,
which provides an XML writer with a streaming API. \c XbelWriter also
has a reference to the QTreeWidget instance where the bookmark hierarchy
is stored.
\snippet serialization/streambookmarks/xbelwriter.h 0
\section1 XbelWriter Class Implementation
The \c XbelWriter constructor accepts a \a treeWidget to initialize within
its definition. We enable \l{QXmlStreamWriter}'s auto-formatting property
to ensure line-breaks and indentations are added automatically to empty
sections between elements, increasing readability as the data is split into
several lines.
\snippet serialization/streambookmarks/xbelwriter.cpp 0
The \c writeFile() function accepts a QIODevice object and sets it using
\c setDevice(). This function then writes the document type
definition(DTD), the start element, the version, and \c{treeWidget}'s
top-level items.
\snippet serialization/streambookmarks/xbelwriter.cpp 1
The \c writeItem() function accepts a QTreeWidgetItem object and writes it
to the stream, depending on its \c tagName, which can either be a "folder",
"bookmark", or "separator".
\snippet serialization/streambookmarks/xbelwriter.cpp 2
\section1 XbelReader Class Definition
The \c XbelReader contains a private instance of QXmlStreamReader, the
companion class to QXmlStreamWriter. \c XbelReader also contains a
reference to the QTreeWidget that is used to group the bookmarks according
to their hierarchy.
\snippet serialization/streambookmarks/xbelreader.h 0
\section1 XbelReader Class Implementation
The \c XbelReader constructor accepts a QTreeWidget to initialize the
\c treeWidget within its definition. A QStyle object is used to set
\c{treeWidget}'s style property. The \c folderIcon is set to QIcon::Normal
mode where the pixmap is only displayed when the user is not interacting
with the icon. The QStyle::SP_DirClosedIcon, QStyle::SP_DirOpenIcon, and
QStyle::SP_FileIcon correspond to standard pixmaps that follow the style
of your GUI.
\snippet serialization/streambookmarks/xbelreader.cpp 0
The \c read() function accepts a QIODevice and sets it using
\l{QXmlStreamReader::}{setDevice()}. The actual process of reading only
takes place if the file is a valid XBEL 1.0 file. Note that the XML input
needs to be well-formed to be accepted by QXmlStreamReader. Otherwise, the
\l{QXmlStreamReader::}{raiseError()} function is used to display an error
message. Since the XBEL reader is only concerned with reading XML elements,
it makes extensive use of the \l{QXmlStreamReader::}{readNextStartElement()}
convenience function.
\snippet serialization/streambookmarks/xbelreader.cpp 1
The \c errorString() function is used if an error occurred, in order to
obtain a description of the error complete with line and column number
information.
\snippet serialization/streambookmarks/xbelreader.cpp 2
The \c readXBEL() function reads the name of a startElement and calls
the appropriate function to read it, depending on whether if its a
"folder", "bookmark" or "separator". Otherwise, it calls
\l{QXmlStreamReader::}{skipCurrentElement()}. The Q_ASSERT() macro is used
to provide a pre-condition for the function.
\snippet serialization/streambookmarks/xbelreader.cpp 3
The \c readTitle() function reads the bookmark's title.
\snippet serialization/streambookmarks/xbelreader.cpp 4
The \c readSeparator() function creates a separator and sets its flags.
The text is set to 30 "0xB7", the HEX equivalent for period. The element
is then skipped using \l{QXmlStreamReader::}{skipCurrentElement()}.
\snippet serialization/streambookmarks/xbelreader.cpp 5
\section1 MainWindow Class Definition
The \c MainWindow class is a subclass of QMainWindow, with a
\c File menu and a \c Help menu.
\snippet serialization/streambookmarks/mainwindow.h 0
\section1 MainWindow Class Implementation
The \c MainWindow constructor instantiates the QTreeWidget object, \c
treeWidget and sets its header with a QStringList object, \c labels.
The constructor also invokes \c createActions() and \c createMenus()
to set up the menus and their corresponding actions. The \c statusBar()
is used to display the message "Ready" and the window's size is fixed
to 480x320 pixels.
\snippet serialization/streambookmarks/mainwindow.cpp 0
The \c open() function enables the user to open an XBEL file using
QFileDialog::getOpenFileName(). A warning message is displayed along
with the \c fileName and \c errorString if the file cannot be read or
if there is a parse error.
\snippet serialization/streambookmarks/mainwindow.cpp 1
The \c saveAs() function displays a QFileDialog, prompting the user for
a \c fileName using QFileDialog::getSaveFileName(). Similar to the
\c open() function, this function also displays a warning message if
the file cannot be written to.
\snippet serialization/streambookmarks/mainwindow.cpp 2
The \c about() function displays a QMessageBox with a brief description
of the example.
\snippet serialization/streambookmarks/mainwindow.cpp 3
In order to implement the \c open(), \c saveAs(), \c exit(), \c about()
and \c aboutQt() functions, we connect them to QAction objects and
add them to the \c fileMenu and \c helpMenu. The connections are as shown
below:
\snippet serialization/streambookmarks/mainwindow.cpp 5
The \c createMenus() function creates the \c fileMenu and \c helpMenu
and adds the QAction objects to them in order to create the menu shown
in the screenshot below:
\table
\row
\li \inlineimage xmlstreamexample-filemenu.png
\li \inlineimage xmlstreamexample-helpmenu.png
\endtable
\snippet serialization/streambookmarks/mainwindow.cpp 5
\section1 \c{main()} Function
The \c main() function instantiates \c MainWindow and invokes the \c show()
function.
\snippet serialization/streambookmarks/main.cpp 0
See the \l{http://pyxml.sourceforge.net/topics/xbel/}
{XML Bookmark Exchange Language Resource Page} for more information
about XBEL files.
*/

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xbel>
<xbel version="1.0">
<folder folded="no">
<title>Qt Resources</title>
<bookmark href="http://qt.io/">
<title>Qt home page</title>
</bookmark>
<bookmark href="https://www.qt.io/partners/">
<title>Qt Partners</title>
</bookmark>
<bookmark href="https://www.qt.io/qt-training/">
<title>Training</title>
</bookmark>
<bookmark href="http://doc.qt.io/">
<title>Qt 5 documentation</title>
</bookmark>
<bookmark href="http://qt-project.org/faq/">
<title>Frequently Asked Questions</title>
</bookmark>
<folder folded="yes">
<title>Community Resources</title>
<bookmark href="http://www.qtcentre.org/content/">
<title>Qt Centre</title>
</bookmark>
<bookmark href="http://www.qtforum.org/">
<title>QtForum.org</title>
</bookmark>
<bookmark href="http://digitalfanatics.org/projects/qt_tutorial/">
<title>The Independent Qt Tutorial</title>
</bookmark>
<bookmark href="http://www.qtforum.de/">
<title>German Qt Forum</title>
</bookmark>
<bookmark href="http://www.korone.net/">
<title>Korean Qt Community Site</title>
</bookmark>
<bookmark href="http://prog.org.ru/">
<title>Russian Qt Forum</title>
</bookmark>
</folder>
</folder>
<folder folded="no">
<title>Online Dictionaries</title>
<bookmark href="http://www.dictionary.com/">
<title>Dictionary.com</title>
</bookmark>
<bookmark href="http://www.m-w.com/">
<title>Merriam-Webster Online</title>
</bookmark>
<bookmark href="http://dictionary.cambridge.org/">
<title>Cambridge Dictionaries Online</title>
</bookmark>
<bookmark href="http://www.onelook.com/">
<title>OneLook Dictionary Search</title>
</bookmark>
<separator/>
<bookmark href="http://dict.tu-chemnitz.de/">
<title>TU Chemnitz German-English Dictionary</title>
</bookmark>
<separator/>
<bookmark href="http://atilf.atilf.fr/tlf.htm">
<title>Trésor de la Langue Française informatisé</title>
</bookmark>
<bookmark href="http://dictionnaires.atilf.fr/dictionnaires/ACADEMIE/">
<title>Dictionnaire de l'Académie Française</title>
</bookmark>
</folder>
</xbel>

View File

@ -0,0 +1,17 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QApplication>
#include "mainwindow.h"
//! [0]
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
mainWin.open();
return app.exec();
}
//! [0]

View File

@ -0,0 +1,142 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "mainwindow.h"
#include "xbelreader.h"
#include "xbelwriter.h"
//! [0]
MainWindow::MainWindow()
{
QStringList labels;
labels << tr("Title") << tr("Location");
treeWidget = new QTreeWidget;
treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
treeWidget->setHeaderLabels(labels);
#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(treeWidget, &QWidget::customContextMenuRequested,
this, &MainWindow::onCustomContextMenuRequested);
#endif
setCentralWidget(treeWidget);
createMenus();
statusBar()->showMessage(tr("Ready"));
setWindowTitle(tr("QXmlStream Bookmarks"));
const QSize availableSize = screen()->availableGeometry().size();
resize(availableSize.width() / 2, availableSize.height() / 3);
}
//! [0]
#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
{
const QTreeWidgetItem *item = treeWidget->itemAt(pos);
if (!item)
return;
const QString url = item->text(1);
QMenu contextMenu;
QAction *copyAction = contextMenu.addAction(tr("Copy Link to Clipboard"));
QAction *openAction = contextMenu.addAction(tr("Open"));
QAction *action = contextMenu.exec(treeWidget->viewport()->mapToGlobal(pos));
if (action == copyAction)
QGuiApplication::clipboard()->setText(url);
else if (action == openAction)
QDesktopServices::openUrl(QUrl(url));
}
#endif // !QT_NO_CONTEXTMENU && !QT_NO_CLIPBOARD
//! [1]
void MainWindow::open()
{
QString fileName =
QFileDialog::getOpenFileName(this, tr("Open Bookmark File"),
QDir::currentPath(),
tr("XBEL Files (*.xbel *.xml)"));
if (fileName.isEmpty())
return;
treeWidget->clear();
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
tr("Cannot read file %1:\n%2.")
.arg(QDir::toNativeSeparators(fileName),
file.errorString()));
return;
}
XbelReader reader(treeWidget);
if (!reader.read(&file)) {
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
tr("Parse error in file %1:\n\n%2")
.arg(QDir::toNativeSeparators(fileName),
reader.errorString()));
} else {
statusBar()->showMessage(tr("File loaded"), 2000);
}
}
//! [1]
//! [2]
void MainWindow::saveAs()
{
QString fileName =
QFileDialog::getSaveFileName(this, tr("Save Bookmark File"),
QDir::currentPath(),
tr("XBEL Files (*.xbel *.xml)"));
if (fileName.isEmpty())
return;
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
tr("Cannot write file %1:\n%2.")
.arg(QDir::toNativeSeparators(fileName),
file.errorString()));
return;
}
XbelWriter writer(treeWidget);
if (writer.writeFile(&file))
statusBar()->showMessage(tr("File saved"), 2000);
}
//! [2]
//! [3]
void MainWindow::about()
{
QMessageBox::about(this, tr("About QXmlStream Bookmarks"),
tr("The <b>QXmlStream Bookmarks</b> example demonstrates how to use Qt's "
"QXmlStream classes to read and write XML documents."));
}
//! [3]
//! [5]
void MainWindow::createMenus()
{
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QAction *openAct = fileMenu->addAction(tr("&Open..."), this, &MainWindow::open);
openAct->setShortcuts(QKeySequence::Open);
QAction *saveAsAct = fileMenu->addAction(tr("&Save As..."), this, &MainWindow::saveAs);
saveAsAct->setShortcuts(QKeySequence::SaveAs);
QAction *exitAct = fileMenu->addAction(tr("E&xit"), this, &QWidget::close);
exitAct->setShortcuts(QKeySequence::Quit);
menuBar()->addSeparator();
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(tr("&About"), this, &MainWindow::about);
helpMenu->addAction(tr("About &Qt"), qApp, &QCoreApplication::quit);
}
//! [5]

View File

@ -0,0 +1,35 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
class QTreeWidget;
QT_END_NAMESPACE
//! [0]
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
public slots:
void open();
void saveAs();
void about();
#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
void onCustomContextMenuRequested(const QPoint &pos);
#endif
private:
void createMenus();
QTreeWidget *treeWidget;
};
//! [0]
#endif

View File

@ -0,0 +1,15 @@
HEADERS = mainwindow.h \
xbelreader.h \
xbelwriter.h
SOURCES = main.cpp \
mainwindow.cpp \
xbelreader.cpp \
xbelwriter.cpp
QT += widgets
requires(qtConfig(filedialog))
EXAMPLE_FILES = frank.xbel jennifer.xbel
# install
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/streambookmarks
INSTALLS += target

View File

@ -0,0 +1,140 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "xbelreader.h"
//! [0]
XbelReader::XbelReader(QTreeWidget *treeWidget)
: treeWidget(treeWidget)
{
QStyle *style = treeWidget->style();
folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirClosedIcon),
QIcon::Normal, QIcon::Off);
folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirOpenIcon),
QIcon::Normal, QIcon::On);
bookmarkIcon.addPixmap(style->standardPixmap(QStyle::SP_FileIcon));
}
//! [0]
//! [1]
bool XbelReader::read(QIODevice *device)
{
xml.setDevice(device);
if (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("xbel")
&& xml.attributes().value(versionAttribute()) == QLatin1String("1.0")) {
readXBEL();
} else {
xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file."));
}
}
return !xml.error();
}
//! [1]
//! [2]
QString XbelReader::errorString() const
{
return QObject::tr("%1\nLine %2, column %3")
.arg(xml.errorString())
.arg(xml.lineNumber())
.arg(xml.columnNumber());
}
//! [2]
//! [3]
void XbelReader::readXBEL()
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("xbel"));
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("folder"))
readFolder(0);
else if (xml.name() == QLatin1String("bookmark"))
readBookmark(0);
else if (xml.name() == QLatin1String("separator"))
readSeparator(0);
else
xml.skipCurrentElement();
}
}
//! [3]
//! [4]
void XbelReader::readTitle(QTreeWidgetItem *item)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("title"));
QString title = xml.readElementText();
item->setText(0, title);
}
//! [4]
//! [5]
void XbelReader::readSeparator(QTreeWidgetItem *item)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("separator"));
QTreeWidgetItem *separator = createChildItem(item);
separator->setFlags(item->flags() & ~Qt::ItemIsSelectable);
separator->setText(0, QString(30, u'\xB7'));
xml.skipCurrentElement();
}
//! [5]
void XbelReader::readFolder(QTreeWidgetItem *item)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("folder"));
QTreeWidgetItem *folder = createChildItem(item);
bool folded = (xml.attributes().value(foldedAttribute()) != QLatin1String("no"));
folder->setExpanded(!folded);
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("title"))
readTitle(folder);
else if (xml.name() == QLatin1String("folder"))
readFolder(folder);
else if (xml.name() == QLatin1String("bookmark"))
readBookmark(folder);
else if (xml.name() == QLatin1String("separator"))
readSeparator(folder);
else
xml.skipCurrentElement();
}
}
void XbelReader::readBookmark(QTreeWidgetItem *item)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("bookmark"));
QTreeWidgetItem *bookmark = createChildItem(item);
bookmark->setFlags(bookmark->flags() | Qt::ItemIsEditable);
bookmark->setIcon(0, bookmarkIcon);
bookmark->setText(0, QObject::tr("Unknown title"));
bookmark->setText(1, xml.attributes().value(hrefAttribute()).toString());
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("title"))
readTitle(bookmark);
else
xml.skipCurrentElement();
}
}
QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item)
{
QTreeWidgetItem *childItem;
if (item) {
childItem = new QTreeWidgetItem(item);
} else {
childItem = new QTreeWidgetItem(treeWidget);
}
childItem->setData(0, Qt::UserRole, xml.name().toString());
return childItem;
}

View File

@ -0,0 +1,50 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef XBELREADER_H
#define XBELREADER_H
#include <QIcon>
#include <QXmlStreamReader>
QT_BEGIN_NAMESPACE
class QTreeWidget;
class QTreeWidgetItem;
QT_END_NAMESPACE
//! [0]
class XbelReader
{
public:
//! [1]
XbelReader(QTreeWidget *treeWidget);
//! [1]
bool read(QIODevice *device);
QString errorString() const;
static inline QString versionAttribute() { return QStringLiteral("version"); }
static inline QString hrefAttribute() { return QStringLiteral("href"); }
static inline QString foldedAttribute() { return QStringLiteral("folded"); }
private:
//! [2]
void readXBEL();
void readTitle(QTreeWidgetItem *item);
void readSeparator(QTreeWidgetItem *item);
void readFolder(QTreeWidgetItem *item);
void readBookmark(QTreeWidgetItem *item);
QTreeWidgetItem *createChildItem(QTreeWidgetItem *item);
QXmlStreamReader xml;
QTreeWidget *treeWidget;
//! [2]
QIcon folderIcon;
QIcon bookmarkIcon;
};
//! [0]
#endif

View File

@ -0,0 +1,60 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "xbelwriter.h"
#include "xbelreader.h"
static inline QString yesValue() { return QStringLiteral("yes"); }
static inline QString noValue() { return QStringLiteral("no"); }
static inline QString titleElement() { return QStringLiteral("title"); }
//! [0]
XbelWriter::XbelWriter(const QTreeWidget *treeWidget)
: treeWidget(treeWidget)
{
xml.setAutoFormatting(true);
}
//! [0]
//! [1]
bool XbelWriter::writeFile(QIODevice *device)
{
xml.setDevice(device);
xml.writeStartDocument();
xml.writeDTD(QStringLiteral("<!DOCTYPE xbel>"));
xml.writeStartElement(QStringLiteral("xbel"));
xml.writeAttribute(XbelReader::versionAttribute(), QStringLiteral("1.0"));
for (int i = 0; i < treeWidget->topLevelItemCount(); ++i)
writeItem(treeWidget->topLevelItem(i));
xml.writeEndDocument();
return true;
}
//! [1]
//! [2]
void XbelWriter::writeItem(const QTreeWidgetItem *item)
{
QString tagName = item->data(0, Qt::UserRole).toString();
if (tagName == QLatin1String("folder")) {
bool folded = !item->isExpanded();
xml.writeStartElement(tagName);
xml.writeAttribute(XbelReader::foldedAttribute(), folded ? yesValue() : noValue());
xml.writeTextElement(titleElement(), item->text(0));
for (int i = 0; i < item->childCount(); ++i)
writeItem(item->child(i));
xml.writeEndElement();
} else if (tagName == QLatin1String("bookmark")) {
xml.writeStartElement(tagName);
if (!item->text(1).isEmpty())
xml.writeAttribute(XbelReader::hrefAttribute(), item->text(1));
xml.writeTextElement(titleElement(), item->text(0));
xml.writeEndElement();
} else if (tagName == QLatin1String("separator")) {
xml.writeEmptyElement(tagName);
}
}
//! [2]

View File

@ -0,0 +1,28 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef XBELWRITER_H
#define XBELWRITER_H
#include <QXmlStreamWriter>
QT_BEGIN_NAMESPACE
class QTreeWidget;
class QTreeWidgetItem;
QT_END_NAMESPACE
//! [0]
class XbelWriter
{
public:
explicit XbelWriter(const QTreeWidget *treeWidget);
bool writeFile(QIODevice *device);
private:
void writeItem(const QTreeWidgetItem *item);
QXmlStreamWriter xml;
const QTreeWidget *treeWidget;
};
//! [0]
#endif