Path: blob/master/Utilities/cmjsoncpp/src/lib_json/json_writer.cpp
3158 views
// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors1// Distributed under MIT license, or public domain if desired and2// recognized in your jurisdiction.3// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE45#if !defined(JSON_IS_AMALGAMATION)6#include "json_tool.h"7#include <json/writer.h>8#endif // if !defined(JSON_IS_AMALGAMATION)9#include <algorithm>10#include <cassert>11#include <cctype>12#include <cstring>13#include <iomanip>14#include <memory>15#include <set>16#include <sstream>17#include <utility>1819#if __cplusplus >= 201103L20#include <cmath>21#include <cstdio>2223#if !defined(isnan)24#define isnan std::isnan25#endif2627#if !defined(isfinite)28#define isfinite std::isfinite29#endif3031#else32#include <cmath>33#include <cstdio>3435#if defined(_MSC_VER)36#if !defined(isnan)37#include <float.h>38#define isnan _isnan39#endif4041#if !defined(isfinite)42#include <float.h>43#define isfinite _finite44#endif4546#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)47#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 148#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES4950#endif //_MSC_VER5152#if defined(__sun) && defined(__SVR4) // Solaris53#if !defined(isfinite)54#include <ieeefp.h>55#define isfinite finite56#endif57#endif5859#if defined(__hpux)60#if !defined(isfinite)61#if defined(__ia64) && !defined(finite)62#define isfinite(x) \63((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x)))64#endif65#endif66#endif6768#if !defined(isnan)69// IEEE standard states that NaN values will not compare to themselves70#define isnan(x) ((x) != (x))71#endif7273#if !defined(__APPLE__)74#if !defined(isfinite)75#define isfinite finite76#endif77#endif78#endif7980#if defined(_MSC_VER)81// Disable warning about strdup being deprecated.82#pragma warning(disable : 4996)83#endif8485namespace Json {8687#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)88using StreamWriterPtr = std::unique_ptr<StreamWriter>;89#else90using StreamWriterPtr = std::auto_ptr<StreamWriter>;91#endif9293String valueToString(LargestInt value) {94UIntToStringBuffer buffer;95char* current = buffer + sizeof(buffer);96if (value == Value::minLargestInt) {97uintToString(LargestUInt(Value::maxLargestInt) + 1, current);98*--current = '-';99} else if (value < 0) {100uintToString(LargestUInt(-value), current);101*--current = '-';102} else {103uintToString(LargestUInt(value), current);104}105assert(current >= buffer);106return current;107}108109String valueToString(LargestUInt value) {110UIntToStringBuffer buffer;111char* current = buffer + sizeof(buffer);112uintToString(value, current);113assert(current >= buffer);114return current;115}116117#if defined(JSON_HAS_INT64)118119String valueToString(Int value) { return valueToString(LargestInt(value)); }120121String valueToString(UInt value) { return valueToString(LargestUInt(value)); }122123#endif // # if defined(JSON_HAS_INT64)124125namespace {126String valueToString(double value, bool useSpecialFloats,127unsigned int precision, PrecisionType precisionType) {128// Print into the buffer. We need not request the alternative representation129// that always has a decimal point because JSON doesn't distinguish the130// concepts of reals and integers.131if (!isfinite(value)) {132static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"},133{"null", "-1e+9999", "1e+9999"}};134return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0135: (value < 0) ? 1136: 2];137}138139String buffer(size_t(36), '\0');140while (true) {141int len = jsoncpp_snprintf(142&*buffer.begin(), buffer.size(),143(precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f",144precision, value);145assert(len >= 0);146auto wouldPrint = static_cast<size_t>(len);147if (wouldPrint >= buffer.size()) {148buffer.resize(wouldPrint + 1);149continue;150}151buffer.resize(wouldPrint);152break;153}154155buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end());156157// try to ensure we preserve the fact that this was given to us as a double on158// input159if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) {160buffer += ".0";161}162163// strip the zero padding from the right164if (precisionType == PrecisionType::decimalPlaces) {165buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision),166buffer.end());167}168169return buffer;170}171} // namespace172173String valueToString(double value, unsigned int precision,174PrecisionType precisionType) {175return valueToString(value, false, precision, precisionType);176}177178String valueToString(bool value) { return value ? "true" : "false"; }179180static bool doesAnyCharRequireEscaping(char const* s, size_t n) {181assert(s || !n);182183return std::any_of(s, s + n, [](unsigned char c) {184return c == '\\' || c == '"' || c < 0x20 || c > 0x7F;185});186}187188static unsigned int utf8ToCodepoint(const char*& s, const char* e) {189const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;190191unsigned int firstByte = static_cast<unsigned char>(*s);192193if (firstByte < 0x80)194return firstByte;195196if (firstByte < 0xE0) {197if (e - s < 2)198return REPLACEMENT_CHARACTER;199200unsigned int calculated =201((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F);202s += 1;203// oversized encoded characters are invalid204return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;205}206207if (firstByte < 0xF0) {208if (e - s < 3)209return REPLACEMENT_CHARACTER;210211unsigned int calculated = ((firstByte & 0x0F) << 12) |212((static_cast<unsigned int>(s[1]) & 0x3F) << 6) |213(static_cast<unsigned int>(s[2]) & 0x3F);214s += 2;215// surrogates aren't valid codepoints itself216// shouldn't be UTF-8 encoded217if (calculated >= 0xD800 && calculated <= 0xDFFF)218return REPLACEMENT_CHARACTER;219// oversized encoded characters are invalid220return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;221}222223if (firstByte < 0xF8) {224if (e - s < 4)225return REPLACEMENT_CHARACTER;226227unsigned int calculated = ((firstByte & 0x07) << 18) |228((static_cast<unsigned int>(s[1]) & 0x3F) << 12) |229((static_cast<unsigned int>(s[2]) & 0x3F) << 6) |230(static_cast<unsigned int>(s[3]) & 0x3F);231s += 3;232// oversized encoded characters are invalid233return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;234}235236return REPLACEMENT_CHARACTER;237}238239static const char hex2[] = "000102030405060708090a0b0c0d0e0f"240"101112131415161718191a1b1c1d1e1f"241"202122232425262728292a2b2c2d2e2f"242"303132333435363738393a3b3c3d3e3f"243"404142434445464748494a4b4c4d4e4f"244"505152535455565758595a5b5c5d5e5f"245"606162636465666768696a6b6c6d6e6f"246"707172737475767778797a7b7c7d7e7f"247"808182838485868788898a8b8c8d8e8f"248"909192939495969798999a9b9c9d9e9f"249"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"250"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"251"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"252"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"253"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"254"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";255256static String toHex16Bit(unsigned int x) {257const unsigned int hi = (x >> 8) & 0xff;258const unsigned int lo = x & 0xff;259String result(4, ' ');260result[0] = hex2[2 * hi];261result[1] = hex2[2 * hi + 1];262result[2] = hex2[2 * lo];263result[3] = hex2[2 * lo + 1];264return result;265}266267static void appendRaw(String& result, unsigned ch) {268result += static_cast<char>(ch);269}270271static void appendHex(String& result, unsigned ch) {272result.append("\\u").append(toHex16Bit(ch));273}274275static String valueToQuotedStringN(const char* value, size_t length,276bool emitUTF8 = false) {277if (value == nullptr)278return "";279280if (!doesAnyCharRequireEscaping(value, length))281return String("\"") + value + "\"";282// We have to walk value and escape any special characters.283// Appending to String is not efficient, but this should be rare.284// (Note: forward slashes are *not* rare, but I am not escaping them.)285String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL286String result;287result.reserve(maxsize); // to avoid lots of mallocs288result += "\"";289char const* end = value + length;290for (const char* c = value; c != end; ++c) {291switch (*c) {292case '\"':293result += "\\\"";294break;295case '\\':296result += "\\\\";297break;298case '\b':299result += "\\b";300break;301case '\f':302result += "\\f";303break;304case '\n':305result += "\\n";306break;307case '\r':308result += "\\r";309break;310case '\t':311result += "\\t";312break;313// case '/':314// Even though \/ is considered a legal escape in JSON, a bare315// slash is also legal, so I see no reason to escape it.316// (I hope I am not misunderstanding something.)317// blep notes: actually escaping \/ may be useful in javascript to avoid </318// sequence.319// Should add a flag to allow this compatibility mode and prevent this320// sequence from occurring.321default: {322if (emitUTF8) {323unsigned codepoint = static_cast<unsigned char>(*c);324if (codepoint < 0x20) {325appendHex(result, codepoint);326} else {327appendRaw(result, codepoint);328}329} else {330unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c`331if (codepoint < 0x20) {332appendHex(result, codepoint);333} else if (codepoint < 0x80) {334appendRaw(result, codepoint);335} else if (codepoint < 0x10000) {336// Basic Multilingual Plane337appendHex(result, codepoint);338} else {339// Extended Unicode. Encode 20 bits as a surrogate pair.340codepoint -= 0x10000;341appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff));342appendHex(result, 0xdc00 + (codepoint & 0x3ff));343}344}345} break;346}347}348result += "\"";349return result;350}351352String valueToQuotedString(const char* value) {353return valueToQuotedStringN(value, strlen(value));354}355356String valueToQuotedString(const char* value, size_t length) {357return valueToQuotedStringN(value, length);358}359360// Class Writer361// //////////////////////////////////////////////////////////////////362Writer::~Writer() = default;363364// Class FastWriter365// //////////////////////////////////////////////////////////////////366367FastWriter::FastWriter()368369= default;370371void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; }372373void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }374375void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }376377String FastWriter::write(const Value& root) {378document_.clear();379writeValue(root);380if (!omitEndingLineFeed_)381document_ += '\n';382return document_;383}384385void FastWriter::writeValue(const Value& value) {386switch (value.type()) {387case nullValue:388if (!dropNullPlaceholders_)389document_ += "null";390break;391case intValue:392document_ += valueToString(value.asLargestInt());393break;394case uintValue:395document_ += valueToString(value.asLargestUInt());396break;397case realValue:398document_ += valueToString(value.asDouble());399break;400case stringValue: {401// Is NULL possible for value.string_? No.402char const* str;403char const* end;404bool ok = value.getString(&str, &end);405if (ok)406document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));407break;408}409case booleanValue:410document_ += valueToString(value.asBool());411break;412case arrayValue: {413document_ += '[';414ArrayIndex size = value.size();415for (ArrayIndex index = 0; index < size; ++index) {416if (index > 0)417document_ += ',';418writeValue(value[index]);419}420document_ += ']';421} break;422case objectValue: {423Value::Members members(value.getMemberNames());424document_ += '{';425for (auto it = members.begin(); it != members.end(); ++it) {426const String& name = *it;427if (it != members.begin())428document_ += ',';429document_ += valueToQuotedStringN(name.data(), name.length());430document_ += yamlCompatibilityEnabled_ ? ": " : ":";431writeValue(value[name]);432}433document_ += '}';434} break;435}436}437438// Class StyledWriter439// //////////////////////////////////////////////////////////////////440441StyledWriter::StyledWriter() = default;442443String StyledWriter::write(const Value& root) {444document_.clear();445addChildValues_ = false;446indentString_.clear();447writeCommentBeforeValue(root);448writeValue(root);449writeCommentAfterValueOnSameLine(root);450document_ += '\n';451return document_;452}453454void StyledWriter::writeValue(const Value& value) {455switch (value.type()) {456case nullValue:457pushValue("null");458break;459case intValue:460pushValue(valueToString(value.asLargestInt()));461break;462case uintValue:463pushValue(valueToString(value.asLargestUInt()));464break;465case realValue:466pushValue(valueToString(value.asDouble()));467break;468case stringValue: {469// Is NULL possible for value.string_? No.470char const* str;471char const* end;472bool ok = value.getString(&str, &end);473if (ok)474pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));475else476pushValue("");477break;478}479case booleanValue:480pushValue(valueToString(value.asBool()));481break;482case arrayValue:483writeArrayValue(value);484break;485case objectValue: {486Value::Members members(value.getMemberNames());487if (members.empty())488pushValue("{}");489else {490writeWithIndent("{");491indent();492auto it = members.begin();493for (;;) {494const String& name = *it;495const Value& childValue = value[name];496writeCommentBeforeValue(childValue);497writeWithIndent(valueToQuotedString(name.c_str(), name.size()));498document_ += " : ";499writeValue(childValue);500if (++it == members.end()) {501writeCommentAfterValueOnSameLine(childValue);502break;503}504document_ += ',';505writeCommentAfterValueOnSameLine(childValue);506}507unindent();508writeWithIndent("}");509}510} break;511}512}513514void StyledWriter::writeArrayValue(const Value& value) {515size_t size = value.size();516if (size == 0)517pushValue("[]");518else {519bool isArrayMultiLine = isMultilineArray(value);520if (isArrayMultiLine) {521writeWithIndent("[");522indent();523bool hasChildValue = !childValues_.empty();524ArrayIndex index = 0;525for (;;) {526const Value& childValue = value[index];527writeCommentBeforeValue(childValue);528if (hasChildValue)529writeWithIndent(childValues_[index]);530else {531writeIndent();532writeValue(childValue);533}534if (++index == size) {535writeCommentAfterValueOnSameLine(childValue);536break;537}538document_ += ',';539writeCommentAfterValueOnSameLine(childValue);540}541unindent();542writeWithIndent("]");543} else // output on a single line544{545assert(childValues_.size() == size);546document_ += "[ ";547for (size_t index = 0; index < size; ++index) {548if (index > 0)549document_ += ", ";550document_ += childValues_[index];551}552document_ += " ]";553}554}555}556557bool StyledWriter::isMultilineArray(const Value& value) {558ArrayIndex const size = value.size();559bool isMultiLine = size * 3 >= rightMargin_;560childValues_.clear();561for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {562const Value& childValue = value[index];563isMultiLine = ((childValue.isArray() || childValue.isObject()) &&564!childValue.empty());565}566if (!isMultiLine) // check if line length > max line length567{568childValues_.reserve(size);569addChildValues_ = true;570ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'571for (ArrayIndex index = 0; index < size; ++index) {572if (hasCommentForValue(value[index])) {573isMultiLine = true;574}575writeValue(value[index]);576lineLength += static_cast<ArrayIndex>(childValues_[index].length());577}578addChildValues_ = false;579isMultiLine = isMultiLine || lineLength >= rightMargin_;580}581return isMultiLine;582}583584void StyledWriter::pushValue(const String& value) {585if (addChildValues_)586childValues_.push_back(value);587else588document_ += value;589}590591void StyledWriter::writeIndent() {592if (!document_.empty()) {593char last = document_[document_.length() - 1];594if (last == ' ') // already indented595return;596if (last != '\n') // Comments may add new-line597document_ += '\n';598}599document_ += indentString_;600}601602void StyledWriter::writeWithIndent(const String& value) {603writeIndent();604document_ += value;605}606607void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); }608609void StyledWriter::unindent() {610assert(indentString_.size() >= indentSize_);611indentString_.resize(indentString_.size() - indentSize_);612}613614void StyledWriter::writeCommentBeforeValue(const Value& root) {615if (!root.hasComment(commentBefore))616return;617618document_ += '\n';619writeIndent();620const String& comment = root.getComment(commentBefore);621String::const_iterator iter = comment.begin();622while (iter != comment.end()) {623document_ += *iter;624if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))625writeIndent();626++iter;627}628629// Comments are stripped of trailing newlines, so add one here630document_ += '\n';631}632633void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {634if (root.hasComment(commentAfterOnSameLine))635document_ += " " + root.getComment(commentAfterOnSameLine);636637if (root.hasComment(commentAfter)) {638document_ += '\n';639document_ += root.getComment(commentAfter);640document_ += '\n';641}642}643644bool StyledWriter::hasCommentForValue(const Value& value) {645return value.hasComment(commentBefore) ||646value.hasComment(commentAfterOnSameLine) ||647value.hasComment(commentAfter);648}649650// Class StyledStreamWriter651// //////////////////////////////////////////////////////////////////652653StyledStreamWriter::StyledStreamWriter(String indentation)654: document_(nullptr), indentation_(std::move(indentation)),655addChildValues_(), indented_(false) {}656657void StyledStreamWriter::write(OStream& out, const Value& root) {658document_ = &out;659addChildValues_ = false;660indentString_.clear();661indented_ = true;662writeCommentBeforeValue(root);663if (!indented_)664writeIndent();665indented_ = true;666writeValue(root);667writeCommentAfterValueOnSameLine(root);668*document_ << "\n";669document_ = nullptr; // Forget the stream, for safety.670}671672void StyledStreamWriter::writeValue(const Value& value) {673switch (value.type()) {674case nullValue:675pushValue("null");676break;677case intValue:678pushValue(valueToString(value.asLargestInt()));679break;680case uintValue:681pushValue(valueToString(value.asLargestUInt()));682break;683case realValue:684pushValue(valueToString(value.asDouble()));685break;686case stringValue: {687// Is NULL possible for value.string_? No.688char const* str;689char const* end;690bool ok = value.getString(&str, &end);691if (ok)692pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));693else694pushValue("");695break;696}697case booleanValue:698pushValue(valueToString(value.asBool()));699break;700case arrayValue:701writeArrayValue(value);702break;703case objectValue: {704Value::Members members(value.getMemberNames());705if (members.empty())706pushValue("{}");707else {708writeWithIndent("{");709indent();710auto it = members.begin();711for (;;) {712const String& name = *it;713const Value& childValue = value[name];714writeCommentBeforeValue(childValue);715writeWithIndent(valueToQuotedString(name.c_str(), name.size()));716*document_ << " : ";717writeValue(childValue);718if (++it == members.end()) {719writeCommentAfterValueOnSameLine(childValue);720break;721}722*document_ << ",";723writeCommentAfterValueOnSameLine(childValue);724}725unindent();726writeWithIndent("}");727}728} break;729}730}731732void StyledStreamWriter::writeArrayValue(const Value& value) {733unsigned size = value.size();734if (size == 0)735pushValue("[]");736else {737bool isArrayMultiLine = isMultilineArray(value);738if (isArrayMultiLine) {739writeWithIndent("[");740indent();741bool hasChildValue = !childValues_.empty();742unsigned index = 0;743for (;;) {744const Value& childValue = value[index];745writeCommentBeforeValue(childValue);746if (hasChildValue)747writeWithIndent(childValues_[index]);748else {749if (!indented_)750writeIndent();751indented_ = true;752writeValue(childValue);753indented_ = false;754}755if (++index == size) {756writeCommentAfterValueOnSameLine(childValue);757break;758}759*document_ << ",";760writeCommentAfterValueOnSameLine(childValue);761}762unindent();763writeWithIndent("]");764} else // output on a single line765{766assert(childValues_.size() == size);767*document_ << "[ ";768for (unsigned index = 0; index < size; ++index) {769if (index > 0)770*document_ << ", ";771*document_ << childValues_[index];772}773*document_ << " ]";774}775}776}777778bool StyledStreamWriter::isMultilineArray(const Value& value) {779ArrayIndex const size = value.size();780bool isMultiLine = size * 3 >= rightMargin_;781childValues_.clear();782for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {783const Value& childValue = value[index];784isMultiLine = ((childValue.isArray() || childValue.isObject()) &&785!childValue.empty());786}787if (!isMultiLine) // check if line length > max line length788{789childValues_.reserve(size);790addChildValues_ = true;791ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'792for (ArrayIndex index = 0; index < size; ++index) {793if (hasCommentForValue(value[index])) {794isMultiLine = true;795}796writeValue(value[index]);797lineLength += static_cast<ArrayIndex>(childValues_[index].length());798}799addChildValues_ = false;800isMultiLine = isMultiLine || lineLength >= rightMargin_;801}802return isMultiLine;803}804805void StyledStreamWriter::pushValue(const String& value) {806if (addChildValues_)807childValues_.push_back(value);808else809*document_ << value;810}811812void StyledStreamWriter::writeIndent() {813// blep intended this to look at the so-far-written string814// to determine whether we are already indented, but815// with a stream we cannot do that. So we rely on some saved state.816// The caller checks indented_.817*document_ << '\n' << indentString_;818}819820void StyledStreamWriter::writeWithIndent(const String& value) {821if (!indented_)822writeIndent();823*document_ << value;824indented_ = false;825}826827void StyledStreamWriter::indent() { indentString_ += indentation_; }828829void StyledStreamWriter::unindent() {830assert(indentString_.size() >= indentation_.size());831indentString_.resize(indentString_.size() - indentation_.size());832}833834void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {835if (!root.hasComment(commentBefore))836return;837838if (!indented_)839writeIndent();840const String& comment = root.getComment(commentBefore);841String::const_iterator iter = comment.begin();842while (iter != comment.end()) {843*document_ << *iter;844if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))845// writeIndent(); // would include newline846*document_ << indentString_;847++iter;848}849indented_ = false;850}851852void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {853if (root.hasComment(commentAfterOnSameLine))854*document_ << ' ' << root.getComment(commentAfterOnSameLine);855856if (root.hasComment(commentAfter)) {857writeIndent();858*document_ << root.getComment(commentAfter);859}860indented_ = false;861}862863bool StyledStreamWriter::hasCommentForValue(const Value& value) {864return value.hasComment(commentBefore) ||865value.hasComment(commentAfterOnSameLine) ||866value.hasComment(commentAfter);867}868869//////////////////////////870// BuiltStyledStreamWriter871872/// Scoped enums are not available until C++11.873struct CommentStyle {874/// Decide whether to write comments.875enum Enum {876None, ///< Drop all comments.877Most, ///< Recover odd behavior of previous versions (not implemented yet).878All ///< Keep all comments.879};880};881882struct BuiltStyledStreamWriter : public StreamWriter {883BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs,884String colonSymbol, String nullSymbol,885String endingLineFeedSymbol, bool useSpecialFloats,886bool emitUTF8, unsigned int precision,887PrecisionType precisionType);888int write(Value const& root, OStream* sout) override;889890private:891void writeValue(Value const& value);892void writeArrayValue(Value const& value);893bool isMultilineArray(Value const& value);894void pushValue(String const& value);895void writeIndent();896void writeWithIndent(String const& value);897void indent();898void unindent();899void writeCommentBeforeValue(Value const& root);900void writeCommentAfterValueOnSameLine(Value const& root);901static bool hasCommentForValue(const Value& value);902903using ChildValues = std::vector<String>;904905ChildValues childValues_;906String indentString_;907unsigned int rightMargin_;908String indentation_;909CommentStyle::Enum cs_;910String colonSymbol_;911String nullSymbol_;912String endingLineFeedSymbol_;913bool addChildValues_ : 1;914bool indented_ : 1;915bool useSpecialFloats_ : 1;916bool emitUTF8_ : 1;917unsigned int precision_;918PrecisionType precisionType_;919};920BuiltStyledStreamWriter::BuiltStyledStreamWriter(921String indentation, CommentStyle::Enum cs, String colonSymbol,922String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats,923bool emitUTF8, unsigned int precision, PrecisionType precisionType)924: rightMargin_(74), indentation_(std::move(indentation)), cs_(cs),925colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)),926endingLineFeedSymbol_(std::move(endingLineFeedSymbol)),927addChildValues_(false), indented_(false),928useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8),929precision_(precision), precisionType_(precisionType) {}930int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) {931sout_ = sout;932addChildValues_ = false;933indented_ = true;934indentString_.clear();935writeCommentBeforeValue(root);936if (!indented_)937writeIndent();938indented_ = true;939writeValue(root);940writeCommentAfterValueOnSameLine(root);941*sout_ << endingLineFeedSymbol_;942sout_ = nullptr;943return 0;944}945void BuiltStyledStreamWriter::writeValue(Value const& value) {946switch (value.type()) {947case nullValue:948pushValue(nullSymbol_);949break;950case intValue:951pushValue(valueToString(value.asLargestInt()));952break;953case uintValue:954pushValue(valueToString(value.asLargestUInt()));955break;956case realValue:957pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_,958precisionType_));959break;960case stringValue: {961// Is NULL is possible for value.string_? No.962char const* str;963char const* end;964bool ok = value.getString(&str, &end);965if (ok)966pushValue(967valueToQuotedStringN(str, static_cast<size_t>(end - str), emitUTF8_));968else969pushValue("");970break;971}972case booleanValue:973pushValue(valueToString(value.asBool()));974break;975case arrayValue:976writeArrayValue(value);977break;978case objectValue: {979Value::Members members(value.getMemberNames());980if (members.empty())981pushValue("{}");982else {983writeWithIndent("{");984indent();985auto it = members.begin();986for (;;) {987String const& name = *it;988Value const& childValue = value[name];989writeCommentBeforeValue(childValue);990writeWithIndent(991valueToQuotedStringN(name.data(), name.length(), emitUTF8_));992*sout_ << colonSymbol_;993writeValue(childValue);994if (++it == members.end()) {995writeCommentAfterValueOnSameLine(childValue);996break;997}998*sout_ << ",";999writeCommentAfterValueOnSameLine(childValue);1000}1001unindent();1002writeWithIndent("}");1003}1004} break;1005}1006}10071008void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {1009unsigned size = value.size();1010if (size == 0)1011pushValue("[]");1012else {1013bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value);1014if (isMultiLine) {1015writeWithIndent("[");1016indent();1017bool hasChildValue = !childValues_.empty();1018unsigned index = 0;1019for (;;) {1020Value const& childValue = value[index];1021writeCommentBeforeValue(childValue);1022if (hasChildValue)1023writeWithIndent(childValues_[index]);1024else {1025if (!indented_)1026writeIndent();1027indented_ = true;1028writeValue(childValue);1029indented_ = false;1030}1031if (++index == size) {1032writeCommentAfterValueOnSameLine(childValue);1033break;1034}1035*sout_ << ",";1036writeCommentAfterValueOnSameLine(childValue);1037}1038unindent();1039writeWithIndent("]");1040} else // output on a single line1041{1042assert(childValues_.size() == size);1043*sout_ << "[";1044if (!indentation_.empty())1045*sout_ << " ";1046for (unsigned index = 0; index < size; ++index) {1047if (index > 0)1048*sout_ << ((!indentation_.empty()) ? ", " : ",");1049*sout_ << childValues_[index];1050}1051if (!indentation_.empty())1052*sout_ << " ";1053*sout_ << "]";1054}1055}1056}10571058bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) {1059ArrayIndex const size = value.size();1060bool isMultiLine = size * 3 >= rightMargin_;1061childValues_.clear();1062for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {1063Value const& childValue = value[index];1064isMultiLine = ((childValue.isArray() || childValue.isObject()) &&1065!childValue.empty());1066}1067if (!isMultiLine) // check if line length > max line length1068{1069childValues_.reserve(size);1070addChildValues_ = true;1071ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'1072for (ArrayIndex index = 0; index < size; ++index) {1073if (hasCommentForValue(value[index])) {1074isMultiLine = true;1075}1076writeValue(value[index]);1077lineLength += static_cast<ArrayIndex>(childValues_[index].length());1078}1079addChildValues_ = false;1080isMultiLine = isMultiLine || lineLength >= rightMargin_;1081}1082return isMultiLine;1083}10841085void BuiltStyledStreamWriter::pushValue(String const& value) {1086if (addChildValues_)1087childValues_.push_back(value);1088else1089*sout_ << value;1090}10911092void BuiltStyledStreamWriter::writeIndent() {1093// blep intended this to look at the so-far-written string1094// to determine whether we are already indented, but1095// with a stream we cannot do that. So we rely on some saved state.1096// The caller checks indented_.10971098if (!indentation_.empty()) {1099// In this case, drop newlines too.1100*sout_ << '\n' << indentString_;1101}1102}11031104void BuiltStyledStreamWriter::writeWithIndent(String const& value) {1105if (!indented_)1106writeIndent();1107*sout_ << value;1108indented_ = false;1109}11101111void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }11121113void BuiltStyledStreamWriter::unindent() {1114assert(indentString_.size() >= indentation_.size());1115indentString_.resize(indentString_.size() - indentation_.size());1116}11171118void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {1119if (cs_ == CommentStyle::None)1120return;1121if (!root.hasComment(commentBefore))1122return;11231124if (!indented_)1125writeIndent();1126const String& comment = root.getComment(commentBefore);1127String::const_iterator iter = comment.begin();1128while (iter != comment.end()) {1129*sout_ << *iter;1130if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))1131// writeIndent(); // would write extra newline1132*sout_ << indentString_;1133++iter;1134}1135indented_ = false;1136}11371138void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(1139Value const& root) {1140if (cs_ == CommentStyle::None)1141return;1142if (root.hasComment(commentAfterOnSameLine))1143*sout_ << " " + root.getComment(commentAfterOnSameLine);11441145if (root.hasComment(commentAfter)) {1146writeIndent();1147*sout_ << root.getComment(commentAfter);1148}1149}11501151// static1152bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {1153return value.hasComment(commentBefore) ||1154value.hasComment(commentAfterOnSameLine) ||1155value.hasComment(commentAfter);1156}11571158///////////////1159// StreamWriter11601161StreamWriter::StreamWriter() : sout_(nullptr) {}1162StreamWriter::~StreamWriter() = default;1163StreamWriter::Factory::~Factory() = default;1164StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); }1165StreamWriterBuilder::~StreamWriterBuilder() = default;1166StreamWriter* StreamWriterBuilder::newStreamWriter() const {1167const String indentation = settings_["indentation"].asString();1168const String cs_str = settings_["commentStyle"].asString();1169const String pt_str = settings_["precisionType"].asString();1170const bool eyc = settings_["enableYAMLCompatibility"].asBool();1171const bool dnp = settings_["dropNullPlaceholders"].asBool();1172const bool usf = settings_["useSpecialFloats"].asBool();1173const bool emitUTF8 = settings_["emitUTF8"].asBool();1174unsigned int pre = settings_["precision"].asUInt();1175CommentStyle::Enum cs = CommentStyle::All;1176if (cs_str == "All") {1177cs = CommentStyle::All;1178} else if (cs_str == "None") {1179cs = CommentStyle::None;1180} else {1181throwRuntimeError("commentStyle must be 'All' or 'None'");1182}1183PrecisionType precisionType(significantDigits);1184if (pt_str == "significant") {1185precisionType = PrecisionType::significantDigits;1186} else if (pt_str == "decimal") {1187precisionType = PrecisionType::decimalPlaces;1188} else {1189throwRuntimeError("precisionType must be 'significant' or 'decimal'");1190}1191String colonSymbol = " : ";1192if (eyc) {1193colonSymbol = ": ";1194} else if (indentation.empty()) {1195colonSymbol = ":";1196}1197String nullSymbol = "null";1198if (dnp) {1199nullSymbol.clear();1200}1201if (pre > 17)1202pre = 17;1203String endingLineFeedSymbol;1204return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol,1205endingLineFeedSymbol, usf, emitUTF8, pre,1206precisionType);1207}12081209bool StreamWriterBuilder::validate(Json::Value* invalid) const {1210static const auto& valid_keys = *new std::set<String>{1211"indentation",1212"commentStyle",1213"enableYAMLCompatibility",1214"dropNullPlaceholders",1215"useSpecialFloats",1216"emitUTF8",1217"precision",1218"precisionType",1219};1220for (auto si = settings_.begin(); si != settings_.end(); ++si) {1221auto key = si.name();1222if (valid_keys.count(key))1223continue;1224if (invalid)1225(*invalid)[key] = *si;1226else1227return false;1228}1229return invalid ? invalid->empty() : true;1230}12311232Value& StreamWriterBuilder::operator[](const String& key) {1233return settings_[key];1234}1235// static1236void StreamWriterBuilder::setDefaults(Json::Value* settings) {1237//! [StreamWriterBuilderDefaults]1238(*settings)["commentStyle"] = "All";1239(*settings)["indentation"] = "\t";1240(*settings)["enableYAMLCompatibility"] = false;1241(*settings)["dropNullPlaceholders"] = false;1242(*settings)["useSpecialFloats"] = false;1243(*settings)["emitUTF8"] = false;1244(*settings)["precision"] = 17;1245(*settings)["precisionType"] = "significant";1246//! [StreamWriterBuilderDefaults]1247}12481249String writeString(StreamWriter::Factory const& factory, Value const& root) {1250OStringStream sout;1251StreamWriterPtr const writer(factory.newStreamWriter());1252writer->write(root, &sout);1253return std::move(sout).str();1254}12551256OStream& operator<<(OStream& sout, Value const& root) {1257StreamWriterBuilder builder;1258StreamWriterPtr const writer(builder.newStreamWriter());1259writer->write(root, &sout);1260return sout;1261}12621263} // namespace Json126412651266