707 lines
14 KiB
C++
707 lines
14 KiB
C++
/*
|
|
* Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include "JsonTextWriter.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <UnicodeChar.h>
|
|
|
|
|
|
namespace BPrivate {
|
|
|
|
|
|
static bool
|
|
b_json_is_7bit_clean(uint8 c)
|
|
{
|
|
return c >= 0x20 && c < 0x7f;
|
|
}
|
|
|
|
|
|
static bool
|
|
b_json_is_illegal(uint8 c)
|
|
{
|
|
return c < 0x20 || c == 0x7f;
|
|
}
|
|
|
|
|
|
static const char*
|
|
b_json_simple_esc_sequence(char c)
|
|
{
|
|
switch (c) {
|
|
case '"':
|
|
return "\\\"";
|
|
case '\\':
|
|
return "\\\\";
|
|
case '/':
|
|
return "\\/";
|
|
case '\b':
|
|
return "\\b";
|
|
case '\f':
|
|
return "\\f";
|
|
case '\n':
|
|
return "\\n";
|
|
case '\r':
|
|
return "\\r";
|
|
case '\t':
|
|
return "\\t";
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static size_t
|
|
b_json_len_7bit_clean_non_esc(uint8* c, size_t length) {
|
|
size_t result = 0;
|
|
|
|
while (result < length
|
|
&& b_json_is_7bit_clean(c[result])
|
|
&& b_json_simple_esc_sequence(c[result]) == NULL) {
|
|
result++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*! The class and sub-classes of it are used as a stack internal to the
|
|
BJsonTextWriter class. As the JSON is parsed, the stack of these
|
|
internal listeners follows the stack of the JSON parsing in terms of
|
|
containers; arrays and objects.
|
|
*/
|
|
|
|
class BJsonTextWriterStackedEventListener : public BJsonEventListener {
|
|
public:
|
|
BJsonTextWriterStackedEventListener(
|
|
BJsonTextWriter* writer,
|
|
BJsonTextWriterStackedEventListener* parent);
|
|
~BJsonTextWriterStackedEventListener();
|
|
|
|
bool Handle(const BJsonEvent& event);
|
|
void HandleError(status_t status, int32 line,
|
|
const char* message);
|
|
void Complete();
|
|
|
|
status_t ErrorStatus();
|
|
|
|
BJsonTextWriterStackedEventListener*
|
|
Parent();
|
|
|
|
protected:
|
|
|
|
status_t StreamNumberNode(const BJsonEvent& event);
|
|
|
|
status_t StreamStringVerbatim(const char* string);
|
|
status_t StreamStringVerbatim(const char* string,
|
|
off_t offset, size_t length);
|
|
|
|
status_t StreamStringEncoded(const char* string);
|
|
status_t StreamStringEncoded(const char* string,
|
|
off_t offset, size_t length);
|
|
|
|
status_t StreamQuotedEncodedString(const char* string);
|
|
status_t StreamQuotedEncodedString(const char* string,
|
|
off_t offset, size_t length);
|
|
|
|
status_t StreamChar(char c);
|
|
|
|
virtual bool WillAdd();
|
|
virtual void DidAdd();
|
|
|
|
void SetStackedListenerOnWriter(
|
|
BJsonTextWriterStackedEventListener*
|
|
stackedListener);
|
|
|
|
BJsonTextWriter*
|
|
fWriter;
|
|
BJsonTextWriterStackedEventListener*
|
|
fParent;
|
|
uint32 fCount;
|
|
|
|
};
|
|
|
|
|
|
class BJsonTextWriterArrayStackedEventListener
|
|
: public BJsonTextWriterStackedEventListener {
|
|
public:
|
|
BJsonTextWriterArrayStackedEventListener(
|
|
BJsonTextWriter* writer,
|
|
BJsonTextWriterStackedEventListener* parent);
|
|
~BJsonTextWriterArrayStackedEventListener();
|
|
|
|
bool Handle(const BJsonEvent& event);
|
|
|
|
protected:
|
|
bool WillAdd();
|
|
};
|
|
|
|
|
|
class BJsonTextWriterObjectStackedEventListener
|
|
: public BJsonTextWriterStackedEventListener {
|
|
public:
|
|
BJsonTextWriterObjectStackedEventListener(
|
|
BJsonTextWriter* writer,
|
|
BJsonTextWriterStackedEventListener* parent);
|
|
~BJsonTextWriterObjectStackedEventListener();
|
|
|
|
bool Handle(const BJsonEvent& event);
|
|
};
|
|
|
|
} // namespace BPrivate
|
|
|
|
|
|
using BPrivate::BJsonTextWriterStackedEventListener;
|
|
using BPrivate::BJsonTextWriterArrayStackedEventListener;
|
|
using BPrivate::BJsonTextWriterObjectStackedEventListener;
|
|
|
|
|
|
// #pragma mark - BJsonTextWriterStackedEventListener
|
|
|
|
|
|
BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener(
|
|
BJsonTextWriter* writer,
|
|
BJsonTextWriterStackedEventListener* parent)
|
|
{
|
|
fWriter = writer;
|
|
fParent = parent;
|
|
fCount = 0;
|
|
}
|
|
|
|
|
|
BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener()
|
|
{
|
|
}
|
|
|
|
|
|
bool
|
|
BJsonTextWriterStackedEventListener::Handle(const BJsonEvent& event)
|
|
{
|
|
status_t writeResult = B_OK;
|
|
|
|
if (fWriter->ErrorStatus() != B_OK)
|
|
return false;
|
|
|
|
switch (event.EventType()) {
|
|
|
|
case B_JSON_NUMBER:
|
|
case B_JSON_STRING:
|
|
case B_JSON_TRUE:
|
|
case B_JSON_FALSE:
|
|
case B_JSON_NULL:
|
|
case B_JSON_OBJECT_START:
|
|
case B_JSON_ARRAY_START:
|
|
if (!WillAdd())
|
|
return false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (event.EventType()) {
|
|
|
|
case B_JSON_NUMBER:
|
|
writeResult = StreamNumberNode(event);
|
|
break;
|
|
|
|
case B_JSON_STRING:
|
|
writeResult = StreamQuotedEncodedString(event.Content());
|
|
break;
|
|
|
|
case B_JSON_TRUE:
|
|
writeResult = StreamStringVerbatim("true", 0, 4);
|
|
break;
|
|
|
|
case B_JSON_FALSE:
|
|
writeResult = StreamStringVerbatim("false", 0, 5);
|
|
break;
|
|
|
|
case B_JSON_NULL:
|
|
writeResult = StreamStringVerbatim("null", 0, 4);
|
|
break;
|
|
|
|
case B_JSON_OBJECT_START:
|
|
{
|
|
writeResult = StreamChar('{');
|
|
|
|
if (writeResult == B_OK) {
|
|
SetStackedListenerOnWriter(
|
|
new BJsonTextWriterObjectStackedEventListener(
|
|
fWriter, this));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case B_JSON_ARRAY_START:
|
|
{
|
|
writeResult = StreamChar('[');
|
|
|
|
if (writeResult == B_OK) {
|
|
SetStackedListenerOnWriter(
|
|
new BJsonTextWriterArrayStackedEventListener(
|
|
fWriter, this));
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
|
|
"unexpected type of json item to add to container");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (writeResult == B_OK)
|
|
DidAdd();
|
|
else {
|
|
HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
|
|
"error writing output");
|
|
}
|
|
|
|
return ErrorStatus() == B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line,
|
|
const char* message)
|
|
{
|
|
fWriter->HandleError(status, line, message);
|
|
}
|
|
|
|
|
|
void
|
|
BJsonTextWriterStackedEventListener::Complete()
|
|
{
|
|
// illegal state.
|
|
HandleError(JSON_EVENT_LISTENER_ANY_LINE, B_NOT_ALLOWED,
|
|
"Complete() called on stacked message listener");
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::ErrorStatus()
|
|
{
|
|
return fWriter->ErrorStatus();
|
|
}
|
|
|
|
|
|
BJsonTextWriterStackedEventListener*
|
|
BJsonTextWriterStackedEventListener::Parent()
|
|
{
|
|
return fParent;
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event)
|
|
{
|
|
return fWriter->StreamNumberNode(event);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string)
|
|
{
|
|
return fWriter->StreamStringVerbatim(string);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string,
|
|
off_t offset, size_t length)
|
|
{
|
|
return fWriter->StreamStringVerbatim(string, offset, length);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string)
|
|
{
|
|
return fWriter->StreamStringEncoded(string);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string,
|
|
off_t offset, size_t length)
|
|
{
|
|
return fWriter->StreamStringEncoded(string, offset, length);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
|
|
const char* string)
|
|
{
|
|
return fWriter->StreamQuotedEncodedString(string);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
|
|
const char* string, off_t offset, size_t length)
|
|
{
|
|
return fWriter->StreamQuotedEncodedString(string, offset, length);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriterStackedEventListener::StreamChar(char c)
|
|
{
|
|
return fWriter->StreamChar(c);
|
|
}
|
|
|
|
|
|
bool
|
|
BJsonTextWriterStackedEventListener::WillAdd()
|
|
{
|
|
return true; // carry on
|
|
}
|
|
|
|
|
|
void
|
|
BJsonTextWriterStackedEventListener::DidAdd()
|
|
{
|
|
fCount++;
|
|
}
|
|
|
|
|
|
void
|
|
BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter(
|
|
BJsonTextWriterStackedEventListener* stackedListener)
|
|
{
|
|
fWriter->SetStackedListener(stackedListener);
|
|
}
|
|
|
|
|
|
// #pragma mark - BJsonTextWriterArrayStackedEventListener
|
|
|
|
|
|
BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener(
|
|
BJsonTextWriter* writer,
|
|
BJsonTextWriterStackedEventListener* parent)
|
|
:
|
|
BJsonTextWriterStackedEventListener(writer, parent)
|
|
{
|
|
}
|
|
|
|
|
|
BJsonTextWriterArrayStackedEventListener
|
|
::~BJsonTextWriterArrayStackedEventListener()
|
|
{
|
|
}
|
|
|
|
|
|
bool
|
|
BJsonTextWriterArrayStackedEventListener::Handle(const BJsonEvent& event)
|
|
{
|
|
status_t writeResult = B_OK;
|
|
|
|
if (fWriter->ErrorStatus() != B_OK)
|
|
return false;
|
|
|
|
switch (event.EventType()) {
|
|
case B_JSON_ARRAY_END:
|
|
{
|
|
writeResult = StreamChar(']');
|
|
|
|
if (writeResult == B_OK) {
|
|
SetStackedListenerOnWriter(fParent);
|
|
delete this;
|
|
return true; // must exit immediately after delete this.
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return BJsonTextWriterStackedEventListener::Handle(event);
|
|
}
|
|
|
|
if(writeResult != B_OK) {
|
|
HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
|
|
"error writing output");
|
|
}
|
|
|
|
return ErrorStatus() == B_OK;
|
|
}
|
|
|
|
|
|
bool
|
|
BJsonTextWriterArrayStackedEventListener::WillAdd()
|
|
{
|
|
status_t writeResult = B_OK;
|
|
|
|
if (writeResult == B_OK && fCount > 0)
|
|
writeResult = StreamChar(',');
|
|
|
|
if (writeResult != B_OK) {
|
|
HandleError(B_IO_ERROR, JSON_EVENT_LISTENER_ANY_LINE,
|
|
"error writing data");
|
|
return false;
|
|
}
|
|
|
|
return BJsonTextWriterStackedEventListener::WillAdd();
|
|
}
|
|
|
|
|
|
// #pragma mark - BJsonTextWriterObjectStackedEventListener
|
|
|
|
|
|
BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener(
|
|
BJsonTextWriter* writer,
|
|
BJsonTextWriterStackedEventListener* parent)
|
|
:
|
|
BJsonTextWriterStackedEventListener(writer, parent)
|
|
{
|
|
}
|
|
|
|
|
|
BJsonTextWriterObjectStackedEventListener
|
|
::~BJsonTextWriterObjectStackedEventListener()
|
|
{
|
|
}
|
|
|
|
|
|
bool
|
|
BJsonTextWriterObjectStackedEventListener::Handle(const BJsonEvent& event)
|
|
{
|
|
status_t writeResult = B_OK;
|
|
|
|
if (fWriter->ErrorStatus() != B_OK)
|
|
return false;
|
|
|
|
switch (event.EventType()) {
|
|
case B_JSON_OBJECT_END:
|
|
{
|
|
writeResult = StreamChar('}');
|
|
|
|
if (writeResult == B_OK) {
|
|
SetStackedListenerOnWriter(fParent);
|
|
delete this;
|
|
return true; // just exit after delete this.
|
|
}
|
|
break;
|
|
}
|
|
|
|
case B_JSON_OBJECT_NAME:
|
|
{
|
|
if (writeResult == B_OK && fCount > 0)
|
|
writeResult = StreamChar(',');
|
|
|
|
if (writeResult == B_OK)
|
|
writeResult = StreamQuotedEncodedString(event.Content());
|
|
|
|
if (writeResult == B_OK)
|
|
writeResult = StreamChar(':');
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return BJsonTextWriterStackedEventListener::Handle(event);
|
|
}
|
|
|
|
if (writeResult != B_OK) {
|
|
HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
|
|
"error writing data");
|
|
}
|
|
|
|
return ErrorStatus() == B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark - BJsonTextWriter
|
|
|
|
|
|
BJsonTextWriter::BJsonTextWriter(
|
|
BDataIO* dataIO)
|
|
:
|
|
fDataIO(dataIO)
|
|
{
|
|
|
|
// this is a preparation for this buffer to easily be used later
|
|
// to efficiently output encoded unicode characters.
|
|
|
|
fUnicodeAssemblyBuffer[0] = '\\';
|
|
fUnicodeAssemblyBuffer[1] = 'u';
|
|
|
|
fStackedListener = new BJsonTextWriterStackedEventListener(this, NULL);
|
|
}
|
|
|
|
|
|
BJsonTextWriter::~BJsonTextWriter()
|
|
{
|
|
BJsonTextWriterStackedEventListener* listener = fStackedListener;
|
|
|
|
while (listener != NULL) {
|
|
BJsonTextWriterStackedEventListener* nextListener = listener->Parent();
|
|
delete listener;
|
|
listener = nextListener;
|
|
}
|
|
|
|
fStackedListener = NULL;
|
|
}
|
|
|
|
|
|
bool
|
|
BJsonTextWriter::Handle(const BJsonEvent& event)
|
|
{
|
|
return fStackedListener->Handle(event);
|
|
}
|
|
|
|
|
|
void
|
|
BJsonTextWriter::Complete()
|
|
{
|
|
// upon construction, this object will add one listener to the
|
|
// stack. On complete, this listener should still be there;
|
|
// otherwise this implies an unterminated structure such as array
|
|
// / object.
|
|
|
|
if (fStackedListener->Parent() != NULL) {
|
|
HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE,
|
|
"unexpected end of input data");
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
BJsonTextWriter::SetStackedListener(
|
|
BJsonTextWriterStackedEventListener* stackedListener)
|
|
{
|
|
fStackedListener = stackedListener;
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamNumberNode(const BJsonEvent& event)
|
|
{
|
|
return StreamStringVerbatim(event.Content());
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamStringVerbatim(const char* string)
|
|
{
|
|
return StreamStringVerbatim(string, 0, strlen(string));
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamStringVerbatim(const char* string,
|
|
off_t offset, size_t length)
|
|
{
|
|
return fDataIO->WriteExactly(&string[offset], length);
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamStringEncoded(const char* string)
|
|
{
|
|
return StreamStringEncoded(string, 0, strlen(string));
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamStringUnicodeCharacter(uint32 c)
|
|
{
|
|
sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32, c);
|
|
// note that the buffer's first two bytes are populated with the JSON
|
|
// prefix for a unicode char.
|
|
return StreamStringVerbatim(fUnicodeAssemblyBuffer, 0, 6);
|
|
}
|
|
|
|
|
|
/*! Note that this method will expect a UTF-8 encoded string. */
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamStringEncoded(const char* string,
|
|
off_t offset, size_t length)
|
|
{
|
|
status_t writeResult = B_OK;
|
|
uint8* string8bit = (uint8*)string;
|
|
size_t i = 0;
|
|
|
|
while (i < length && writeResult == B_OK) {
|
|
uint8 c = string8bit[offset + i];
|
|
const char* simpleEsc = b_json_simple_esc_sequence(c);
|
|
|
|
if (simpleEsc != NULL) {
|
|
// here the character to emit is something like a tab or a quote
|
|
// in this case the output JSON should escape it so that it looks
|
|
// like \t or \n in the output.
|
|
writeResult = StreamStringVerbatim(simpleEsc, 0, 2);
|
|
i++;
|
|
} else {
|
|
if (b_json_is_7bit_clean(c)) {
|
|
// in this case the first character is a simple one that can be
|
|
// output without any special handling. Find the sequence of
|
|
// such characters and output them as a sequence so that it's
|
|
// included as one write operation.
|
|
size_t l = 1 + b_json_len_7bit_clean_non_esc(
|
|
&string8bit[offset + i + 1], length - (offset + i + 1));
|
|
writeResult = StreamStringVerbatim(&string[offset + i], 0, l);
|
|
i += static_cast<size_t>(l);
|
|
} else {
|
|
if (b_json_is_illegal(c)) {
|
|
fprintf(stderr, "! string encoding error - illegal "
|
|
"character [%" B_PRIu32 "]\n", static_cast<uint32>(c));
|
|
i++;
|
|
} else {
|
|
// now we have a UTF-8 sequence. Read the UTF-8 sequence
|
|
// to get the unicode character and then encode that as
|
|
// JSON.
|
|
const char* unicodeStr = &string[offset + i];
|
|
uint32 unicodeCharacter = BUnicodeChar::FromUTF8(
|
|
&unicodeStr);
|
|
writeResult = StreamStringUnicodeCharacter(
|
|
unicodeCharacter);
|
|
i += static_cast<size_t>(unicodeStr - &string[offset + i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return writeResult;
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamQuotedEncodedString(const char* string)
|
|
{
|
|
return StreamQuotedEncodedString(string, 0, strlen(string));
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamQuotedEncodedString(const char* string,
|
|
off_t offset, size_t length)
|
|
{
|
|
status_t write_result = B_OK;
|
|
|
|
if (write_result == B_OK)
|
|
write_result = StreamChar('\"');
|
|
|
|
if (write_result == B_OK)
|
|
write_result = StreamStringEncoded(string, offset, length);
|
|
|
|
if (write_result == B_OK)
|
|
write_result = StreamChar('\"');
|
|
|
|
return write_result;
|
|
}
|
|
|
|
|
|
status_t
|
|
BJsonTextWriter::StreamChar(char c)
|
|
{
|
|
return fDataIO->WriteExactly(&c, 1);
|
|
}
|