2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
3 * Distributed under the terms of the MIT License.
7 #include "JsonTextWriter.h"
12 #include <UnicodeChar.h>
19 b_json_is_7bit_clean(uint8 c
)
21 return c
>= 0x20 && c
< 0x7f;
26 b_json_is_illegal(uint8 c
)
28 return c
< 0x20 || c
== 0x7f;
33 b_json_simple_esc_sequence(char c
)
58 /*! The class and sub-classes of it are used as a stack internal to the
59 BJsonTextWriter class. As the JSON is parsed, the stack of these
60 internal listeners follows the stack of the JSON parsing in terms of
61 containers; arrays and objects.
64 class BJsonTextWriterStackedEventListener
: public BJsonEventListener
{
66 BJsonTextWriterStackedEventListener(
67 BJsonTextWriter
* writer
,
68 BJsonTextWriterStackedEventListener
* parent
);
69 ~BJsonTextWriterStackedEventListener();
71 bool Handle(const BJsonEvent
& event
);
72 void HandleError(status_t status
, int32 line
,
76 status_t
ErrorStatus();
78 BJsonTextWriterStackedEventListener
*
83 status_t
StreamNumberNode(const BJsonEvent
& event
);
85 status_t
StreamStringVerbatim(const char* string
);
86 status_t
StreamStringVerbatim(const char* string
,
87 off_t offset
, size_t length
);
89 status_t
StreamStringEncoded(const char* string
);
90 status_t
StreamStringEncoded(const char* string
,
91 off_t offset
, size_t length
);
93 status_t
StreamQuotedEncodedString(const char* string
);
94 status_t
StreamQuotedEncodedString(const char* string
,
95 off_t offset
, size_t length
);
97 status_t
StreamChar(char c
);
99 virtual bool WillAdd();
100 virtual void DidAdd();
102 void SetStackedListenerOnWriter(
103 BJsonTextWriterStackedEventListener
*
108 BJsonTextWriterStackedEventListener
*
115 class BJsonTextWriterArrayStackedEventListener
116 : public BJsonTextWriterStackedEventListener
{
118 BJsonTextWriterArrayStackedEventListener(
119 BJsonTextWriter
* writer
,
120 BJsonTextWriterStackedEventListener
* parent
);
121 ~BJsonTextWriterArrayStackedEventListener();
123 bool Handle(const BJsonEvent
& event
);
130 class BJsonTextWriterObjectStackedEventListener
131 : public BJsonTextWriterStackedEventListener
{
133 BJsonTextWriterObjectStackedEventListener(
134 BJsonTextWriter
* writer
,
135 BJsonTextWriterStackedEventListener
* parent
);
136 ~BJsonTextWriterObjectStackedEventListener();
138 bool Handle(const BJsonEvent
& event
);
141 } // namespace BPrivate
144 using BPrivate::BJsonTextWriterStackedEventListener
;
145 using BPrivate::BJsonTextWriterArrayStackedEventListener
;
146 using BPrivate::BJsonTextWriterObjectStackedEventListener
;
149 // #pragma mark - BJsonTextWriterStackedEventListener
152 BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener(
153 BJsonTextWriter
* writer
,
154 BJsonTextWriterStackedEventListener
* parent
)
162 BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener()
168 BJsonTextWriterStackedEventListener::Handle(const BJsonEvent
& event
)
170 status_t writeResult
= B_OK
;
172 if (fWriter
->ErrorStatus() != B_OK
)
175 switch (event
.EventType()) {
182 case B_JSON_OBJECT_START
:
183 case B_JSON_ARRAY_START
:
192 switch (event
.EventType()) {
195 writeResult
= StreamNumberNode(event
);
199 writeResult
= StreamQuotedEncodedString(event
.Content());
203 writeResult
= StreamStringVerbatim("true", 0, 4);
207 writeResult
= StreamStringVerbatim("false", 0, 5);
211 writeResult
= StreamStringVerbatim("null", 0, 4);
214 case B_JSON_OBJECT_START
:
216 writeResult
= StreamChar('{');
218 if (writeResult
== B_OK
) {
219 SetStackedListenerOnWriter(
220 new BJsonTextWriterObjectStackedEventListener(
226 case B_JSON_ARRAY_START
:
228 writeResult
= StreamChar('[');
230 if (writeResult
== B_OK
) {
231 SetStackedListenerOnWriter(
232 new BJsonTextWriterArrayStackedEventListener(
240 HandleError(B_NOT_ALLOWED
, JSON_EVENT_LISTENER_ANY_LINE
,
241 "unexpected type of json item to add to container");
246 if (writeResult
== B_OK
)
249 HandleError(writeResult
, JSON_EVENT_LISTENER_ANY_LINE
,
250 "error writing output");
253 return ErrorStatus() == B_OK
;
258 BJsonTextWriterStackedEventListener::HandleError(status_t status
, int32 line
,
261 fWriter
->HandleError(status
, line
, message
);
266 BJsonTextWriterStackedEventListener::Complete()
269 HandleError(JSON_EVENT_LISTENER_ANY_LINE
, B_NOT_ALLOWED
,
270 "Complete() called on stacked message listener");
275 BJsonTextWriterStackedEventListener::ErrorStatus()
277 return fWriter
->ErrorStatus();
281 BJsonTextWriterStackedEventListener
*
282 BJsonTextWriterStackedEventListener::Parent()
289 BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent
& event
)
291 return fWriter
->StreamNumberNode(event
);
296 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string
)
298 return fWriter
->StreamStringVerbatim(string
);
303 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string
,
304 off_t offset
, size_t length
)
306 return fWriter
->StreamStringVerbatim(string
, offset
, length
);
311 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string
)
313 return fWriter
->StreamStringEncoded(string
);
318 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string
,
319 off_t offset
, size_t length
)
321 return fWriter
->StreamStringEncoded(string
, offset
, length
);
326 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
329 return fWriter
->StreamQuotedEncodedString(string
);
334 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
335 const char* string
, off_t offset
, size_t length
)
337 return fWriter
->StreamQuotedEncodedString(string
, offset
, length
);
342 BJsonTextWriterStackedEventListener::StreamChar(char c
)
344 return fWriter
->StreamChar(c
);
349 BJsonTextWriterStackedEventListener::WillAdd()
351 return true; // carry on
356 BJsonTextWriterStackedEventListener::DidAdd()
363 BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter(
364 BJsonTextWriterStackedEventListener
* stackedListener
)
366 fWriter
->SetStackedListener(stackedListener
);
370 // #pragma mark - BJsonTextWriterArrayStackedEventListener
373 BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener(
374 BJsonTextWriter
* writer
,
375 BJsonTextWriterStackedEventListener
* parent
)
377 BJsonTextWriterStackedEventListener(writer
, parent
)
382 BJsonTextWriterArrayStackedEventListener
383 ::~BJsonTextWriterArrayStackedEventListener()
389 BJsonTextWriterArrayStackedEventListener::Handle(const BJsonEvent
& event
)
391 status_t writeResult
= B_OK
;
393 if (fWriter
->ErrorStatus() != B_OK
)
396 switch (event
.EventType()) {
397 case B_JSON_ARRAY_END
:
399 writeResult
= StreamChar(']');
401 if (writeResult
== B_OK
) {
402 SetStackedListenerOnWriter(fParent
);
404 return true; // must exit immediately after delete this.
410 return BJsonTextWriterStackedEventListener::Handle(event
);
413 if(writeResult
!= B_OK
) {
414 HandleError(writeResult
, JSON_EVENT_LISTENER_ANY_LINE
,
415 "error writing output");
418 return ErrorStatus() == B_OK
;
423 BJsonTextWriterArrayStackedEventListener::WillAdd()
425 status_t writeResult
= B_OK
;
427 if (writeResult
== B_OK
&& fCount
> 0)
428 writeResult
= StreamChar(',');
430 if (writeResult
!= B_OK
) {
431 HandleError(B_IO_ERROR
, JSON_EVENT_LISTENER_ANY_LINE
,
432 "error writing data");
436 return BJsonTextWriterStackedEventListener::WillAdd();
440 // #pragma mark - BJsonTextWriterObjectStackedEventListener
443 BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener(
444 BJsonTextWriter
* writer
,
445 BJsonTextWriterStackedEventListener
* parent
)
447 BJsonTextWriterStackedEventListener(writer
, parent
)
452 BJsonTextWriterObjectStackedEventListener
453 ::~BJsonTextWriterObjectStackedEventListener()
459 BJsonTextWriterObjectStackedEventListener::Handle(const BJsonEvent
& event
)
461 status_t writeResult
= B_OK
;
463 if (fWriter
->ErrorStatus() != B_OK
)
466 switch (event
.EventType()) {
467 case B_JSON_OBJECT_END
:
469 writeResult
= StreamChar('}');
471 if (writeResult
== B_OK
) {
472 SetStackedListenerOnWriter(fParent
);
474 return true; // just exit after delete this.
479 case B_JSON_OBJECT_NAME
:
481 if (writeResult
== B_OK
&& fCount
> 0)
482 writeResult
= StreamChar(',');
484 if (writeResult
== B_OK
)
485 writeResult
= StreamQuotedEncodedString(event
.Content());
487 if (writeResult
== B_OK
)
488 writeResult
= StreamChar(':');
494 return BJsonTextWriterStackedEventListener::Handle(event
);
497 if (writeResult
!= B_OK
) {
498 HandleError(writeResult
, JSON_EVENT_LISTENER_ANY_LINE
,
499 "error writing data");
502 return ErrorStatus() == B_OK
;
506 // #pragma mark - BJsonTextWriter
509 BJsonTextWriter::BJsonTextWriter(
515 // this is a preparation for this buffer to easily be used later
516 // to efficiently output encoded unicode characters.
518 fUnicodeAssemblyBuffer
[0] = '\\';
519 fUnicodeAssemblyBuffer
[1] = 'u';
521 fStackedListener
= new BJsonTextWriterStackedEventListener(this, NULL
);
525 BJsonTextWriter::~BJsonTextWriter()
527 BJsonTextWriterStackedEventListener
* listener
= fStackedListener
;
529 while (listener
!= NULL
) {
530 BJsonTextWriterStackedEventListener
* nextListener
= listener
->Parent();
532 listener
= nextListener
;
535 fStackedListener
= NULL
;
540 BJsonTextWriter::Handle(const BJsonEvent
& event
)
542 return fStackedListener
->Handle(event
);
547 BJsonTextWriter::Complete()
549 // upon construction, this object will add one listener to the
550 // stack. On complete, this listener should still be there;
551 // otherwise this implies an unterminated structure such as array
554 if (fStackedListener
->Parent() != NULL
) {
555 HandleError(B_BAD_DATA
, JSON_EVENT_LISTENER_ANY_LINE
,
556 "unexpected end of input data");
562 BJsonTextWriter::SetStackedListener(
563 BJsonTextWriterStackedEventListener
* stackedListener
)
565 fStackedListener
= stackedListener
;
570 BJsonTextWriter::StreamNumberNode(const BJsonEvent
& event
)
572 return StreamStringVerbatim(event
.Content());
577 BJsonTextWriter::StreamStringVerbatim(const char* string
)
579 return StreamStringVerbatim(string
, 0, strlen(string
));
584 BJsonTextWriter::StreamStringVerbatim(const char* string
,
585 off_t offset
, size_t length
)
587 return fDataIO
->WriteExactly(&string
[offset
], length
);
592 BJsonTextWriter::StreamStringEncoded(const char* string
)
594 return StreamStringEncoded(string
, 0, strlen(string
));
598 /*! Note that this method will expect a UTF-8 encoded string. */
601 BJsonTextWriter::StreamStringEncoded(const char* string
,
602 off_t offset
, size_t length
)
604 status_t writeResult
= B_OK
;
605 uint8
* string8bit
= (uint8
*)string
;
607 while (writeResult
== B_OK
&& length
!= 0) {
608 uint8 c
= string8bit
[offset
];
609 const char* simpleEsc
= b_json_simple_esc_sequence(c
);
611 // simple escape sequence involving the backslash + one character.
613 if (simpleEsc
!= NULL
) {
614 writeResult
= StreamStringVerbatim(simpleEsc
, 0, 2);
616 if (writeResult
== B_OK
) {
622 if (b_json_is_7bit_clean(c
)) {
624 // roll forward while the characters are simple and then
625 // output them at as a block verbatim.
627 uint32 count7BitClean
= 1;
629 while (count7BitClean
< length
630 && b_json_is_7bit_clean(
631 string8bit
[offset
+ count7BitClean
])) {
635 writeResult
= StreamStringVerbatim(&string
[offset
], 0,
638 if (writeResult
== B_OK
) {
639 offset
+= count7BitClean
;
640 length
-= count7BitClean
;
643 if (b_json_is_illegal(c
)) {
644 fprintf(stderr
, "! string encoding error - illegal "
645 "character [%" B_PRIu32
"]\n", static_cast<uint32
>(c
));
649 // if the character is < 128 then it can be rendered
650 // verbatim - check how many are like this and then
651 // render those verbatim.
652 const char* stringInitial
= &string
[offset
];
653 uint32 unicodeCharacter
= BUnicodeChar::FromUTF8(
656 sprintf(&fUnicodeAssemblyBuffer
[2], "%04" B_PRIx32
,
658 writeResult
= StreamStringVerbatim(fUnicodeAssemblyBuffer
,
661 if (writeResult
== B_OK
) {
662 uint32 sequence_length
663 = (uint32
)(stringInitial
- &string
[offset
]);
664 offset
+= sequence_length
;
665 length
-= sequence_length
;
677 BJsonTextWriter::StreamQuotedEncodedString(const char* string
)
679 return StreamQuotedEncodedString(string
, 0, strlen(string
));
684 BJsonTextWriter::StreamQuotedEncodedString(const char* string
,
685 off_t offset
, size_t length
)
687 status_t write_result
= B_OK
;
689 if (write_result
== B_OK
)
690 write_result
= StreamChar('\"');
692 if (write_result
== B_OK
)
693 write_result
= StreamStringEncoded(string
, offset
, length
);
695 if (write_result
== B_OK
)
696 write_result
= StreamChar('\"');
703 BJsonTextWriter::StreamChar(char c
)
705 return fDataIO
->WriteExactly(&c
, 1);