Files
CMake/Utilities/cmjsoncpp/src/lib_json/json_writer.cpp
Oleksandr Koval 209daa20b2 Code style: add missed explicit 'this->'
CMake uses explicit 'this->' style. Using custom clang-tidy check we can
detect and fix places where 'this->' was missed.
2021-01-05 14:32:36 +02:00

1373 lines
38 KiB
C++

// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors
// 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 <cassert>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#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) && !defined(__GNUC__)
# 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
// Solaris
#if defined(__sun)
# include <ieeefp.h>
# if !defined(isfinite)
# define isfinite finite
# endif
#endif
// AIX
#if defined(_AIX)
# if !defined(isfinite)
# define isfinite finite
# endif
#endif
// HP-UX
#if defined(__hpux)
# if !defined(isfinite)
# if defined(__ia64) && !defined(finite) && !defined(__GNUC__)
# define isfinite(x) \
((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _Isfinite(x)))
# else
# include <math.h>
# define isfinite finite
# endif
# endif
#endif
// Ancient glibc
#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 2
# if !defined(isfinite)
# define isfinite __finite
# endif
#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 (!strchr(buffer, '.') && !strchr(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()
{
this->yamlCompatiblityEnabled_ = true;
}
void FastWriter::dropNullPlaceholders()
{
this->dropNullPlaceholders_ = true;
}
void FastWriter::omitEndingLineFeed()
{
this->omitEndingLineFeed_ = true;
}
JSONCPP_STRING FastWriter::write(const Value& root)
{
this->document_.clear();
this->writeValue(root);
if (!this->omitEndingLineFeed_)
this->document_ += "\n";
return this->document_;
}
void FastWriter::writeValue(const Value& value)
{
switch (value.type()) {
case nullValue:
if (!this->dropNullPlaceholders_)
this->document_ += "null";
break;
case intValue:
this->document_ += valueToString(value.asLargestInt());
break;
case uintValue:
this->document_ += valueToString(value.asLargestUInt());
break;
case realValue:
this->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)
this->document_ +=
valueToQuotedStringN(str, static_cast<unsigned>(end - str));
break;
}
case booleanValue:
this->document_ += valueToString(value.asBool());
break;
case arrayValue: {
this->document_ += '[';
ArrayIndex size = value.size();
for (ArrayIndex index = 0; index < size; ++index) {
if (index > 0)
this->document_ += ',';
this->writeValue(value[index]);
}
this->document_ += ']';
} break;
case objectValue: {
Value::Members members(value.getMemberNames());
this->document_ += '{';
for (Value::Members::iterator it = members.begin(); it != members.end();
++it) {
const JSONCPP_STRING& name = *it;
if (it != members.begin())
this->document_ += ',';
this->document_ += valueToQuotedStringN(
name.data(), static_cast<unsigned>(name.length()));
this->document_ += this->yamlCompatiblityEnabled_ ? ": " : ":";
this->writeValue(value[name]);
}
this->document_ += '}';
} break;
}
}
// Class StyledWriter
// //////////////////////////////////////////////////////////////////
StyledWriter::StyledWriter()
: rightMargin_(74)
, indentSize_(3)
, addChildValues_()
{
}
JSONCPP_STRING StyledWriter::write(const Value& root)
{
this->document_.clear();
this->addChildValues_ = false;
this->indentString_.clear();
this->writeCommentBeforeValue(root);
this->writeValue(root);
this->writeCommentAfterValueOnSameLine(root);
this->document_ += "\n";
return this->document_;
}
void StyledWriter::writeValue(const Value& value)
{
switch (value.type()) {
case nullValue:
this->pushValue("null");
break;
case intValue:
this->pushValue(valueToString(value.asLargestInt()));
break;
case uintValue:
this->pushValue(valueToString(value.asLargestUInt()));
break;
case realValue:
this->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)
this->pushValue(
valueToQuotedStringN(str, static_cast<unsigned>(end - str)));
else
this->pushValue("");
break;
}
case booleanValue:
this->pushValue(valueToString(value.asBool()));
break;
case arrayValue:
this->writeArrayValue(value);
break;
case objectValue: {
Value::Members members(value.getMemberNames());
if (members.empty())
this->pushValue("{}");
else {
this->writeWithIndent("{");
this->indent();
Value::Members::iterator it = members.begin();
for (;;) {
const JSONCPP_STRING& name = *it;
const Value& childValue = value[name];
this->writeCommentBeforeValue(childValue);
this->writeWithIndent(valueToQuotedString(name.c_str()));
this->document_ += " : ";
this->writeValue(childValue);
if (++it == members.end()) {
this->writeCommentAfterValueOnSameLine(childValue);
break;
}
this->document_ += ',';
this->writeCommentAfterValueOnSameLine(childValue);
}
this->unindent();
this->writeWithIndent("}");
}
} break;
}
}
void StyledWriter::writeArrayValue(const Value& value)
{
unsigned size = value.size();
if (size == 0)
this->pushValue("[]");
else {
bool isArrayMultiLine = this->isMultineArray(value);
if (isArrayMultiLine) {
this->writeWithIndent("[");
this->indent();
bool hasChildValue = !this->childValues_.empty();
unsigned index = 0;
for (;;) {
const Value& childValue = value[index];
this->writeCommentBeforeValue(childValue);
if (hasChildValue)
this->writeWithIndent(this->childValues_[index]);
else {
this->writeIndent();
this->writeValue(childValue);
}
if (++index == size) {
this->writeCommentAfterValueOnSameLine(childValue);
break;
}
this->document_ += ',';
this->writeCommentAfterValueOnSameLine(childValue);
}
this->unindent();
this->writeWithIndent("]");
} else // output on a single line
{
assert(this->childValues_.size() == size);
this->document_ += "[ ";
for (unsigned index = 0; index < size; ++index) {
if (index > 0)
this->document_ += ", ";
this->document_ += this->childValues_[index];
}
this->document_ += " ]";
}
}
}
bool StyledWriter::isMultineArray(const Value& value)
{
ArrayIndex const size = value.size();
bool isMultiLine = size * 3 >= this->rightMargin_;
this->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
{
this->childValues_.reserve(size);
this->addChildValues_ = true;
ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
for (ArrayIndex index = 0; index < size; ++index) {
if (this->hasCommentForValue(value[index])) {
isMultiLine = true;
}
this->writeValue(value[index]);
lineLength +=
static_cast<ArrayIndex>(this->childValues_[index].length());
}
this->addChildValues_ = false;
isMultiLine = isMultiLine || lineLength >= this->rightMargin_;
}
return isMultiLine;
}
void StyledWriter::pushValue(const JSONCPP_STRING& value)
{
if (this->addChildValues_)
this->childValues_.push_back(value);
else
this->document_ += value;
}
void StyledWriter::writeIndent()
{
if (!this->document_.empty()) {
char last = this->document_[this->document_.length() - 1];
if (last == ' ') // already indented
return;
if (last != '\n') // Comments may add new-line
this->document_ += '\n';
}
this->document_ += this->indentString_;
}
void StyledWriter::writeWithIndent(const JSONCPP_STRING& value)
{
this->writeIndent();
this->document_ += value;
}
void StyledWriter::indent()
{
this->indentString_ += JSONCPP_STRING(this->indentSize_, ' ');
}
void StyledWriter::unindent()
{
assert(this->indentString_.size() >= this->indentSize_);
this->indentString_.resize(this->indentString_.size() - this->indentSize_);
}
void StyledWriter::writeCommentBeforeValue(const Value& root)
{
if (!root.hasComment(commentBefore))
return;
this->document_ += "\n";
this->writeIndent();
const JSONCPP_STRING& comment = root.getComment(commentBefore);
JSONCPP_STRING::const_iterator iter = comment.begin();
while (iter != comment.end()) {
this->document_ += *iter;
if (*iter == '\n' && (iter != comment.end() && *(iter + 1) == '/'))
this->writeIndent();
++iter;
}
// Comments are stripped of trailing newlines, so add one here
this->document_ += "\n";
}
void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root)
{
if (root.hasComment(commentAfterOnSameLine))
this->document_ += " " + root.getComment(commentAfterOnSameLine);
if (root.hasComment(commentAfter)) {
this->document_ += "\n";
this->document_ += root.getComment(commentAfter);
this->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)
{
this->document_ = &out;
this->addChildValues_ = false;
this->indentString_.clear();
this->indented_ = true;
this->writeCommentBeforeValue(root);
if (!this->indented_)
this->writeIndent();
this->indented_ = true;
this->writeValue(root);
this->writeCommentAfterValueOnSameLine(root);
*this->document_ << "\n";
this->document_ = NULL; // Forget the stream, for safety.
}
void StyledStreamWriter::writeValue(const Value& value)
{
switch (value.type()) {
case nullValue:
this->pushValue("null");
break;
case intValue:
this->pushValue(valueToString(value.asLargestInt()));
break;
case uintValue:
this->pushValue(valueToString(value.asLargestUInt()));
break;
case realValue:
this->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)
this->pushValue(
valueToQuotedStringN(str, static_cast<unsigned>(end - str)));
else
this->pushValue("");
break;
}
case booleanValue:
this->pushValue(valueToString(value.asBool()));
break;
case arrayValue:
this->writeArrayValue(value);
break;
case objectValue: {
Value::Members members(value.getMemberNames());
if (members.empty())
this->pushValue("{}");
else {
this->writeWithIndent("{");
this->indent();
Value::Members::iterator it = members.begin();
for (;;) {
const JSONCPP_STRING& name = *it;
const Value& childValue = value[name];
this->writeCommentBeforeValue(childValue);
this->writeWithIndent(valueToQuotedString(name.c_str()));
*this->document_ << " : ";
this->writeValue(childValue);
if (++it == members.end()) {
this->writeCommentAfterValueOnSameLine(childValue);
break;
}
*this->document_ << ",";
this->writeCommentAfterValueOnSameLine(childValue);
}
this->unindent();
this->writeWithIndent("}");
}
} break;
}
}
void StyledStreamWriter::writeArrayValue(const Value& value)
{
unsigned size = value.size();
if (size == 0)
this->pushValue("[]");
else {
bool isArrayMultiLine = this->isMultineArray(value);
if (isArrayMultiLine) {
this->writeWithIndent("[");
this->indent();
bool hasChildValue = !this->childValues_.empty();
unsigned index = 0;
for (;;) {
const Value& childValue = value[index];
this->writeCommentBeforeValue(childValue);
if (hasChildValue)
this->writeWithIndent(this->childValues_[index]);
else {
if (!this->indented_)
this->writeIndent();
this->indented_ = true;
this->writeValue(childValue);
this->indented_ = false;
}
if (++index == size) {
this->writeCommentAfterValueOnSameLine(childValue);
break;
}
*this->document_ << ",";
this->writeCommentAfterValueOnSameLine(childValue);
}
this->unindent();
this->writeWithIndent("]");
} else // output on a single line
{
assert(this->childValues_.size() == size);
*this->document_ << "[ ";
for (unsigned index = 0; index < size; ++index) {
if (index > 0)
*this->document_ << ", ";
*this->document_ << this->childValues_[index];
}
*this->document_ << " ]";
}
}
}
bool StyledStreamWriter::isMultineArray(const Value& value)
{
ArrayIndex const size = value.size();
bool isMultiLine = size * 3 >= this->rightMargin_;
this->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
{
this->childValues_.reserve(size);
this->addChildValues_ = true;
ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
for (ArrayIndex index = 0; index < size; ++index) {
if (this->hasCommentForValue(value[index])) {
isMultiLine = true;
}
this->writeValue(value[index]);
lineLength +=
static_cast<ArrayIndex>(this->childValues_[index].length());
}
this->addChildValues_ = false;
isMultiLine = isMultiLine || lineLength >= this->rightMargin_;
}
return isMultiLine;
}
void StyledStreamWriter::pushValue(const JSONCPP_STRING& value)
{
if (this->addChildValues_)
this->childValues_.push_back(value);
else
*this->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_.
*this->document_ << '\n' << this->indentString_;
}
void StyledStreamWriter::writeWithIndent(const JSONCPP_STRING& value)
{
if (!this->indented_)
this->writeIndent();
*this->document_ << value;
this->indented_ = false;
}
void StyledStreamWriter::indent()
{
this->indentString_ += this->indentation_;
}
void StyledStreamWriter::unindent()
{
assert(this->indentString_.size() >= this->indentation_.size());
this->indentString_.resize(this->indentString_.size() -
this->indentation_.size());
}
void StyledStreamWriter::writeCommentBeforeValue(const Value& root)
{
if (!root.hasComment(commentBefore))
return;
if (!this->indented_)
this->writeIndent();
const JSONCPP_STRING& comment = root.getComment(commentBefore);
JSONCPP_STRING::const_iterator iter = comment.begin();
while (iter != comment.end()) {
*this->document_ << *iter;
if (*iter == '\n' && (iter != comment.end() && *(iter + 1) == '/'))
// writeIndent(); // would include newline
*this->document_ << this->indentString_;
++iter;
}
this->indented_ = false;
}
void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root)
{
if (root.hasComment(commentAfterOnSameLine))
*this->document_ << ' ' << root.getComment(commentAfterOnSameLine);
if (root.hasComment(commentAfter)) {
this->writeIndent();
*this->document_ << root.getComment(commentAfter);
}
this->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)
{
this->sout_ = sout;
this->addChildValues_ = false;
this->indented_ = true;
this->indentString_.clear();
this->writeCommentBeforeValue(root);
if (!this->indented_)
this->writeIndent();
this->indented_ = true;
this->writeValue(root);
this->writeCommentAfterValueOnSameLine(root);
*this->sout_ << this->endingLineFeedSymbol_;
this->sout_ = NULL;
return 0;
}
void BuiltStyledStreamWriter::writeValue(Value const& value)
{
switch (value.type()) {
case nullValue:
this->pushValue(this->nullSymbol_);
break;
case intValue:
this->pushValue(valueToString(value.asLargestInt()));
break;
case uintValue:
this->pushValue(valueToString(value.asLargestUInt()));
break;
case realValue:
this->pushValue(valueToString(value.asDouble(), this->useSpecialFloats_,
this->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)
this->pushValue(
valueToQuotedStringN(str, static_cast<unsigned>(end - str)));
else
this->pushValue("");
break;
}
case booleanValue:
this->pushValue(valueToString(value.asBool()));
break;
case arrayValue:
this->writeArrayValue(value);
break;
case objectValue: {
Value::Members members(value.getMemberNames());
if (members.empty())
this->pushValue("{}");
else {
this->writeWithIndent("{");
this->indent();
Value::Members::iterator it = members.begin();
for (;;) {
JSONCPP_STRING const& name = *it;
Value const& childValue = value[name];
this->writeCommentBeforeValue(childValue);
this->writeWithIndent(valueToQuotedStringN(
name.data(), static_cast<unsigned>(name.length())));
*this->sout_ << this->colonSymbol_;
this->writeValue(childValue);
if (++it == members.end()) {
this->writeCommentAfterValueOnSameLine(childValue);
break;
}
*this->sout_ << ",";
this->writeCommentAfterValueOnSameLine(childValue);
}
this->unindent();
this->writeWithIndent("}");
}
} break;
}
}
void BuiltStyledStreamWriter::writeArrayValue(Value const& value)
{
unsigned size = value.size();
if (size == 0)
this->pushValue("[]");
else {
bool isMultiLine =
(this->cs_ == CommentStyle::All) || this->isMultineArray(value);
if (isMultiLine) {
this->writeWithIndent("[");
this->indent();
bool hasChildValue = !this->childValues_.empty();
unsigned index = 0;
for (;;) {
Value const& childValue = value[index];
this->writeCommentBeforeValue(childValue);
if (hasChildValue)
this->writeWithIndent(this->childValues_[index]);
else {
if (!this->indented_)
this->writeIndent();
this->indented_ = true;
this->writeValue(childValue);
this->indented_ = false;
}
if (++index == size) {
this->writeCommentAfterValueOnSameLine(childValue);
break;
}
*this->sout_ << ",";
this->writeCommentAfterValueOnSameLine(childValue);
}
this->unindent();
this->writeWithIndent("]");
} else // output on a single line
{
assert(this->childValues_.size() == size);
*this->sout_ << "[";
if (!this->indentation_.empty())
*this->sout_ << " ";
for (unsigned index = 0; index < size; ++index) {
if (index > 0)
*this->sout_ << ((!this->indentation_.empty()) ? ", " : ",");
*this->sout_ << this->childValues_[index];
}
if (!this->indentation_.empty())
*this->sout_ << " ";
*this->sout_ << "]";
}
}
}
bool BuiltStyledStreamWriter::isMultineArray(Value const& value)
{
ArrayIndex const size = value.size();
bool isMultiLine = size * 3 >= this->rightMargin_;
this->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
{
this->childValues_.reserve(size);
this->addChildValues_ = true;
ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
for (ArrayIndex index = 0; index < size; ++index) {
if (hasCommentForValue(value[index])) {
isMultiLine = true;
}
this->writeValue(value[index]);
lineLength +=
static_cast<ArrayIndex>(this->childValues_[index].length());
}
this->addChildValues_ = false;
isMultiLine = isMultiLine || lineLength >= this->rightMargin_;
}
return isMultiLine;
}
void BuiltStyledStreamWriter::pushValue(JSONCPP_STRING const& value)
{
if (this->addChildValues_)
this->childValues_.push_back(value);
else
*this->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 (!this->indentation_.empty()) {
// In this case, drop newlines too.
*this->sout_ << '\n' << this->indentString_;
}
}
void BuiltStyledStreamWriter::writeWithIndent(JSONCPP_STRING const& value)
{
if (!this->indented_)
this->writeIndent();
*this->sout_ << value;
this->indented_ = false;
}
void BuiltStyledStreamWriter::indent()
{
this->indentString_ += this->indentation_;
}
void BuiltStyledStreamWriter::unindent()
{
assert(this->indentString_.size() >= this->indentation_.size());
this->indentString_.resize(this->indentString_.size() -
this->indentation_.size());
}
void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root)
{
if (this->cs_ == CommentStyle::None)
return;
if (!root.hasComment(commentBefore))
return;
if (!this->indented_)
this->writeIndent();
const JSONCPP_STRING& comment = root.getComment(commentBefore);
JSONCPP_STRING::const_iterator iter = comment.begin();
while (iter != comment.end()) {
*this->sout_ << *iter;
if (*iter == '\n' && (iter != comment.end() && *(iter + 1) == '/'))
// writeIndent(); // would write extra newline
*this->sout_ << this->indentString_;
++iter;
}
this->indented_ = false;
}
void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(
Value const& root)
{
if (this->cs_ == CommentStyle::None)
return;
if (root.hasComment(commentAfterOnSameLine))
*this->sout_ << " " + root.getComment(commentAfterOnSameLine);
if (root.hasComment(commentAfter)) {
this->writeIndent();
*this->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(&this->settings_);
}
StreamWriterBuilder::~StreamWriterBuilder()
{
}
StreamWriter* StreamWriterBuilder::newStreamWriter() const
{
JSONCPP_STRING indentation = this->settings_["indentation"].asString();
JSONCPP_STRING cs_str = this->settings_["commentStyle"].asString();
bool eyc = this->settings_["enableYAMLCompatibility"].asBool();
bool dnp = this->settings_["dropNullPlaceholders"].asBool();
bool usf = this->settings_["useSpecialFloats"].asBool();
unsigned int pre = this->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 = this->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] = this->settings_[key];
}
}
return 0u == inv.size();
}
Value& StreamWriterBuilder::operator[](JSONCPP_STRING key)
{
return this->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