| // 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 |
| |
| 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), "%.16g", value); |
| #else |
| len = sprintf_s(buffer, sizeof(buffer), "%.16g", value); |
| #endif |
| #else |
| if (isfinite(value)) { |
| len = snprintf(buffer, sizeof(buffer), "%.16g", 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(); |
| 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 |