15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/json/json_writer.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <cmath>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/json/string_escape.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/strings/string_number_conversions.h"
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/stringprintf.h"
13868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/values.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace base {
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_WIN)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)static const char kPrettyPrintLineEnding[] = "\r\n";
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#else
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)static const char kPrettyPrintLineEnding[] = "\n";
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)// static
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void JSONWriter::Write(const Value* const node, std::string* json) {
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  WriteWithOptions(node, 0, json);
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)// static
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void JSONWriter::WriteWithOptions(const Value* const node, int options,
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                  std::string* json) {
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  json->clear();
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Is there a better way to estimate the size of the output?
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  json->reserve(1024);
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool omit_binary_values = !!(options & OPTIONS_OMIT_BINARY_VALUES);
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool omit_double_type_preservation =
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      !!(options & OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION);
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool pretty_print = !!(options & OPTIONS_PRETTY_PRINT);
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
41d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)  JSONWriter writer(omit_binary_values, omit_double_type_preservation,
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    pretty_print, json);
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  writer.BuildJSONString(node, 0);
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (pretty_print)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    json->append(kPrettyPrintLineEnding);
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
49d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)JSONWriter::JSONWriter(bool omit_binary_values,
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       bool omit_double_type_preservation, bool pretty_print,
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       std::string* json)
52d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)    : omit_binary_values_(omit_binary_values),
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      omit_double_type_preservation_(omit_double_type_preservation),
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pretty_print_(pretty_print),
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      json_string_(json) {
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(json);
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void JSONWriter::BuildJSONString(const Value* const node, int depth) {
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (node->GetType()) {
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_NULL:
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      json_string_->append("null");
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_BOOLEAN:
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bool value;
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bool result = node->GetAsBoolean(&value);
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DCHECK(result);
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        json_string_->append(value ? "true" : "false");
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_INTEGER:
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        int value;
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bool result = node->GetAsInteger(&value);
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DCHECK(result);
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        base::StringAppendF(json_string_, "%d", value);
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_DOUBLE:
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        double value;
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bool result = node->GetAsDouble(&value);
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DCHECK(result);
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (omit_double_type_preservation_ &&
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            value <= kint64max &&
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            value >= kint64min &&
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            std::floor(value) == value) {
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append(Int64ToString(static_cast<int64>(value)));
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          break;
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        std::string real = DoubleToString(value);
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // Ensure that the number has a .0 if there's no decimal or 'e'.  This
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // makes sure that when we read the JSON back, it's interpreted as a
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // real rather than an int.
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (real.find('.') == std::string::npos &&
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            real.find('e') == std::string::npos &&
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            real.find('E') == std::string::npos) {
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          real.append(".0");
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // The JSON spec requires that non-integer values in the range (-1,1)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // have a zero before the decimal point - ".52" is not valid, "0.52" is.
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (real[0] == '.') {
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          real.insert(0, "0");
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        } else if (real.length() > 1 && real[0] == '-' && real[1] == '.') {
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          // "-.1" bad "-0.1" good
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          real.insert(1, "0");
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        json_string_->append(real);
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_STRING:
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        std::string value;
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bool result = node->GetAsString(&value);
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DCHECK(result);
121d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)        EscapeJSONString(value, true, json_string_);
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_LIST:
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        json_string_->append("[");
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (pretty_print_)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append(" ");
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        const ListValue* list = static_cast<const ListValue*>(node);
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for (size_t i = 0; i < list->GetSize(); ++i) {
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          const Value* value = NULL;
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          bool result = list->Get(i, &value);
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          DCHECK(result);
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if (omit_binary_values_ && value->GetType() == Value::TYPE_BINARY) {
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue;
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          }
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if (i != 0) {
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            json_string_->append(",");
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (pretty_print_)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              json_string_->append(" ");
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          }
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          BuildJSONString(value, depth);
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (pretty_print_)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append(" ");
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        json_string_->append("]");
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_DICTIONARY:
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        json_string_->append("{");
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (pretty_print_)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append(kPrettyPrintLineEnding);
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        const DictionaryValue* dict =
163d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)            static_cast<const DictionaryValue*>(node);
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        bool first_entry = true;
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        for (DictionaryValue::Iterator itr(*dict); !itr.IsAtEnd();
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)             itr.Advance(), first_entry = false) {
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          if (omit_binary_values_ &&
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              itr.value().GetType() == Value::TYPE_BINARY) {
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue;
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          }
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          if (!first_entry) {
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            json_string_->append(",");
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (pretty_print_)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              json_string_->append(kPrettyPrintLineEnding);
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          }
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if (pretty_print_)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            IndentLine(depth + 1);
180d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)
181d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)          EscapeJSONString(itr.key(), true, json_string_);
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if (pretty_print_) {
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            json_string_->append(": ");
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          } else {
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            json_string_->append(":");
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          }
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          BuildJSONString(&itr.value(), depth + 1);
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (pretty_print_) {
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append(kPrettyPrintLineEnding);
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          IndentLine(depth);
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append("}");
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        } else {
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          json_string_->append("}");
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case Value::TYPE_BINARY:
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (!omit_binary_values_) {
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          NOTREACHED() << "Cannot serialize binary value.";
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default:
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NOTREACHED() << "unknown json type";
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void JSONWriter::IndentLine(int depth) {
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // It may be faster to keep an indent string so we don't have to keep
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // reallocating.
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  json_string_->append(std::string(depth * 3, ' '));
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace base
220