michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "JSStreamWriter.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" // for ArrayLength michael@0: #include "nsDataHashtable.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "nsUTF8Utils.h" michael@0: michael@0: #if _MSC_VER michael@0: #define snprintf _snprintf michael@0: #endif michael@0: michael@0: #define ARRAY (void*)1 michael@0: #define OBJECT (void*)2 michael@0: michael@0: // Escape a UTF8 string to a stream. When an illegal encoding michael@0: // is found it will insert "INVALID" and the function will return. michael@0: static void EscapeToStream(std::ostream& stream, const char* str) { michael@0: stream << "\""; michael@0: michael@0: size_t len = strlen(str); michael@0: const char* end = &str[len]; michael@0: while (str < end) { michael@0: bool err; michael@0: const char* utf8CharStart = str; michael@0: uint32_t ucs4Char = UTF8CharEnumerator::NextChar(&str, end, &err); michael@0: michael@0: if (err) { michael@0: // Encoding error michael@0: stream << "INVALID\""; michael@0: return; michael@0: } michael@0: michael@0: // See http://www.ietf.org/rfc/rfc4627.txt?number=4627 michael@0: // characters that must be escaped: quotation mark, michael@0: // reverse solidus, and the control characters michael@0: // (U+0000 through U+001F). michael@0: if (ucs4Char == '\"') { michael@0: stream << "\\\""; michael@0: } else if (ucs4Char == '\\') { michael@0: stream << "\\\\"; michael@0: } else if (ucs4Char > 0xFF) { michael@0: char16_t chr[2]; michael@0: ConvertUTF8toUTF16 encoder(chr); michael@0: encoder.write(utf8CharStart, uint32_t(str-utf8CharStart)); michael@0: char escChar[13]; michael@0: snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X\\u%04X", chr[0], chr[1]); michael@0: stream << escChar; michael@0: } else if (ucs4Char < 0x1F || ucs4Char > 0xFF) { michael@0: char escChar[7]; michael@0: snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X", ucs4Char); michael@0: stream << escChar; michael@0: } else { michael@0: stream << char(ucs4Char); michael@0: } michael@0: } michael@0: stream << "\""; michael@0: } michael@0: michael@0: JSStreamWriter::JSStreamWriter(std::ostream& aStream) michael@0: : mStream(aStream) michael@0: , mNeedsComma(false) michael@0: , mNeedsName(false) michael@0: { } michael@0: michael@0: JSStreamWriter::~JSStreamWriter() michael@0: { michael@0: MOZ_ASSERT(mStack.GetSize() == 0); michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::BeginObject() michael@0: { michael@0: MOZ_ASSERT(!mNeedsName); michael@0: if (mNeedsComma && mStack.Peek() == ARRAY) { michael@0: mStream << ","; michael@0: } michael@0: mStream << "{"; michael@0: mNeedsComma = false; michael@0: mNeedsName = true; michael@0: mStack.Push(OBJECT); michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::EndObject() michael@0: { michael@0: MOZ_ASSERT(mStack.Peek() == OBJECT); michael@0: mStream << "}"; michael@0: mNeedsComma = true; michael@0: mNeedsName = false; michael@0: mStack.Pop(); michael@0: if (mStack.GetSize() > 0 && mStack.Peek() == OBJECT) { michael@0: mNeedsName = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::BeginArray() michael@0: { michael@0: MOZ_ASSERT(!mNeedsName); michael@0: if (mNeedsComma && mStack.Peek() == ARRAY) { michael@0: mStream << ","; michael@0: } michael@0: mStream << "["; michael@0: mNeedsComma = false; michael@0: mStack.Push(ARRAY); michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::EndArray() michael@0: { michael@0: MOZ_ASSERT(!mNeedsName); michael@0: MOZ_ASSERT(mStack.Peek() == ARRAY); michael@0: mStream << "]"; michael@0: mNeedsComma = true; michael@0: mStack.Pop(); michael@0: if (mStack.GetSize() > 0 && mStack.Peek() == OBJECT) { michael@0: mNeedsName = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::Name(const char *aName) michael@0: { michael@0: MOZ_ASSERT(mNeedsName); michael@0: if (mNeedsComma && mStack.Peek() == OBJECT) { michael@0: mStream << ","; michael@0: } michael@0: EscapeToStream(mStream, aName); michael@0: mStream << ":"; michael@0: mNeedsName = false; michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::Value(int aValue) michael@0: { michael@0: MOZ_ASSERT(!mNeedsName); michael@0: if (mNeedsComma && mStack.Peek() == ARRAY) { michael@0: mStream << ","; michael@0: } michael@0: mStream << aValue; michael@0: mNeedsComma = true; michael@0: if (mStack.Peek() == OBJECT) { michael@0: mNeedsName = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::Value(double aValue) michael@0: { michael@0: MOZ_ASSERT(!mNeedsName); michael@0: if (mNeedsComma && mStack.Peek() == ARRAY) { michael@0: mStream << ","; michael@0: } michael@0: mStream.precision(18); michael@0: mStream << aValue; michael@0: mNeedsComma = true; michael@0: if (mStack.Peek() == OBJECT) { michael@0: mNeedsName = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSStreamWriter::Value(const char *aValue) michael@0: { michael@0: MOZ_ASSERT(!mNeedsName); michael@0: if (mNeedsComma && mStack.Peek() == ARRAY) { michael@0: mStream << ","; michael@0: } michael@0: EscapeToStream(mStream, aValue); michael@0: mNeedsComma = true; michael@0: if (mStack.Peek() == OBJECT) { michael@0: mNeedsName = true; michael@0: } michael@0: }