mirror of
https://github.com/open-source-parsers/jsoncpp.git
synced 2025-08-04 10:36:23 -04:00

Tests are currently failing when git cloning on Windows with autocrlf = true. In that setup multiline comments contain \r\n EOLs. The test code assumes that comments contain \n EOLs and opens the .actual files (etc.) with "wt" which converts \n to \r\n. Thus we end up with \r\r\n EOLs in the output, which triggers a test failure. Instead we should cannonicalize comments while reading so that they contain only \n EOLs. This approach simplifies other parts of the reader and writer logic, and requires no changes to the test. It is a breaking change, but probably the Right Thing going forward. This change also fixes dereferencing past the end of the comment string in StyledWriter::writeCommentBeforeValue. Tests should be added with appropriate .gitattributes for the input files to ensure that we run tests for DOS, Mac, and Unix EOL files on all platforms. For now this change is enough to unblock Windows builds. issue #116
659 lines
18 KiB
C++
659 lines
18 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 <utility>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <math.h>
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
|
|
#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
|
|
|
|
#if defined(__sun) && defined(__SVR4) //Solaris
|
|
#include <ieeefp.h>
|
|
#define isfinite finite
|
|
#endif
|
|
|
|
namespace Json {
|
|
|
|
static bool containsControlCharacter(const char* str) {
|
|
while (*str) {
|
|
if (isControlCharacter(*(str++)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string valueToString(LargestInt value) {
|
|
UIntToStringBuffer buffer;
|
|
char* current = buffer + sizeof(buffer);
|
|
bool isNegative = value < 0;
|
|
if (isNegative)
|
|
value = -value;
|
|
uintToString(LargestUInt(value), current);
|
|
if (isNegative)
|
|
*--current = '-';
|
|
assert(current >= buffer);
|
|
return current;
|
|
}
|
|
|
|
std::string valueToString(LargestUInt value) {
|
|
UIntToStringBuffer buffer;
|
|
char* current = buffer + sizeof(buffer);
|
|
uintToString(value, current);
|
|
assert(current >= buffer);
|
|
return current;
|
|
}
|
|
|
|
#if defined(JSON_HAS_INT64)
|
|
|
|
std::string valueToString(Int value) {
|
|
return valueToString(LargestInt(value));
|
|
}
|
|
|
|
std::string valueToString(UInt value) {
|
|
return valueToString(LargestUInt(value));
|
|
}
|
|
|
|
#endif // # if defined(JSON_HAS_INT64)
|
|
|
|
std::string valueToString(double value) {
|
|
// Allocate a buffer that is more than large enough to store the 16 digits of
|
|
// precision requested below.
|
|
char buffer[32];
|
|
int len = -1;
|
|
|
|
// 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 defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with
|
|
// visual studio 2005 to
|
|
// avoid warning.
|
|
#if defined(WINCE)
|
|
len = _snprintf(buffer, sizeof(buffer), "%.17g", value);
|
|
#else
|
|
len = sprintf_s(buffer, sizeof(buffer), "%.17g", value);
|
|
#endif
|
|
#else
|
|
if (isfinite(value)) {
|
|
len = snprintf(buffer, sizeof(buffer), "%.17g", value);
|
|
} else {
|
|
// IEEE standard states that NaN values will not compare to themselves
|
|
if (value != value) {
|
|
len = snprintf(buffer, sizeof(buffer), "null");
|
|
} else if (value < 0) {
|
|
len = snprintf(buffer, sizeof(buffer), "-1e+9999");
|
|
} else {
|
|
len = snprintf(buffer, sizeof(buffer), "1e+9999");
|
|
}
|
|
// For those, we do not need to call fixNumLoc, but it is fast.
|
|
}
|
|
#endif
|
|
assert(len >= 0);
|
|
fixNumericLocale(buffer, buffer + len);
|
|
return buffer;
|
|
}
|
|
|
|
std::string valueToString(bool value) { return value ? "true" : "false"; }
|
|
|
|
std::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 std::string("\"") + value + "\"";
|
|
// We have to walk value and escape any special characters.
|
|
// Appending to std::string is not efficient, but this should be rare.
|
|
// (Note: forward slashes are *not* rare, but I am not escaping them.)
|
|
std::string::size_type maxsize =
|
|
strlen(value) * 2 + 3; // allescaped+quotes+NULL
|
|
std::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)) {
|
|
std::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; }
|
|
|
|
std::string FastWriter::write(const Value& root) {
|
|
document_ = "";
|
|
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:
|
|
document_ += valueToQuotedString(value.asCString());
|
|
break;
|
|
case booleanValue:
|
|
document_ += valueToString(value.asBool());
|
|
break;
|
|
case arrayValue: {
|
|
document_ += '[';
|
|
int size = value.size();
|
|
for (int 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 std::string& name = *it;
|
|
if (it != members.begin())
|
|
document_ += ',';
|
|
document_ += valueToQuotedString(name.c_str());
|
|
document_ += yamlCompatiblityEnabled_ ? ": " : ":";
|
|
writeValue(value[name]);
|
|
}
|
|
document_ += '}';
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// Class StyledWriter
|
|
// //////////////////////////////////////////////////////////////////
|
|
|
|
StyledWriter::StyledWriter()
|
|
: rightMargin_(74), indentSize_(3), addChildValues_() {}
|
|
|
|
std::string StyledWriter::write(const Value& root) {
|
|
document_ = "";
|
|
addChildValues_ = false;
|
|
indentString_ = "";
|
|
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:
|
|
pushValue(valueToQuotedString(value.asCString()));
|
|
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 std::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) {
|
|
int size = value.size();
|
|
bool isMultiLine = size * 3 >= rightMargin_;
|
|
childValues_.clear();
|
|
for (int index = 0; index < size && !isMultiLine; ++index) {
|
|
const Value& childValue = value[index];
|
|
isMultiLine =
|
|
isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
|
|
childValue.size() > 0);
|
|
}
|
|
if (!isMultiLine) // check if line length > max line length
|
|
{
|
|
childValues_.reserve(size);
|
|
addChildValues_ = true;
|
|
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
|
for (int index = 0; index < size; ++index) {
|
|
writeValue(value[index]);
|
|
lineLength += int(childValues_[index].length());
|
|
}
|
|
addChildValues_ = false;
|
|
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
|
}
|
|
return isMultiLine;
|
|
}
|
|
|
|
void StyledWriter::pushValue(const std::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 std::string& value) {
|
|
writeIndent();
|
|
document_ += value;
|
|
}
|
|
|
|
void StyledWriter::indent() { indentString_ += std::string(indentSize_, ' '); }
|
|
|
|
void StyledWriter::unindent() {
|
|
assert(int(indentString_.size()) >= indentSize_);
|
|
indentString_.resize(indentString_.size() - indentSize_);
|
|
}
|
|
|
|
void StyledWriter::writeCommentBeforeValue(const Value& root) {
|
|
if (!root.hasComment(commentBefore))
|
|
return;
|
|
|
|
document_ += "\n";
|
|
writeIndent();
|
|
const std::string& comment = root.getComment(commentBefore);
|
|
std::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(std::string indentation)
|
|
: document_(NULL), rightMargin_(74), indentation_(indentation),
|
|
addChildValues_() {}
|
|
|
|
void StyledStreamWriter::write(std::ostream& out, const Value& root) {
|
|
document_ = &out;
|
|
addChildValues_ = false;
|
|
indentString_ = "";
|
|
writeCommentBeforeValue(root);
|
|
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:
|
|
pushValue(valueToQuotedString(value.asCString()));
|
|
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 std::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 {
|
|
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 StyledStreamWriter::isMultineArray(const Value& value) {
|
|
int size = value.size();
|
|
bool isMultiLine = size * 3 >= rightMargin_;
|
|
childValues_.clear();
|
|
for (int index = 0; index < size && !isMultiLine; ++index) {
|
|
const Value& childValue = value[index];
|
|
isMultiLine =
|
|
isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
|
|
childValue.size() > 0);
|
|
}
|
|
if (!isMultiLine) // check if line length > max line length
|
|
{
|
|
childValues_.reserve(size);
|
|
addChildValues_ = true;
|
|
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
|
for (int index = 0; index < size; ++index) {
|
|
writeValue(value[index]);
|
|
lineLength += int(childValues_[index].length());
|
|
}
|
|
addChildValues_ = false;
|
|
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
|
}
|
|
return isMultiLine;
|
|
}
|
|
|
|
void StyledStreamWriter::pushValue(const std::string& value) {
|
|
if (addChildValues_)
|
|
childValues_.push_back(value);
|
|
else
|
|
*document_ << value;
|
|
}
|
|
|
|
void StyledStreamWriter::writeIndent() {
|
|
/*
|
|
Some comments in this method would have been nice. ;-)
|
|
|
|
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_ << '\n' << indentString_;
|
|
}
|
|
|
|
void StyledStreamWriter::writeWithIndent(const std::string& value) {
|
|
writeIndent();
|
|
*document_ << value;
|
|
}
|
|
|
|
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;
|
|
*document_ << root.getComment(commentBefore);
|
|
*document_ << "\n";
|
|
}
|
|
|
|
void StyledStreamWriter::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 StyledStreamWriter::hasCommentForValue(const Value& value) {
|
|
return value.hasComment(commentBefore) ||
|
|
value.hasComment(commentAfterOnSameLine) ||
|
|
value.hasComment(commentAfter);
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& sout, const Value& root) {
|
|
Json::StyledStreamWriter writer;
|
|
writer.write(sout, root);
|
|
return sout;
|
|
}
|
|
|
|
} // namespace Json
|