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
|
#ifndef Q_CLANG_QDOC
|
||||||
namespace {
|
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.
|
// Overflow math.
|
||||||
// This provides efficient implementations for int, unsigned, qsizetype and
|
// 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
|
// 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 <qendian.h>
|
||||||
#include <qlocale.h>
|
#include <qlocale.h>
|
||||||
#include <private/qnumeric_p.h>
|
#include <private/qnumeric_p.h>
|
||||||
#include <qscopedvaluerollback.h>
|
|
||||||
#include <private/qsimd_p.h>
|
#include <private/qsimd_p.h>
|
||||||
#include <qstack.h>
|
|
||||||
|
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
@ -768,58 +766,6 @@ using namespace QtCbor;
|
|||||||
// in qcborstream.cpp
|
// in qcborstream.cpp
|
||||||
extern void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error);
|
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)
|
static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::EncodingOptions opt)
|
||||||
{
|
{
|
||||||
if (qt_is_nan(d)) {
|
if (qt_is_nan(d)) {
|
||||||
@ -869,146 +815,6 @@ static inline int typeOrder(Element e1, Element e2)
|
|||||||
return comparable(e1) - comparable(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()
|
QCborContainerPrivate::~QCborContainerPrivate()
|
||||||
{
|
{
|
||||||
// delete our elements
|
// 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)
|
void QCborValueRef::toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt)
|
||||||
{
|
{
|
||||||
concrete().toCbor(writer, opt);
|
concrete().toCbor(writer, opt);
|
||||||
|
@ -56,6 +56,8 @@
|
|||||||
#include <private/qglobal_p.h>
|
#include <private/qglobal_p.h>
|
||||||
#include <private/qutfcodec_p.h>
|
#include <private/qutfcodec_p.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
namespace QtCbor {
|
namespace QtCbor {
|
||||||
|
@ -24,6 +24,7 @@ HEADERS += \
|
|||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
serialization/qcborstream.cpp \
|
serialization/qcborstream.cpp \
|
||||||
|
serialization/qcbordiagnostic.cpp \
|
||||||
serialization/qcborvalue.cpp \
|
serialization/qcborvalue.cpp \
|
||||||
serialization/qdatastream.cpp \
|
serialization/qdatastream.cpp \
|
||||||
serialization/qjson.cpp \
|
serialization/qjson.cpp \
|
||||||
|
@ -95,6 +95,8 @@ private slots:
|
|||||||
void fromCbor();
|
void fromCbor();
|
||||||
void validation_data();
|
void validation_data();
|
||||||
void validation();
|
void validation();
|
||||||
|
void toDiagnosticNotation_data();
|
||||||
|
void toDiagnosticNotation();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp)
|
// 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)
|
QTEST_MAIN(tst_QCborValue)
|
||||||
|
|
||||||
#include "tst_qcborvalue.moc"
|
#include "tst_qcborvalue.moc"
|
||||||
|
@ -300,12 +300,12 @@ void tst_QCborValue_Json::nonStringKeysInMaps_data()
|
|||||||
QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE=";
|
QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE=";
|
||||||
QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01";
|
QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01";
|
||||||
|
|
||||||
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[ ]";
|
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[]";
|
||||||
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{ }";
|
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{}";
|
||||||
QTest::newRow("array") << QCborValue(QCborArray{1, true, 2.5, "Hello"})
|
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"}})
|
QTest::newRow("map") << QCborValue(QCborMap{{"Hello", 0}, {0, "Hello"}})
|
||||||
<< "{ \"Hello\": 0, 0: \"Hello\" }";
|
<< "{\"Hello\": 0, 0: \"Hello\"}";
|
||||||
|
|
||||||
QDateTime dt = QDateTime::currentDateTimeUtc();
|
QDateTime dt = QDateTime::currentDateTimeUtc();
|
||||||
QUrl url("https://example.com");
|
QUrl url("https://example.com");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user