QCborValue: move the toDiagnosticNotation() function to its own file
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 <marten.nordheim@qt.io> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
parent
3cd9d6d19b
commit
7cb5b324f0
@ -163,6 +163,58 @@ Q_DECL_CONST_FUNCTION static inline bool qt_is_finite(float f)
|
||||
|
||||
#ifndef Q_CLANG_QDOC
|
||||
namespace {
|
||||
/*!
|
||||
Returns true if the double \a v can be converted to type \c T, false if
|
||||
it's out of range. If the conversion is successful, the converted value is
|
||||
stored in \a value; if it was not successful, \a value will contain the
|
||||
minimum or maximum of T, depending on the sign of \a d. If \c T is
|
||||
unsigned, then \a value contains the absolute value of \a v.
|
||||
|
||||
This function works for v containing infinities, but not NaN. It's the
|
||||
caller's responsibility to exclude that possibility before calling it.
|
||||
*/
|
||||
template <typename T> static inline bool convertDoubleTo(double v, T *value)
|
||||
{
|
||||
Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer);
|
||||
|
||||
// The [conv.fpint] (7.10 Floating-integral conversions) section of the C++
|
||||
// standard says only exact conversions are guaranteed. Converting
|
||||
// integrals to floating-point with loss of precision has implementation-
|
||||
// defined behavior whether the next higher or next lower is returned;
|
||||
// converting FP to integral is UB if it can't be represented.
|
||||
//
|
||||
// That means we can't write UINT64_MAX+1. Writing ldexp(1, 64) would be
|
||||
// correct, but Clang, ICC and MSVC don't realize that it's a constant and
|
||||
// the math call stays in the compiled code.
|
||||
|
||||
double supremum;
|
||||
if (std::numeric_limits<T>::is_signed) {
|
||||
supremum = -1.0 * std::numeric_limits<T>::min(); // -1 * (-2^63) = 2^63, exact (for T = qint64)
|
||||
*value = std::numeric_limits<T>::min();
|
||||
if (v < std::numeric_limits<T>::min())
|
||||
return false;
|
||||
} else {
|
||||
using ST = typename std::make_signed<T>::type;
|
||||
supremum = -2.0 * std::numeric_limits<ST>::min(); // -2 * (-2^63) = 2^64, exact (for T = quint64)
|
||||
v = fabs(v);
|
||||
}
|
||||
|
||||
*value = std::numeric_limits<T>::max();
|
||||
if (v >= supremum)
|
||||
return false;
|
||||
|
||||
// Now we can convert, these two conversions cannot be UB
|
||||
*value = T(v);
|
||||
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_GCC("-Wfloat-equal")
|
||||
QT_WARNING_DISABLE_CLANG("-Wfloat-equal")
|
||||
|
||||
return *value == v;
|
||||
|
||||
QT_WARNING_POP
|
||||
}
|
||||
|
||||
// Overflow math.
|
||||
// This provides efficient implementations for int, unsigned, qsizetype and
|
||||
// size_t. Implementations for 8- and 16-bit types will work but may not be as
|
||||
|
281
src/corelib/serialization/qcbordiagnostic.cpp
Normal file
281
src/corelib/serialization/qcbordiagnostic.cpp
Normal file
@ -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 <private/qnumeric_p.h>
|
||||
#include <qstack.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('"')
|
||||
+ 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("<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(), toJsonDocument(), QJsonDocument::toJson()
|
||||
*/
|
||||
QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const
|
||||
{
|
||||
return DiagnosticNotation::create(*this, opts);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
@ -47,9 +47,7 @@
|
||||
#include <qendian.h>
|
||||
#include <qlocale.h>
|
||||
#include <private/qnumeric_p.h>
|
||||
#include <qscopedvaluerollback.h>
|
||||
#include <private/qsimd_p.h>
|
||||
#include <qstack.h>
|
||||
|
||||
#include <new>
|
||||
|
||||
@ -768,58 +766,6 @@ using namespace QtCbor;
|
||||
// in qcborstream.cpp
|
||||
extern void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error);
|
||||
|
||||
/*!
|
||||
Returns true if the double \a v can be converted to type \c T, false if
|
||||
it's out of range. If the conversion is successful, the converted value is
|
||||
stored in \a value; if it was not successful, \a value will contain the
|
||||
minimum or maximum of T, depending on the sign of \a d. If \c T is
|
||||
unsigned, then \a value contains the absolute value of \a v.
|
||||
|
||||
This function works for v containing infinities, but not NaN. It's the
|
||||
caller's responsibility to exclude that possibility before calling it.
|
||||
*/
|
||||
template <typename T> static inline bool convertDoubleTo(double v, T *value)
|
||||
{
|
||||
Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer);
|
||||
|
||||
// The [conv.fpint] (7.10 Floating-integral conversions) section of the C++
|
||||
// standard says only exact conversions are guaranteed. Converting
|
||||
// integrals to floating-point with loss of precision has implementation-
|
||||
// defined behavior whether the next higher or next lower is returned;
|
||||
// converting FP to integral is UB if it can't be represented.
|
||||
//
|
||||
// That means we can't write UINT64_MAX+1. Writing ldexp(1, 64) would be
|
||||
// correct, but only Clang, ICC and MSVC don't realize that it's a constant
|
||||
// and the math call stays in the compiled code.
|
||||
|
||||
double supremum;
|
||||
if (std::numeric_limits<T>::is_signed) {
|
||||
supremum = -1.0 * std::numeric_limits<T>::min(); // -1 * (-2^63) = 2^63, exact (for T = qint64)
|
||||
*value = std::numeric_limits<T>::min();
|
||||
if (v < std::numeric_limits<T>::min())
|
||||
return false;
|
||||
} else {
|
||||
using ST = typename std::make_signed<T>::type;
|
||||
supremum = -2.0 * std::numeric_limits<ST>::min(); // -2 * (-2^63) = 2^64, exact (for T = quint64)
|
||||
v = fabs(v);
|
||||
}
|
||||
|
||||
*value = std::numeric_limits<T>::max();
|
||||
if (v >= supremum)
|
||||
return false;
|
||||
|
||||
// Now we can convert, these two conversions cannot be UB
|
||||
*value = T(v);
|
||||
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_GCC("-Wfloat-equal")
|
||||
QT_WARNING_DISABLE_CLANG("-Wfloat-equal")
|
||||
|
||||
return *value == v;
|
||||
|
||||
QT_WARNING_POP
|
||||
}
|
||||
|
||||
static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::EncodingOptions opt)
|
||||
{
|
||||
if (qt_is_nan(d)) {
|
||||
@ -869,146 +815,6 @@ static inline int typeOrder(Element e1, Element e2)
|
||||
return comparable(e1) - comparable(e2);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class DiagnosticNotation
|
||||
{
|
||||
public:
|
||||
static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts)
|
||||
{
|
||||
DiagnosticNotation dn(opts);
|
||||
return dn.createFromValue(v);
|
||||
}
|
||||
|
||||
private:
|
||||
QStack<int> byteArrayFormatStack;
|
||||
QCborValue::DiagnosticNotationOptions opts;
|
||||
int nestingLevel = 0;
|
||||
|
||||
DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_)
|
||||
: opts(opts_)
|
||||
{
|
||||
byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16));
|
||||
}
|
||||
|
||||
QString createFromValue(const QCborValue &v);
|
||||
};
|
||||
}
|
||||
|
||||
QString DiagnosticNotation::createFromValue(const QCborValue &v)
|
||||
{
|
||||
QString indent(1, QLatin1Char(' '));
|
||||
QString indented = indent;
|
||||
if (opts & QCborValue::LineWrapped) {
|
||||
indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' '));
|
||||
indented = indent + QLatin1String(" ");
|
||||
}
|
||||
QScopedValueRollback<int> rollback(nestingLevel);
|
||||
++nestingLevel;
|
||||
|
||||
auto createFromArray = [=](const QCborArray &a) {
|
||||
QString result;
|
||||
QLatin1String comma;
|
||||
for (auto v : a) {
|
||||
result += comma + indented + createFromValue(v);
|
||||
comma = QLatin1String(",");
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto createFromMap = [=](const QCborMap &m) {
|
||||
QString result;
|
||||
QLatin1String comma;
|
||||
for (auto v : m) {
|
||||
result += comma + indented + createFromValue(v.first) +
|
||||
QLatin1String(": ") + createFromValue(v.second);
|
||||
comma = QLatin1String(",");
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto 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;
|
||||
};
|
||||
auto isByteArrayEncodingTag = [](QCborTag tag) {
|
||||
switch (quint64(tag)) {
|
||||
case quint64(QCborKnownTags::ExpectedBase16):
|
||||
case quint64(QCborKnownTags::ExpectedBase64):
|
||||
case quint64(QCborKnownTags::ExpectedBase64url):
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (v.type()) {
|
||||
case QCborValue::Integer:
|
||||
return QString::number(v.toInteger());
|
||||
case QCborValue::ByteArray:
|
||||
switch (byteArrayFormatStack.top()) {
|
||||
case int(QCborKnownTags::ExpectedBase16):
|
||||
return QString::fromLatin1("h'" +
|
||||
v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') +
|
||||
'\'');
|
||||
case int(QCborKnownTags::ExpectedBase64):
|
||||
return QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\'');
|
||||
default:
|
||||
case int(QCborKnownTags::ExpectedBase64url):
|
||||
return QString::fromLatin1("b64'" +
|
||||
v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) +
|
||||
'\'');
|
||||
}
|
||||
case QCborValue::String:
|
||||
// ### TODO: Needs escaping!
|
||||
return QLatin1Char('"') + v.toString() + QLatin1Char('"');
|
||||
case QCborValue::Array:
|
||||
return QLatin1Char('[') + createFromArray(v.toArray()) + indent + QLatin1Char(']');
|
||||
case QCborValue::Map:
|
||||
return QLatin1Char('{') + createFromMap(v.toMap()) + indent + QLatin1Char('}');
|
||||
case QCborValue::False:
|
||||
return QStringLiteral("false");
|
||||
case QCborValue::True:
|
||||
return QStringLiteral("true");
|
||||
case QCborValue::Null:
|
||||
return QStringLiteral("null");
|
||||
case QCborValue::Undefined:
|
||||
return QStringLiteral("undefined");
|
||||
case QCborValue::Double:
|
||||
return makeFpString(v.toDouble());
|
||||
case QCborValue::Invalid:
|
||||
return QStringLiteral("<invalid>");
|
||||
|
||||
case QCborValue::Tag:
|
||||
case QCborValue::SimpleType:
|
||||
default: // tags and other simple types that are recognized
|
||||
break; // are all handled below
|
||||
}
|
||||
|
||||
if (v.isTag()) {
|
||||
bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag());
|
||||
if (byteArrayFormat)
|
||||
byteArrayFormatStack.push(int(v.tag()));
|
||||
QString result = QString::fromLatin1("%1(%2)").arg(quint64(v.tag())).arg(createFromValue(v.taggedValue()));
|
||||
if (byteArrayFormat)
|
||||
byteArrayFormatStack.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
// must be a simple type
|
||||
return QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType()));
|
||||
}
|
||||
|
||||
QCborContainerPrivate::~QCborContainerPrivate()
|
||||
{
|
||||
// delete our elements
|
||||
@ -2508,37 +2314,6 @@ Q_NEVER_INLINE void QCborValue::toCbor(QCborStreamWriter &writer, EncodingOption
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates the diagnostic notation equivalent of this CBOR object and return
|
||||
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);
|
||||
}
|
||||
|
||||
void QCborValueRef::toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt)
|
||||
{
|
||||
concrete().toCbor(writer, opt);
|
||||
|
@ -56,6 +56,8 @@
|
||||
#include <private/qglobal_p.h>
|
||||
#include <private/qutfcodec_p.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace QtCbor {
|
||||
|
@ -24,6 +24,7 @@ HEADERS += \
|
||||
|
||||
SOURCES += \
|
||||
serialization/qcborstream.cpp \
|
||||
serialization/qcbordiagnostic.cpp \
|
||||
serialization/qcborvalue.cpp \
|
||||
serialization/qdatastream.cpp \
|
||||
serialization/qjson.cpp \
|
||||
|
@ -95,6 +95,8 @@ private slots:
|
||||
void fromCbor();
|
||||
void validation_data();
|
||||
void validation();
|
||||
void toDiagnosticNotation_data();
|
||||
void toDiagnosticNotation();
|
||||
};
|
||||
|
||||
// Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp)
|
||||
@ -1517,6 +1519,169 @@ void tst_QCborValue::validation()
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QCborValue::toDiagnosticNotation_data()
|
||||
{
|
||||
QTest::addColumn<QCborValue>("v");
|
||||
QTest::addColumn<int>("opts");
|
||||
QTest::addColumn<QString>("expected");
|
||||
QDateTime dt = QDateTime::currentDateTimeUtc();
|
||||
QUuid uuid = QUuid::createUuid();
|
||||
|
||||
QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>();
|
||||
auto st = [](QCborSimpleType t) { return QVariant::fromValue<SimpleTypeWrapper>(t); };
|
||||
auto add = [me](const QCborValue &v, const QString &exp) {
|
||||
auto addRow = [=](const char *prefix) -> QTestData & {
|
||||
QCborValue::Type t = v.type();
|
||||
if (t == QCborValue::Integer)
|
||||
return QTest::addRow("%sInteger:%lld", prefix, v.toInteger());
|
||||
if (t == QCborValue::Double)
|
||||
return QTest::addRow("%sDouble:%g", prefix, v.toDouble());
|
||||
if (t == QCborValue::ByteArray)
|
||||
return QTest::addRow("%sByteArray:%d", prefix, v.toByteArray().size());
|
||||
if (t == QCborValue::String)
|
||||
return QTest::addRow("%sString:%d", prefix, v.toString().size());
|
||||
|
||||
QByteArray typeString = me.valueToKey(t);
|
||||
Q_ASSERT(!typeString.isEmpty());
|
||||
return QTest::newRow(prefix + typeString);
|
||||
};
|
||||
addRow("") << v << int(QCborValue::DiagnosticNotationOptions{}) << exp;
|
||||
addRow("LW:") << v << int(QCborValue::LineWrapped) << exp;
|
||||
addRow("Array:") << QCborValue(QCborArray{v}) << int(QCborValue::DiagnosticNotationOptions{}) << '[' + exp + ']';
|
||||
addRow("Mapped:") << QCborValue(QCborMap{{2, v}}) << int(QCborValue::DiagnosticNotationOptions{}) << "{2: " + exp + '}';
|
||||
addRow("Mapping:") << QCborValue(QCborMap{{v, 2}}) << int(QCborValue::DiagnosticNotationOptions{}) << '{' + exp + ": 2}";
|
||||
};
|
||||
|
||||
// empty arrays and maps
|
||||
QTest::newRow("EmptyArray")
|
||||
<< QCborValue(QCborArray()) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "[]";
|
||||
QTest::newRow("EmptyMap")
|
||||
<< QCborValue(QCborMap()) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "{}";
|
||||
|
||||
add(QCborValue(), "undefined");
|
||||
add(QCborValue::Null, "null");
|
||||
add(false, "false");
|
||||
add(true, "true");
|
||||
add(QCborSimpleType(0), "simple(0)");
|
||||
QTest::newRow("SimpleType-255")
|
||||
<< QCborValue(QCborSimpleType(255)) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "simple(255)";
|
||||
add(0, "0");
|
||||
add(1, "1");
|
||||
add(-1, "-1");
|
||||
add(std::numeric_limits<qint64>::min(), QString::number(std::numeric_limits<qint64>::min()));
|
||||
add(std::numeric_limits<qint64>::max(), QString::number(std::numeric_limits<qint64>::max()));
|
||||
add(0., "0.0");
|
||||
add(1.25, "1.25");
|
||||
add(-1.25, "-1.25");
|
||||
add(qInf(), "inf");
|
||||
add(-qInf(), "-inf");
|
||||
add(qQNaN(), "nan");
|
||||
add(QByteArray(), "h''");
|
||||
add(QByteArray("Hello"), "h'48656c6c6f'");
|
||||
add(QLatin1String(), QLatin1String("\"\""));
|
||||
add("Hello", "\"Hello\"");
|
||||
add("\"Hello\\World\"", "\"\\\"Hello\\\\World\\\"\"");
|
||||
add(QCborValue(dt), "0(\"" + dt.toString(Qt::ISODateWithMs) + "\")");
|
||||
add(QCborValue(QUrl("http://example.com")), "32(\"http://example.com\")");
|
||||
add(QCborValue(QRegularExpression("^.*$")), "35(\"^.*$\")");
|
||||
add(QCborValue(uuid), "37(h'" + uuid.toString(QUuid::Id128) + "')");
|
||||
|
||||
// arrays and maps with more than one element
|
||||
QTest::newRow("2Array")
|
||||
<< QCborValue(QCborArray{0, 1}) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "[0, 1]";
|
||||
QTest::newRow("2Map")
|
||||
<< QCborValue(QCborMap{{0, 1}, {"foo", "bar"}}) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "{0: 1, \"foo\": \"bar\"}";
|
||||
|
||||
// line wrapping in arrays and maps
|
||||
QTest::newRow("LW:EmptyArray")
|
||||
<< QCborValue(QCborArray()) << int(QCborValue::LineWrapped)
|
||||
<< "[\n]";
|
||||
QTest::newRow("LW:EmptyMap")
|
||||
<< QCborValue(QCborMap()) << int(QCborValue::LineWrapped)
|
||||
<< "{\n}";
|
||||
QTest::newRow("LW:Array:Integer:0")
|
||||
<< QCborValue(QCborArray{0}) << int(QCborValue::LineWrapped)
|
||||
<< "[\n 0\n]";
|
||||
QTest::newRow("LW:Array:String:5")
|
||||
<< QCborValue(QCborArray{"Hello"}) << int(QCborValue::LineWrapped)
|
||||
<< "[\n \"Hello\"\n]";
|
||||
QTest::newRow("LW:Map:0-0")
|
||||
<< QCborValue(QCborMap{{0, 0}}) << int(QCborValue::LineWrapped)
|
||||
<< "{\n 0: 0\n}";
|
||||
QTest::newRow("LW:Map:String:5")
|
||||
<< QCborValue(QCborMap{{0, "Hello"}}) << int(QCborValue::LineWrapped)
|
||||
<< "{\n 0: \"Hello\"\n}";
|
||||
QTest::newRow("LW:2Array")
|
||||
<< QCborValue(QCborArray{0, 1}) << int(QCborValue::LineWrapped)
|
||||
<< "[\n 0,\n 1\n]";
|
||||
QTest::newRow("LW:2Map")
|
||||
<< QCborValue(QCborMap{{0, 0}, {"foo", "bar"}}) << int(QCborValue::LineWrapped)
|
||||
<< "{\n 0: 0,\n \"foo\": \"bar\"\n}";
|
||||
|
||||
// nested arrays and maps
|
||||
QTest::newRow("Array:EmptyArray")
|
||||
<< QCborValue(QCborArray() << QCborArray()) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "[[]]";
|
||||
QTest::newRow("Array:EmptyMap")
|
||||
<< QCborValue(QCborArray() << QCborMap()) << int(QCborValue::DiagnosticNotationOptions{})
|
||||
<< "[{}]";
|
||||
QTest::newRow("LW:Array:EmptyArray")
|
||||
<< QCborValue(QCborArray() << QCborArray()) << int(QCborValue::LineWrapped)
|
||||
<< "[\n [\n ]\n]";
|
||||
QTest::newRow("LW:Array:EmptyMap")
|
||||
<< QCborValue(QCborArray() << QCborMap()) << int(QCborValue::LineWrapped)
|
||||
<< "[\n {\n }\n]";
|
||||
QTest::newRow("LW:Array:2Array")
|
||||
<< QCborValue(QCborArray() << QCborArray{0, 1}) << int(QCborValue::LineWrapped)
|
||||
<< "[\n [\n 0,\n 1\n ]\n]";
|
||||
QTest::newRow("LW:Map:2Array")
|
||||
<< QCborValue(QCborMap{{0, QCborArray{0, 1}}}) << int(QCborValue::LineWrapped)
|
||||
<< "{\n 0: [\n 0,\n 1\n ]\n}";
|
||||
QTest::newRow("LW:Map:2Map")
|
||||
<< QCborValue(QCborMap{{-1, QCborMap{{0, 0}, {"foo", "bar"}}}}) << int(QCborValue::LineWrapped)
|
||||
<< "{\n -1: {\n 0: 0,\n \"foo\": \"bar\"\n }\n}";
|
||||
|
||||
// extended formatting for byte arrays
|
||||
QTest::newRow("Extended:ByteArray:0")
|
||||
<< QCborValue(QByteArray()) << int(QCborValue::ExtendedFormat)
|
||||
<< "h''";
|
||||
QTest::newRow("Extended:ByteArray:5")
|
||||
<< QCborValue(QByteArray("Hello")) << int(QCborValue::ExtendedFormat)
|
||||
<< "h'48 65 6c 6c 6f'";
|
||||
QTest::newRow("Extended:ByteArray:Base64url")
|
||||
<< QCborValue(QCborKnownTags::ExpectedBase64url, QByteArray("\xff\xef"))
|
||||
<< int(QCborValue::ExtendedFormat) << "21(b64'_-8')";
|
||||
QTest::newRow("Extended:ByteArray:Base64")
|
||||
<< QCborValue(QCborKnownTags::ExpectedBase64, QByteArray("\xff\xef"))
|
||||
<< int(QCborValue::ExtendedFormat) << "22(b64'/+8=')";
|
||||
|
||||
// formatting applies through arrays too
|
||||
QTest::newRow("Extended:Array:ByteArray:Base64url")
|
||||
<< QCborValue(QCborKnownTags::ExpectedBase64url, QCborArray{QByteArray("\xff\xef")})
|
||||
<< int(QCborValue::ExtendedFormat) << "21([b64'_-8'])";
|
||||
// and only the innermost applies
|
||||
QTest::newRow("ByteArray:multiple-tags")
|
||||
<< QCborValue(QCborKnownTags::ExpectedBase64url,
|
||||
QCborArray{QCborValue(QCborKnownTags::ExpectedBase16, QByteArray("Hello")),
|
||||
QByteArray("\xff\xef")})
|
||||
<< int(QCborValue::ExtendedFormat) << "21([23(h'48 65 6c 6c 6f'), b64'_-8'])";
|
||||
}
|
||||
|
||||
void tst_QCborValue::toDiagnosticNotation()
|
||||
{
|
||||
QFETCH(QCborValue, v);
|
||||
QFETCH(QString, expected);
|
||||
QFETCH(int, opts);
|
||||
|
||||
QString result = v.toDiagnosticNotation(QCborValue::DiagnosticNotationOptions(opts));
|
||||
QCOMPARE(result, expected);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QCborValue)
|
||||
|
||||
#include "tst_qcborvalue.moc"
|
||||
|
@ -300,12 +300,12 @@ void tst_QCborValue_Json::nonStringKeysInMaps_data()
|
||||
QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE=";
|
||||
QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01";
|
||||
|
||||
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[ ]";
|
||||
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{ }";
|
||||
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[]";
|
||||
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{}";
|
||||
QTest::newRow("array") << QCborValue(QCborArray{1, true, 2.5, "Hello"})
|
||||
<< "[ 1, true, 2.5, \"Hello\" ]";
|
||||
<< "[1, true, 2.5, \"Hello\"]";
|
||||
QTest::newRow("map") << QCborValue(QCborMap{{"Hello", 0}, {0, "Hello"}})
|
||||
<< "{ \"Hello\": 0, 0: \"Hello\" }";
|
||||
<< "{\"Hello\": 0, 0: \"Hello\"}";
|
||||
|
||||
QDateTime dt = QDateTime::currentDateTimeUtc();
|
||||
QUrl url("https://example.com");
|
||||
|
Loading…
x
Reference in New Issue
Block a user