From 7cb5b324f05b3d9e530856bd0eb2ddf4e34b2f55 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 14 Jul 2018 16:42:08 -0700 Subject: QCborValue: move the toDiagnosticNotation() function to its own file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we ever need to add QCborValue to the bootstrap library, it's unlikely that we'll need this part. And by splitting it, I can make the code handle more cases, that hadn't been properly handled before. Change-Id: I2f630efbbce54f14bfa9fffd154160c0ad893695 Reviewed-by: MÃ¥rten Nordheim Reviewed-by: Edward Welbourne --- src/corelib/serialization/qcbordiagnostic.cpp | 281 ++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/corelib/serialization/qcbordiagnostic.cpp (limited to 'src/corelib/serialization/qcbordiagnostic.cpp') diff --git a/src/corelib/serialization/qcbordiagnostic.cpp b/src/corelib/serialization/qcbordiagnostic.cpp new file mode 100644 index 0000000000..8e641ff7cf --- /dev/null +++ b/src/corelib/serialization/qcbordiagnostic.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcborvalue.h" +#include "qcborvalue_p.h" + +#include "qcborarray.h" +#include "qcbormap.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { +class DiagnosticNotation +{ +public: + static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts) + { + DiagnosticNotation dn(opts); + dn.appendValue(v); + return dn.result; + } + +private: + QStack byteArrayFormatStack; + QString separator; + QString result; + QCborValue::DiagnosticNotationOptions opts; + int nestingLevel = 0; + + struct Nest { + enum { IndentationWidth = 4 }; + DiagnosticNotation *dn; + Nest(DiagnosticNotation *that) : dn(that) + { + ++dn->nestingLevel; + static const char indent[IndentationWidth + 1] = " "; + if (dn->opts & QCborValue::LineWrapped) + dn->separator += QLatin1String(indent, IndentationWidth); + } + ~Nest() + { + --dn->nestingLevel; + if (dn->opts & QCborValue::LineWrapped) + dn->separator.chop(IndentationWidth); + } + }; + + DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_) + : separator(QLatin1String(opts_ & QCborValue::LineWrapped ? "\n" : "")), opts(opts_) + { + byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16)); + } + + void appendString(const QString &s); + void appendArray(const QCborArray &a); + void appendMap(const QCborMap &m); + void appendValue(const QCborValue &v); +}; +} + +static QString makeFpString(double d) +{ + QString s; + quint64 v; + if (qt_is_inf(d)) { + s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf"); + } else if (qt_is_nan(d)) { + s = QStringLiteral("nan"); + } else if (convertDoubleTo(d, &v)) { + s = QString::fromLatin1("%1.0").arg(v); + if (d < 0) + s.prepend(QLatin1Char('-')); + } else { + s = QString::number(d, 'g', QLocale::FloatingPointShortest); + if (!s.contains(QLatin1Char('.')) && !s.contains('e')) + s += QLatin1Char('.'); + } + return s; +} + +static bool isByteArrayEncodingTag(QCborTag tag) +{ + switch (quint64(tag)) { + case quint64(QCborKnownTags::ExpectedBase16): + case quint64(QCborKnownTags::ExpectedBase64): + case quint64(QCborKnownTags::ExpectedBase64url): + return true; + } + return false; +} + +void DiagnosticNotation::appendString(const QString &s) +{ + result += QLatin1Char('"') + + QString(s) + .replace(QLatin1Char('\\'), QLatin1String("\\\\")) + .replace(QLatin1Char('"'), QLatin1String("\\\"")) + + QLatin1Char('"'); +} + +void DiagnosticNotation::appendArray(const QCborArray &a) +{ + result += QLatin1Char('['); + + // length 2 (including the space) when not line wrapping + QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2); + { + Nest n(this); + QLatin1String comma; + for (auto v : a) { + result += comma + separator; + comma = commaValue; + appendValue(v); + } + } + + result += separator + QLatin1Char(']'); +} + +void DiagnosticNotation::appendMap(const QCborMap &m) +{ + result += QLatin1Char('{'); + + // length 2 (including the space) when not line wrapping + QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2); + { + Nest n(this); + QLatin1String comma; + for (auto v : m) { + result += comma + separator; + comma = commaValue; + appendValue(v.first); + result += QLatin1String(": "); + appendValue(v.second); + } + } + + result += separator + QLatin1Char('}'); +}; + +void DiagnosticNotation::appendValue(const QCborValue &v) +{ + switch (v.type()) { + case QCborValue::Integer: + result += QString::number(v.toInteger()); + return; + case QCborValue::ByteArray: + switch (byteArrayFormatStack.top()) { + case int(QCborKnownTags::ExpectedBase16): + result += QString::fromLatin1("h'" + + v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') + + '\''); + return; + case int(QCborKnownTags::ExpectedBase64): + result += QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\''); + return; + default: + case int(QCborKnownTags::ExpectedBase64url): + result += QString::fromLatin1("b64'" + + v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + + '\''); + return; + } + case QCborValue::String: + return appendString(v.toString()); + case QCborValue::Array: + return appendArray(v.toArray()); + case QCborValue::Map: + return appendMap(v.toMap()); + case QCborValue::False: + result += QLatin1String("false"); + return; + case QCborValue::True: + result += QLatin1String("true"); + return; + case QCborValue::Null: + result += QLatin1String("null"); + return; + case QCborValue::Undefined: + result += QLatin1String("undefined"); + return; + case QCborValue::Double: + result += makeFpString(v.toDouble()); + return; + case QCborValue::Invalid: + result += QStringLiteral(""); + return; + + default: + // Only tags, extended types, and simple types remain; see below. + break; + } + + if (v.isTag()) { + // We handle all extended types as regular tags, so it won't matter + // whether we understand that tag or not. + bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag()); + if (byteArrayFormat) + byteArrayFormatStack.push(int(v.tag())); + result += QString::number(quint64(v.tag())) + QLatin1Char('('); + appendValue(v.taggedValue()); + result += QLatin1Char(')'); + if (byteArrayFormat) + byteArrayFormatStack.pop(); + } else { + // must be a simple type + result += QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType())); + } +} + +/*! + Creates the diagnostic notation equivalent of this CBOR object and returns + it. The \a opts parameter controls the dialect of the notation. Diagnostic + notation is useful in debugging, to aid the developer in understanding what + value is stored in the QCborValue or in a CBOR stream. For that reason, the + Qt API provides no support for parsing the diagnostic back into the + in-memory format or CBOR stream, though the representation is unique and it + would be possible. + + CBOR diagnostic notation is specified by + \l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049. + It is a text representation of the CBOR stream and it is very similar to + JSON, but it supports the CBOR types not found in JSON. The extended format + enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is + currently in some IETF drafts and its format is subject to change. + + This function produces the equivalent representation of the stream that + toCbor() would produce, without any transformation option provided there. + This also implies this function may not produce a representation of the + stream that was used to create the object, if it was created using + fromCbor(), as that function may have applied transformations. For a + high-fidelity notation of a stream, without transformation, see the \c + cbordump example. + + \sa toCbor(), toJsonDocument(), QJsonDocument::toJson() + */ +QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const +{ + return DiagnosticNotation::create(*this, opts); +} + +QT_END_NAMESPACE -- cgit v1.2.3