diff options
Diffstat (limited to 'src/corelib/serialization/qcbordiagnostic.cpp')
-rw-r--r-- | src/corelib/serialization/qcbordiagnostic.cpp | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/src/corelib/serialization/qcbordiagnostic.cpp b/src/corelib/serialization/qcbordiagnostic.cpp new file mode 100644 index 0000000000..75feaded17 --- /dev/null +++ b/src/corelib/serialization/qcbordiagnostic.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** 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 <private/qnumeric_p.h> +#include <qstack.h> +#include <private/qtools_p.h> + +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<int> 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('"'); + + const QChar *begin = s.begin(); + const QChar *end = s.end(); + while (begin < end) { + // find the longest span comprising only non-escaped characters + const QChar *ptr = begin; + for ( ; ptr < end; ++ptr) { + ushort uc = ptr->unicode(); + if (uc == '\\' || uc == '"' || uc < ' ' || uc >= 0x7f) + break; + } + + if (ptr != begin) + result.append(begin, ptr - begin); + + if (ptr == end) + break; + + // there's an escaped character + static const char escapeMap[16] = { + // The C escape characters \a \b \t \n \v \f and \r indexed by + // their ASCII values + 0, 0, 0, 0, + 0, 0, 0, 'a', + 'b', 't', 'n', 'v', + 'f', 'r', 0, 0 + }; + int buflen = 2; + QChar buf[10]; + buf[0] = QLatin1Char('\\'); + buf[1] = QChar::Null; + char16_t uc = ptr->unicode(); + + if (uc < sizeof(escapeMap)) + buf[1] = QLatin1Char(escapeMap[uc]); + else if (uc == '"' || uc == '\\') + buf[1] = QChar(uc); + + if (buf[1] == QChar::Null) { + using QtMiscUtils::toHexUpper; + if (ptr->isHighSurrogate() && (ptr + 1) != end && ptr[1].isLowSurrogate()) { + // properly-paired surrogates + ++ptr; + char32_t ucs4 = QChar::surrogateToUcs4(uc, ptr->unicode()); + buf[1] = 'U'; + buf[2] = '0'; // toHexUpper(ucs4 >> 28); + buf[3] = '0'; // toHexUpper(ucs4 >> 24); + buf[4] = toHexUpper(ucs4 >> 20); + buf[5] = toHexUpper(ucs4 >> 16); + buf[6] = toHexUpper(ucs4 >> 12); + buf[7] = toHexUpper(ucs4 >> 8); + buf[8] = toHexUpper(ucs4 >> 4); + buf[9] = toHexUpper(ucs4); + buflen = 10; + } else { + buf[1] = 'u'; + buf[2] = toHexUpper(uc >> 12); + buf[3] = toHexUpper(uc >> 8); + buf[4] = toHexUpper(uc >> 4); + buf[5] = toHexUpper(uc); + buflen = 6; + } + } + + result.append(buf, buflen); + begin = ptr + 1; + } + + result += 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("<invalid>"); + 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(), QJsonDocument::toJson() + */ +QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const +{ + return DiagnosticNotation::create(*this, opts); +} + +QT_END_NAMESPACE |