jsoncpp/src/lib_json/json_writer.cpp
David West bc5dbc6d41 Patch for bug #53 on version 0.5.0
This is a patch that we have utilized at IDEXX Labs for the the bug described above.
We have tested and verified this on x86 32 and 64 bit linux and 32 bit arm.
2014-08-13 02:03:33 -07:00

690 lines
19 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 >= 1400 // VC++ 8.0
// Disable warning about strdup being deprecated.
#pragma warning(disable : 4996)
#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];
// 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)
_snprintf(buffer, sizeof(buffer), "%.16g", value);
#else
sprintf_s(buffer, sizeof(buffer), "%.16g", value);
#endif
#else
if ( isfinite( value ))
{
snprintf(buffer, sizeof(buffer), "%.16g", value);
}
else
{
// IEEE standard states that NaN values will not compare to themselves
if ( value != value)
{
snprintf(buffer, sizeof(buffer), "null");
}
else if ( value < 0)
{
snprintf(buffer, sizeof(buffer), "-1e+9999");
}
else
{
snprintf(buffer, sizeof(buffer), "1e+9999");
}
// nothing more to do, return.
return buffer;
}
#endif
fixNumericLocale(buffer, buffer + strlen(buffer));
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) {}
void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; }
void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }
std::string FastWriter::write(const Value &root) {
document_ = "";
writeValue(root);
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();
std::string normalizedComment = normalizeEOL(root.getComment(commentBefore));
std::string::const_iterator iter = normalizedComment.begin();
while (iter != normalizedComment.end()) {
document_ += *iter;
if (*iter == '\n' && *(iter + 1) == '/')
writeIndent();
++iter;
}
// Comments are stripped of newlines, so add one here
document_ += "\n";
}
void StyledWriter::writeCommentAfterValueOnSameLine(const Value &root) {
if (root.hasComment(commentAfterOnSameLine))
document_ += " " + normalizeEOL(root.getComment(commentAfterOnSameLine));
if (root.hasComment(commentAfter)) {
document_ += "\n";
document_ += normalizeEOL(root.getComment(commentAfter));
document_ += "\n";
}
}
bool StyledWriter::hasCommentForValue(const Value &value) {
return value.hasComment(commentBefore) ||
value.hasComment(commentAfterOnSameLine) ||
value.hasComment(commentAfter);
}
std::string StyledWriter::normalizeEOL(const std::string &text) {
std::string normalized;
normalized.reserve(text.length());
const char *begin = text.c_str();
const char *end = begin + text.length();
const char *current = begin;
while (current != end) {
char c = *current++;
if (c == '\r') // mac or dos EOL
{
if (*current == '\n') // convert dos EOL
++current;
normalized += '\n';
} else // handle unix EOL & other char
normalized += c;
}
return normalized;
}
// 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_ << normalizeEOL(root.getComment(commentBefore));
*document_ << "\n";
}
void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value &root) {
if (root.hasComment(commentAfterOnSameLine))
*document_ << " " + normalizeEOL(root.getComment(commentAfterOnSameLine));
if (root.hasComment(commentAfter)) {
*document_ << "\n";
*document_ << normalizeEOL(root.getComment(commentAfter));
*document_ << "\n";
}
}
bool StyledStreamWriter::hasCommentForValue(const Value &value) {
return value.hasComment(commentBefore) ||
value.hasComment(commentAfterOnSameLine) ||
value.hasComment(commentAfter);
}
std::string StyledStreamWriter::normalizeEOL(const std::string &text) {
std::string normalized;
normalized.reserve(text.length());
const char *begin = text.c_str();
const char *end = begin + text.length();
const char *current = begin;
while (current != end) {
char c = *current++;
if (c == '\r') // mac or dos EOL
{
if (*current == '\n') // convert dos EOL
++current;
normalized += '\n';
} else // handle unix EOL & other char
normalized += c;
}
return normalized;
}
std::ostream &operator<<(std::ostream &sout, const Value &root) {
Json::StyledStreamWriter writer;
writer.write(sout, root);
return sout;
}
} // namespace Json
// vim: et ts=2 sts=2 sw=2 tw=0