diff options
Diffstat (limited to 'examples/corelib/serialization')
59 files changed, 1102 insertions, 1401 deletions
diff --git a/examples/corelib/serialization/CMakeLists.txt b/examples/corelib/serialization/CMakeLists.txt index 7dd5d476d1..e32e4df441 100644 --- a/examples/corelib/serialization/CMakeLists.txt +++ b/examples/corelib/serialization/CMakeLists.txt @@ -1,11 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR 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) +if(NOT ANDROID) + qt_internal_add_example(cbordump) + qt_internal_add_example(convert) + qt_internal_add_example(savegame) endif() if(TARGET Qt6::Widgets) qt_internal_add_example(streambookmarks) diff --git a/examples/corelib/serialization/cbordump/CMakeLists.txt b/examples/corelib/serialization/cbordump/CMakeLists.txt index 813b02b9c0..b2c3a536e3 100644 --- a/examples/corelib/serialization/cbordump/CMakeLists.txt +++ b/examples/corelib/serialization/cbordump/CMakeLists.txt @@ -1,15 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(cbordump LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/cbordump") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() @@ -23,7 +21,14 @@ target_link_libraries(cbordump PRIVATE ) install(TARGETS cbordump - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET cbordump + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/cbordump/cbortag.py b/examples/corelib/serialization/cbordump/cbortag.py index 8634dcdc20..26a0f969e4 100755 --- a/examples/corelib/serialization/cbordump/cbortag.py +++ b/examples/corelib/serialization/cbordump/cbortag.py @@ -165,7 +165,7 @@ def main(argv, speak): struct CborTagDescription {{ QCborTag tag; - const char *description; // with space and parentheses + const char *description; // with space and parentheses }}; // {title} diff --git a/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc b/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc index bfb19768d8..a4dc01116f 100644 --- a/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc +++ b/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc @@ -3,7 +3,7 @@ /*! \example serialization/cbordump - \examplecategory {Input/Output} + \examplecategory {Data Processing & I/O} \meta tag {network} \title Parsing and displaying CBOR data diff --git a/examples/corelib/serialization/cbordump/main.cpp b/examples/corelib/serialization/cbordump/main.cpp index 4c02abd304..03c940452e 100644 --- a/examples/corelib/serialization/cbordump/main.cpp +++ b/examples/corelib/serialization/cbordump/main.cpp @@ -14,6 +14,8 @@ #include <stdarg.h> #include <stdio.h> +using namespace Qt::StringLiterals; + /* * To regenerate: * curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml @@ -27,7 +29,7 @@ struct CborTagDescription { QCborTag tag; - const char *description; // with space and parentheses + const char *description; // with space and parentheses }; // Concise Binary Object Representation (CBOR) Tags @@ -131,22 +133,18 @@ static const CborTagDescription tagDescriptions[] = { enum { // See RFC 7049 section 2. - SmallValueBitLength = 5, - SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */ - Value8Bit = 24, - Value16Bit = 25, - Value32Bit = 26, - Value64Bit = 27 + 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 - }; + enum DumpOption { ShowCompact = 0x01, ShowWidthIndicators = 0x02, ShowAnnotated = 0x04 }; Q_DECLARE_FLAGS(DumpOptions, DumpOption) CborDumper(QFile *f, DumpOptions opts_); @@ -183,8 +181,7 @@ static int cborNumberSize(quint64 value) return normalSize; } -CborDumper::CborDumper(QFile *f, DumpOptions opts_) - : opts(opts_) +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)); @@ -231,7 +228,8 @@ QCborError CborDumper::dump() return err; } -template <typename T> static inline bool canConvertTo(double v) +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 @@ -252,31 +250,32 @@ template <typename T> static inline bool canConvertTo(double v) return v == floor(v); } -static QString fpToString(double v, const char *suffix) +static QString fpToString(double v, QLatin1StringView suffix = ""_L1) { if (qIsInf(v)) - return v < 0 ? QStringLiteral("-inf") : QStringLiteral("inf"); + return v < 0 ? "-inf"_L1 : "inf"_L1; if (qIsNaN(v)) - return QStringLiteral("nan"); + return "nan"_L1; if (canConvertTo<qint64>(v)) - return QString::number(qint64(v)) + ".0" + suffix; + return QString::number(qint64(v)) + ".0"_L1 + suffix; if (canConvertTo<quint64>(v)) - return QString::number(quint64(v)) + ".0" + suffix; + return QString::number(quint64(v)) + ".0"_L1 + suffix; QString s = QString::number(v, 'g', QLocale::FloatingPointShortest); - if (!s.contains('.') && !s.contains('e')) - s += '.'; - s += suffix; + if (!s.contains(u'.') && !s.contains(u'e')) + s += u'.'; + if (suffix.size()) + s += suffix; return s; }; void CborDumper::dumpOne(int nestingLevel) { - QString indent(1, QLatin1Char(' ')); + QString indent(1, u' '); QString indented = indent; if (!opts.testFlag(ShowCompact)) { - indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' ')); - indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' ')); + indent = u'\n' + QString(4 * nestingLevel, u' '); + indented = u'\n' + QString(4 + 4 * nestingLevel, u' '); } switch (reader.type()) { @@ -316,7 +315,7 @@ void CborDumper::dumpOne(int nestingLevel) printStringWidthIndicator(r.data.size()); r = reader.readByteArray(); - comma = QLatin1Char(',') + indented; + comma = u',' + indented; } } else { auto r = reader.readString(); @@ -325,7 +324,7 @@ void CborDumper::dumpOne(int nestingLevel) printStringWidthIndicator(r.data.toUtf8().size()); r = reader.readString(); - comma = QLatin1Char(',') + indented; + comma = u',' + indented; } } @@ -381,7 +380,7 @@ void CborDumper::dumpOne(int nestingLevel) if (reader.next()) { printWidthIndicator(quint64(tag)); printf("("); - dumpOne(nestingLevel); // same level! + dumpOne(nestingLevel); // same level! printf(")"); } @@ -413,15 +412,15 @@ void CborDumper::dumpOne(int nestingLevel) break; case QCborStreamReader::Float16: - printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"))); + printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"_L1))); reader.next(); break; case QCborStreamReader::Float: - printf("%s", qPrintable(fpToString(reader.toFloat(), "f"))); + printf("%s", qPrintable(fpToString(reader.toFloat(), "f"_L1))); reader.next(); break; case QCborStreamReader::Double: - printf("%s", qPrintable(fpToString(reader.toDouble(), ""))); + printf("%s", qPrintable(fpToString(reader.toDouble()))); reader.next(); break; case QCborStreamReader::Invalid: @@ -446,7 +445,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) if (cborNumberSize(value) != actualSize) printf(" (overlong)"); }; - auto print = [=](const char *descr, const char *fmt, ...) { + auto print = [&](const char *descr, const char *fmt, ...) { qint64 prevOffset = offset; offset = reader.currentOffset(); if (prevOffset == offset) @@ -474,13 +473,13 @@ void CborDumper::dumpOneDetailed(int nestingLevel) }; auto printFp = [=](const char *descr, double d) { - QString s = fpToString(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) { + auto printString = [&](const char *descr) { constexpr qsizetype ChunkSizeLimit = std::numeric_limits<int>::max(); QByteArray indent(nestingLevel * 2, ' '); const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk "); @@ -489,7 +488,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) qsizetype size = reader.currentStringChunkSize(); if (size < 0) - return; // error + return; // error if (size >= ChunkSizeLimit) { fprintf(stderr, "String length too big, %lli\n", qint64(size)); exit(EXIT_FAILURE); @@ -534,7 +533,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) printf(" %s%s", indent.constData(), section.toHex(' ').constData()); // print the decode - QByteArray spaces(width > 0 ? width - section.size() * 3 + 1: 0, ' '); + 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) @@ -546,7 +545,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) // get the next chunk size = reader.currentStringChunkSize(); if (size < 0) - return; // error + return; // error if (size >= ChunkSizeLimit) { fprintf(stderr, "String length too big, %lli\n", qint64(size)); exit(EXIT_FAILURE); @@ -685,7 +684,9 @@ void CborDumper::printByteArray(const QByteArray &ba) break; case quint8(QCborKnownTags::ExpectedBase64url): - printf("b64'%s'", ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData()); + printf("b64'%s'", + ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + .constData()); break; } } @@ -726,23 +727,20 @@ int main(int argc, char *argv[]) setlocale(LC_ALL, "C"); QCommandLineParser parser; - parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool")); + parser.setApplicationDescription("CBOR Dumper tool"_L1); parser.addHelpOption(); - QCommandLineOption compact({QStringLiteral("c"), QStringLiteral("compact")}, - QStringLiteral("Use compact form (no line breaks)")); + QCommandLineOption compact({"c"_L1, "compact"_L1}, "Use compact form (no line breaks)"_L1); parser.addOption(compact); - QCommandLineOption showIndicators({QStringLiteral("i"), QStringLiteral("indicators")}, - QStringLiteral("Show indicators for width of lengths and integrals")); + QCommandLineOption showIndicators({ "i"_L1, "indicators"_L1 }, + "Show indicators for width of lengths and integrals"_L1); parser.addOption(showIndicators); - QCommandLineOption verbose({QStringLiteral("a"), QStringLiteral("annotated")}, - QStringLiteral("Show bytes and annotated decoding")); + QCommandLineOption verbose({"a"_L1, "annotated"_L1}, "Show bytes and annotated decoding"_L1); parser.addOption(verbose); - parser.addPositionalArgument(QStringLiteral("[source]"), - QStringLiteral("CBOR file to read from")); + parser.addPositionalArgument("[source]"_L1, "CBOR file to read from"_L1); parser.process(app); diff --git a/examples/corelib/serialization/convert/CMakeLists.txt b/examples/corelib/serialization/convert/CMakeLists.txt index 5a10a78a5a..24ad5bbb6a 100644 --- a/examples/corelib/serialization/convert/CMakeLists.txt +++ b/examples/corelib/serialization/convert/CMakeLists.txt @@ -1,27 +1,27 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(convert LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") 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 + converter.cpp converter.h datastreamconverter.cpp datastreamconverter.h + debugtextdumper.cpp debugtextdumper.h jsonconverter.cpp jsonconverter.h main.cpp nullconverter.cpp nullconverter.h textconverter.cpp textconverter.h + variantorderedmap.h xmlconverter.cpp xmlconverter.h ) @@ -30,7 +30,14 @@ target_link_libraries(convert PRIVATE ) install(TARGETS convert - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET convert + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/convert/cborconverter.cpp b/examples/corelib/serialization/convert/cborconverter.cpp index 85ca8c12ed..969f2741e0 100644 --- a/examples/corelib/serialization/convert/cborconverter.cpp +++ b/examples/corelib/serialization/convert/cborconverter.cpp @@ -2,20 +2,24 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "cborconverter.h" +#include "variantorderedmap.h" +#include <QCborArray> +#include <QCborMap> #include <QCborStreamReader> #include <QCborStreamWriter> -#include <QCborMap> -#include <QCborArray> #include <QCborValue> #include <QDataStream> -#include <QFloat16> +#include <QDebug> #include <QFile> +#include <QFloat16> #include <QMetaType> #include <QTextStream> #include <stdio.h> +using namespace Qt::StringLiterals; + static CborConverter cborConverter; static CborDiagnosticDumper cborDiagnosticDumper; @@ -55,9 +59,9 @@ QT_END_NAMESPACE // non-string keys in CBOR maps (QVariantMap can't handle those). Instead, we // have our own set of converter functions so we can keep the keys properly. +//! [0] static QVariant convertCborValue(const QCborValue &value); -//! [0] static QVariant convertCborMap(const QCborMap &map) { VariantOrderedMap result; @@ -85,8 +89,9 @@ static QVariant convertCborValue(const QCborValue &value) return value.toVariant(); } //! [0] -enum TrimFloatingPoint { Double, Float, Float16 }; + //! [1] +enum TrimFloatingPoint { Double, Float, Float16 }; static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming) { if (v.userType() == QMetaType::QVariantList) { @@ -118,41 +123,28 @@ static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrim } //! [1] -QString CborDiagnosticDumper::name() +QString CborDiagnosticDumper::name() const { - return QStringLiteral("cbor-dump"); + return "cbor-dump"_L1; } -Converter::Direction CborDiagnosticDumper::directions() +Converter::Directions CborDiagnosticDumper::directions() const { - return Out; + return Direction::Out; } -Converter::Options CborDiagnosticDumper::outputOptions() +Converter::Options CborDiagnosticDumper::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *CborDiagnosticDumper::optionsHelp() +const char *CborDiagnosticDumper::optionsHelp() const { 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) +void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { QCborValue::DiagnosticNotationOptions opts = QCborValue::LineWrapped; for (const QString &s : options) { @@ -175,14 +167,12 @@ void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, cons } } - fprintf(stderr, "Unknown CBOR diagnostic option '%s'. Available options are:\n%s", - qPrintable(s), diagnosticHelp); - exit(EXIT_FAILURE); + qFatal("Unknown CBOR diagnostic option '%s'. Available options are:\n%s", + qPrintable(s), diagnosticHelp); } QTextStream out(f); - out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) - << Qt::endl; + out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) << Qt::endl; } CborConverter::CborConverter() @@ -190,37 +180,36 @@ CborConverter::CborConverter() qRegisterMetaType<QCborTag>(); } -QString CborConverter::name() +QString CborConverter::name() const { return "cbor"; } -Converter::Direction CborConverter::directions() +Converter::Directions CborConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options CborConverter::outputOptions() +Converter::Options CborConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *CborConverter::optionsHelp() +const char *CborConverter::optionsHelp() const { return cborOptionHelp; } -bool CborConverter::probeFile(QIODevice *f) +bool CborConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) { - if (file->fileName().endsWith(QLatin1String(".cbor"))) + if (file->fileName().endsWith(".cbor"_L1)) return true; } return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3); } -//! [2] -QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant CborConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { const char *ptr = nullptr; if (auto file = qobject_cast<QFile *>(f)) @@ -237,28 +226,25 @@ QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) QCborValue contents = QCborValue::fromCbor(reader); qint64 offset = reader.currentOffset(); if (reader.lastError()) { - fprintf(stderr, "Error loading CBOR contents (byte %lld): %s\n", offset, - qPrintable(reader.lastError().toString())); - fprintf(stderr, " bytes: %s\n", - (ptr ? mapped.mid(offset, 9) : f->read(9)).toHex(' ').constData()); - exit(EXIT_FAILURE); + qFatal().nospace() + << "Error loading CBOR contents (byte " << offset + << "): " << reader.lastError().toString() + << "\n bytes: " << (ptr ? mapped.mid(offset, 9) : f->read(9)); } else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) { - fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n"); + qWarning("Warning: bytes remaining at the end of the CBOR stream"); } if (outputConverter == nullptr) outputConverter = &cborDiagnosticDumper; - else if (outputConverter == null) + else if (isNull(outputConverter)) return QVariant(); else if (!outputConverter->outputOptions().testFlag(SupportsArbitraryMapKeys)) return contents.toVariant(); return convertCborValue(contents); } -//! [2] -//! [3] -void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) + +void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) const { - //! [3] bool useSignature = true; bool useIntegers = true; enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes; @@ -313,13 +299,13 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri } } - fprintf(stderr, "Unknown CBOR format option '%s'. Valid options are:\n%s", - qPrintable(s), cborOptionHelp); - exit(EXIT_FAILURE); + qFatal("Unknown CBOR format option '%s'. Valid options are:\n%s", + qPrintable(s), cborOptionHelp); } - //! [4] - QCborValue v = convertFromVariant(contents, - useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); + + QCborValue v = + convertFromVariant(contents, + useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); QCborStreamWriter writer(f); if (useSignature) writer.append(QCborKnownTags::Signature); @@ -333,4 +319,3 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri opts |= QCborValue::UseFloat16; v.toCbor(writer, opts); } -//! [4] diff --git a/examples/corelib/serialization/convert/cborconverter.h b/examples/corelib/serialization/convert/cborconverter.h index d19c9eb33e..db68f99fda 100644 --- a/examples/corelib/serialization/convert/cborconverter.h +++ b/examples/corelib/serialization/convert/cborconverter.h @@ -10,13 +10,12 @@ 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; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; class CborConverter : public Converter @@ -26,13 +25,14 @@ public: // 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; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // CBORCONVERTER_H diff --git a/examples/corelib/serialization/convert/convert.pro b/examples/corelib/serialization/convert/convert.pro index 4c6b0b557a..7592de7a22 100644 --- a/examples/corelib/serialization/convert/convert.pro +++ b/examples/corelib/serialization/convert/convert.pro @@ -11,18 +11,22 @@ target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/convert INSTALLS += target SOURCES += main.cpp \ + converter.cpp \ cborconverter.cpp \ - jsonconverter.cpp \ datastreamconverter.cpp \ + debugtextdumper.cpp \ + jsonconverter.cpp \ + nullconverter.cpp \ textconverter.cpp \ - xmlconverter.cpp \ - nullconverter.cpp + xmlconverter.cpp HEADERS += \ converter.h \ cborconverter.h \ - jsonconverter.h \ datastreamconverter.h \ + debugtextdumper.h \ + jsonconverter.h \ + nullconverter.h \ textconverter.h \ - xmlconverter.h \ - nullconverter.h + variantorderedmap.h \ + xmlconverter.h diff --git a/examples/corelib/serialization/convert/converter.cpp b/examples/corelib/serialization/convert/converter.cpp new file mode 100644 index 0000000000..a57f305971 --- /dev/null +++ b/examples/corelib/serialization/convert/converter.cpp @@ -0,0 +1,44 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "converter.h" + +//! [0] +Converter::Converter() +{ + converters().append(this); +} + +Converter::~Converter() +{ + converters().removeAll(this); +} + +QList<const Converter *> &Converter::converters() +{ + Q_CONSTINIT static QList<const Converter *> store; + return store; +} + +const QList<const Converter *> &Converter::allConverters() +{ + return converters(); +} +//! [0] + +// Some virtual methods that Converter classes needn't override, when not relevant: +Converter::Options Converter::outputOptions() const { return {}; } +const char *Converter::optionsHelp() const { return nullptr; } +bool Converter::probeFile(QIODevice *) const { return false; } + +// The virtual method they should override if they claim to support In: +QVariant Converter::loadFile(QIODevice *, const Converter *&outputConverter) const +{ + Q_ASSERT(!directions().testFlag(Converter::Direction::In)); + // For those that don't, this should never be called. + Q_UNIMPLEMENTED(); + // But every implementation should at least do this: + if (!outputConverter) + outputConverter = this; + return QVariant(); +} diff --git a/examples/corelib/serialization/convert/converter.h b/examples/corelib/serialization/convert/converter.h index 4da4d47267..40b7575a1e 100644 --- a/examples/corelib/serialization/convert/converter.h +++ b/examples/corelib/serialization/convert/converter.h @@ -5,53 +5,41 @@ #define CONVERTER_H #include <QIODevice> -#include <QPair> -#include <QVariant> -#include <QVariantMap> #include <QList> +#include <QStringList> +#include <QVariant> -class VariantOrderedMap : public QList<QPair<QVariant, QVariant>> -{ -public: - VariantOrderedMap() = default; - VariantOrderedMap(const QVariantMap &map) - { - reserve(map.size()); - for (auto it = map.begin(); it != map.end(); ++it) - append({it.key(), it.value()}); - } -}; -using Map = VariantOrderedMap; -Q_DECLARE_METATYPE(Map) - +//! [0] class Converter { + static QList<const Converter *> &converters(); protected: Converter(); + static bool isNull(const Converter *converter); // in nullconverter.cpp public: - static Converter *null; + static const QList<const Converter *> &allConverters(); - enum Direction { - In = 1, Out = 2, InOut = 3 - }; + enum class Direction { In = 1, Out = 2, InOut = In | Out }; + Q_DECLARE_FLAGS(Directions, Direction) - enum Option { - SupportsArbitraryMapKeys = 0x01 - }; + 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; + virtual QString name() const = 0; + virtual Directions directions() const = 0; + virtual Options outputOptions() const; + virtual const char *optionsHelp() const; + virtual bool probeFile(QIODevice *f) const; + virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const; + virtual void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const = 0; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Directions) Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Options) +//! [0] #endif // CONVERTER_H diff --git a/examples/corelib/serialization/convert/datastreamconverter.cpp b/examples/corelib/serialization/convert/datastreamconverter.cpp index 451688e378..ce28fcb98e 100644 --- a/examples/corelib/serialization/convert/datastreamconverter.cpp +++ b/examples/corelib/serialization/convert/datastreamconverter.cpp @@ -2,20 +2,22 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "datastreamconverter.h" +#include "debugtextdumper.h" +#include "variantorderedmap.h" #include <QDataStream> -#include <QDebug> -#include <QTextStream> + +using namespace Qt::StringLiterals; static const char dataStreamOptionHelp[] = "byteorder=host|big|little Byte order to use.\n" - "version=<n> QDataStream version (default: Qt 5.0).\n" + "version=<n> QDataStream version (default: Qt 6.0).\n" ; static const char signature[] = "qds"; -static DataStreamDumper dataStreamDumper; -static DataStreamConverter DataStreamConverter; +static DataStreamConverter dataStreamConverter; +static DebugTextDumper debugTextDumper; QDataStream &operator<<(QDataStream &ds, const VariantOrderedMap &map) { @@ -42,126 +44,44 @@ QDataStream &operator>>(QDataStream &ds, VariantOrderedMap &map) 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() +QString DataStreamConverter::name() const { - return QStringLiteral("datastream"); + return "datastream"_L1; } -Converter::Direction DataStreamConverter::directions() +Converter::Directions DataStreamConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options DataStreamConverter::outputOptions() +Converter::Options DataStreamConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *DataStreamConverter::optionsHelp() +const char *DataStreamConverter::optionsHelp() const { return dataStreamOptionHelp; } -bool DataStreamConverter::probeFile(QIODevice *f) +bool DataStreamConverter::probeFile(QIODevice *f) const { return f->isReadable() && f->peek(sizeof(signature) - 1) == signature; } -QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant DataStreamConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) - outputConverter = &dataStreamDumper; + outputConverter = &debugTextDumper; char c; - if (f->read(sizeof(signature) -1) != signature || - !f->getChar(&c) || (c != 'l' && c != 'B')) { - fprintf(stderr, "Could not load QDataStream file: invalid signature.\n"); - exit(EXIT_FAILURE); - } + if (f->read(sizeof(signature) - 1) != signature || !f->getChar(&c) || (c != 'l' && c != 'B')) + qFatal("Could not load QDataStream file: invalid signature."); QDataStream ds(f); ds.setByteOrder(c == 'l' ? QDataStream::LittleEndian : QDataStream::BigEndian); @@ -175,9 +95,10 @@ QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter return result; } -void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { - QDataStream::Version version = QDataStream::Qt_5_0; + QDataStream::Version version = QDataStream::Qt_6_0; auto order = QDataStream::ByteOrder(QSysInfo::ByteOrder); for (const QString &option : options) { const QStringList pair = option.split('='); @@ -202,18 +123,16 @@ void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const continue; } - fprintf(stderr, "Invalid version number '%s': must be a number from 1 to %d.\n", - qPrintable(pair.last()), QDataStream::Qt_DefaultCompiledVersion); - exit(EXIT_FAILURE); + qFatal("Invalid version number '%s': must be a number from 1 to %d.", + qPrintable(pair.last()), QDataStream::Qt_DefaultCompiledVersion); } } - fprintf(stderr, "Unknown QDataStream formatting option '%s'. Available options are:\n%s", + qFatal("Unknown QDataStream formatting option '%s'. Available options are:\n%s", qPrintable(option), dataStreamOptionHelp); - exit(EXIT_FAILURE); } - char c = order == QDataStream::LittleEndian ? 'l' : 'B'; + char c = order == QDataStream::LittleEndian ? 'l' : 'B'; f->write(signature); f->write(&c, 1); diff --git a/examples/corelib/serialization/convert/datastreamconverter.h b/examples/corelib/serialization/convert/datastreamconverter.h index 95c8861e0e..201f3c4754 100644 --- a/examples/corelib/serialization/convert/datastreamconverter.h +++ b/examples/corelib/serialization/convert/datastreamconverter.h @@ -6,19 +6,6 @@ #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: @@ -26,13 +13,14 @@ public: // 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; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // DATASTREAMCONVERTER_H diff --git a/examples/corelib/serialization/convert/debugtextdumper.cpp b/examples/corelib/serialization/convert/debugtextdumper.cpp new file mode 100644 index 0000000000..f010bd8e2a --- /dev/null +++ b/examples/corelib/serialization/convert/debugtextdumper.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "debugtextdumper.h" +#include "variantorderedmap.h" + +#include <QDebug> +#include <QTextStream> + +using namespace Qt::StringLiterals; + +// Static instance is declared in datastreamconverter.cpp, since it uses it. + +static QString dumpVariant(const QVariant &v, const QString &indent = "\n"_L1) +{ + QString result; + QString indented = indent + " "_L1; + + 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 = "Map {"_L1; + for (const auto &pair : map) { + result += indented + dumpVariant(pair.first, indented); + result.chop(1); // remove comma + result += " => "_L1 + dumpVariant(pair.second, indented); + } + result.chop(1); // remove comma + result += indent + "},"_L1; + } else if (type == QMetaType::QVariantList) { + const QVariantList list = v.toList(); + + result = "List ["_L1; + for (const auto &item : list) + result += indented + dumpVariant(item, indented); + result.chop(1); // remove comma + result += indent + "],"_L1; + } else { + QDebug debug(&result); + debug.nospace() << v << ','; + } + return result; +} + +QString DebugTextDumper::name() const +{ + return "debugtext-dump"_L1; +} + +Converter::Directions DebugTextDumper::directions() const +{ + return Direction::Out; +} + +Converter::Options DebugTextDumper::outputOptions() const +{ + return SupportsArbitraryMapKeys; +} + +void DebugTextDumper::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const +{ + if (!options.isEmpty()) { + qFatal("Unknown option '%s' to debug text output. This format has no options.", + qPrintable(options.first())); + } + QString s = dumpVariant(contents); + s[s.size() - 1] = u'\n'; // replace the comma with newline + + QTextStream out(f); + out << s; +} diff --git a/examples/corelib/serialization/convert/debugtextdumper.h b/examples/corelib/serialization/convert/debugtextdumper.h new file mode 100644 index 0000000000..7d3d762104 --- /dev/null +++ b/examples/corelib/serialization/convert/debugtextdumper.h @@ -0,0 +1,20 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef DEBUGTEXTDUMPER_H +#define DEBUGTEXTDUMPER_H + +#include "converter.h" + +class DebugTextDumper : public Converter +{ + // Converter interface +public: + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; +}; + +#endif // DEBUGTEXTDUMPER_H diff --git a/examples/corelib/serialization/convert/doc/images/convert.png b/examples/corelib/serialization/convert/doc/images/convert.png Binary files differindex 8d6816a626..53e05b4333 100644 --- a/examples/corelib/serialization/convert/doc/images/convert.png +++ b/examples/corelib/serialization/convert/doc/images/convert.png diff --git a/examples/corelib/serialization/convert/doc/src/convert.qdoc b/examples/corelib/serialization/convert/doc/src/convert.qdoc index 7baef83de2..187e81a85e 100644 --- a/examples/corelib/serialization/convert/doc/src/convert.qdoc +++ b/examples/corelib/serialization/convert/doc/src/convert.qdoc @@ -3,80 +3,154 @@ /*! \example serialization/convert - \examplecategory {Input/Output} + \examplecategory {Data Processing & I/O} \meta tag {network} - \title Convert Example + \title Serialization Converter - \brief The Convert example demonstrates how to convert between different - serialization formats. + \brief How to convert between different serialization formats. - The Convert example converts between the serialization formats JSON, CBOR, - XML, QDataStream and text. It can also auto detect the format being used. - Not all formats support both input and output, and they have different - sets of which types they support. QDataStream and XML are the richest, - followed by CBOR, then JSON, and then the plain text one. + This example converts between JSON, CBOR, XML, QDataStream and some simple + text formats. It can auto-detect the format being used, or be told which + format to use. Not all formats support both input and output, and they have + different sets of which content datatypes they support. QDataStream and XML + are the richest, followed by CBOR, then JSON, and then the plain text + formats. Conversion via the less capable formats is apt to lose structure + from the data. \image convert.png + \sa {Parsing and displaying CBOR data}, {Saving and Loading a Game} + \section1 The Converter Class - The Converter class is the abstract superclass for all the converters to - and from all the formats. They all convert to and from the QVariant class, - which is used to represent all the datastructures internally. - The 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. + The Converter class is the abstract superclass for all the converters to and + from all the formats. They all convert from or to the QVariant class, which + is used to represent all the datastructures internally. - \section1 The CborConverter Class + \snippet serialization/convert/converter.h 0 - 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 Converter constructor and destructor manage a list of available + converters used by the main program so that it knows what converters are + available. Each converter type defines a static instance that ensures it is + constructed and thus available to the main program via this list. The \c + allConverters() method provides \c main()'s code with access to the list. - 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 + \snippet serialization/convert/converter.cpp 0 - A CBOR-file is written using the saveFile() function. - \snippet serialization/convert/cborconverter.cpp 3 - \snippet serialization/convert/cborconverter.cpp 4 + 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. These enable the main program to report what converters are + available in its help text for the command-line options to select input and + output formats. + + \snippet serialization/convert/main.cpp 0 + + The optionsHelp() function is used to report the various command-line + options supported by the available formats, when queried using its \c + {--format-options <format>} command-line option. + + \snippet serialization/convert/main.cpp 1 + + The outputOptions() function reports the output capabilities of a converter. + At present the only optional feature is support for arbitrary keys in + mappings from keys to values. An input converter's loadFile() can use this + information to tailor the form in which it presents the data it has read, to + be as faithfully represented by the output converter as its capabilities + permit. + + The probeFile() function is used to determine if a file matches the format + of the converter. The main program uses this to determine what format to use + when reading or writing a file, based on its name and potentially content, + when the user has not specified the format to use on the command-line. + + The loadFile() function deserializes data. The caller tells loadFile() which + serializer it intends to use, so that loadFile() can query its + outputOptions() to determine the form in which to represent the loaded data. + If the caller hasn't settled on a choice of output converter, loadFile() + supplies it with a default output converter suitable to the data it is + returning. + + The saveFile() function serializes data. It is passed options from the + command-line, as described by loadHelp(), that can tune the details of how + it represents the data when saving to file. + + Both loadFile() and saveFile() can be used with an arbitrary \l QIODevice. + This means that a Converter could also be used with a network socket or + other source of data, to read from or write to. In the present program, the + main program always passes a \l QFile, accessing either a file on disk or + one of the standard streams of the process. + + \section2 The Available Converters + + Several converters are supported, illustrating how the converter program + could be adapted to other formats, should the need arise. See the source + code for each for its details. The CBOR converters serve as a relatively + full-featured illustration of the ways converters can work, that we'll look + into in more detail below. This table summarizes the available converters: + + \table + \header \li Class \li mode \li format + \row \li CborConverter \li In/Out \li CBOR + \row \li CborDiagnosticDumper \li Out \li CBOR diagnostic + \row \li DataStreamConverter \li In/Out \li QDataStream + \row \li DebugTextDumper \li Out \li Lossless, non-standard, human-readable + \row \li JsonConverter \li In/Out \li JSON + \row \li NullConverter \li Out \li No output + \row \li TextConverter \li In/Out \li Structured plain text + \row \li XmlConverter \li In/Out \li XML + \endtable + + Those that support input use themselves as loadFile()'s fallback converter, + except for the CBOR and QDataStream converters, which use their respective + output-only dumper companion classes. The null converter can be used as + output converter when running the program for the sake of any validation or + verification that an input converter may perform. + + \section2 The CborConverter and CborDiagnosticDumper Classes + + The CborConverter class supports serializing to and from the CBOR format. + It supports various options to configure the output of floating point values + and a \c{signature} option to determine whether to start its output with a + CBOR tag that serves as a file header, identifying the file as containing + CBOR data. - \sa {CBOR Support in Qt} + There is also a CborDiagnosticDumper class to output in CBOR diagnostic + notation. It does not support loading data. The form of its output can be + configured using two options. One selects whether to use the (more verbose) + extended CBOR diagnostic format. The other control whether each CBOR value + appears on a separate line. - \section1 The DataStreamConverter Class + The plain diagnostic notation is similar to JSON, but not exactly, because + it supports displaying the contents of a CBOR stream losslessly, while a + conversion to JSON can be lossy. CborConverter's loadFile() uses + CborDiagnosticDumper for the fallback output converter, if its caller hasn't + determined the output format for itself. - The 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. + The convertCborValue(), convertCborMap() and convertCborArray() helper + functions are used to convert a QCborValue to a QVariant, for the benefit of + CborConverter::loadFile(). - \section1 The JsonConverter Class + \snippet serialization/convert/cborconverter.cpp 0 - The JsonConverter class is used to serialize to and from the JSON-format. - \sa {JSON Support in Qt} + The convertFromVariant() function is used to convert a QVariant to a + QCborValue for output by the \c saveFile() of either class. - \section1 The XmlConverter Class + \snippet serialization/convert/cborconverter.cpp 1 - The XmlConverter class is used to serialize to and from the XML-format. + \sa {CBOR Support in Qt} - \section1 The TextConverter Class + \section1 The convert program - The TextConverter class is used to serialize to and from a text format. + The \c main() function sets up a \l QApplication and a \l QCommandLineParser + to make sense of the options the user has specified and provide help if the + user asks for it. It uses the values obtained for the various \l + QCommandLineOption instances describing the user's choices, plus the + positional arguments for file names, to prepare the converters it will use. - \section1 The NullConverter Class + It then uses its input converter to load data (and possibly resolve its + choice of output converter, if it hasn't selected one yet) and its output + converter to serialize that data, taking account of any output options the + user has supplied on the command-line. - The NullConverter class is an output serializer that does nothing. + \snippet serialization/convert/main.cpp 2 */ diff --git a/examples/corelib/serialization/convert/jsonconverter.cpp b/examples/corelib/serialization/convert/jsonconverter.cpp index f52c9db554..1b59ed5c1f 100644 --- a/examples/corelib/serialization/convert/jsonconverter.cpp +++ b/examples/corelib/serialization/convert/jsonconverter.cpp @@ -9,49 +9,39 @@ #include <QJsonObject> #include <QJsonValue> +using namespace Qt::StringLiterals; + static JsonConverter jsonConverter; -static const char jsonOptionHelp[] = - "compact=no|yes Use compact JSON form.\n"; +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); - } + if (!doc.isObject() && !doc.isArray()) + qFatal("Could not convert contents to JSON."); return doc; } -JsonConverter::JsonConverter() -{ -} - -QString JsonConverter::name() -{ - return "json"; -} - -Converter::Direction JsonConverter::directions() +QString JsonConverter::name() const { - return InOut; + return "json"_L1; } -Converter::Options JsonConverter::outputOptions() +Converter::Directions JsonConverter::directions() const { - return {}; + return Direction::InOut; } -const char *JsonConverter::optionsHelp() +const char *JsonConverter::optionsHelp() const { return jsonOptionHelp; } -bool JsonConverter::probeFile(QIODevice *f) +bool JsonConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) { - if (file->fileName().endsWith(QLatin1String(".json"))) + if (file->fileName().endsWith(".json"_L1)) return true; } @@ -62,7 +52,7 @@ bool JsonConverter::probeFile(QIODevice *f) return false; } -QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant JsonConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) outputConverter = this; @@ -78,27 +68,26 @@ QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) if (doc.isNull()) doc = QJsonDocument::fromJson(f->readAll(), &error); if (error.error) { - fprintf(stderr, "Could not parse JSON content: offset %d: %s", - error.offset, qPrintable(error.errorString())); - exit(EXIT_FAILURE); + qFatal("Could not parse JSON content: offset %d: %s", + error.offset, qPrintable(error.errorString())); } - if (outputConverter == null) + if (isNull(outputConverter)) return QVariant(); return doc.toVariant(); } -void JsonConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void JsonConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { QJsonDocument::JsonFormat format = QJsonDocument::Indented; for (const QString &s : options) { - if (s == QLatin1String("compact=no")) { + if (s == "compact=no"_L1) { format = QJsonDocument::Indented; - } else if (s == QLatin1String("compact=yes")) { + } else if (s == "compact=yes"_L1) { format = QJsonDocument::Compact; } else { - fprintf(stderr, "Unknown option '%s' to JSON output. Valid options are:\n%s", - qPrintable(s), jsonOptionHelp); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to JSON output. Valid options are:\n%s", + qPrintable(s), jsonOptionHelp); } } diff --git a/examples/corelib/serialization/convert/jsonconverter.h b/examples/corelib/serialization/convert/jsonconverter.h index 40430a6b70..e1dd1ecdb4 100644 --- a/examples/corelib/serialization/convert/jsonconverter.h +++ b/examples/corelib/serialization/convert/jsonconverter.h @@ -8,18 +8,15 @@ 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; + QString name() const override; + Directions directions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // JSONCONVERTER_H diff --git a/examples/corelib/serialization/convert/main.cpp b/examples/corelib/serialization/convert/main.cpp index 00c626e1c8..d3021fadca 100644 --- a/examples/corelib/serialization/convert/main.cpp +++ b/examples/corelib/serialization/convert/main.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2018 Intel Corporation. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "converter.h" @@ -11,177 +12,144 @@ #include <stdio.h> -static QList<Converter *> *availableConverters; +using namespace Qt::StringLiterals; -Converter::Converter() +static const Converter *prepareConverter(QString format, Converter::Direction direction, + QFile *stream) { - if (!availableConverters) - availableConverters = new QList<Converter *>; - availableConverters->append(this); -} + const bool out = direction == Converter::Direction::Out; + const QIODevice::OpenMode mode = out + ? QIODevice::WriteOnly | QIODevice::Truncate + : QIODevice::ReadOnly; + const char *dirn = out ? "output" : "input"; + + if (stream->fileName().isEmpty()) + stream->open(out ? stdout : stdin, mode); + else + stream->open(mode); + + if (!stream->isOpen()) { + qFatal("Could not open \"%s\" for %s: %s", + qPrintable(stream->fileName()), dirn, qPrintable(stream->errorString())); + } else if (format == "auto"_L1) { + for (const Converter *conv : Converter::allConverters()) { + if (conv->directions().testFlag(direction) && conv->probeFile(stream)) + return conv; + } + if (out) // Failure to identify output format can be remedied by loadFile(). + return nullptr; -Converter::~Converter() -{ - availableConverters->removeAll(this); + // Input format, however, we must know before we can call that: + qFatal("Could not determine input format. Specify it with the -I option."); + } else { + for (const Converter *conv : Converter::allConverters()) { + if (conv->name() == format) { + if (!conv->directions().testFlag(direction)) { + qWarning("File format \"%s\" cannot be used for %s", + qPrintable(format), dirn); + continue; // on the off chance there's another with the same name + } + return conv; + } + } + qFatal("Unknown %s file format \"%s\"", dirn, qPrintable(format)); + } + Q_UNREACHABLE_RETURN(nullptr); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); +//! [0] QStringList inputFormats; QStringList outputFormats; - for (Converter *conv : std::as_const(*availableConverters)) { + for (const Converter *conv : Converter::allConverters()) { auto direction = conv->directions(); QString name = conv->name(); - if (direction & Converter::In) + if (direction.testFlag(Converter::Direction::In)) inputFormats << name; - if (direction & Converter::Out) + if (direction.testFlag(Converter::Direction::Out)) outputFormats << name; } +//! [0] inputFormats.sort(); outputFormats.sort(); - inputFormats.prepend("auto"); - outputFormats.prepend("auto"); + inputFormats.prepend("auto"_L1); + outputFormats.prepend("auto"_L1); QCommandLineParser parser; - parser.setApplicationDescription(QStringLiteral("Qt file format conversion tool")); + parser.setApplicationDescription("Qt serialization format conversion tool"_L1); 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"); + QCommandLineOption inputFormatOption(QStringList{ "I"_L1, "input-format"_L1 }); + inputFormatOption.setDescription( + "Select the input format for the input file. Available formats: "_L1 + + inputFormats.join(", "_L1)); + inputFormatOption.setValueName("format"_L1); 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"); + QCommandLineOption outputFormatOption(QStringList{ "O"_L1, "output-format"_L1 }); + outputFormatOption.setDescription( + "Select the output format for the output file. Available formats: "_L1 + + outputFormats.join(", "_L1)); + outputFormatOption.setValueName("format"_L1); 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..."); + QCommandLineOption optionOption(QStringList{ "o"_L1, "option"_L1 }); + optionOption.setDescription( + "Format-specific options. Use --format-options to find out what options are available."_L1); + optionOption.setValueName("options..."_L1); 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"); + QCommandLineOption formatOptionsOption("format-options"_L1); + formatOptionsOption.setDescription( + "Prints the list of valid options for --option for the converter format <format>."_L1); + formatOptionsOption.setValueName("format"_L1); 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.addPositionalArgument("[source]"_L1, "File to read from (stdin if none)"_L1); + parser.addPositionalArgument("[destination]"_L1, "File to write to (stdout if none)"_L1); parser.process(app); if (parser.isSet(formatOptionsOption)) { QString format = parser.value(formatOptionsOption); - for (Converter *conv : std::as_const(*availableConverters)) { +//! [1] + for (const Converter *conv : Converter::allConverters()) { if (conv->name() == format) { const char *help = conv->optionsHelp(); - if (help) - printf("The following options are available for format '%s':\n\n%s", qPrintable(format), help); - else - printf("Format '%s' supports no options.\n", qPrintable(format)); + if (help) { + qInfo("The following options are available for format '%s':\n\n%s", + qPrintable(format), help); + } else { + qInfo("Format '%s' supports no options.", qPrintable(format)); + } return EXIT_SUCCESS; } } +//! [1] - 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; - } + qFatal("Unknown file format '%s'", qPrintable(format)); } +//! [2] QStringList files = parser.positionalArguments(); QFile input(files.value(0)); QFile output(files.value(1)); + const Converter *inconv = prepareConverter(parser.value(inputFormatOption), + Converter::Direction::In, &input); + const Converter *outconv = prepareConverter(parser.value(outputFormatOption), + Converter::Direction::Out, &output); - if (input.fileName().isEmpty()) - input.open(stdin, QIODevice::ReadOnly); - else - input.open(QIODevice::ReadOnly); - if (!input.isOpen()) { - fprintf(stderr, "Could not open \"%s\" for reading: %s\n", - qPrintable(input.fileName()), qPrintable(input.errorString())); - return EXIT_FAILURE; - } - - if (output.fileName().isEmpty()) - output.open(stdout, QIODevice::WriteOnly | QIODevice::Truncate); - else - output.open(QIODevice::WriteOnly | QIODevice::Truncate); - if (!output.isOpen()) { - fprintf(stderr, "Could not open \"%s\" for writing: %s\n", - qPrintable(output.fileName()), qPrintable(output.errorString())); - return EXIT_FAILURE; - } - - if (!inconv) { - // probe the input to find a file format - for (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 + // Now finally perform the conversion: QVariant data = inconv->loadFile(&input, outconv); - Q_ASSERT_X(outconv, "Converter Tool", + Q_ASSERT_X(outconv, "Serialization Converter", "Internal error: converter format did not provide default"); outconv->saveFile(&output, data, parser.values(optionOption)); return EXIT_SUCCESS; +//! [2] } diff --git a/examples/corelib/serialization/convert/nullconverter.cpp b/examples/corelib/serialization/convert/nullconverter.cpp index a3f0bcd99b..fb8be5c944 100644 --- a/examples/corelib/serialization/convert/nullconverter.cpp +++ b/examples/corelib/serialization/convert/nullconverter.cpp @@ -3,48 +3,35 @@ #include "nullconverter.h" -static NullConverter nullConverter; -Converter* Converter::null = &nullConverter; - -QString NullConverter::name() -{ - return QLatin1String("null"); -} - -Converter::Direction NullConverter::directions() -{ - return Out; -} +using namespace Qt::StringLiterals; -Converter::Options NullConverter::outputOptions() +static NullConverter nullConverter; +bool Converter::isNull(const Converter *converter) { - return SupportsArbitraryMapKeys; + return converter == &nullConverter; } -const char *NullConverter::optionsHelp() +QString NullConverter::name() const { - return nullptr; + return "null"_L1; } -bool NullConverter::probeFile(QIODevice *f) +Converter::Directions NullConverter::directions() const { - Q_UNUSED(f); - return false; + return Direction::Out; } -QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter) +Converter::Options NullConverter::outputOptions() const { - Q_UNUSED(f); - Q_UNUSED(outputConverter); - outputConverter = this; - return QVariant(); + return SupportsArbitraryMapKeys; } -void NullConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void NullConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { if (!options.isEmpty()) { - fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n", qPrintable(options.first())); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to null output. This format has no options.", + qPrintable(options.first())); } Q_UNUSED(f); diff --git a/examples/corelib/serialization/convert/nullconverter.h b/examples/corelib/serialization/convert/nullconverter.h index b2c69593f5..1bdf9f22f7 100644 --- a/examples/corelib/serialization/convert/nullconverter.h +++ b/examples/corelib/serialization/convert/nullconverter.h @@ -10,13 +10,11 @@ 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; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // NULLCONVERTER_H diff --git a/examples/corelib/serialization/convert/textconverter.cpp b/examples/corelib/serialization/convert/textconverter.cpp index b02ce12f66..1f8b4f9c19 100644 --- a/examples/corelib/serialization/convert/textconverter.cpp +++ b/examples/corelib/serialization/convert/textconverter.cpp @@ -6,6 +6,8 @@ #include <QFile> #include <QTextStream> +using namespace Qt::StringLiterals; + static void dumpVariant(QTextStream &out, const QVariant &v) { switch (v.userType()) { @@ -42,68 +44,52 @@ static void dumpVariant(QTextStream &out, const QVariant &v) } } -QString TextConverter::name() -{ - return QStringLiteral("text"); -} - -Converter::Direction TextConverter::directions() -{ - return InOut; -} - -Converter::Options TextConverter::outputOptions() +QString TextConverter::name() const { - return {}; + return "text"_L1; } -const char *TextConverter::optionsHelp() +Converter::Directions TextConverter::directions() const { - return nullptr; + return Direction::InOut; } -bool TextConverter::probeFile(QIODevice *f) +bool TextConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) - return file->fileName().endsWith(QLatin1String(".txt")); + return file->fileName().endsWith(".txt"_L1); return false; } -QVariant TextConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant TextConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) outputConverter = this; QVariantList list; QTextStream in(f); - QString line ; + 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) { + if (qint64 v = line.toLongLong(&ok); ok) + list.append(v); + else if (double d = line.toDouble(&ok); ok) list.append(d); - continue; - } - - list.append(line); + else + list.append(line); } return list; } -void TextConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void TextConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { if (!options.isEmpty()) { - fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n", qPrintable(options.first())); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to text output. This format has no options.", + qPrintable(options.first())); } QTextStream out(f); diff --git a/examples/corelib/serialization/convert/textconverter.h b/examples/corelib/serialization/convert/textconverter.h index 6379ffc82f..526f295517 100644 --- a/examples/corelib/serialization/convert/textconverter.h +++ b/examples/corelib/serialization/convert/textconverter.h @@ -8,16 +8,14 @@ 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; + QString name() const override; + Directions directions() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // TEXTCONVERTER_H diff --git a/examples/corelib/serialization/convert/variantorderedmap.h b/examples/corelib/serialization/convert/variantorderedmap.h new file mode 100644 index 0000000000..c65316b182 --- /dev/null +++ b/examples/corelib/serialization/convert/variantorderedmap.h @@ -0,0 +1,24 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef VARIANTORDEREDMAP_H +#define VARIANTORDEREDMAP_H + +#include <QList> +#include <QPair> +#include <QVariant> +#include <QVariantMap> + +class VariantOrderedMap : public QList<QPair<QVariant, QVariant>> +{ +public: + VariantOrderedMap() = default; + VariantOrderedMap(const QVariantMap &map) + { + reserve(map.size()); + for (auto it = map.begin(); it != map.end(); ++it) + append({it.key(), it.value()}); + } +}; + +#endif // VARIANTORDEREDMAP_H diff --git a/examples/corelib/serialization/convert/xmlconverter.cpp b/examples/corelib/serialization/convert/xmlconverter.cpp index 080528f678..ef71fecb9f 100644 --- a/examples/corelib/serialization/convert/xmlconverter.cpp +++ b/examples/corelib/serialization/convert/xmlconverter.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "xmlconverter.h" +#include "variantorderedmap.h" #include <QBitArray> #include <QtCborCommon> @@ -13,8 +14,9 @@ #include <QXmlStreamReader> #include <QXmlStreamWriter> -static const char xmlOptionHelp[] = - "compact=no|yes Use compact XML form.\n"; +using namespace Qt::StringLiterals; + +static const char xmlOptionHelp[] = "compact=no|yes Use compact XML form.\n"; static XmlConverter xmlConverter; @@ -23,7 +25,7 @@ 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"))) { + while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "list"_L1)) { xml.readNext(); switch (xml.tokenType()) { case QXmlStreamReader::StartElement: @@ -47,20 +49,19 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); } xml.readNext(); return list; } -static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Converter::Options options) +static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, + Converter::Options options) { QVariant key, value; - while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("entry"))) { + while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "entry"_L1)) { xml.readNext(); switch (xml.tokenType()) { case QXmlStreamReader::StartElement: @@ -89,10 +90,8 @@ static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Conv break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); } return { key, value }; @@ -103,11 +102,11 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) QVariantMap map1; VariantOrderedMap map2; - while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("map"))) { + while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "map"_L1)) { xml.readNext(); switch (xml.tokenType()) { case QXmlStreamReader::StartElement: - if (xml.name() == QLatin1String("entry")) { + if (xml.name() == "entry"_L1) { auto pair = mapEntryFromXml(xml, options); if (options & Converter::SupportsArbitraryMapKeys) map2.append(pair); @@ -134,10 +133,8 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); } xml.readNext(); @@ -149,18 +146,17 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options) { QStringView name = xml.name(); - if (name == QLatin1String("list")) + if (name == "list"_L1) return listFromXml(xml, options); - if (name == QLatin1String("map")) + if (name == "map"_L1) 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); + if (name != "value"_L1) { + qFatal("%lld:%lld: Invalid XML key '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString())); } QXmlStreamAttributes attrs = xml.attributes(); - QStringView type = attrs.value(QLatin1String("type")); + QStringView type = attrs.value("type"_L1); forever { xml.readNext(); @@ -169,10 +165,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options if (xml.isCDATA() || xml.isCharacters() || xml.isEndElement()) break; - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(name.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(name.toString())); } QStringView text = xml.text(); @@ -180,45 +174,43 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options text = text.trimmed(); QVariant result; - bool ok; if (type.isEmpty()) { // ok - } else if (type == QLatin1String("number")) { + } else if (type == "number"_L1) { // try integer first + bool ok; 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); + qFatal("%lld:%lld: Invalid XML: could not interpret '%s' as a number.", + xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); } + result = d; } - } else if (type == QLatin1String("bytes")) { + } else if (type == "bytes"_L1) { QByteArray data = text.toLatin1(); QStringView encoding = attrs.value("encoding"); - if (encoding == QLatin1String("base64url")) { + if (encoding == "base64url"_L1) { result = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding); - } else if (encoding == QLatin1String("hex")) { + } else if (encoding == "hex"_L1) { result = QByteArray::fromHex(data); - } else if (encoding.isEmpty() || encoding == QLatin1String("base64")) { + } else if (encoding.isEmpty() || encoding == "base64"_L1) { result = QByteArray::fromBase64(data); } else { - fprintf(stderr, "%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.", + xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString())); } - } else if (type == QLatin1String("string")) { + } else if (type == "string"_L1) { result = text.toString(); - } else if (type == QLatin1String("null")) { + } else if (type == "null"_L1) { result = QVariant::fromValue(nullptr); - } else if (type == QLatin1String("CBOR simple type")) { + } else if (type == "CBOR simple type"_L1) { result = QVariant::fromValue(QCborSimpleType(text.toShort())); - } else if (type == QLatin1String("bits")) { + } else if (type == "bits"_L1) { QBitArray ba; ba.resize(text.size()); qsizetype n = 0; @@ -229,36 +221,33 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options } else if (c == '0') { ++n; } else if (!c.isSpace()) { - fprintf(stderr, "%lld:%lld: Invalid XML: invalid bit string '%s'.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: invalid bit string '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); } } ba.resize(n); result = ba; } else { int id = QMetaType::UnknownType; - if (type == QLatin1String("datetime")) + if (type == "datetime"_L1) id = QMetaType::QDateTime; - else if (type == QLatin1String("url")) + else if (type == "url"_L1) id = QMetaType::QUrl; - else if (type == QLatin1String("uuid")) + else if (type == "uuid"_L1) id = QMetaType::QUuid; - else if (type == QLatin1String("regex")) + else if (type == "regex"_L1) 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); + qFatal("%lld:%lld: Invalid XML: unknown type '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); } result = text.toString(); if (!result.convert(QMetaType(id))) { - fprintf(stderr, "%lld:%lld: Invalid XML: could not parse content as type '%s'.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: could not parse content as type '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); } } @@ -267,10 +256,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options } while (xml.isComment() || xml.isWhitespace()); if (!xml.isEndElement()) { - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(name.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(name.toString())); } xml.readNext(); @@ -287,9 +274,9 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) 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); + const VariantOrderedMap map = (type == QMetaType::QVariantMap) + ? VariantOrderedMap(v.toMap()) + : qvariant_cast<VariantOrderedMap>(v); xml.writeStartElement("map"); for (const auto &pair : map) { @@ -301,7 +288,7 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) xml.writeEndElement(); } else { xml.writeStartElement("value"); - QString typeString = QStringLiteral("type"); + QString typeString = "type"_L1; switch (type) { case QMetaType::Short: case QMetaType::UShort: @@ -390,8 +377,7 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) xml.writeAttribute(typeString, QString::fromLatin1(typeName)); xml.writeCharacters(copy.toString()); } else { - fprintf(stderr, "XML: don't know how to serialize type '%s'.\n", typeName); - exit(EXIT_FAILURE); + qFatal("XML: don't know how to serialize type '%s'.", typeName); } } } @@ -399,37 +385,37 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) } } -QString XmlConverter::name() +QString XmlConverter::name() const { - return QStringLiteral("xml"); + return "xml"_L1; } -Converter::Direction XmlConverter::directions() +Converter::Directions XmlConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options XmlConverter::outputOptions() +Converter::Options XmlConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *XmlConverter::optionsHelp() +const char *XmlConverter::optionsHelp() const { return xmlOptionHelp; } -bool XmlConverter::probeFile(QIODevice *f) +bool XmlConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) { - if (file->fileName().endsWith(QLatin1String(".xml"))) + if (file->fileName().endsWith(".xml"_L1)) return true; } return f->isReadable() && f->peek(5) == "<?xml"; } -QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant XmlConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) outputConverter = this; @@ -437,26 +423,24 @@ QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter) QXmlStreamReader xml(f); xml.readNextStartElement(); QVariant v = variantFromXml(xml, outputConverter->outputOptions()); - if (xml.hasError()) { - fprintf(stderr, "XML error: %s", qPrintable(xml.errorString())); - exit(EXIT_FAILURE); - } + if (xml.hasError()) + qFatal("XML error: %s", qPrintable(xml.errorString())); return v; } -void XmlConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void XmlConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { bool compact = false; for (const QString &s : options) { - if (s == QLatin1String("compact=no")) { + if (s == "compact=no"_L1) { compact = false; - } else if (s == QLatin1String("compact=yes")) { + } else if (s == "compact=yes"_L1) { compact = true; } else { - fprintf(stderr, "Unknown option '%s' to XML output. Valid options are:\n%s", - qPrintable(s), xmlOptionHelp); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to XML output. Valid options are:\n%s", + qPrintable(s), xmlOptionHelp); } } diff --git a/examples/corelib/serialization/convert/xmlconverter.h b/examples/corelib/serialization/convert/xmlconverter.h index 19bde6c7c6..4888b0616f 100644 --- a/examples/corelib/serialization/convert/xmlconverter.h +++ b/examples/corelib/serialization/convert/xmlconverter.h @@ -10,13 +10,14 @@ 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; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // XMLCONVERTER_H diff --git a/examples/corelib/serialization/rsslisting/CMakeLists.txt b/examples/corelib/serialization/rsslisting/CMakeLists.txt deleted file mode 100644 index 405a01ce56..0000000000 --- a/examples/corelib/serialization/rsslisting/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -# 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}" -) diff --git a/examples/corelib/serialization/rsslisting/doc/images/rsslisting.png b/examples/corelib/serialization/rsslisting/doc/images/rsslisting.png Binary files differdeleted file mode 100644 index 0b05375f93..0000000000 --- a/examples/corelib/serialization/rsslisting/doc/images/rsslisting.png +++ /dev/null diff --git a/examples/corelib/serialization/rsslisting/doc/src/rsslisting.qdoc b/examples/corelib/serialization/rsslisting/doc/src/rsslisting.qdoc deleted file mode 100644 index b86fab1e1b..0000000000 --- a/examples/corelib/serialization/rsslisting/doc/src/rsslisting.qdoc +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example serialization/rsslisting - \examplecategory {Networking} - \meta tag {serialization} - \title A minimal RSS listing application - - \brief A demonstration of how to fetch and display a network resource. - - This example shows how to fetch a resource the user has requested and - display data contained in the response, illustrated by an RSS listing - application. (RDF Site Summary, or Really Simple Syndication, is a standard - format for communicating updates to web sites. See - https://www.rssboard.org/rss-specification for details.) The user inferface - in the illustration is simple, as the focus of this example is on how to use - networking, but naturally a more sophisticated interface would be wanted for - a serious RSS reader. - - The example also illustrates how to do asynchronous parsing of data as it is - received, preserving state in member variables so that an incremental parser - can consume chunks of data as they arrive over the network. Constituents of - the parsed content may start in one chunk of data but not be completed until - a later chunk, requiring the parser to retain state between calls. - - \image rsslisting.cpp - - The main program is fairly minimal. It simply instantiates a \l QApplication - and the \c RSSListing widget, shows the latter and hands over control to the - former. For the sake of illustration, it gives the widget the Qt blog's URL - as default value for the resource to check. - - \snippet serialization/rsslisting/main.cpp 0 - - \section1 The RSSListing class - - \snippet serialization/rsslisting/rsslisting.h 0 - - The widget itself provides a simple user interface for specifying the URL to - fetch and, once available updates are displayed, controlling the downloading - of updated items. A \l QLineEdit provides for input of the URL, and a - \l QTreeWidget for display of the results once fetched. - - The widget downloads and parses the RSS (a form of XML) asynchronously, - feeding the data to an XML reader as it arrives. This supports reading of - very large data sources. Because the data is streamed from the network - through the XML reader, there is no need to retain the full text of the XML - in memory. In other context, a similar approach can allow the user to - interrupt such incremental loading. - - \section2 Construction - - \snippet serialization/rsslisting/rsslisting.cpp setup - - The constructor sets up the assorted components of the widget and connects - their various signals to the slots it shall use to handle them. - - The user interface consists of a line edit, a push button, and a list view - widget. The line edit is used for entering the URL to fetch; the push button - starts the process of fetching updates. The line edit is empty by default, - but the constructor's caller can override that, as our \c main() has done. - In any case, the user can replace the default with the URL of another RSS - feed. - - The list view shows the updated items reported in the RSS feed. - Double-clicking on one of these sends its URL to the user's browser or other - user agent using \l QDesktopServices::openUrl(). - - \section2 The slots - - \snippet serialization/rsslisting/rsslisting.cpp slots - - All slots are kept simple by delegating any hard work to private methods. - - When the user completes input of a URL, either by clicking the "Fetch" - button or by pressing the return key in the line edit, the \c fetch() slot - disables the "Fetch" button and disables further editing of the line edit. - It clears the display of available updates and delegates to \c get() the - initiating of an HTTP GET request. - - When data is received, the network reply triggers its \l {QNetworkReply::} - {readyRead()} signal, which \c get() connects to the \c consumeData() - slot. This checks the response got a successful status code and, if it did, - calls \c parseXml() to consume the data. - - If the network reply gets an error, this is delivered to the \c error() - slot, which reports the error, clears the XML stream reader then disconnects - from the reply and deletes it. - - On completion (whether successful or otherwise) of a network reply, the \c - finished() slot restores the UI to be ready to accept a new URL to fetch by - re-enabling the line edit and "Fetch" button. - - \section2 The get() method - - \snippet serialization/rsslisting/rsslisting.cpp get - - The private \c get() method is used by the \c fetch() slot to initiate an - HTTP GET request. It first clears the XML stream reader and, if a reply is - currently active, disconnects and deletes it. If the URL it has been passed - is valid, it asks the network access manager to GET it. It connects its - relevant slots to signals of the resulting reply (if any) and sets up its - XML stream reader to read data from the reply - a network reply object is - also a \c QIODevice, from which data can be read. - - \section2 The parseXml() method - - \snippet serialization/rsslisting/rsslisting.cpp parse - - When data is received, and thus made available to the XML stream reader, \c - parseXml() reads from the XML stream, checking for \c item elements and, - within them, \c title and \c link elements. It will use the \c{rss:about} - attribute of an \c item as URL in the Link column of the tree-view, failing - that the content of its \c link element; and it uses the content of the \c - title element in the Title column of the tree-view. As each \c item element - closes, its details are turned into a new row in the tree widget, with the - extracted title and URL in the Title and Link columns. - - The variables that keep track of the parsing state - \c linkString, \c - titleString and \c currentTag - are member variables of the \c RSSListing - class, even though they are only accessed from this method, because this - method may be called repeatedly, as new data arrives, and one chunk of - received data may start an element that isn't completed until a later chunk - arrives. This enables the parser to operate asynchronously as the data - arrives, instead of having to wait until all the data has arrived. - - \sa QNetworkReply, QXmlStreamReader -*/ diff --git a/examples/corelib/serialization/rsslisting/main.cpp b/examples/corelib/serialization/rsslisting/main.cpp deleted file mode 100644 index 7a64db8a0c..0000000000 --- a/examples/corelib/serialization/rsslisting/main.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "rsslisting.h" -#include <QtWidgets> -using namespace Qt::StringLiterals; - -//! [0] -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - RSSListing rsslisting(u"https://www.qt.io/blog/rss.xml"_s); - rsslisting.show(); - return app.exec(); -} -//! [0] diff --git a/examples/corelib/serialization/rsslisting/rsslisting.cpp b/examples/corelib/serialization/rsslisting/rsslisting.cpp deleted file mode 100644 index ed7c163c76..0000000000 --- a/examples/corelib/serialization/rsslisting/rsslisting.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "rsslisting.h" - -#include <QtCore> -#include <QtWidgets> -#include <QtNetwork> - -//! [setup] -RSSListing::RSSListing(const QString &url, QWidget *parent) - : QWidget(parent), currentReply(0) -{ - connect(&manager, &QNetworkAccessManager::finished, this, &RSSListing::finished); - - lineEdit = new QLineEdit(this); - lineEdit->setText(url); - connect(lineEdit, &QLineEdit::returnPressed, this, &RSSListing::fetch); - - fetchButton = new QPushButton(tr("Fetch"), this); - connect(fetchButton, &QPushButton::clicked, this, &RSSListing::fetch); - - treeWidget = new QTreeWidget(this); - connect(treeWidget, &QTreeWidget::itemActivated, - // Open the link in the browser: - this, [](QTreeWidgetItem *item) { QDesktopServices::openUrl(QUrl(item->text(1))); }); - treeWidget->setHeaderLabels(QStringList { tr("Title"), tr("Link") }); - treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - - QHBoxLayout *hboxLayout = new QHBoxLayout; - hboxLayout->addWidget(lineEdit); - hboxLayout->addWidget(fetchButton); - - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addLayout(hboxLayout); - layout->addWidget(treeWidget); - - setWindowTitle(tr("RSS listing example")); - resize(640, 480); -} -//! [setup] - -//! [slots] -void RSSListing::fetch() -{ - lineEdit->setReadOnly(true); - fetchButton->setEnabled(false); - treeWidget->clear(); - - get(QUrl(lineEdit->text())); -} - -void RSSListing::consumeData() -{ - int statusCode = currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode >= 200 && statusCode < 300) - parseXml(); -} - -void RSSListing::error(QNetworkReply::NetworkError) -{ - qWarning("error retrieving RSS feed"); - xml.clear(); - currentReply->disconnect(this); - currentReply->deleteLater(); - currentReply = nullptr; -} - -void RSSListing::finished(QNetworkReply *reply) -{ - Q_UNUSED(reply); - lineEdit->setReadOnly(false); - fetchButton->setEnabled(true); -} -//! [slots] - -// Private methods - -//! [get] -void RSSListing::get(const QUrl &url) -{ - if (currentReply) { - currentReply->disconnect(this); - currentReply->deleteLater(); - } - currentReply = url.isValid() ? manager.get(QNetworkRequest(url)) : nullptr; - if (currentReply) { - connect(currentReply, &QNetworkReply::readyRead, this, &RSSListing::consumeData); - connect(currentReply, &QNetworkReply::errorOccurred, this, &RSSListing::error); - - } - xml.setDevice(currentReply); // Equivalent to clear() if currentReply is null. -} -//! [get] - -// TODO: this is a candidate for showing how to use coroutines, once available. -//! [parse] -void RSSListing::parseXml() -{ - while (!xml.atEnd()) { - xml.readNext(); - if (xml.isStartElement()) { - if (xml.name() == u"item") { - linkString = xml.attributes().value("rss:about").toString(); - titleString.clear(); - } - 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); - } - } 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(); -} -//! [parse] diff --git a/examples/corelib/serialization/rsslisting/rsslisting.h b/examples/corelib/serialization/rsslisting/rsslisting.h deleted file mode 100644 index 499bc5d1d4..0000000000 --- a/examples/corelib/serialization/rsslisting/rsslisting.h +++ /dev/null @@ -1,54 +0,0 @@ -// 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 <QXmlStreamReader> - -QT_BEGIN_NAMESPACE -class QLineEdit; -class QPushButton; -class QTreeWidget; -class QTreeWidgetItem; -class QUrl; -QT_END_NAMESPACE - -//! [0] -class RSSListing : public QWidget -{ - Q_OBJECT -public: - explicit RSSListing(const QString &url = QString(), QWidget *widget = nullptr); - -public slots: - void fetch(); - void finished(QNetworkReply *reply); - void consumeData(); - void error(QNetworkReply::NetworkError); - -private: - void parseXml(); - void get(const QUrl &url); - - // Parser state: - QXmlStreamReader xml; - QString currentTag; - QString linkString; - QString titleString; - - // Network state: - QNetworkAccessManager manager; - QNetworkReply *currentReply; - - // UI elements: - QLineEdit *lineEdit; - QTreeWidget *treeWidget; - QPushButton *fetchButton; -}; -//! [0] - -#endif diff --git a/examples/corelib/serialization/rsslisting/rsslisting.pro b/examples/corelib/serialization/rsslisting/rsslisting.pro deleted file mode 100644 index 7619755b8f..0000000000 --- a/examples/corelib/serialization/rsslisting/rsslisting.pro +++ /dev/null @@ -1,8 +0,0 @@ -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 diff --git a/examples/corelib/serialization/savegame/CMakeLists.txt b/examples/corelib/serialization/savegame/CMakeLists.txt index 8871a9d687..14621ccc23 100644 --- a/examples/corelib/serialization/savegame/CMakeLists.txt +++ b/examples/corelib/serialization/savegame/CMakeLists.txt @@ -1,15 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(savegame LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/savegame") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() @@ -26,7 +24,14 @@ target_link_libraries(savegame PRIVATE ) install(TARGETS savegame - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET savegame + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/savegame/character.cpp b/examples/corelib/serialization/savegame/character.cpp index 039aa1fa94..863fcb9153 100644 --- a/examples/corelib/serialization/savegame/character.cpp +++ b/examples/corelib/serialization/savegame/character.cpp @@ -6,15 +6,10 @@ #include <QMetaEnum> #include <QTextStream> -Character::Character() - = default; - -Character::Character(const QString &name, - int level, - Character::ClassType classType) : - mName(name), - mLevel(level), - mClassType(classType) +Character::Character() = default; + +Character::Character(const QString &name, int level, Character::ClassType classType) + : mName(name), mLevel(level), mClassType(classType) { } diff --git a/examples/corelib/serialization/savegame/character.h b/examples/corelib/serialization/savegame/character.h index cffa5fa659..0504750320 100644 --- a/examples/corelib/serialization/savegame/character.h +++ b/examples/corelib/serialization/savegame/character.h @@ -16,9 +16,7 @@ class Character Q_GADGET public: - enum ClassType { - Warrior, Mage, Archer - }; + enum ClassType { Warrior, Mage, Archer }; Q_ENUM(ClassType) Character(); @@ -37,6 +35,7 @@ public: QJsonObject toJson() const; void print(QTextStream &s, int indentation = 0) const; + private: QString mName; int mLevel = 0; diff --git a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc index 37a461d7bd..46fca15b62 100644 --- a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc +++ b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc @@ -3,11 +3,10 @@ /*! \example serialization/savegame - \examplecategory {Input/Output} - \title JSON Save Game Example + \examplecategory {Data Processing & I/O} + \title Saving and Loading a Game - \brief The JSON Save Game example demonstrates how to save and load a - small game using QJsonDocument, QJsonObject and QJsonArray. + \brief How to save and load a game using Qt's JSON or CBOR classes. Many games provide save functionality, so that the player's progress through the game can be saved and loaded at a later time. The process of saving a diff --git a/examples/corelib/serialization/savegame/game.cpp b/examples/corelib/serialization/savegame/game.cpp index 85a2619b42..f99ecb8b51 100644 --- a/examples/corelib/serialization/savegame/game.cpp +++ b/examples/corelib/serialization/savegame/game.cpp @@ -11,6 +11,8 @@ #include <QRandomGenerator> #include <QTextStream> +using namespace Qt::StringLiterals; + Character Game::player() const { return mPlayer; @@ -25,37 +27,32 @@ QList<Level> Game::levels() const void Game::newGame() { mPlayer = Character(); - mPlayer.setName(QStringLiteral("Hero")); + mPlayer.setName("Hero"_L1); mPlayer.setClassType(Character::Archer); mPlayer.setLevel(QRandomGenerator::global()->bounded(15, 21)); mLevels.clear(); mLevels.reserve(2); - Level village(QStringLiteral("Village")); + Level village("Village"_L1); 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)); + villageNpcs.append(Character("Barry the Blacksmith"_L1, + QRandomGenerator::global()->bounded(8, 11), Character::Warrior)); + villageNpcs.append(Character("Terry the Trader"_L1, + QRandomGenerator::global()->bounded(6, 8), Character::Warrior)); village.setNpcs(villageNpcs); mLevels.append(village); - Level dungeon(QStringLiteral("Dungeon")); + Level dungeon("Dungeon"_L1); 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)); + dungeonNpcs.append(Character("Eric the Evil"_L1, + QRandomGenerator::global()->bounded(18, 26), Character::Mage)); + dungeonNpcs.append(Character("Eric's Left Minion"_L1, + QRandomGenerator::global()->bounded(5, 7), Character::Warrior)); + dungeonNpcs.append(Character("Eric's Right Minion"_L1, + QRandomGenerator::global()->bounded(4, 9), Character::Warrior)); dungeon.setNpcs(dungeonNpcs); mLevels.append(dungeon); } @@ -64,9 +61,7 @@ void Game::newGame() //! [loadGame] bool Game::loadGame(Game::SaveFormat saveFormat) { - QFile loadFile(saveFormat == Json - ? QStringLiteral("save.json") - : QStringLiteral("save.dat")); + QFile loadFile(saveFormat == Json ? "save.json"_L1 : "save.dat"_L1); if (!loadFile.open(QIODevice::ReadOnly)) { qWarning("Couldn't open save file."); @@ -76,15 +71,13 @@ bool Game::loadGame(Game::SaveFormat saveFormat) QByteArray saveData = loadFile.readAll(); QJsonDocument loadDoc(saveFormat == Json - ? QJsonDocument::fromJson(saveData) - : QJsonDocument(QCborValue::fromCbor(saveData).toMap().toJsonObject())); + ? 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"; + QTextStream(stdout) << "Loaded save for " << loadDoc["player"]["name"].toString() + << " using " << (saveFormat != Json ? "CBOR" : "JSON") << "...\n"; return true; } //! [loadGame] @@ -92,9 +85,7 @@ bool Game::loadGame(Game::SaveFormat saveFormat) //! [saveGame] bool Game::saveGame(Game::SaveFormat saveFormat) const { - QFile saveFile(saveFormat == Json - ? QStringLiteral("save.json") - : QStringLiteral("save.dat")); + QFile saveFile(saveFormat == Json ? "save.json"_L1 : "save.dat"_L1); if (!saveFile.open(QIODevice::WriteOnly)) { qWarning("Couldn't open save file."); @@ -102,9 +93,8 @@ bool Game::saveGame(Game::SaveFormat saveFormat) const } QJsonObject gameObject = toJson(); - saveFile.write(saveFormat == Json - ? QJsonDocument(gameObject).toJson() - : QCborValue::fromJsonValue(gameObject).toCbor()); + saveFile.write(saveFormat == Json ? QJsonDocument(gameObject).toJson() + : QCborValue::fromJsonValue(gameObject).toCbor()); return true; } diff --git a/examples/corelib/serialization/savegame/game.h b/examples/corelib/serialization/savegame/game.h index 2d7630c7df..5ba5952923 100644 --- a/examples/corelib/serialization/savegame/game.h +++ b/examples/corelib/serialization/savegame/game.h @@ -16,9 +16,7 @@ QT_FORWARD_DECLARE_CLASS(QTextStream) class Game { public: - enum SaveFormat { - Json, Binary - }; + enum SaveFormat { Json, Binary }; Character player() const; QList<Level> levels() const; @@ -31,6 +29,7 @@ public: QJsonObject toJson() const; void print(QTextStream &s, int indentation = 0) const; + private: Character mPlayer; QList<Level> mLevels; diff --git a/examples/corelib/serialization/savegame/level.cpp b/examples/corelib/serialization/savegame/level.cpp index 489a25e204..f30d35e57f 100644 --- a/examples/corelib/serialization/savegame/level.cpp +++ b/examples/corelib/serialization/savegame/level.cpp @@ -6,9 +6,7 @@ #include <QJsonArray> #include <QTextStream> -Level::Level(const QString &name) : mName(name) -{ -} +Level::Level(const QString &name) : mName(name) { } QString Level::name() const { @@ -61,8 +59,7 @@ void Level::print(QTextStream &s, int indentation) const { const QString indent(indentation * 2, ' '); - s << indent << "Name:\t" << mName << "\n" - << indent << "NPCs:\n"; + s << indent << "Name:\t" << mName << "\n" << indent << "NPCs:\n"; for (const Character &character : mNpcs) character.print(s, indentation + 1); } diff --git a/examples/corelib/serialization/savegame/level.h b/examples/corelib/serialization/savegame/level.h index ad8d0fd593..e487e55ae3 100644 --- a/examples/corelib/serialization/savegame/level.h +++ b/examples/corelib/serialization/savegame/level.h @@ -27,6 +27,7 @@ public: QJsonObject toJson() const; void print(QTextStream &s, int indentation = 0) const; + private: QString mName; QList<Character> mNpcs; diff --git a/examples/corelib/serialization/serialization.pro b/examples/corelib/serialization/serialization.pro index 9f0ced0282..e20fcb57fd 100644 --- a/examples/corelib/serialization/serialization.pro +++ b/examples/corelib/serialization/serialization.pro @@ -6,6 +6,4 @@ SUBDIRS = \ qtHaveModule(widgets) { SUBDIRS += streambookmarks - qtHaveModule(network): SUBDIRS += \ - rsslisting } diff --git a/examples/corelib/serialization/streambookmarks/CMakeLists.txt b/examples/corelib/serialization/streambookmarks/CMakeLists.txt index 1d0ab8690b..bad55fa661 100644 --- a/examples/corelib/serialization/streambookmarks/CMakeLists.txt +++ b/examples/corelib/serialization/streambookmarks/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR 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() @@ -33,7 +27,14 @@ target_link_libraries(streambookmarks PRIVATE ) install(TARGETS streambookmarks - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET streambookmarks + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/streambookmarks/doc/images/filemenu.png b/examples/corelib/serialization/streambookmarks/doc/images/filemenu.png Binary files differnew file mode 100644 index 0000000000..1a92895ccf --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/images/filemenu.png diff --git a/examples/corelib/serialization/streambookmarks/doc/images/helpmenu.png b/examples/corelib/serialization/streambookmarks/doc/images/helpmenu.png Binary files differnew file mode 100644 index 0000000000..baa98bee96 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/images/helpmenu.png diff --git a/examples/corelib/serialization/streambookmarks/doc/images/screenshot.png b/examples/corelib/serialization/streambookmarks/doc/images/screenshot.png Binary files differnew file mode 100644 index 0000000000..422873b6d0 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/images/screenshot.png diff --git a/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-filemenu.png b/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-filemenu.png Binary files differdeleted file mode 100644 index e074fb7c41..0000000000 --- a/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-filemenu.png +++ /dev/null diff --git a/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-helpmenu.png b/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-helpmenu.png Binary files differdeleted file mode 100644 index 0dc4392b94..0000000000 --- a/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-helpmenu.png +++ /dev/null diff --git a/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-screenshot.png b/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-screenshot.png Binary files differdeleted file mode 100644 index bbaa423061..0000000000 --- a/examples/corelib/serialization/streambookmarks/doc/images/xmlstreamexample-screenshot.png +++ /dev/null diff --git a/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc b/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc index a1498f0ff5..8e32dd8d0b 100644 --- a/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc +++ b/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc @@ -3,170 +3,221 @@ /*! \example serialization/streambookmarks - \examplecategory {Input/Output} + \examplecategory {Data Processing & I/O} \meta tag {network} \title QXmlStream Bookmarks Example - \brief Demonstrates how to read and write to XBEL files. + \brief Demonstrates how to read and write 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. + The QXmlStream Bookmarks example provides a viewer for XML Bookmark Exchange + Language (XBEL) files. It can read bookmarks using Qt's QXmlStreamReader and + write them back out again using QXmlStreamWriter. As this example aims to + show how to use these reader and writer types, it provides no means to open + a bookmark, add a new one, or merge two bookmark files, and only minimal + scope for editing bookmarks. None the less, it could surely be extended with + such features, if desired. - \image xmlstreamexample-screenshot.png + \image 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. + The \c XbelWriter class takes a \l{QTreeWidget}{tree widget} describing a + hierarchy of folders containing bookmarks. Its \c writeFile() provides the + means to write out this hierarchy, in XBEL format, to a given output device. + + Internally, it records the tree widget it was given and packages a private + instance of QXmlStreamWriter, which provides it with the means to stream + XML. It has an internal \c writeItem() to write each item in its tree. \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. + The \c XbelWriter constructor accepts the \a treeWidget it will describe. It + stores that and enables \l{QXmlStreamWriter}'s auto-formatting property. + This last splits the data into several lines, with indentation to indicate + the structure of the tree, which makes the XML output easier to read. \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. + The \c writeFile() function accepts a QIODevice object and directs its + QXmlStreamWriter member to write to this device, using \c setDevice(). This + function then writes the document type definition(DTD), the start element, + the version, and delegates writing of each of the \c{treeWidget}'s top-level + items to \c writeItem(). Finally, it closes the document and returns. \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". + The \c writeItem() function accepts a QTreeWidgetItem object and writes to + its XML stream a representation of the object, which depends on its \c + UserRole, which can be one of a \c{"folder"}, \c{"bookmark"}, + or \c{"separator"}. Within each folder, it calls itself recursively on each + child item, to recursively include a representation of each child within the + folder's XML element. \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. + The \c XbelReader takes a \l{QTreeWidget}{tree widget} to populate with + items describing a bookmark hierarchy. It supports reading XBEL data from a + QIODevice as a source of these items. If parsing of the XBEL data fails, it + can report what went wrong. + + Internally, it records the QTreeWidget that it will populate and packages an + instance of QXmlStreamReader, the companion class to QXmlStreamWriter, which + it will use to read XBEL data. \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. + Since the XBEL reader is only concerned with reading XML elements, it makes + extensive use of the \l{QXmlStreamReader::}{readNextStartElement()} + convenience function. + + The \c XbelReader constructor requires a QTreeWidget that it will populate. + It populates the tree widget's style with suitable icons: a folder icon that + changes form to indicate whether each folder as open or closed; and a + standard file icon for the individual bookmarks within those folders. \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. + The \c read() function accepts a QIODevice. It directs its QXmlStreamReader + member to read content from that device. Note that the XML input must be + well-formed to be accepted by QXmlStreamReader. First it reads the outer + structure and verifies the content is an XBEL 1.0 file; if it is, \c read() + delegates the actual reading of content to the internal \c readXBEL(). + + Otherwise, the \l{QXmlStreamReader::}{raiseError()} function is used to + record an error message. The reader itself may also do the same if it + encounters errors in the input. When \c read() has finished, it returns + true if there were no errors. \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. + If \c read() returns false, its caller can obtain a description of the + error, complete with line and column number within the stream, by calling + the \c errorString() function. \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. + The \c readXBEL() function reads the name of a startElement and calls the + appropriate function to read it, depending on whether if its tag name + is \c{"folder"}, \c{"bookmark"} or \c{"separator"}. Any other elements + encountered are skipped. The function starts with a precondition, verifying + that the XML reader has just opened an \c{"xbel"} element. \snippet serialization/streambookmarks/xbelreader.cpp 3 - The \c readTitle() function reads the bookmark's title. + The \c readBookmark() function creates a new editable item representing a + single bookmark. It records the XML \c{"href"} attribute of the current + element as second column text of the item and provisionally sets its first + column text to \c{"Unknown title"} before scanning the rest of the element + for a title element to over-ride that, skipping any unrecognized child + elements. - \snippet serialization/streambookmarks/xbelreader.cpp 4 + \snippet serialization/streambookmarks/xbelreader.cpp 5 - 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()}. + The \c readTitle() function reads a bookmark's title and records it as the + title (first column text) of the item for which it was called. - \snippet serialization/streambookmarks/xbelreader.cpp 5 + \snippet serialization/streambookmarks/xbelreader.cpp 6 + + The \c readSeparator() function creates a separator and sets its flags. The + separator item's text is set to 30 centered dots. The rest of the element is + then skipped using \l{QXmlStreamReader::}{skipCurrentElement()}. + + \snippet serialization/streambookmarks/xbelreader.cpp 6 + + The \c readFolder() function creates an item and iterates the content of the + folder element, adding children to this item to represent the contents of + the folder element. The loop over folder content is similar in form to the + one in \c readXBEL(), save that it now accepts a title element to set the + title of the folder. + + \snippet serialization/streambookmarks/xbelreader.cpp 7 + + The \c createChildItem() helper function creates a new tree widget item + that's either a child of the given item or, if no parent item is given, a + direct child of the tree widget. It sets the new item's \c UserRole to the + tag name of the current XML element, matching how XbelWriter::writeFile() + uses that \c UserRole. + + \snippet serialization/streambookmarks/xbelreader.cpp 8 \section1 MainWindow Class Definition - The \c MainWindow class is a subclass of QMainWindow, with a - \c File menu and a \c Help menu. + 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. + The \c MainWindow constructor sets up its QTreeWidget object, \c treeWidget, + as its own central widget, with column headings for the title and location + of each book-mark. It configures a custom menu that enables the user to + perform actions on individual bookmarks within the tree widget. + + It invokes \c createMenus() to set up its own menus and their corresponding + actions. It sets its title, announces itself as ready and sets its size to a + reasonable proportion of the available screen space. \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. + A custom menu, triggered when the user right-clicks on a bookmark, provides + for copying the bookmark as a link or directing a desktop browser to open + the URL it references. This menu is implemented (when relevant features are + enabled) by \c onCustomContextMenuRequested(). \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. + The \c createMenus() function creates the \c fileMenu and \c helpMenu and + adds QAction objects to them, bound variously to the \c open(), \c saveAs() + and \c about() functions, along with QWidget::close() and + QApplication::aboutQt(). The connections are as shown below: \snippet serialization/streambookmarks/mainwindow.cpp 2 - The \c about() function displays a QMessageBox with a brief description - of the example. + This creates the menu shown in the screenshots below: - \snippet serialization/streambookmarks/mainwindow.cpp 3 + \table + \row + \li \inlineimage filemenu.png + \li \inlineimage helpmenu.png + \endtable - 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: + The \c open() function, when triggered, offers the user a file dialog to use + to select a bookmarks file. If a file is selected, it is parsed using an \c + XBelReader to populate the \c treeWidget with bookmarks. If problems arise + with opening or parsing the file, a suitable warning message is displayed to + the user, including file name and error message. Otherwise, the bookmarks + read from the file are displayed and the window's status bar briefly reports + that the file has been loaded. - \snippet serialization/streambookmarks/mainwindow.cpp 5 + \snippet serialization/streambookmarks/mainwindow.cpp 3 - 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: + The \c saveAs() function displays a QFileDialog, prompting the user for a \c + fileName, to which to save a copy of the bookmarks data. Similar to the \c + open() function, this function also displays a warning message if the file + cannot be written to. - \table - \row - \li \inlineimage xmlstreamexample-filemenu.png - \li \inlineimage xmlstreamexample-helpmenu.png - \endtable + \snippet serialization/streambookmarks/mainwindow.cpp 4 + + The \c about() function displays a QMessageBox with a brief description of + the example, or general information about Qt and the version of it in use. \snippet serialization/streambookmarks/mainwindow.cpp 5 \section1 \c{main()} Function The \c main() function instantiates \c MainWindow and invokes the \c show() - function. + function to display it, then its \c open(), as this is most likely what the + user shall want to do first. \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. + See the \l{https://pyxml.sourceforge.net/topics/xbel/} {XML Bookmark + Exchange Language Resource Page} for more information about XBEL files. */ diff --git a/examples/corelib/serialization/streambookmarks/jennifer.xbel b/examples/corelib/serialization/streambookmarks/jennifer.xbel index 2501c118af..d504236830 100644 --- a/examples/corelib/serialization/streambookmarks/jennifer.xbel +++ b/examples/corelib/serialization/streambookmarks/jennifer.xbel @@ -3,66 +3,66 @@ <xbel version="1.0"> <folder folded="no"> <title>Qt Resources</title> - <bookmark href="http://qt.io/"> + <bookmark href="https://www.qt.io/"> <title>Qt home page</title> </bookmark> - <bookmark href="https://www.qt.io/partners/"> + <bookmark href="https://www.qt.io/contact-us/partners"> <title>Qt Partners</title> </bookmark> - <bookmark href="https://www.qt.io/qt-training/"> - <title>Training</title> + <bookmark href="https://www.qt.io/qt-professional-services"> + <title>Professional Services</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 href="https://doc.qt.io/"> + <title>Qt Documentation</title> </bookmark> <folder folded="yes"> <title>Community Resources</title> - <bookmark href="http://www.qtcentre.org/content/"> + <bookmark href="https://contribute.qt-project.org"> + <title>The Qt Project</title> + </bookmark> + <bookmark href="https://www.qtcentre.org/content/"> <title>Qt Centre</title> </bookmark> - <bookmark href="http://www.qtforum.org/"> - <title>QtForum.org</title> + <bookmark href="https://forum.qt.io/"> + <title>Forum.Qt.org</title> </bookmark> - <bookmark href="http://digitalfanatics.org/projects/qt_tutorial/"> + <bookmark href="https://digitalfanatics.org/projects/qt_tutorial/"> <title>The Independent Qt Tutorial</title> </bookmark> - <bookmark href="http://www.qtforum.de/"> + <bookmark href="https://www.qtforum.de/"> <title>German Qt Forum</title> </bookmark> - <bookmark href="http://www.korone.net/"> + <bookmark href="https://www.qt-dev.com/"> <title>Korean Qt Community Site</title> </bookmark> - <bookmark href="http://prog.org.ru/"> + <bookmark href="http://www.prog.org.ru/"> <title>Russian Qt Forum</title> </bookmark> </folder> </folder> <folder folded="no"> <title>Online Dictionaries</title> - <bookmark href="http://www.dictionary.com/"> + <bookmark href="https://www.dictionary.com/"> <title>Dictionary.com</title> </bookmark> - <bookmark href="http://www.m-w.com/"> + <bookmark href="https://www.merriam-webster.com/"> <title>Merriam-Webster Online</title> </bookmark> - <bookmark href="http://dictionary.cambridge.org/"> + <bookmark href="https://dictionary.cambridge.org/"> <title>Cambridge Dictionaries Online</title> </bookmark> - <bookmark href="http://www.onelook.com/"> + <bookmark href="https://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 href="https://dict.tu-chemnitz.de/"> + <title>BEOLINGUS, a service of TU Chemnitz</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/"> + <bookmark href="https://www.dictionnaire-academie.fr/"> <title>Dictionnaire de l'Académie Française</title> </bookmark> </folder> diff --git a/examples/corelib/serialization/streambookmarks/main.cpp b/examples/corelib/serialization/streambookmarks/main.cpp index 75b5d646c6..0fd317de43 100644 --- a/examples/corelib/serialization/streambookmarks/main.cpp +++ b/examples/corelib/serialization/streambookmarks/main.cpp @@ -1,10 +1,10 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QApplication> - #include "mainwindow.h" +#include <QApplication> + //! [0] int main(int argc, char *argv[]) { diff --git a/examples/corelib/serialization/streambookmarks/mainwindow.cpp b/examples/corelib/serialization/streambookmarks/mainwindow.cpp index 644681d706..a863f77ab7 100644 --- a/examples/corelib/serialization/streambookmarks/mainwindow.cpp +++ b/examples/corelib/serialization/streambookmarks/mainwindow.cpp @@ -1,22 +1,33 @@ // 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" +#include <QFileDialog> +#include <QHeaderView> +#include <QMenuBar> +#include <QMessageBox> +#include <QStatusBar> +#include <QTreeWidget> + +#include <QAction> +#if QT_CONFIG(clipboard) +# include <QClipboard> +#endif +#include <QDesktopServices> +#include <QApplication> +#include <QScreen> + +using namespace Qt::StringLiterals; + //! [0] -MainWindow::MainWindow() +MainWindow::MainWindow() : treeWidget(new QTreeWidget) { - 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->setHeaderLabels(QStringList{tr("Title"), tr("Location")}); +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(treeWidget, &QWidget::customContextMenuRequested, this, &MainWindow::onCustomContextMenuRequested); @@ -33,7 +44,8 @@ MainWindow::MainWindow() } //! [0] -#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD) +//! [1] +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) void MainWindow::onCustomContextMenuRequested(const QPoint &pos) { const QTreeWidgetItem *item = treeWidget->itemAt(pos); @@ -49,59 +61,77 @@ void MainWindow::onCustomContextMenuRequested(const QPoint &pos) else if (action == openAction) QDesktopServices::openUrl(QUrl(url)); } -#endif // !QT_NO_CONTEXTMENU && !QT_NO_CLIPBOARD - +#endif // QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) //! [1] + +//! [2] +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, &QApplication::aboutQt); +} +//! [2] + +//! [3] void MainWindow::open() { - QString fileName = - QFileDialog::getOpenFileName(this, tr("Open Bookmark File"), - QDir::currentPath(), - tr("XBEL Files (*.xbel *.xml)")); - if (fileName.isEmpty()) + QFileDialog fileDialog(this, tr("Open Bookmark File"), QDir::currentPath()); + fileDialog.setMimeTypeFilters({"application/x-xbel"_L1}); + if (fileDialog.exec() != QDialog::Accepted) return; treeWidget->clear(); - + const QString fileName = fileDialog.selectedFiles().constFirst(); 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())); + .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())); + 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] +//! [3] -//! [2] +//! [4] void MainWindow::saveAs() { - QString fileName = - QFileDialog::getSaveFileName(this, tr("Save Bookmark File"), - QDir::currentPath(), - tr("XBEL Files (*.xbel *.xml)")); - if (fileName.isEmpty()) + QFileDialog fileDialog(this, tr("Save Bookmark File"), QDir::currentPath()); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setDefaultSuffix("xbel"_L1); + fileDialog.setMimeTypeFilters({"application/x-xbel"_L1}); + if (fileDialog.exec() != QDialog::Accepted) return; + const QString fileName = fileDialog.selectedFiles().constFirst(); 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())); + .arg(QDir::toNativeSeparators(fileName), file.errorString())); return; } @@ -109,34 +139,13 @@ void MainWindow::saveAs() 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] +//! [4] //! [5] -void MainWindow::createMenus() +void MainWindow::about() { - 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); + 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.")); } //! [5] diff --git a/examples/corelib/serialization/streambookmarks/mainwindow.h b/examples/corelib/serialization/streambookmarks/mainwindow.h index 7a4a922e43..d9efe6b5a5 100644 --- a/examples/corelib/serialization/streambookmarks/mainwindow.h +++ b/examples/corelib/serialization/streambookmarks/mainwindow.h @@ -22,13 +22,13 @@ public slots: void open(); void saveAs(); void about(); -#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD) +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) void onCustomContextMenuRequested(const QPoint &pos); #endif private: void createMenus(); - QTreeWidget *treeWidget; + QTreeWidget *const treeWidget; }; //! [0] diff --git a/examples/corelib/serialization/streambookmarks/streambookmarks.pro b/examples/corelib/serialization/streambookmarks/streambookmarks.pro index 9b067c7bba..34d2caae82 100644 --- a/examples/corelib/serialization/streambookmarks/streambookmarks.pro +++ b/examples/corelib/serialization/streambookmarks/streambookmarks.pro @@ -8,7 +8,7 @@ SOURCES = main.cpp \ QT += widgets requires(qtConfig(filedialog)) -EXAMPLE_FILES = frank.xbel jennifer.xbel +EXAMPLE_FILES = jennifer.xbel # install target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/streambookmarks diff --git a/examples/corelib/serialization/streambookmarks/xbelreader.cpp b/examples/corelib/serialization/streambookmarks/xbelreader.cpp index 74e25f12a8..c622cf6642 100644 --- a/examples/corelib/serialization/streambookmarks/xbelreader.cpp +++ b/examples/corelib/serialization/streambookmarks/xbelreader.cpp @@ -1,20 +1,21 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QtWidgets> - #include "xbelreader.h" +#include <QStyle> +#include <QTreeWidget> + +using namespace Qt::StringLiterals; + //! [0] -XbelReader::XbelReader(QTreeWidget *treeWidget) - : treeWidget(treeWidget) +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); + 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] @@ -25,12 +26,10 @@ bool XbelReader::read(QIODevice *device) xml.setDevice(device); if (xml.readNextStartElement()) { - if (xml.name() == QLatin1String("xbel") - && xml.attributes().value(versionAttribute()) == QLatin1String("1.0")) { + if (xml.name() == "xbel"_L1 && xml.attributes().value("version"_L1) == "1.0"_L1) readXBEL(); - } else { + else xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file.")); - } } return !xml.error(); @@ -50,15 +49,15 @@ QString XbelReader::errorString() const //! [3] void XbelReader::readXBEL() { - Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("xbel")); + Q_ASSERT(xml.isStartElement() && xml.name() == "xbel"_L1); 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); + if (xml.name() == "folder"_L1) + readFolder(nullptr); + else if (xml.name() == "bookmark"_L1) + readBookmark(nullptr); + else if (xml.name() == "separator"_L1) + readSeparator(nullptr); else xml.skipCurrentElement(); } @@ -66,75 +65,76 @@ void XbelReader::readXBEL() //! [3] //! [4] -void XbelReader::readTitle(QTreeWidgetItem *item) +void XbelReader::readBookmark(QTreeWidgetItem *item) { - Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("title")); + Q_ASSERT(xml.isStartElement() && xml.name() == "bookmark"_L1); - QString title = xml.readElementText(); - item->setText(0, title); + 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("href"_L1).toString()); + + while (xml.readNextStartElement()) { + if (xml.name() == "title"_L1) + readTitle(bookmark); + else + xml.skipCurrentElement(); + } } //! [4] //! [5] +void XbelReader::readTitle(QTreeWidgetItem *item) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "title"_L1); + item->setText(0, xml.readElementText()); +} +//! [5] + +//! [6] void XbelReader::readSeparator(QTreeWidgetItem *item) { - Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("separator")); + Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1); + constexpr char16_t midDot = u'\xB7'; + static const QString dots(30, midDot); QTreeWidgetItem *separator = createChildItem(item); - separator->setFlags(item->flags() & ~Qt::ItemIsSelectable); - separator->setText(0, QString(30, u'\xB7')); + separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{}); + separator->setText(0, dots); xml.skipCurrentElement(); } -//! [5] +//! [6] +//! [7] void XbelReader::readFolder(QTreeWidgetItem *item) { - Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("folder")); + Q_ASSERT(xml.isStartElement() && xml.name() == "folder"_L1); QTreeWidgetItem *folder = createChildItem(item); - bool folded = (xml.attributes().value(foldedAttribute()) != QLatin1String("no")); + bool folded = xml.attributes().value("folded"_L1) != "no"_L1; folder->setExpanded(!folded); while (xml.readNextStartElement()) { - if (xml.name() == QLatin1String("title")) + if (xml.name() == "title"_L1) readTitle(folder); - else if (xml.name() == QLatin1String("folder")) + else if (xml.name() == "folder"_L1) readFolder(folder); - else if (xml.name() == QLatin1String("bookmark")) + else if (xml.name() == "bookmark"_L1) readBookmark(folder); - else if (xml.name() == QLatin1String("separator")) + else if (xml.name() == "separator"_L1) readSeparator(folder); else xml.skipCurrentElement(); } } +//! [7] -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(); - } -} - +//! [8] QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item) { - QTreeWidgetItem *childItem; - if (item) { - childItem = new QTreeWidgetItem(item); - } else { - childItem = new QTreeWidgetItem(treeWidget); - } + QTreeWidgetItem *childItem = item ? new QTreeWidgetItem(item) : new QTreeWidgetItem(treeWidget); childItem->setData(0, Qt::UserRole, xml.name().toString()); return childItem; } +//! [8] diff --git a/examples/corelib/serialization/streambookmarks/xbelreader.h b/examples/corelib/serialization/streambookmarks/xbelreader.h index 81a59b32b5..a3fa59d813 100644 --- a/examples/corelib/serialization/streambookmarks/xbelreader.h +++ b/examples/corelib/serialization/streambookmarks/xbelreader.h @@ -21,13 +21,8 @@ public: //! [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(); diff --git a/examples/corelib/serialization/streambookmarks/xbelwriter.cpp b/examples/corelib/serialization/streambookmarks/xbelwriter.cpp index 6cfcd2bc1a..e50f47a5a5 100644 --- a/examples/corelib/serialization/streambookmarks/xbelwriter.cpp +++ b/examples/corelib/serialization/streambookmarks/xbelwriter.cpp @@ -1,18 +1,14 @@ // 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"); } +#include <QTreeWidget> + +using namespace Qt::StringLiterals; //! [0] -XbelWriter::XbelWriter(const QTreeWidget *treeWidget) - : treeWidget(treeWidget) +XbelWriter::XbelWriter(const QTreeWidget *treeWidget) : treeWidget(treeWidget) { xml.setAutoFormatting(true); } @@ -24,9 +20,9 @@ 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")); + xml.writeDTD("<!DOCTYPE xbel>"_L1); + xml.writeStartElement("xbel"_L1); + xml.writeAttribute("version"_L1, "1.0"_L1); for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) writeItem(treeWidget->topLevelItem(i)); @@ -39,21 +35,21 @@ bool XbelWriter::writeFile(QIODevice *device) void XbelWriter::writeItem(const QTreeWidgetItem *item) { QString tagName = item->data(0, Qt::UserRole).toString(); - if (tagName == QLatin1String("folder")) { + if (tagName == "folder"_L1) { bool folded = !item->isExpanded(); xml.writeStartElement(tagName); - xml.writeAttribute(XbelReader::foldedAttribute(), folded ? yesValue() : noValue()); - xml.writeTextElement(titleElement(), item->text(0)); + xml.writeAttribute("folded"_L1, folded ? "yes"_L1 : "no"_L1); + xml.writeTextElement("title"_L1, item->text(0)); for (int i = 0; i < item->childCount(); ++i) writeItem(item->child(i)); xml.writeEndElement(); - } else if (tagName == QLatin1String("bookmark")) { + } else if (tagName == "bookmark"_L1) { xml.writeStartElement(tagName); if (!item->text(1).isEmpty()) - xml.writeAttribute(XbelReader::hrefAttribute(), item->text(1)); - xml.writeTextElement(titleElement(), item->text(0)); + xml.writeAttribute("href"_L1, item->text(1)); + xml.writeTextElement("title"_L1, item->text(0)); xml.writeEndElement(); - } else if (tagName == QLatin1String("separator")) { + } else if (tagName == "separator"_L1) { xml.writeEmptyElement(tagName); } } |