Pretty: add the ability to stream out to a generic thing
Instead of always requiring FILE *. This does mean a slight code size
increase, since we now have to pass an extra parameter down the function
stack, plus we're making indirect calls, but it's worth it.
Signed-off-by: Thiago Macieira <thiago.macieira@intel.com>
diff --git a/src/cbor.h b/src/cbor.h
index e55ed1f..6a09a67 100644
--- a/src/cbor.h
+++ b/src/cbor.h
@@ -591,6 +591,13 @@
CborPrettyDefaultFlags = CborPrettyIndicateIndetermineLength
};
+typedef CborError (*CborStreamFunction)(void *token, const char *fmt, ...)
+#ifdef __GNUC__
+ __attribute__((__format__(printf, 2, 3)))
+#endif
+;
+
+CBOR_API CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags);
CBOR_API CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags);
CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value);
CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value)
diff --git a/src/cborpretty.c b/src/cborpretty.c
index 4ffe31f..f38d676 100644
--- a/src/cborpretty.c
+++ b/src/cborpretty.c
@@ -36,6 +36,7 @@
#include <float.h>
#include <inttypes.h>
#include <math.h>
+#include <stdarg.h>
#include <stdio.h>
#include <string.h>
@@ -147,29 +148,42 @@
* \value CborPrettyDefaultFlags Default conversion flags.
*/
-static void printRecursionLimit(FILE *out)
+static CborError cbor_fprintf(void *out, const char *fmt, ...)
{
- fputs("<nesting too deep, recursion stopped>", out);
+ int n;
+
+ va_list list;
+ va_start(list, fmt);
+ n = vfprintf((FILE *)out, fmt, list);
+ va_end(list);
+
+ return n < 0 ? CborErrorIO : CborNoError;
}
-static CborError hexDump(FILE *out, const void *ptr, size_t n)
+static void printRecursionLimit(CborStreamFunction stream, void *out)
+{
+ stream(out, "<nesting too deep, recursion stopped>");
+}
+
+static CborError hexDump(CborStreamFunction stream, void *out, const void *ptr, size_t n)
{
const uint8_t *buffer = (const uint8_t *)ptr;
- while (n--) {
- int r = fprintf(out, "%02" PRIx8, *buffer++);
- if (r < 0)
- return CborErrorIO;
- }
- return CborNoError;
+ CborError err = CborNoError;
+ while (n-- && !err)
+ err = stream(out, "%02" PRIx8, *buffer++);
+
+ return err;
}
/* This function decodes buffer as UTF-8 and prints as escaped UTF-16.
* On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */
-static CborError utf8EscapedDump(FILE *out, const void *ptr, size_t n)
+static CborError utf8EscapedDump(CborStreamFunction stream, void *out, const void *ptr, size_t n)
{
const uint8_t *buffer = (const uint8_t *)ptr;
const uint8_t * const end = buffer + n;
- while (buffer < end) {
+ CborError err = CborNoError;
+
+ while (buffer < end && !err) {
uint32_t uc = get_utf8(&buffer, end);
if (uc == ~0U)
return CborErrorInvalidUtf8TextString;
@@ -177,8 +191,7 @@
if (uc < 0x80) {
/* single-byte UTF-8 */
if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') {
- if (fprintf(out, "%c", (char)uc) < 0)
- return CborErrorIO;
+ err = stream(out, "%c", (char)uc);
continue;
}
@@ -206,26 +219,23 @@
default:
goto print_utf16;
}
- if (fprintf(out, "\\%c", escaped) < 0)
- return CborErrorIO;
+ err = stream(out, "\\%c", escaped);
continue;
}
/* now print the sequence */
if (uc > 0xffffU) {
/* needs surrogate pairs */
- if (fprintf(out, "\\u%04" PRIX32 "\\u%04" PRIX32,
- (uc >> 10) + 0xd7c0, /* high surrogate */
- (uc % 0x0400) + 0xdc00) < 0)
- return CborErrorIO;
+ err = stream(out, "\\u%04" PRIX32 "\\u%04" PRIX32,
+ (uc >> 10) + 0xd7c0, /* high surrogate */
+ (uc % 0x0400) + 0xdc00);
} else {
print_utf16:
/* no surrogate pair needed */
- if (fprintf(out, "\\u%04" PRIX32, uc) < 0)
- return CborErrorIO;
+ err = stream(out, "\\u%04" PRIX32, uc);
}
}
- return CborNoError;
+ return err;
}
static const char *resolve_indicator(const uint8_t *ptr, const uint8_t *end, int flags)
@@ -278,41 +288,39 @@
return resolve_indicator(it->ptr, it->parser->end, flags);
}
-static CborError value_to_pretty(FILE *out, CborValue *it, int flags, int recursionsLeft);
-static CborError container_to_pretty(FILE *out, CborValue *it, CborType containerType,
+static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft);
+static CborError container_to_pretty(CborStreamFunction stream, void *out, CborValue *it, CborType containerType,
int flags, int recursionsLeft)
{
if (!recursionsLeft) {
- printRecursionLimit(out);
+ printRecursionLimit(stream, out);
return CborNoError; /* do allow the dumping to continue */
}
const char *comma = "";
- while (!cbor_value_at_end(it)) {
- if (fprintf(out, "%s", comma) < 0)
- return CborErrorIO;
+ CborError err = CborNoError;
+ while (!cbor_value_at_end(it) && !err) {
+ err = stream(out, "%s", comma);
comma = ", ";
- CborError err = value_to_pretty(out, it, flags, recursionsLeft);
- if (err)
- return err;
+ if (!err)
+ err = value_to_pretty(stream, out, it, flags, recursionsLeft);
if (containerType == CborArrayType)
continue;
/* map: that was the key, so get the value */
- if (fprintf(out, ": ") < 0)
- return CborErrorIO;
- err = value_to_pretty(out, it, flags, recursionsLeft);
- if (err)
- return err;
+ if (!err)
+ err = stream(out, ": ");
+ if (!err)
+ err = value_to_pretty(stream, out, it, flags, recursionsLeft);
}
- return CborNoError;
+ return err;
}
-static CborError value_to_pretty(FILE *out, CborValue *it, int flags, int recursionsLeft)
+static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft)
{
- CborError err;
+ CborError err = CborNoError;
CborType type = cbor_value_get_type(it);
switch (type) {
case CborArrayType:
@@ -322,15 +330,16 @@
const char *indicator = get_indicator(it, flags);
const char *space = *indicator ? " " : indicator;
- if (fprintf(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space) < 0)
- return CborErrorIO;
+ err = stream(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space);
+ if (err)
+ return err;
err = cbor_value_enter_container(it, &recursed);
if (err) {
it->ptr = recursed.ptr;
return err; /* parse error */
}
- err = container_to_pretty(out, &recursed, type, flags, recursionsLeft - 1);
+ err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1);
if (err) {
it->ptr = recursed.ptr;
return err; /* parse error */
@@ -339,9 +348,7 @@
if (err)
return err; /* parse error */
- if (fprintf(out, type == CborArrayType ? "]" : "}") < 0)
- return CborErrorIO;
- return CborNoError;
+ return stream(out, type == CborArrayType ? "]" : "}");
}
case CborIntegerType: {
@@ -349,24 +356,21 @@
cbor_value_get_raw_integer(it, &val); /* can't fail */
if (cbor_value_is_unsigned_integer(it)) {
- if (fprintf(out, "%" PRIu64, val) < 0)
- return CborErrorIO;
+ err = stream(out, "%" PRIu64, val);
} else {
/* CBOR stores the negative number X as -1 - X
* (that is, -1 is stored as 0, -2 as 1 and so forth) */
if (++val) { /* unsigned overflow may happen */
- if (fprintf(out, "-%" PRIu64, val) < 0)
- return CborErrorIO;
+ err = stream(out, "-%" PRIu64, val);
} else {
/* overflown
* 0xffff`ffff`ffff`ffff + 1 =
* 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */
- if (fprintf(out, "-18446744073709551616") < 0)
- return CborErrorIO;
+ err = stream(out, "-18446744073709551616");
}
}
- if (fprintf(out, "%s", get_indicator(it, flags)) < 0)
- return CborErrorIO;
+ if (!err)
+ err = stream(out, "%s", get_indicator(it, flags));
break;
}
@@ -386,90 +390,79 @@
}
if (showingFragments) {
- if (fputs("(_ ", out) < 0)
- return CborErrorIO;
- err = _cbor_value_prepare_string_iteration(it);
- if (err)
- return err;
+ err = stream(out, "(_ ");
+ if (!err)
+ err = _cbor_value_prepare_string_iteration(it);
} else {
- if (fputs(open, out) < 0)
- return CborErrorIO;
+ err = stream(out, "%s", open);
}
- while (1) {
+ while (!err) {
if (showingFragments || indicator == NULL) {
/* any iteration, except the second for a non-chunked string */
indicator = resolve_indicator(it->ptr, it->parser->end, flags);
}
err = _cbor_value_get_string_chunk(it, &ptr, &n, it);
- if (err)
- return err;
if (!ptr)
break;
- if (showingFragments && fprintf(out, "%s%s", separator, open) < 0)
- return CborErrorIO;
- err = (type == CborByteStringType ? hexDump(out, ptr, n) : utf8EscapedDump(out, ptr, n));
- if (err)
- return err;
- if (showingFragments) {
- if (fprintf(out, "%c%s", close, indicator) < 0)
- return CborErrorIO;
+ if (!err && showingFragments)
+ err = stream(out, "%s%s", separator, open);
+ if (!err)
+ err = (type == CborByteStringType ?
+ hexDump(stream, out, ptr, n) :
+ utf8EscapedDump(stream, out, ptr, n));
+ if (!err && showingFragments) {
+ err = stream(out, "%c%s", close, indicator);
separator = ", ";
}
}
- if (showingFragments && fputc(')', out) < 0)
- return CborErrorIO;
- if (!showingFragments && fprintf(out, "%c%s", close, indicator) < 0)
- return CborErrorIO;
- return CborNoError;
+ if (!err) {
+ if (showingFragments)
+ err = stream(out, ")");
+ else
+ err = stream(out, "%c%s", close, indicator);
+ }
+ return err;
}
case CborTagType: {
CborTag tag;
cbor_value_get_tag(it, &tag); /* can't fail */
- if (fprintf(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags)) < 0)
- return CborErrorIO;
- err = cbor_value_advance_fixed(it);
- if (err)
- return err;
- if (recursionsLeft)
- err = value_to_pretty(out, it, flags, recursionsLeft - 1);
- else
- printRecursionLimit(out);
- if (err)
- return err;
- if (fprintf(out, ")") < 0)
- return CborErrorIO;
- return CborNoError;
+ err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags));
+ if (!err)
+ err = cbor_value_advance_fixed(it);
+ if (!err && recursionsLeft)
+ err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1);
+ else if (!err)
+ printRecursionLimit(stream, out);
+ if (!err)
+ err = stream(out, ")");
+ return err;
}
case CborSimpleType: {
/* simple types can't fail and can't have overlong encoding */
uint8_t simple_type;
cbor_value_get_simple_type(it, &simple_type);
- if (fprintf(out, "simple(%" PRIu8 ")", simple_type) < 0)
- return CborErrorIO;
+ err = stream(out, "simple(%" PRIu8 ")", simple_type);
break;
}
case CborNullType:
- if (fprintf(out, "null") < 0)
- return CborErrorIO;
+ err = stream(out, "null");
break;
case CborUndefinedType:
- if (fprintf(out, "undefined") < 0)
- return CborErrorIO;
+ err = stream(out, "undefined");
break;
case CborBooleanType: {
bool val;
cbor_value_get_boolean(it, &val); /* can't fail */
- if (fprintf(out, val ? "true" : "false") < 0)
- return CborErrorIO;
+ err = stream(out, val ? "true" : "false");
break;
}
@@ -504,27 +497,51 @@
if (ival == fabs(val)) {
/* this double value fits in a 64-bit integer, so show it as such
* (followed by a floating point suffix, to disambiguate) */
- r = fprintf(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix);
+ err = stream(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix);
} else {
/* this number is definitely not a 64-bit integer */
- r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix);
+ err = stream(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix);
}
- if (r < 0)
- return CborErrorIO;
break;
}
case CborInvalidType:
- if (fprintf(out, "invalid") < 0)
- return CborErrorIO;
+ err = stream(out, "invalid");
+ if (err)
+ return err;
return CborErrorUnknownType;
}
- err = cbor_value_advance_fixed(it);
+ if (!err)
+ err = cbor_value_advance_fixed(it);
return err;
}
/**
+ * Converts the current CBOR type pointed by \a value to its textual
+ * representation and writes it to the stream by calling the \a streamFunction.
+ * If an error occurs, this function returns an error code similar to
+ * CborParsing.
+ *
+ * The textual representation can be controlled by the \a flags parameter (see
+ * CborPrettyFlags for more information).
+ *
+ * If no error ocurred, this function advances \a value to the next element.
+ * Often, concatenating the text representation of multiple elements can be
+ * done by appending a comma to the output stream.
+ *
+ * The \a streamFunction function will be called with the \a token value as the
+ * first parameter and a printf-style format string as the second, with a variable
+ * number of further parameters.
+ *
+ * \sa cbor_value_to_pretty(), cbor_value_to_json_advance()
+ */
+CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags)
+{
+ return value_to_pretty(streamFunction, token, value, flags, CBOR_PARSER_MAX_RECURSIONS);
+}
+
+/**
* \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value)
*
* Converts the current CBOR type pointed by \a value to its textual
@@ -543,11 +560,11 @@
* Often, concatenating the text representation of multiple elements can be
* done by appending a comma to the output stream.
*
- * \sa cbor_value_to_pretty(), cbor_value_to_json_advance()
+ * \sa cbor_value_to_pretty(), cbor_value_to_pretty_stream(), cbor_value_to_json_advance()
*/
CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value)
{
- return value_to_pretty(out, value, CborPrettyDefaultFlags, CBOR_PARSER_MAX_RECURSIONS);
+ return value_to_pretty(cbor_fprintf, out, value, CborPrettyDefaultFlags, CBOR_PARSER_MAX_RECURSIONS);
}
/**
@@ -562,11 +579,11 @@
* Often, concatenating the text representation of multiple elements can be
* done by appending a comma to the output stream.
*
- * \sa cbor_value_to_pretty(), cbor_value_to_json_advance()
+ * \sa cbor_value_to_pretty_stream(), cbor_value_to_pretty(), cbor_value_to_json_advance()
*/
CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags)
{
- return value_to_pretty(out, value, flags, CBOR_PARSER_MAX_RECURSIONS);
+ return value_to_pretty(cbor_fprintf, out, value, flags, CBOR_PARSER_MAX_RECURSIONS);
}
/** @} */
diff --git a/tests/parser/tst_parser.cpp b/tests/parser/tst_parser.cpp
index b4dc679..28705c1 100644
--- a/tests/parser/tst_parser.cpp
+++ b/tests/parser/tst_parser.cpp
@@ -25,12 +25,8 @@
#define _XOPEN_SOURCE 700
#include <QtTest>
#include "cbor.h"
-#include <locale.h>
#include <stdio.h>
-
-#ifndef Q_CC_MSVC
-extern "C" FILE *open_memstream(char **bufptr, size_t *sizeptr);
-#endif
+#include <stdarg.h>
Q_DECLARE_METATYPE(CborError)
namespace QTest {
@@ -103,40 +99,23 @@
void recursionLimit();
};
+static CborError qstring_printf(void *out, const char *fmt, ...)
+{
+ auto str = static_cast<QString *>(out);
+ va_list va;
+ va_start(va, fmt);
+ *str += QString::vasprintf(fmt, va);
+ va_end(va);
+ return CborNoError;
+};
+
CborError parseOne(CborValue *it, QString *parsed)
{
- CborError err;
- char *buffer;
- size_t size;
-
int flags = CborPrettyShowStringFragments | CborPrettyIndicateIndetermineLength |
CborPrettyIndicateOverlongNumbers;
- setlocale(LC_ALL, "C");
-#ifdef Q_CC_MSVC
- // no open_memstream, so use a temporary file
- QTemporaryFile tmp("./output.XXXXXX.txt");
- tmp.open();
-
- FILE *f = fopen(QFile::encodeName(tmp.fileName()), "w+");
- if (!f)
- return CborErrorIO;
- err = cbor_value_to_pretty_advance_flags(f, it, flags);
- size = ftell(f);
- rewind(f);
-
- buffer = static_cast<char *>(malloc(size));
- size = fread(buffer, 1, size, f);
- fclose(f);
-#else
- FILE *f = open_memstream(&buffer, &size);
- err = cbor_value_to_pretty_advance_flags(f, it, flags);
- fclose(f);
-#endif
-
- *parsed = QString::fromLatin1(buffer, int(size));
- free(buffer);
- return err;
+ parsed->clear();
+ return cbor_value_to_pretty_stream(qstring_printf, parsed, it, flags);
}
CborError parseOneChunk(CborValue *it, QString *parsed)
@@ -331,13 +310,7 @@
QTest::newRow("2.f^64") << raw("\xfa\x5f\x80\0\0") << "1.8446744073709552e+19f";
QTest::newRow("2.^64") << raw("\xfb\x43\xf0\0\0\0\0\0\0") << "1.8446744073709552e+19";
- QTest::newRow("nan_f16") << raw("\xf9\x7e\x00")
-#ifdef Q_CC_MSVC
- << "-nan(ind)"
-#else
- << "nan"
-#endif
- ;
+ QTest::newRow("nan_f16") << raw("\xf9\x7e\x00") << "nan";
QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << "nan";
QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << "nan";
QTest::newRow("-inf_f16") << raw("\xf9\xfc\x00") << "-inf";