| // 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 |