⛏️ index : haiku.git

/*
 * 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);
}