forked from OSchip/llvm-project
830 lines
20 KiB
C++
830 lines
20 KiB
C++
#include <json/writer.h>
|
|
#include <utility>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
#if _MSC_VER >= 1400 // VC++ 8.0
|
|
#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated.
|
|
#endif
|
|
|
|
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 *¤t )
|
|
{
|
|
*--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), "%#.16g", value);
|
|
#else
|
|
sprintf(buffer, "%#.16g", 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") == nullptr && !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+nullptr
|
|
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 )
|
|
{
|
|
}
|
|
|
|
|
|
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_(nullptr)
|
|
, 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_ = nullptr; // 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
|