1 // Copyright 2011 Baptiste Lepilleur
2 // Distributed under MIT license, or public domain if desired and
3 // recognized in your jurisdiction.
4 // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5
6 #if !defined(JSON_IS_AMALGAMATION)
7 # include <json/writer.h>
8 # include "json_tool.h"
9 #endif // if !defined(JSON_IS_AMALGAMATION)
10 #include <utility>
11 #include <assert.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <sstream>
15 #include <iomanip>
16
17 #if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
18 #pragma warning( disable : 4996 ) // disable warning about strdup being deprecated.
19 #endif
20
21 namespace Json {
22
containsControlCharacter(const char * str)23 static bool containsControlCharacter( const char* str )
24 {
25 while ( *str )
26 {
27 if ( isControlCharacter( *(str++) ) )
28 return true;
29 }
30 return false;
31 }
32
33
valueToString(LargestInt value)34 std::string valueToString( LargestInt value )
35 {
36 UIntToStringBuffer buffer;
37 char *current = buffer + sizeof(buffer);
38 bool isNegative = value < 0;
39 if ( isNegative )
40 value = -value;
41 uintToString( LargestUInt(value), current );
42 if ( isNegative )
43 *--current = '-';
44 assert( current >= buffer );
45 return current;
46 }
47
48
valueToString(LargestUInt value)49 std::string valueToString( LargestUInt value )
50 {
51 UIntToStringBuffer buffer;
52 char *current = buffer + sizeof(buffer);
53 uintToString( value, current );
54 assert( current >= buffer );
55 return current;
56 }
57
58 #if defined(JSON_HAS_INT64)
59
valueToString(Int value)60 std::string valueToString( Int value )
61 {
62 return valueToString( LargestInt(value) );
63 }
64
65
valueToString(UInt value)66 std::string valueToString( UInt value )
67 {
68 return valueToString( LargestUInt(value) );
69 }
70
71 #endif // # if defined(JSON_HAS_INT64)
72
73
valueToString(double value)74 std::string valueToString( double value )
75 {
76 // Allocate a buffer that is more than large enough to store the 16 digits of
77 // precision requested below.
78 char buffer[32];
79
80 // Print into the buffer. We need not request the alternative representation
81 // that always has a decimal point because JSON doesn't distingish the
82 // concepts of reals and integers.
83 #if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning.
84 sprintf_s(buffer, sizeof(buffer), "%.16g", value);
85 #else
86 snprintf(buffer, sizeof(buffer), "%.16g", value);
87 #endif
88
89 return buffer;
90 }
91
92
valueToString(bool value)93 std::string valueToString( bool value )
94 {
95 return value ? "true" : "false";
96 }
97
valueToQuotedString(const char * value)98 std::string valueToQuotedString( const char *value )
99 {
100 if (value == NULL)
101 return "";
102 // Not sure how to handle unicode...
103 if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value ))
104 return std::string("\"") + value + "\"";
105 // We have to walk value and escape any special characters.
106 // Appending to std::string is not efficient, but this should be rare.
107 // (Note: forward slashes are *not* rare, but I am not escaping them.)
108 std::string::size_type maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL
109 std::string result;
110 result.reserve(maxsize); // to avoid lots of mallocs
111 result += "\"";
112 for (const char* c=value; *c != 0; ++c)
113 {
114 switch(*c)
115 {
116 case '\"':
117 result += "\\\"";
118 break;
119 case '\\':
120 result += "\\\\";
121 break;
122 case '\b':
123 result += "\\b";
124 break;
125 case '\f':
126 result += "\\f";
127 break;
128 case '\n':
129 result += "\\n";
130 break;
131 case '\r':
132 result += "\\r";
133 break;
134 case '\t':
135 result += "\\t";
136 break;
137 //case '/':
138 // Even though \/ is considered a legal escape in JSON, a bare
139 // slash is also legal, so I see no reason to escape it.
140 // (I hope I am not misunderstanding something.
141 // blep notes: actually escaping \/ may be useful in javascript to avoid </
142 // sequence.
143 // Should add a flag to allow this compatibility mode and prevent this
144 // sequence from occurring.
145 default:
146 if ( isControlCharacter( *c ) )
147 {
148 std::ostringstream oss;
149 oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int>(*c);
150 result += oss.str();
151 }
152 else
153 {
154 result += *c;
155 }
156 break;
157 }
158 }
159 result += "\"";
160 return result;
161 }
162
163 // Class Writer
164 // //////////////////////////////////////////////////////////////////
~Writer()165 Writer::~Writer()
166 {
167 }
168
169
170 // Class FastWriter
171 // //////////////////////////////////////////////////////////////////
172
FastWriter()173 FastWriter::FastWriter()
174 : yamlCompatiblityEnabled_( false ),
175 dropNullPlaceholders_( false )
176 {
177 }
178
179
180 void
enableYAMLCompatibility()181 FastWriter::enableYAMLCompatibility()
182 {
183 yamlCompatiblityEnabled_ = true;
184 }
185
186
187 void
dropNullPlaceholders()188 FastWriter::dropNullPlaceholders()
189 {
190 dropNullPlaceholders_ = true;
191 }
192
193
194 std::string
write(const Value & root)195 FastWriter::write( const Value &root )
196 {
197 document_ = "";
198 writeValue( root );
199 document_ += "\n";
200 return document_;
201 }
202
203
204 void
writeValue(const Value & value)205 FastWriter::writeValue( const Value &value )
206 {
207 switch ( value.type() )
208 {
209 case nullValue:
210 if (!dropNullPlaceholders_) document_ += "null";
211 break;
212 case intValue:
213 document_ += valueToString( value.asLargestInt() );
214 break;
215 case uintValue:
216 document_ += valueToString( value.asLargestUInt() );
217 break;
218 case realValue:
219 document_ += valueToString( value.asDouble() );
220 break;
221 case stringValue:
222 document_ += valueToQuotedString( value.asCString() );
223 break;
224 case booleanValue:
225 document_ += valueToString( value.asBool() );
226 break;
227 case arrayValue:
228 {
229 document_ += "[";
230 int size = value.size();
231 for ( int index =0; index < size; ++index )
232 {
233 if ( index > 0 )
234 document_ += ",";
235 writeValue( value[index] );
236 }
237 document_ += "]";
238 }
239 break;
240 case objectValue:
241 {
242 Value::Members members( value.getMemberNames() );
243 document_ += "{";
244 for ( Value::Members::iterator it = members.begin();
245 it != members.end();
246 ++it )
247 {
248 const std::string &name = *it;
249 if ( it != members.begin() )
250 document_ += ",";
251 document_ += valueToQuotedString( name.c_str() );
252 document_ += yamlCompatiblityEnabled_ ? ": "
253 : ":";
254 writeValue( value[name] );
255 }
256 document_ += "}";
257 }
258 break;
259 }
260 }
261
262
263 // Class StyledWriter
264 // //////////////////////////////////////////////////////////////////
265
StyledWriter()266 StyledWriter::StyledWriter()
267 : rightMargin_( 74 )
268 , indentSize_( 3 )
269 , addChildValues_()
270 {
271 }
272
273
274 std::string
write(const Value & root)275 StyledWriter::write( const Value &root )
276 {
277 document_ = "";
278 addChildValues_ = false;
279 indentString_ = "";
280 writeCommentBeforeValue( root );
281 writeValue( root );
282 writeCommentAfterValueOnSameLine( root );
283 document_ += "\n";
284 return document_;
285 }
286
287
288 void
writeValue(const Value & value)289 StyledWriter::writeValue( const Value &value )
290 {
291 switch ( value.type() )
292 {
293 case nullValue:
294 pushValue( "null" );
295 break;
296 case intValue:
297 pushValue( valueToString( value.asLargestInt() ) );
298 break;
299 case uintValue:
300 pushValue( valueToString( value.asLargestUInt() ) );
301 break;
302 case realValue:
303 pushValue( valueToString( value.asDouble() ) );
304 break;
305 case stringValue:
306 pushValue( valueToQuotedString( value.asCString() ) );
307 break;
308 case booleanValue:
309 pushValue( valueToString( value.asBool() ) );
310 break;
311 case arrayValue:
312 writeArrayValue( value);
313 break;
314 case objectValue:
315 {
316 Value::Members members( value.getMemberNames() );
317 if ( members.empty() )
318 pushValue( "{}" );
319 else
320 {
321 writeWithIndent( "{" );
322 indent();
323 Value::Members::iterator it = members.begin();
324 for (;;)
325 {
326 const std::string &name = *it;
327 const Value &childValue = value[name];
328 writeCommentBeforeValue( childValue );
329 writeWithIndent( valueToQuotedString( name.c_str() ) );
330 document_ += " : ";
331 writeValue( childValue );
332 if ( ++it == members.end() )
333 {
334 writeCommentAfterValueOnSameLine( childValue );
335 break;
336 }
337 document_ += ",";
338 writeCommentAfterValueOnSameLine( childValue );
339 }
340 unindent();
341 writeWithIndent( "}" );
342 }
343 }
344 break;
345 }
346 }
347
348
349 void
writeArrayValue(const Value & value)350 StyledWriter::writeArrayValue( const Value &value )
351 {
352 unsigned size = value.size();
353 if ( size == 0 )
354 pushValue( "[]" );
355 else
356 {
357 bool isArrayMultiLine = isMultineArray( value );
358 if ( isArrayMultiLine )
359 {
360 writeWithIndent( "[" );
361 indent();
362 bool hasChildValue = !childValues_.empty();
363 unsigned index =0;
364 for (;;)
365 {
366 const Value &childValue = value[index];
367 writeCommentBeforeValue( childValue );
368 if ( hasChildValue )
369 writeWithIndent( childValues_[index] );
370 else
371 {
372 writeIndent();
373 writeValue( childValue );
374 }
375 if ( ++index == size )
376 {
377 writeCommentAfterValueOnSameLine( childValue );
378 break;
379 }
380 document_ += ",";
381 writeCommentAfterValueOnSameLine( childValue );
382 }
383 unindent();
384 writeWithIndent( "]" );
385 }
386 else // output on a single line
387 {
388 assert( childValues_.size() == size );
389 document_ += "[ ";
390 for ( unsigned index =0; index < size; ++index )
391 {
392 if ( index > 0 )
393 document_ += ", ";
394 document_ += childValues_[index];
395 }
396 document_ += " ]";
397 }
398 }
399 }
400
401
402 bool
isMultineArray(const Value & value)403 StyledWriter::isMultineArray( const Value &value )
404 {
405 int size = value.size();
406 bool isMultiLine = size*3 >= rightMargin_ ;
407 childValues_.clear();
408 for ( int index =0; index < size && !isMultiLine; ++index )
409 {
410 const Value &childValue = value[index];
411 isMultiLine = isMultiLine ||
412 ( (childValue.isArray() || childValue.isObject()) &&
413 childValue.size() > 0 );
414 }
415 if ( !isMultiLine ) // check if line length > max line length
416 {
417 childValues_.reserve( size );
418 addChildValues_ = true;
419 int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]'
420 for ( int index =0; index < size && !isMultiLine; ++index )
421 {
422 writeValue( value[index] );
423 lineLength += int( childValues_[index].length() );
424 isMultiLine = isMultiLine && hasCommentForValue( value[index] );
425 }
426 addChildValues_ = false;
427 isMultiLine = isMultiLine || lineLength >= rightMargin_;
428 }
429 return isMultiLine;
430 }
431
432
433 void
pushValue(const std::string & value)434 StyledWriter::pushValue( const std::string &value )
435 {
436 if ( addChildValues_ )
437 childValues_.push_back( value );
438 else
439 document_ += value;
440 }
441
442
443 void
writeIndent()444 StyledWriter::writeIndent()
445 {
446 if ( !document_.empty() )
447 {
448 char last = document_[document_.length()-1];
449 if ( last == ' ' ) // already indented
450 return;
451 if ( last != '\n' ) // Comments may add new-line
452 document_ += '\n';
453 }
454 document_ += indentString_;
455 }
456
457
458 void
writeWithIndent(const std::string & value)459 StyledWriter::writeWithIndent( const std::string &value )
460 {
461 writeIndent();
462 document_ += value;
463 }
464
465
466 void
indent()467 StyledWriter::indent()
468 {
469 indentString_ += std::string( indentSize_, ' ' );
470 }
471
472
473 void
unindent()474 StyledWriter::unindent()
475 {
476 assert( int(indentString_.size()) >= indentSize_ );
477 indentString_.resize( indentString_.size() - indentSize_ );
478 }
479
480
481 void
writeCommentBeforeValue(const Value & root)482 StyledWriter::writeCommentBeforeValue( const Value &root )
483 {
484 if ( !root.hasComment( commentBefore ) )
485 return;
486 document_ += normalizeEOL( root.getComment( commentBefore ) );
487 document_ += "\n";
488 }
489
490
491 void
writeCommentAfterValueOnSameLine(const Value & root)492 StyledWriter::writeCommentAfterValueOnSameLine( const Value &root )
493 {
494 if ( root.hasComment( commentAfterOnSameLine ) )
495 document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) );
496
497 if ( root.hasComment( commentAfter ) )
498 {
499 document_ += "\n";
500 document_ += normalizeEOL( root.getComment( commentAfter ) );
501 document_ += "\n";
502 }
503 }
504
505
506 bool
hasCommentForValue(const Value & value)507 StyledWriter::hasCommentForValue( const Value &value )
508 {
509 return value.hasComment( commentBefore )
510 || value.hasComment( commentAfterOnSameLine )
511 || value.hasComment( commentAfter );
512 }
513
514
515 std::string
normalizeEOL(const std::string & text)516 StyledWriter::normalizeEOL( const std::string &text )
517 {
518 std::string normalized;
519 normalized.reserve( text.length() );
520 const char *begin = text.c_str();
521 const char *end = begin + text.length();
522 const char *current = begin;
523 while ( current != end )
524 {
525 char c = *current++;
526 if ( c == '\r' ) // mac or dos EOL
527 {
528 if ( *current == '\n' ) // convert dos EOL
529 ++current;
530 normalized += '\n';
531 }
532 else // handle unix EOL & other char
533 normalized += c;
534 }
535 return normalized;
536 }
537
538
539 // Class StyledStreamWriter
540 // //////////////////////////////////////////////////////////////////
541
StyledStreamWriter(std::string indentation)542 StyledStreamWriter::StyledStreamWriter( std::string indentation )
543 : document_(NULL)
544 , rightMargin_( 74 )
545 , indentation_( indentation )
546 , addChildValues_()
547 {
548 }
549
550
551 void
write(std::ostream & out,const Value & root)552 StyledStreamWriter::write( std::ostream &out, const Value &root )
553 {
554 document_ = &out;
555 addChildValues_ = false;
556 indentString_ = "";
557 writeCommentBeforeValue( root );
558 writeValue( root );
559 writeCommentAfterValueOnSameLine( root );
560 *document_ << "\n";
561 document_ = NULL; // Forget the stream, for safety.
562 }
563
564
565 void
writeValue(const Value & value)566 StyledStreamWriter::writeValue( const Value &value )
567 {
568 switch ( value.type() )
569 {
570 case nullValue:
571 pushValue( "null" );
572 break;
573 case intValue:
574 pushValue( valueToString( value.asLargestInt() ) );
575 break;
576 case uintValue:
577 pushValue( valueToString( value.asLargestUInt() ) );
578 break;
579 case realValue:
580 pushValue( valueToString( value.asDouble() ) );
581 break;
582 case stringValue:
583 pushValue( valueToQuotedString( value.asCString() ) );
584 break;
585 case booleanValue:
586 pushValue( valueToString( value.asBool() ) );
587 break;
588 case arrayValue:
589 writeArrayValue( value);
590 break;
591 case objectValue:
592 {
593 Value::Members members( value.getMemberNames() );
594 if ( members.empty() )
595 pushValue( "{}" );
596 else
597 {
598 writeWithIndent( "{" );
599 indent();
600 Value::Members::iterator it = members.begin();
601 for (;;)
602 {
603 const std::string &name = *it;
604 const Value &childValue = value[name];
605 writeCommentBeforeValue( childValue );
606 writeWithIndent( valueToQuotedString( name.c_str() ) );
607 *document_ << " : ";
608 writeValue( childValue );
609 if ( ++it == members.end() )
610 {
611 writeCommentAfterValueOnSameLine( childValue );
612 break;
613 }
614 *document_ << ",";
615 writeCommentAfterValueOnSameLine( childValue );
616 }
617 unindent();
618 writeWithIndent( "}" );
619 }
620 }
621 break;
622 }
623 }
624
625
626 void
writeArrayValue(const Value & value)627 StyledStreamWriter::writeArrayValue( const Value &value )
628 {
629 unsigned size = value.size();
630 if ( size == 0 )
631 pushValue( "[]" );
632 else
633 {
634 bool isArrayMultiLine = isMultineArray( value );
635 if ( isArrayMultiLine )
636 {
637 writeWithIndent( "[" );
638 indent();
639 bool hasChildValue = !childValues_.empty();
640 unsigned index =0;
641 for (;;)
642 {
643 const Value &childValue = value[index];
644 writeCommentBeforeValue( childValue );
645 if ( hasChildValue )
646 writeWithIndent( childValues_[index] );
647 else
648 {
649 writeIndent();
650 writeValue( childValue );
651 }
652 if ( ++index == size )
653 {
654 writeCommentAfterValueOnSameLine( childValue );
655 break;
656 }
657 *document_ << ",";
658 writeCommentAfterValueOnSameLine( childValue );
659 }
660 unindent();
661 writeWithIndent( "]" );
662 }
663 else // output on a single line
664 {
665 assert( childValues_.size() == size );
666 *document_ << "[ ";
667 for ( unsigned index =0; index < size; ++index )
668 {
669 if ( index > 0 )
670 *document_ << ", ";
671 *document_ << childValues_[index];
672 }
673 *document_ << " ]";
674 }
675 }
676 }
677
678
679 bool
isMultineArray(const Value & value)680 StyledStreamWriter::isMultineArray( const Value &value )
681 {
682 int size = value.size();
683 bool isMultiLine = size*3 >= rightMargin_ ;
684 childValues_.clear();
685 for ( int index =0; index < size && !isMultiLine; ++index )
686 {
687 const Value &childValue = value[index];
688 isMultiLine = isMultiLine ||
689 ( (childValue.isArray() || childValue.isObject()) &&
690 childValue.size() > 0 );
691 }
692 if ( !isMultiLine ) // check if line length > max line length
693 {
694 childValues_.reserve( size );
695 addChildValues_ = true;
696 int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]'
697 for ( int index =0; index < size && !isMultiLine; ++index )
698 {
699 writeValue( value[index] );
700 lineLength += int( childValues_[index].length() );
701 isMultiLine = isMultiLine && hasCommentForValue( value[index] );
702 }
703 addChildValues_ = false;
704 isMultiLine = isMultiLine || lineLength >= rightMargin_;
705 }
706 return isMultiLine;
707 }
708
709
710 void
pushValue(const std::string & value)711 StyledStreamWriter::pushValue( const std::string &value )
712 {
713 if ( addChildValues_ )
714 childValues_.push_back( value );
715 else
716 *document_ << value;
717 }
718
719
720 void
writeIndent()721 StyledStreamWriter::writeIndent()
722 {
723 /*
724 Some comments in this method would have been nice. ;-)
725
726 if ( !document_.empty() )
727 {
728 char last = document_[document_.length()-1];
729 if ( last == ' ' ) // already indented
730 return;
731 if ( last != '\n' ) // Comments may add new-line
732 *document_ << '\n';
733 }
734 */
735 *document_ << '\n' << indentString_;
736 }
737
738
739 void
writeWithIndent(const std::string & value)740 StyledStreamWriter::writeWithIndent( const std::string &value )
741 {
742 writeIndent();
743 *document_ << value;
744 }
745
746
747 void
indent()748 StyledStreamWriter::indent()
749 {
750 indentString_ += indentation_;
751 }
752
753
754 void
unindent()755 StyledStreamWriter::unindent()
756 {
757 assert( indentString_.size() >= indentation_.size() );
758 indentString_.resize( indentString_.size() - indentation_.size() );
759 }
760
761
762 void
writeCommentBeforeValue(const Value & root)763 StyledStreamWriter::writeCommentBeforeValue( const Value &root )
764 {
765 if ( !root.hasComment( commentBefore ) )
766 return;
767 *document_ << normalizeEOL( root.getComment( commentBefore ) );
768 *document_ << "\n";
769 }
770
771
772 void
writeCommentAfterValueOnSameLine(const Value & root)773 StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root )
774 {
775 if ( root.hasComment( commentAfterOnSameLine ) )
776 *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) );
777
778 if ( root.hasComment( commentAfter ) )
779 {
780 *document_ << "\n";
781 *document_ << normalizeEOL( root.getComment( commentAfter ) );
782 *document_ << "\n";
783 }
784 }
785
786
787 bool
hasCommentForValue(const Value & value)788 StyledStreamWriter::hasCommentForValue( const Value &value )
789 {
790 return value.hasComment( commentBefore )
791 || value.hasComment( commentAfterOnSameLine )
792 || value.hasComment( commentAfter );
793 }
794
795
796 std::string
normalizeEOL(const std::string & text)797 StyledStreamWriter::normalizeEOL( const std::string &text )
798 {
799 std::string normalized;
800 normalized.reserve( text.length() );
801 const char *begin = text.c_str();
802 const char *end = begin + text.length();
803 const char *current = begin;
804 while ( current != end )
805 {
806 char c = *current++;
807 if ( c == '\r' ) // mac or dos EOL
808 {
809 if ( *current == '\n' ) // convert dos EOL
810 ++current;
811 normalized += '\n';
812 }
813 else // handle unix EOL & other char
814 normalized += c;
815 }
816 return normalized;
817 }
818
819
operator <<(std::ostream & sout,const Value & root)820 std::ostream& operator<<( std::ostream &sout, const Value &root )
821 {
822 Json::StyledStreamWriter writer;
823 writer.write(sout, root);
824 return sout;
825 }
826
827
828 } // namespace Json
829