//------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. */ //============================================================================== namespace Json { static bool isControlCharacter (char ch) { return ch > 0 && ch <= 0x1F; } static bool containsControlCharacter ( const char* str ) { while ( *str ) { if ( isControlCharacter ( * (str++) ) ) return true; } return false; } static void uintToString ( unsigned int value, char*& current ) { *--current = 0; do { *--current = (value % 10) + '0'; value /= 10; } while ( value != 0 ); } std::string valueToString ( Int value ) { char buffer[32]; char* current = buffer + sizeof (buffer); bool isNegative = value < 0; if ( isNegative ) value = -value; uintToString ( UInt (value), current ); if ( isNegative ) *--current = '-'; assert ( current >= buffer ); return current; } std::string valueToString ( UInt value ) { char buffer[32]; char* current = buffer + sizeof (buffer); uintToString ( value, current ); assert ( current >= buffer ); return current; } std::string valueToString ( double value ) { char buffer[32]; #if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. sprintf_s (buffer, sizeof (buffer), "%#f", value); #else sprintf (buffer, "%#f", value); #endif char* ch = buffer + strlen (buffer) - 1; if (*ch != '0') return buffer; // nothing to truncate, so save time while (ch > buffer && *ch == '0') { --ch; } char* last_nonzero = ch; while (ch >= buffer) { switch (*ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': --ch; continue; case '.': // Truncate zeroes to save bytes in output, but keep one. * (last_nonzero + 2) = '\0'; return buffer; default: return buffer; } } return buffer; } std::string valueToString ( bool value ) { return value ? "true" : "false"; } std::string valueToQuotedString ( const char* value ) { // 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.) unsigned 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 (*c); result += oss.str (); } else { result += *c; } break; } } result += "\""; return result; } // Class Writer // ////////////////////////////////////////////////////////////////// Writer::~Writer () { } // Class FastWriter // ////////////////////////////////////////////////////////////////// FastWriter::FastWriter () : yamlCompatiblityEnabled_ ( false ) { } void FastWriter::enableYAMLCompatibility () { yamlCompatiblityEnabled_ = true; } std::string FastWriter::write ( const Value& root ) { document_ = ""; writeValue ( root ); document_ += "\n"; return document_; } void FastWriter::writeValue ( const Value& value ) { switch ( value.type () ) { case nullValue: document_ += "null"; break; case intValue: document_ += valueToString ( value.asInt () ); break; case uintValue: document_ += valueToString ( value.asUInt () ); 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 ) { } 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.asInt () ) ); break; case uintValue: pushValue ( valueToString ( value.asUInt () ) ); 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 (); while ( true ) { 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; while ( true ) { 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 && !isMultiLine; ++index ) { writeValue ( value[index] ); lineLength += int ( childValues_[index].length () ); isMultiLine = isMultiLine && hasCommentForValue ( value[index] ); } 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_ += normalizeEOL ( root.getComment ( commentBefore ) ); 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 ) { } 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.asInt () ) ); break; case uintValue: pushValue ( valueToString ( value.asUInt () ) ); 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 (); while ( true ) { 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; while ( true ) { 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 && !isMultiLine; ++index ) { writeValue ( value[index] ); lineLength += int ( childValues_[index].length () ); isMultiLine = isMultiLine && hasCommentForValue ( value[index] ); } 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