mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-04 16:25:27 +08:00
qt 6.5.1 original
This commit is contained in:
12
examples/corelib/serialization/CMakeLists.txt
Normal file
12
examples/corelib/serialization/CMakeLists.txt
Normal 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()
|
29
examples/corelib/serialization/cbordump/CMakeLists.txt
Normal file
29
examples/corelib/serialization/cbordump/CMakeLists.txt
Normal 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}"
|
||||
)
|
13
examples/corelib/serialization/cbordump/cbordump.pro
Normal file
13
examples/corelib/serialization/cbordump/cbordump.pro
Normal 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
|
BIN
examples/corelib/serialization/cbordump/doc/images/cbordump.png
Normal file
BIN
examples/corelib/serialization/cbordump/doc/images/cbordump.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
@ -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}
|
||||
*/
|
859
examples/corelib/serialization/cbordump/main.cpp
Normal file
859
examples/corelib/serialization/cbordump/main.cpp
Normal 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;
|
||||
}
|
27
examples/corelib/serialization/cbordump/tag-transform.xslt
Normal file
27
examples/corelib/serialization/cbordump/tag-transform.xslt
Normal 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>
|
36
examples/corelib/serialization/convert/CMakeLists.txt
Normal file
36
examples/corelib/serialization/convert/CMakeLists.txt
Normal 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}"
|
||||
)
|
336
examples/corelib/serialization/convert/cborconverter.cpp
Normal file
336
examples/corelib/serialization/convert/cborconverter.cpp
Normal 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]
|
38
examples/corelib/serialization/convert/cborconverter.h
Normal file
38
examples/corelib/serialization/convert/cborconverter.h
Normal 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
|
28
examples/corelib/serialization/convert/convert.pro
Normal file
28
examples/corelib/serialization/convert/convert.pro
Normal 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
|
57
examples/corelib/serialization/convert/converter.h
Normal file
57
examples/corelib/serialization/convert/converter.h
Normal 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
|
225
examples/corelib/serialization/convert/datastreamconverter.cpp
Normal file
225
examples/corelib/serialization/convert/datastreamconverter.cpp
Normal 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;
|
||||
}
|
38
examples/corelib/serialization/convert/datastreamconverter.h
Normal file
38
examples/corelib/serialization/convert/datastreamconverter.h
Normal 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
|
BIN
examples/corelib/serialization/convert/doc/images/convert.png
Normal file
BIN
examples/corelib/serialization/convert/doc/images/convert.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
81
examples/corelib/serialization/convert/doc/src/convert.qdoc
Normal file
81
examples/corelib/serialization/convert/doc/src/convert.qdoc
Normal 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.
|
||||
*/
|
106
examples/corelib/serialization/convert/jsonconverter.cpp
Normal file
106
examples/corelib/serialization/convert/jsonconverter.cpp
Normal 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));
|
||||
}
|
25
examples/corelib/serialization/convert/jsonconverter.h
Normal file
25
examples/corelib/serialization/convert/jsonconverter.h
Normal 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
|
187
examples/corelib/serialization/convert/main.cpp
Normal file
187
examples/corelib/serialization/convert/main.cpp
Normal 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;
|
||||
}
|
52
examples/corelib/serialization/convert/nullconverter.cpp
Normal file
52
examples/corelib/serialization/convert/nullconverter.cpp
Normal 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);
|
||||
}
|
22
examples/corelib/serialization/convert/nullconverter.h
Normal file
22
examples/corelib/serialization/convert/nullconverter.h
Normal 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
|
113
examples/corelib/serialization/convert/textconverter.cpp
Normal file
113
examples/corelib/serialization/convert/textconverter.cpp
Normal 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;
|
23
examples/corelib/serialization/convert/textconverter.h
Normal file
23
examples/corelib/serialization/convert/textconverter.h
Normal 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
|
468
examples/corelib/serialization/convert/xmlconverter.cpp
Normal file
468
examples/corelib/serialization/convert/xmlconverter.cpp
Normal 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();
|
||||
}
|
22
examples/corelib/serialization/convert/xmlconverter.h
Normal file
22
examples/corelib/serialization/convert/xmlconverter.h
Normal 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
|
38
examples/corelib/serialization/rsslisting/CMakeLists.txt
Normal file
38
examples/corelib/serialization/rsslisting/CMakeLists.txt
Normal 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}"
|
||||
)
|
26
examples/corelib/serialization/rsslisting/main.cpp
Normal file
26
examples/corelib/serialization/rsslisting/main.cpp
Normal 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();
|
||||
}
|
211
examples/corelib/serialization/rsslisting/rsslisting.cpp
Normal file
211
examples/corelib/serialization/rsslisting/rsslisting.cpp
Normal 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;
|
||||
}
|
55
examples/corelib/serialization/rsslisting/rsslisting.h
Normal file
55
examples/corelib/serialization/rsslisting/rsslisting.h
Normal 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
|
||||
|
8
examples/corelib/serialization/rsslisting/rsslisting.pro
Normal file
8
examples/corelib/serialization/rsslisting/rsslisting.pro
Normal 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
|
32
examples/corelib/serialization/savegame/CMakeLists.txt
Normal file
32
examples/corelib/serialization/savegame/CMakeLists.txt
Normal 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}"
|
||||
)
|
82
examples/corelib/serialization/savegame/character.cpp
Normal file
82
examples/corelib/serialization/savegame/character.cpp
Normal 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";
|
||||
}
|
45
examples/corelib/serialization/savegame/character.h
Normal file
45
examples/corelib/serialization/savegame/character.h
Normal 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
|
163
examples/corelib/serialization/savegame/doc/src/savegame.qdoc
Normal file
163
examples/corelib/serialization/savegame/doc/src/savegame.qdoc
Normal 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}
|
||||
*/
|
160
examples/corelib/serialization/savegame/game.cpp
Normal file
160
examples/corelib/serialization/savegame/game.cpp
Normal 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);
|
||||
}
|
38
examples/corelib/serialization/savegame/game.h
Normal file
38
examples/corelib/serialization/savegame/game.h
Normal 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
|
70
examples/corelib/serialization/savegame/level.cpp
Normal file
70
examples/corelib/serialization/savegame/level.cpp
Normal 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);
|
||||
}
|
34
examples/corelib/serialization/savegame/level.h
Normal file
34
examples/corelib/serialization/savegame/level.h
Normal 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
|
39
examples/corelib/serialization/savegame/main.cpp
Normal file
39
examples/corelib/serialization/savegame/main.cpp
Normal 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]
|
21
examples/corelib/serialization/savegame/savegame.pro
Normal file
21
examples/corelib/serialization/savegame/savegame.pro
Normal 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
|
11
examples/corelib/serialization/serialization.pro
Normal file
11
examples/corelib/serialization/serialization.pro
Normal file
@ -0,0 +1,11 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = \
|
||||
cbordump \
|
||||
convert \
|
||||
savegame
|
||||
|
||||
qtHaveModule(widgets) {
|
||||
SUBDIRS += streambookmarks
|
||||
qtHaveModule(network): SUBDIRS += \
|
||||
rsslisting
|
||||
}
|
@ -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 |
@ -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.
|
||||
*/
|
69
examples/corelib/serialization/streambookmarks/jennifer.xbel
Normal file
69
examples/corelib/serialization/streambookmarks/jennifer.xbel
Normal 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>
|
17
examples/corelib/serialization/streambookmarks/main.cpp
Normal file
17
examples/corelib/serialization/streambookmarks/main.cpp
Normal 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]
|
142
examples/corelib/serialization/streambookmarks/mainwindow.cpp
Normal file
142
examples/corelib/serialization/streambookmarks/mainwindow.cpp
Normal 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]
|
35
examples/corelib/serialization/streambookmarks/mainwindow.h
Normal file
35
examples/corelib/serialization/streambookmarks/mainwindow.h
Normal 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
|
@ -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
|
140
examples/corelib/serialization/streambookmarks/xbelreader.cpp
Normal file
140
examples/corelib/serialization/streambookmarks/xbelreader.cpp
Normal 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;
|
||||
}
|
50
examples/corelib/serialization/streambookmarks/xbelreader.h
Normal file
50
examples/corelib/serialization/streambookmarks/xbelreader.h
Normal 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
|
@ -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]
|
28
examples/corelib/serialization/streambookmarks/xbelwriter.h
Normal file
28
examples/corelib/serialization/streambookmarks/xbelwriter.h
Normal 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
|
Reference in New Issue
Block a user