
The output of snprintf might produce ',' separators for decimal places if certain locales are set. This commit moves the converversion from ',' to '.' to correct place. Otherwise an additional ".0" might be appended.
1224 lines
35 KiB
C++
1224 lines
35 KiB
C++
// Copyright 2011 Baptiste Lepilleur
|
|
// Distributed under MIT license, or public domain if desired and
|
|
// recognized in your jurisdiction.
|
|
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
|
|
|
#if !defined(JSON_IS_AMALGAMATION)
|
|
#include <json/writer.h>
|
|
#include "json_tool.h"
|
|
#endif // if !defined(JSON_IS_AMALGAMATION)
|
|
#include <iomanip>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <utility>
|
|
#include <set>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0
|
|
#include <float.h>
|
|
#define isfinite _finite
|
|
#elif defined(__sun) && defined(__SVR4) //Solaris
|
|
#if !defined(isfinite)
|
|
#include <ieeefp.h>
|
|
#define isfinite finite
|
|
#endif
|
|
#elif defined(_AIX)
|
|
#if !defined(isfinite)
|
|
#include <math.h>
|
|
#define isfinite finite
|
|
#endif
|
|
#elif defined(__hpux)
|
|
#if !defined(isfinite)
|
|
#if defined(__ia64) && !defined(finite)
|
|
#define isfinite(x) ((sizeof(x) == sizeof(float) ? \
|
|
_Isfinitef(x) : _IsFinite(x)))
|
|
#else
|
|
#include <math.h>
|
|
#define isfinite finite
|
|
#endif
|
|
#endif
|
|
#else
|
|
#include <cmath>
|
|
#if !(defined(__QNXNTO__)) // QNX already defines isfinite
|
|
#define isfinite std::isfinite
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(_MSC_VER)
|
|
#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
|
|
#define snprintf sprintf_s
|
|
#elif _MSC_VER >= 1900 // VC++ 14.0 and above
|
|
#define snprintf std::snprintf
|
|
#else
|
|
#define snprintf _snprintf
|
|
#endif
|
|
#elif defined(__ANDROID__) || defined(__QNXNTO__)
|
|
#define snprintf snprintf
|
|
#elif __cplusplus >= 201103L
|
|
#if !defined(__MINGW32__) && !defined(__CYGWIN__)
|
|
#define snprintf std::snprintf
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(__BORLANDC__)
|
|
#include <float.h>
|
|
#define isfinite _finite
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
|
|
// Disable warning about strdup being deprecated.
|
|
#pragma warning(disable : 4996)
|
|
#endif
|
|
|
|
namespace Json {
|
|
|
|
#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
|
|
typedef std::unique_ptr<StreamWriter> StreamWriterPtr;
|
|
#else
|
|
typedef std::auto_ptr<StreamWriter> StreamWriterPtr;
|
|
#endif
|
|
|
|
static bool containsControlCharacter(const char* str) {
|
|
while (*str) {
|
|
if (isControlCharacter(*(str++)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool containsControlCharacter0(const char* str, unsigned len) {
|
|
char const* end = str + len;
|
|
while (end != str) {
|
|
if (isControlCharacter(*str) || 0==*str)
|
|
return true;
|
|
++str;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
JSONCPP_STRING valueToString(LargestInt value) {
|
|
UIntToStringBuffer buffer;
|
|
char* current = buffer + sizeof(buffer);
|
|
if (value == Value::minLargestInt) {
|
|
uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
|
|
*--current = '-';
|
|
} else if (value < 0) {
|
|
uintToString(LargestUInt(-value), current);
|
|
*--current = '-';
|
|
} else {
|
|
uintToString(LargestUInt(value), current);
|
|
}
|
|
assert(current >= buffer);
|
|
return current;
|
|
}
|
|
|
|
JSONCPP_STRING valueToString(LargestUInt value) {
|
|
UIntToStringBuffer buffer;
|
|
char* current = buffer + sizeof(buffer);
|
|
uintToString(value, current);
|
|
assert(current >= buffer);
|
|
return current;
|
|
}
|
|
|
|
#if defined(JSON_HAS_INT64)
|
|
|
|
JSONCPP_STRING valueToString(Int value) {
|
|
return valueToString(LargestInt(value));
|
|
}
|
|
|
|
JSONCPP_STRING valueToString(UInt value) {
|
|
return valueToString(LargestUInt(value));
|
|
}
|
|
|
|
#endif // # if defined(JSON_HAS_INT64)
|
|
|
|
namespace {
|
|
JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision) {
|
|
// Allocate a buffer that is more than large enough to store the 16 digits of
|
|
// precision requested below.
|
|
char buffer[36];
|
|
int len = -1;
|
|
|
|
char formatString[15];
|
|
snprintf(formatString, sizeof(formatString), "%%.%dg", precision);
|
|
|
|
// Print into the buffer. We need not request the alternative representation
|
|
// that always has a decimal point because JSON doesn't distingish the
|
|
// concepts of reals and integers.
|
|
if (isfinite(value)) {
|
|
len = snprintf(buffer, sizeof(buffer), formatString, value);
|
|
fixNumericLocale(buffer, buffer + len);
|
|
|
|
// try to ensure we preserve the fact that this was given to us as a double on input
|
|
if (!strstr(buffer, ".") && !strstr(buffer, "e")) {
|
|
strcat(buffer, ".0");
|
|
}
|
|
|
|
} else {
|
|
// IEEE standard states that NaN values will not compare to themselves
|
|
if (value != value) {
|
|
len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null");
|
|
} else if (value < 0) {
|
|
len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999");
|
|
} else {
|
|
len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999");
|
|
}
|
|
}
|
|
assert(len >= 0);
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
JSONCPP_STRING valueToString(double value) { return valueToString(value, false, 17); }
|
|
|
|
JSONCPP_STRING valueToString(bool value) { return value ? "true" : "false"; }
|
|
|
|
JSONCPP_STRING valueToQuotedString(const char* value) {
|
|
if (value == NULL)
|
|
return "";
|
|
// Not sure how to handle unicode...
|
|
if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL &&
|
|
!containsControlCharacter(value))
|
|
return JSONCPP_STRING("\"") + value + "\"";
|
|
// We have to walk value and escape any special characters.
|
|
// Appending to JSONCPP_STRING is not efficient, but this should be rare.
|
|
// (Note: forward slashes are *not* rare, but I am not escaping them.)
|
|
JSONCPP_STRING::size_type maxsize =
|
|
strlen(value) * 2 + 3; // allescaped+quotes+NULL
|
|
JSONCPP_STRING result;
|
|
result.reserve(maxsize); // to avoid lots of mallocs
|
|
result += "\"";
|
|
for (const char* c = value; *c != 0; ++c) {
|
|
switch (*c) {
|
|
case '\"':
|
|
result += "\\\"";
|
|
break;
|
|
case '\\':
|
|
result += "\\\\";
|
|
break;
|
|
case '\b':
|
|
result += "\\b";
|
|
break;
|
|
case '\f':
|
|
result += "\\f";
|
|
break;
|
|
case '\n':
|
|
result += "\\n";
|
|
break;
|
|
case '\r':
|
|
result += "\\r";
|
|
break;
|
|
case '\t':
|
|
result += "\\t";
|
|
break;
|
|
// case '/':
|
|
// Even though \/ is considered a legal escape in JSON, a bare
|
|
// slash is also legal, so I see no reason to escape it.
|
|
// (I hope I am not misunderstanding something.
|
|
// blep notes: actually escaping \/ may be useful in javascript to avoid </
|
|
// sequence.
|
|
// Should add a flag to allow this compatibility mode and prevent this
|
|
// sequence from occurring.
|
|
default:
|
|
if (isControlCharacter(*c)) {
|
|
JSONCPP_OSTRINGSTREAM oss;
|
|
oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
|
|
<< std::setw(4) << static_cast<int>(*c);
|
|
result += oss.str();
|
|
} else {
|
|
result += *c;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
result += "\"";
|
|
return result;
|
|
}
|
|
|
|
// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp
|
|
static char const* strnpbrk(char const* s, char const* accept, size_t n) {
|
|
assert((s || !n) && accept);
|
|
|
|
char const* const end = s + n;
|
|
for (char const* cur = s; cur < end; ++cur) {
|
|
int const c = *cur;
|
|
for (char const* a = accept; *a; ++a) {
|
|
if (*a == c) {
|
|
return cur;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) {
|
|
if (value == NULL)
|
|
return "";
|
|
// Not sure how to handle unicode...
|
|
if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL &&
|
|
!containsControlCharacter0(value, length))
|
|
return JSONCPP_STRING("\"") + value + "\"";
|
|
// We have to walk value and escape any special characters.
|
|
// Appending to JSONCPP_STRING is not efficient, but this should be rare.
|
|
// (Note: forward slashes are *not* rare, but I am not escaping them.)
|
|
JSONCPP_STRING::size_type maxsize =
|
|
length * 2 + 3; // allescaped+quotes+NULL
|
|
JSONCPP_STRING result;
|
|
result.reserve(maxsize); // to avoid lots of mallocs
|
|
result += "\"";
|
|
char const* end = value + length;
|
|
for (const char* c = value; c != end; ++c) {
|
|
switch (*c) {
|
|
case '\"':
|
|
result += "\\\"";
|
|
break;
|
|
case '\\':
|
|
result += "\\\\";
|
|
break;
|
|
case '\b':
|
|
result += "\\b";
|
|
break;
|
|
case '\f':
|
|
result += "\\f";
|
|
break;
|
|
case '\n':
|
|
result += "\\n";
|
|
break;
|
|
case '\r':
|
|
result += "\\r";
|
|
break;
|
|
case '\t':
|
|
result += "\\t";
|
|
break;
|
|
// case '/':
|
|
// Even though \/ is considered a legal escape in JSON, a bare
|
|
// slash is also legal, so I see no reason to escape it.
|
|
// (I hope I am not misunderstanding something.)
|
|
// blep notes: actually escaping \/ may be useful in javascript to avoid </
|
|
// sequence.
|
|
// Should add a flag to allow this compatibility mode and prevent this
|
|
// sequence from occurring.
|
|
default:
|
|
if ((isControlCharacter(*c)) || (*c == 0)) {
|
|
JSONCPP_OSTRINGSTREAM oss;
|
|
oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
|
|
<< std::setw(4) << static_cast<int>(*c);
|
|
result += oss.str();
|
|
} else {
|
|
result += *c;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
result += "\"";
|
|
return result;
|
|
}
|
|
|
|
// Class Writer
|
|
// //////////////////////////////////////////////////////////////////
|
|
Writer::~Writer() {}
|
|
|
|
// Class FastWriter
|
|
// //////////////////////////////////////////////////////////////////
|
|
|
|
FastWriter::FastWriter()
|
|
: yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false),
|
|
omitEndingLineFeed_(false) {}
|
|
|
|
void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; }
|
|
|
|
void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }
|
|
|
|
void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }
|
|
|
|
JSONCPP_STRING FastWriter::write(const Value& root) {
|
|
document_.clear();
|
|
writeValue(root);
|
|
if (!omitEndingLineFeed_)
|
|
document_ += "\n";
|
|
return document_;
|
|
}
|
|
|
|
void FastWriter::writeValue(const Value& value) {
|
|
switch (value.type()) {
|
|
case nullValue:
|
|
if (!dropNullPlaceholders_)
|
|
document_ += "null";
|
|
break;
|
|
case intValue:
|
|
document_ += valueToString(value.asLargestInt());
|
|
break;
|
|
case uintValue:
|
|
document_ += valueToString(value.asLargestUInt());
|
|
break;
|
|
case realValue:
|
|
document_ += valueToString(value.asDouble());
|
|
break;
|
|
case stringValue:
|
|
{
|
|
// Is NULL possible for value.string_? No.
|
|
char const* str;
|
|
char const* end;
|
|
bool ok = value.getString(&str, &end);
|
|
if (ok) document_ += valueToQuotedStringN(str, static_cast<unsigned>(end-str));
|
|
break;
|
|
}
|
|
case booleanValue:
|
|
document_ += valueToString(value.asBool());
|
|
break;
|
|
case arrayValue: {
|
|
document_ += '[';
|
|
ArrayIndex size = value.size();
|
|
for (ArrayIndex index = 0; index < size; ++index) {
|
|
if (index > 0)
|
|
document_ += ',';
|
|
writeValue(value[index]);
|
|
}
|
|
document_ += ']';
|
|
} break;
|
|
case objectValue: {
|
|
Value::Members members(value.getMemberNames());
|
|
document_ += '{';
|
|
for (Value::Members::iterator it = members.begin(); it != members.end();
|
|
++it) {
|
|
const JSONCPP_STRING& name = *it;
|
|
if (it != members.begin())
|
|
document_ += ',';
|
|
document_ += valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()));
|
|
document_ += yamlCompatiblityEnabled_ ? ": " : ":";
|
|
writeValue(value[name]);
|
|
}
|
|
document_ += '}';
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// Class StyledWriter
|
|
// //////////////////////////////////////////////////////////////////
|
|
|
|
StyledWriter::StyledWriter()
|
|
: rightMargin_(74), indentSize_(3), addChildValues_() {}
|
|
|
|
JSONCPP_STRING StyledWriter::write(const Value& root) {
|
|
document_.clear();
|
|
addChildValues_ = false;
|
|
indentString_.clear();
|
|
writeCommentBeforeValue(root);
|
|
writeValue(root);
|
|
writeCommentAfterValueOnSameLine(root);
|
|
document_ += "\n";
|
|
return document_;
|
|
}
|
|
|
|
void StyledWriter::writeValue(const Value& value) {
|
|
switch (value.type()) {
|
|
case nullValue:
|
|
pushValue("null");
|
|
break;
|
|
case intValue:
|
|
pushValue(valueToString(value.asLargestInt()));
|
|
break;
|
|
case uintValue:
|
|
pushValue(valueToString(value.asLargestUInt()));
|
|
break;
|
|
case realValue:
|
|
pushValue(valueToString(value.asDouble()));
|
|
break;
|
|
case stringValue:
|
|
{
|
|
// Is NULL possible for value.string_? No.
|
|
char const* str;
|
|
char const* end;
|
|
bool ok = value.getString(&str, &end);
|
|
if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
|
|
else pushValue("");
|
|
break;
|
|
}
|
|
case booleanValue:
|
|
pushValue(valueToString(value.asBool()));
|
|
break;
|
|
case arrayValue:
|
|
writeArrayValue(value);
|
|
break;
|
|
case objectValue: {
|
|
Value::Members members(value.getMemberNames());
|
|
if (members.empty())
|
|
pushValue("{}");
|
|
else {
|
|
writeWithIndent("{");
|
|
indent();
|
|
Value::Members::iterator it = members.begin();
|
|
for (;;) {
|
|
const JSONCPP_STRING& name = *it;
|
|
const Value& childValue = value[name];
|
|
writeCommentBeforeValue(childValue);
|
|
writeWithIndent(valueToQuotedString(name.c_str()));
|
|
document_ += " : ";
|
|
writeValue(childValue);
|
|
if (++it == members.end()) {
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
break;
|
|
}
|
|
document_ += ',';
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
}
|
|
unindent();
|
|
writeWithIndent("}");
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void StyledWriter::writeArrayValue(const Value& value) {
|
|
unsigned size = value.size();
|
|
if (size == 0)
|
|
pushValue("[]");
|
|
else {
|
|
bool isArrayMultiLine = isMultineArray(value);
|
|
if (isArrayMultiLine) {
|
|
writeWithIndent("[");
|
|
indent();
|
|
bool hasChildValue = !childValues_.empty();
|
|
unsigned index = 0;
|
|
for (;;) {
|
|
const Value& childValue = value[index];
|
|
writeCommentBeforeValue(childValue);
|
|
if (hasChildValue)
|
|
writeWithIndent(childValues_[index]);
|
|
else {
|
|
writeIndent();
|
|
writeValue(childValue);
|
|
}
|
|
if (++index == size) {
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
break;
|
|
}
|
|
document_ += ',';
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
}
|
|
unindent();
|
|
writeWithIndent("]");
|
|
} else // output on a single line
|
|
{
|
|
assert(childValues_.size() == size);
|
|
document_ += "[ ";
|
|
for (unsigned index = 0; index < size; ++index) {
|
|
if (index > 0)
|
|
document_ += ", ";
|
|
document_ += childValues_[index];
|
|
}
|
|
document_ += " ]";
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StyledWriter::isMultineArray(const Value& value) {
|
|
ArrayIndex const size = value.size();
|
|
bool isMultiLine = size * 3 >= rightMargin_;
|
|
childValues_.clear();
|
|
for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
|
|
const Value& childValue = value[index];
|
|
isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
|
|
childValue.size() > 0);
|
|
}
|
|
if (!isMultiLine) // check if line length > max line length
|
|
{
|
|
childValues_.reserve(size);
|
|
addChildValues_ = true;
|
|
ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
|
for (ArrayIndex index = 0; index < size; ++index) {
|
|
if (hasCommentForValue(value[index])) {
|
|
isMultiLine = true;
|
|
}
|
|
writeValue(value[index]);
|
|
lineLength += static_cast<ArrayIndex>(childValues_[index].length());
|
|
}
|
|
addChildValues_ = false;
|
|
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
|
}
|
|
return isMultiLine;
|
|
}
|
|
|
|
void StyledWriter::pushValue(const JSONCPP_STRING& value) {
|
|
if (addChildValues_)
|
|
childValues_.push_back(value);
|
|
else
|
|
document_ += value;
|
|
}
|
|
|
|
void StyledWriter::writeIndent() {
|
|
if (!document_.empty()) {
|
|
char last = document_[document_.length() - 1];
|
|
if (last == ' ') // already indented
|
|
return;
|
|
if (last != '\n') // Comments may add new-line
|
|
document_ += '\n';
|
|
}
|
|
document_ += indentString_;
|
|
}
|
|
|
|
void StyledWriter::writeWithIndent(const JSONCPP_STRING& value) {
|
|
writeIndent();
|
|
document_ += value;
|
|
}
|
|
|
|
void StyledWriter::indent() { indentString_ += JSONCPP_STRING(indentSize_, ' '); }
|
|
|
|
void StyledWriter::unindent() {
|
|
assert(indentString_.size() >= indentSize_);
|
|
indentString_.resize(indentString_.size() - indentSize_);
|
|
}
|
|
|
|
void StyledWriter::writeCommentBeforeValue(const Value& root) {
|
|
if (!root.hasComment(commentBefore))
|
|
return;
|
|
|
|
document_ += "\n";
|
|
writeIndent();
|
|
const JSONCPP_STRING& comment = root.getComment(commentBefore);
|
|
JSONCPP_STRING::const_iterator iter = comment.begin();
|
|
while (iter != comment.end()) {
|
|
document_ += *iter;
|
|
if (*iter == '\n' &&
|
|
(iter != comment.end() && *(iter + 1) == '/'))
|
|
writeIndent();
|
|
++iter;
|
|
}
|
|
|
|
// Comments are stripped of trailing newlines, so add one here
|
|
document_ += "\n";
|
|
}
|
|
|
|
void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
|
|
if (root.hasComment(commentAfterOnSameLine))
|
|
document_ += " " + root.getComment(commentAfterOnSameLine);
|
|
|
|
if (root.hasComment(commentAfter)) {
|
|
document_ += "\n";
|
|
document_ += root.getComment(commentAfter);
|
|
document_ += "\n";
|
|
}
|
|
}
|
|
|
|
bool StyledWriter::hasCommentForValue(const Value& value) {
|
|
return value.hasComment(commentBefore) ||
|
|
value.hasComment(commentAfterOnSameLine) ||
|
|
value.hasComment(commentAfter);
|
|
}
|
|
|
|
// Class StyledStreamWriter
|
|
// //////////////////////////////////////////////////////////////////
|
|
|
|
StyledStreamWriter::StyledStreamWriter(JSONCPP_STRING indentation)
|
|
: document_(NULL), rightMargin_(74), indentation_(indentation),
|
|
addChildValues_() {}
|
|
|
|
void StyledStreamWriter::write(JSONCPP_OSTREAM& out, const Value& root) {
|
|
document_ = &out;
|
|
addChildValues_ = false;
|
|
indentString_.clear();
|
|
indented_ = true;
|
|
writeCommentBeforeValue(root);
|
|
if (!indented_) writeIndent();
|
|
indented_ = true;
|
|
writeValue(root);
|
|
writeCommentAfterValueOnSameLine(root);
|
|
*document_ << "\n";
|
|
document_ = NULL; // Forget the stream, for safety.
|
|
}
|
|
|
|
void StyledStreamWriter::writeValue(const Value& value) {
|
|
switch (value.type()) {
|
|
case nullValue:
|
|
pushValue("null");
|
|
break;
|
|
case intValue:
|
|
pushValue(valueToString(value.asLargestInt()));
|
|
break;
|
|
case uintValue:
|
|
pushValue(valueToString(value.asLargestUInt()));
|
|
break;
|
|
case realValue:
|
|
pushValue(valueToString(value.asDouble()));
|
|
break;
|
|
case stringValue:
|
|
{
|
|
// Is NULL possible for value.string_? No.
|
|
char const* str;
|
|
char const* end;
|
|
bool ok = value.getString(&str, &end);
|
|
if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
|
|
else pushValue("");
|
|
break;
|
|
}
|
|
case booleanValue:
|
|
pushValue(valueToString(value.asBool()));
|
|
break;
|
|
case arrayValue:
|
|
writeArrayValue(value);
|
|
break;
|
|
case objectValue: {
|
|
Value::Members members(value.getMemberNames());
|
|
if (members.empty())
|
|
pushValue("{}");
|
|
else {
|
|
writeWithIndent("{");
|
|
indent();
|
|
Value::Members::iterator it = members.begin();
|
|
for (;;) {
|
|
const JSONCPP_STRING& name = *it;
|
|
const Value& childValue = value[name];
|
|
writeCommentBeforeValue(childValue);
|
|
writeWithIndent(valueToQuotedString(name.c_str()));
|
|
*document_ << " : ";
|
|
writeValue(childValue);
|
|
if (++it == members.end()) {
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
break;
|
|
}
|
|
*document_ << ",";
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
}
|
|
unindent();
|
|
writeWithIndent("}");
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void StyledStreamWriter::writeArrayValue(const Value& value) {
|
|
unsigned size = value.size();
|
|
if (size == 0)
|
|
pushValue("[]");
|
|
else {
|
|
bool isArrayMultiLine = isMultineArray(value);
|
|
if (isArrayMultiLine) {
|
|
writeWithIndent("[");
|
|
indent();
|
|
bool hasChildValue = !childValues_.empty();
|
|
unsigned index = 0;
|
|
for (;;) {
|
|
const Value& childValue = value[index];
|
|
writeCommentBeforeValue(childValue);
|
|
if (hasChildValue)
|
|
writeWithIndent(childValues_[index]);
|
|
else {
|
|
if (!indented_) writeIndent();
|
|
indented_ = true;
|
|
writeValue(childValue);
|
|
indented_ = false;
|
|
}
|
|
if (++index == size) {
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
break;
|
|
}
|
|
*document_ << ",";
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
}
|
|
unindent();
|
|
writeWithIndent("]");
|
|
} else // output on a single line
|
|
{
|
|
assert(childValues_.size() == size);
|
|
*document_ << "[ ";
|
|
for (unsigned index = 0; index < size; ++index) {
|
|
if (index > 0)
|
|
*document_ << ", ";
|
|
*document_ << childValues_[index];
|
|
}
|
|
*document_ << " ]";
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StyledStreamWriter::isMultineArray(const Value& value) {
|
|
ArrayIndex const size = value.size();
|
|
bool isMultiLine = size * 3 >= rightMargin_;
|
|
childValues_.clear();
|
|
for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
|
|
const Value& childValue = value[index];
|
|
isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
|
|
childValue.size() > 0);
|
|
}
|
|
if (!isMultiLine) // check if line length > max line length
|
|
{
|
|
childValues_.reserve(size);
|
|
addChildValues_ = true;
|
|
ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
|
for (ArrayIndex index = 0; index < size; ++index) {
|
|
if (hasCommentForValue(value[index])) {
|
|
isMultiLine = true;
|
|
}
|
|
writeValue(value[index]);
|
|
lineLength += static_cast<ArrayIndex>(childValues_[index].length());
|
|
}
|
|
addChildValues_ = false;
|
|
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
|
}
|
|
return isMultiLine;
|
|
}
|
|
|
|
void StyledStreamWriter::pushValue(const JSONCPP_STRING& value) {
|
|
if (addChildValues_)
|
|
childValues_.push_back(value);
|
|
else
|
|
*document_ << value;
|
|
}
|
|
|
|
void StyledStreamWriter::writeIndent() {
|
|
// blep intended this to look at the so-far-written string
|
|
// to determine whether we are already indented, but
|
|
// with a stream we cannot do that. So we rely on some saved state.
|
|
// The caller checks indented_.
|
|
*document_ << '\n' << indentString_;
|
|
}
|
|
|
|
void StyledStreamWriter::writeWithIndent(const JSONCPP_STRING& value) {
|
|
if (!indented_) writeIndent();
|
|
*document_ << value;
|
|
indented_ = false;
|
|
}
|
|
|
|
void StyledStreamWriter::indent() { indentString_ += indentation_; }
|
|
|
|
void StyledStreamWriter::unindent() {
|
|
assert(indentString_.size() >= indentation_.size());
|
|
indentString_.resize(indentString_.size() - indentation_.size());
|
|
}
|
|
|
|
void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
|
|
if (!root.hasComment(commentBefore))
|
|
return;
|
|
|
|
if (!indented_) writeIndent();
|
|
const JSONCPP_STRING& comment = root.getComment(commentBefore);
|
|
JSONCPP_STRING::const_iterator iter = comment.begin();
|
|
while (iter != comment.end()) {
|
|
*document_ << *iter;
|
|
if (*iter == '\n' &&
|
|
(iter != comment.end() && *(iter + 1) == '/'))
|
|
// writeIndent(); // would include newline
|
|
*document_ << indentString_;
|
|
++iter;
|
|
}
|
|
indented_ = false;
|
|
}
|
|
|
|
void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
|
|
if (root.hasComment(commentAfterOnSameLine))
|
|
*document_ << ' ' << root.getComment(commentAfterOnSameLine);
|
|
|
|
if (root.hasComment(commentAfter)) {
|
|
writeIndent();
|
|
*document_ << root.getComment(commentAfter);
|
|
}
|
|
indented_ = false;
|
|
}
|
|
|
|
bool StyledStreamWriter::hasCommentForValue(const Value& value) {
|
|
return value.hasComment(commentBefore) ||
|
|
value.hasComment(commentAfterOnSameLine) ||
|
|
value.hasComment(commentAfter);
|
|
}
|
|
|
|
//////////////////////////
|
|
// BuiltStyledStreamWriter
|
|
|
|
/// Scoped enums are not available until C++11.
|
|
struct CommentStyle {
|
|
/// Decide whether to write comments.
|
|
enum Enum {
|
|
None, ///< Drop all comments.
|
|
Most, ///< Recover odd behavior of previous versions (not implemented yet).
|
|
All ///< Keep all comments.
|
|
};
|
|
};
|
|
|
|
struct BuiltStyledStreamWriter : public StreamWriter
|
|
{
|
|
BuiltStyledStreamWriter(
|
|
JSONCPP_STRING const& indentation,
|
|
CommentStyle::Enum cs,
|
|
JSONCPP_STRING const& colonSymbol,
|
|
JSONCPP_STRING const& nullSymbol,
|
|
JSONCPP_STRING const& endingLineFeedSymbol,
|
|
bool useSpecialFloats,
|
|
unsigned int precision);
|
|
int write(Value const& root, JSONCPP_OSTREAM* sout) JSONCPP_OVERRIDE;
|
|
private:
|
|
void writeValue(Value const& value);
|
|
void writeArrayValue(Value const& value);
|
|
bool isMultineArray(Value const& value);
|
|
void pushValue(JSONCPP_STRING const& value);
|
|
void writeIndent();
|
|
void writeWithIndent(JSONCPP_STRING const& value);
|
|
void indent();
|
|
void unindent();
|
|
void writeCommentBeforeValue(Value const& root);
|
|
void writeCommentAfterValueOnSameLine(Value const& root);
|
|
static bool hasCommentForValue(const Value& value);
|
|
|
|
typedef std::vector<JSONCPP_STRING> ChildValues;
|
|
|
|
ChildValues childValues_;
|
|
JSONCPP_STRING indentString_;
|
|
unsigned int rightMargin_;
|
|
JSONCPP_STRING indentation_;
|
|
CommentStyle::Enum cs_;
|
|
JSONCPP_STRING colonSymbol_;
|
|
JSONCPP_STRING nullSymbol_;
|
|
JSONCPP_STRING endingLineFeedSymbol_;
|
|
bool addChildValues_ : 1;
|
|
bool indented_ : 1;
|
|
bool useSpecialFloats_ : 1;
|
|
unsigned int precision_;
|
|
};
|
|
BuiltStyledStreamWriter::BuiltStyledStreamWriter(
|
|
JSONCPP_STRING const& indentation,
|
|
CommentStyle::Enum cs,
|
|
JSONCPP_STRING const& colonSymbol,
|
|
JSONCPP_STRING const& nullSymbol,
|
|
JSONCPP_STRING const& endingLineFeedSymbol,
|
|
bool useSpecialFloats,
|
|
unsigned int precision)
|
|
: rightMargin_(74)
|
|
, indentation_(indentation)
|
|
, cs_(cs)
|
|
, colonSymbol_(colonSymbol)
|
|
, nullSymbol_(nullSymbol)
|
|
, endingLineFeedSymbol_(endingLineFeedSymbol)
|
|
, addChildValues_(false)
|
|
, indented_(false)
|
|
, useSpecialFloats_(useSpecialFloats)
|
|
, precision_(precision)
|
|
{
|
|
}
|
|
int BuiltStyledStreamWriter::write(Value const& root, JSONCPP_OSTREAM* sout)
|
|
{
|
|
sout_ = sout;
|
|
addChildValues_ = false;
|
|
indented_ = true;
|
|
indentString_.clear();
|
|
writeCommentBeforeValue(root);
|
|
if (!indented_) writeIndent();
|
|
indented_ = true;
|
|
writeValue(root);
|
|
writeCommentAfterValueOnSameLine(root);
|
|
*sout_ << endingLineFeedSymbol_;
|
|
sout_ = NULL;
|
|
return 0;
|
|
}
|
|
void BuiltStyledStreamWriter::writeValue(Value const& value) {
|
|
switch (value.type()) {
|
|
case nullValue:
|
|
pushValue(nullSymbol_);
|
|
break;
|
|
case intValue:
|
|
pushValue(valueToString(value.asLargestInt()));
|
|
break;
|
|
case uintValue:
|
|
pushValue(valueToString(value.asLargestUInt()));
|
|
break;
|
|
case realValue:
|
|
pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_));
|
|
break;
|
|
case stringValue:
|
|
{
|
|
// Is NULL is possible for value.string_? No.
|
|
char const* str;
|
|
char const* end;
|
|
bool ok = value.getString(&str, &end);
|
|
if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
|
|
else pushValue("");
|
|
break;
|
|
}
|
|
case booleanValue:
|
|
pushValue(valueToString(value.asBool()));
|
|
break;
|
|
case arrayValue:
|
|
writeArrayValue(value);
|
|
break;
|
|
case objectValue: {
|
|
Value::Members members(value.getMemberNames());
|
|
if (members.empty())
|
|
pushValue("{}");
|
|
else {
|
|
writeWithIndent("{");
|
|
indent();
|
|
Value::Members::iterator it = members.begin();
|
|
for (;;) {
|
|
JSONCPP_STRING const& name = *it;
|
|
Value const& childValue = value[name];
|
|
writeCommentBeforeValue(childValue);
|
|
writeWithIndent(valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length())));
|
|
*sout_ << colonSymbol_;
|
|
writeValue(childValue);
|
|
if (++it == members.end()) {
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
break;
|
|
}
|
|
*sout_ << ",";
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
}
|
|
unindent();
|
|
writeWithIndent("}");
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
|
|
unsigned size = value.size();
|
|
if (size == 0)
|
|
pushValue("[]");
|
|
else {
|
|
bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value);
|
|
if (isMultiLine) {
|
|
writeWithIndent("[");
|
|
indent();
|
|
bool hasChildValue = !childValues_.empty();
|
|
unsigned index = 0;
|
|
for (;;) {
|
|
Value const& childValue = value[index];
|
|
writeCommentBeforeValue(childValue);
|
|
if (hasChildValue)
|
|
writeWithIndent(childValues_[index]);
|
|
else {
|
|
if (!indented_) writeIndent();
|
|
indented_ = true;
|
|
writeValue(childValue);
|
|
indented_ = false;
|
|
}
|
|
if (++index == size) {
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
break;
|
|
}
|
|
*sout_ << ",";
|
|
writeCommentAfterValueOnSameLine(childValue);
|
|
}
|
|
unindent();
|
|
writeWithIndent("]");
|
|
} else // output on a single line
|
|
{
|
|
assert(childValues_.size() == size);
|
|
*sout_ << "[";
|
|
if (!indentation_.empty()) *sout_ << " ";
|
|
for (unsigned index = 0; index < size; ++index) {
|
|
if (index > 0)
|
|
*sout_ << ((!indentation_.empty()) ? ", " : ",");
|
|
*sout_ << childValues_[index];
|
|
}
|
|
if (!indentation_.empty()) *sout_ << " ";
|
|
*sout_ << "]";
|
|
}
|
|
}
|
|
}
|
|
|
|
bool BuiltStyledStreamWriter::isMultineArray(Value const& value) {
|
|
ArrayIndex const size = value.size();
|
|
bool isMultiLine = size * 3 >= rightMargin_;
|
|
childValues_.clear();
|
|
for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
|
|
Value const& childValue = value[index];
|
|
isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
|
|
childValue.size() > 0);
|
|
}
|
|
if (!isMultiLine) // check if line length > max line length
|
|
{
|
|
childValues_.reserve(size);
|
|
addChildValues_ = true;
|
|
ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
|
for (ArrayIndex index = 0; index < size; ++index) {
|
|
if (hasCommentForValue(value[index])) {
|
|
isMultiLine = true;
|
|
}
|
|
writeValue(value[index]);
|
|
lineLength += static_cast<ArrayIndex>(childValues_[index].length());
|
|
}
|
|
addChildValues_ = false;
|
|
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
|
}
|
|
return isMultiLine;
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::pushValue(JSONCPP_STRING const& value) {
|
|
if (addChildValues_)
|
|
childValues_.push_back(value);
|
|
else
|
|
*sout_ << value;
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::writeIndent() {
|
|
// blep intended this to look at the so-far-written string
|
|
// to determine whether we are already indented, but
|
|
// with a stream we cannot do that. So we rely on some saved state.
|
|
// The caller checks indented_.
|
|
|
|
if (!indentation_.empty()) {
|
|
// In this case, drop newlines too.
|
|
*sout_ << '\n' << indentString_;
|
|
}
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::writeWithIndent(JSONCPP_STRING const& value) {
|
|
if (!indented_) writeIndent();
|
|
*sout_ << value;
|
|
indented_ = false;
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
|
|
|
|
void BuiltStyledStreamWriter::unindent() {
|
|
assert(indentString_.size() >= indentation_.size());
|
|
indentString_.resize(indentString_.size() - indentation_.size());
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
|
|
if (cs_ == CommentStyle::None) return;
|
|
if (!root.hasComment(commentBefore))
|
|
return;
|
|
|
|
if (!indented_) writeIndent();
|
|
const JSONCPP_STRING& comment = root.getComment(commentBefore);
|
|
JSONCPP_STRING::const_iterator iter = comment.begin();
|
|
while (iter != comment.end()) {
|
|
*sout_ << *iter;
|
|
if (*iter == '\n' &&
|
|
(iter != comment.end() && *(iter + 1) == '/'))
|
|
// writeIndent(); // would write extra newline
|
|
*sout_ << indentString_;
|
|
++iter;
|
|
}
|
|
indented_ = false;
|
|
}
|
|
|
|
void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
|
|
if (cs_ == CommentStyle::None) return;
|
|
if (root.hasComment(commentAfterOnSameLine))
|
|
*sout_ << " " + root.getComment(commentAfterOnSameLine);
|
|
|
|
if (root.hasComment(commentAfter)) {
|
|
writeIndent();
|
|
*sout_ << root.getComment(commentAfter);
|
|
}
|
|
}
|
|
|
|
// static
|
|
bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
|
|
return value.hasComment(commentBefore) ||
|
|
value.hasComment(commentAfterOnSameLine) ||
|
|
value.hasComment(commentAfter);
|
|
}
|
|
|
|
///////////////
|
|
// StreamWriter
|
|
|
|
StreamWriter::StreamWriter()
|
|
: sout_(NULL)
|
|
{
|
|
}
|
|
StreamWriter::~StreamWriter()
|
|
{
|
|
}
|
|
StreamWriter::Factory::~Factory()
|
|
{}
|
|
StreamWriterBuilder::StreamWriterBuilder()
|
|
{
|
|
setDefaults(&settings_);
|
|
}
|
|
StreamWriterBuilder::~StreamWriterBuilder()
|
|
{}
|
|
StreamWriter* StreamWriterBuilder::newStreamWriter() const
|
|
{
|
|
JSONCPP_STRING indentation = settings_["indentation"].asString();
|
|
JSONCPP_STRING cs_str = settings_["commentStyle"].asString();
|
|
bool eyc = settings_["enableYAMLCompatibility"].asBool();
|
|
bool dnp = settings_["dropNullPlaceholders"].asBool();
|
|
bool usf = settings_["useSpecialFloats"].asBool();
|
|
unsigned int pre = settings_["precision"].asUInt();
|
|
CommentStyle::Enum cs = CommentStyle::All;
|
|
if (cs_str == "All") {
|
|
cs = CommentStyle::All;
|
|
} else if (cs_str == "None") {
|
|
cs = CommentStyle::None;
|
|
} else {
|
|
throwRuntimeError("commentStyle must be 'All' or 'None'");
|
|
}
|
|
JSONCPP_STRING colonSymbol = " : ";
|
|
if (eyc) {
|
|
colonSymbol = ": ";
|
|
} else if (indentation.empty()) {
|
|
colonSymbol = ":";
|
|
}
|
|
JSONCPP_STRING nullSymbol = "null";
|
|
if (dnp) {
|
|
nullSymbol.clear();
|
|
}
|
|
if (pre > 17) pre = 17;
|
|
JSONCPP_STRING endingLineFeedSymbol;
|
|
return new BuiltStyledStreamWriter(
|
|
indentation, cs,
|
|
colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre);
|
|
}
|
|
static void getValidWriterKeys(std::set<JSONCPP_STRING>* valid_keys)
|
|
{
|
|
valid_keys->clear();
|
|
valid_keys->insert("indentation");
|
|
valid_keys->insert("commentStyle");
|
|
valid_keys->insert("enableYAMLCompatibility");
|
|
valid_keys->insert("dropNullPlaceholders");
|
|
valid_keys->insert("useSpecialFloats");
|
|
valid_keys->insert("precision");
|
|
}
|
|
bool StreamWriterBuilder::validate(Json::Value* invalid) const
|
|
{
|
|
Json::Value my_invalid;
|
|
if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL
|
|
Json::Value& inv = *invalid;
|
|
std::set<JSONCPP_STRING> valid_keys;
|
|
getValidWriterKeys(&valid_keys);
|
|
Value::Members keys = settings_.getMemberNames();
|
|
size_t n = keys.size();
|
|
for (size_t i = 0; i < n; ++i) {
|
|
JSONCPP_STRING const& key = keys[i];
|
|
if (valid_keys.find(key) == valid_keys.end()) {
|
|
inv[key] = settings_[key];
|
|
}
|
|
}
|
|
return 0u == inv.size();
|
|
}
|
|
Value& StreamWriterBuilder::operator[](JSONCPP_STRING key)
|
|
{
|
|
return settings_[key];
|
|
}
|
|
// static
|
|
void StreamWriterBuilder::setDefaults(Json::Value* settings)
|
|
{
|
|
//! [StreamWriterBuilderDefaults]
|
|
(*settings)["commentStyle"] = "All";
|
|
(*settings)["indentation"] = "\t";
|
|
(*settings)["enableYAMLCompatibility"] = false;
|
|
(*settings)["dropNullPlaceholders"] = false;
|
|
(*settings)["useSpecialFloats"] = false;
|
|
(*settings)["precision"] = 17;
|
|
//! [StreamWriterBuilderDefaults]
|
|
}
|
|
|
|
JSONCPP_STRING writeString(StreamWriter::Factory const& builder, Value const& root) {
|
|
JSONCPP_OSTRINGSTREAM sout;
|
|
StreamWriterPtr const writer(builder.newStreamWriter());
|
|
writer->write(root, &sout);
|
|
return sout.str();
|
|
}
|
|
|
|
JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM& sout, Value const& root) {
|
|
StreamWriterBuilder builder;
|
|
StreamWriterPtr const writer(builder.newStreamWriter());
|
|
writer->write(root, &sout);
|
|
return sout;
|
|
}
|
|
|
|
} // namespace Json
|