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