/* * Copyright 2017, Andrew Lindesay * Distributed under the terms of the MIT License. */ #include "JsonTextWriter.h" #include #include #include 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; } } /*! 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)); } /*! 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; while (writeResult == B_OK && length != 0) { uint8 c = string8bit[offset]; const char* simpleEsc = b_json_simple_esc_sequence(c); // simple escape sequence involving the backslash + one character. if (simpleEsc != NULL) { writeResult = StreamStringVerbatim(simpleEsc, 0, 2); if (writeResult == B_OK) { offset++; length--; } } else { if (b_json_is_7bit_clean(c)) { // roll forward while the characters are simple and then // output them at as a block verbatim. uint32 count7BitClean = 1; while (count7BitClean < length && b_json_is_7bit_clean( string8bit[offset + count7BitClean])) { count7BitClean++; } writeResult = StreamStringVerbatim(&string[offset], 0, count7BitClean); if (writeResult == B_OK) { offset += count7BitClean; length -= count7BitClean; } } else { if (b_json_is_illegal(c)) { fprintf(stderr, "! string encoding error - illegal " "character [%" B_PRIu32 "]\n", static_cast(c)); offset++; length--; } else { // if the character is < 128 then it can be rendered // verbatim - check how many are like this and then // render those verbatim. const char* stringInitial = &string[offset]; uint32 unicodeCharacter = BUnicodeChar::FromUTF8( &stringInitial); sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32, unicodeCharacter); writeResult = StreamStringVerbatim(fUnicodeAssemblyBuffer, 0, 6); if (writeResult == B_OK) { uint32 sequence_length = (uint32)(stringInitial - &string[offset]); offset += sequence_length; length -= sequence_length; } } } } } 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); }