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