xref: /haiku/src/kits/shared/JsonTextWriter.cpp (revision 5f4f984a94d150153bcb00a2ed780d0437859543)
1 /*
2  * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "JsonTextWriter.h"
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 
12 #include <UnicodeChar.h>
13 
14 
15 namespace BPrivate {
16 
17 
18 static bool
19 b_json_is_7bit_clean(uint8 c)
20 {
21 	return c >= 0x20 && c < 0x7f;
22 }
23 
24 
25 static bool
26 b_json_is_illegal(uint8 c)
27 {
28 	return c < 0x20 || c == 0x7f;
29 }
30 
31 
32 static const char*
33 b_json_simple_esc_sequence(char c)
34 {
35 	switch (c) {
36 		case '"':
37 			return "\\\"";
38 		case '\\':
39 			return "\\\\";
40 		case '/':
41 			return "\\/";
42 		case '\b':
43 			return "\\b";
44 		case '\f':
45 			return "\\f";
46 		case '\n':
47 			return "\\n";
48 		case '\r':
49 			return "\\r";
50 		case '\t':
51 			return "\\t";
52 		default:
53 			return NULL;
54 	}
55 }
56 
57 
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.
62 */
63 
64 class BJsonTextWriterStackedEventListener : public BJsonEventListener {
65 public:
66 								BJsonTextWriterStackedEventListener(
67 									BJsonTextWriter* writer,
68 									BJsonTextWriterStackedEventListener* parent);
69 								~BJsonTextWriterStackedEventListener();
70 
71 				bool			Handle(const BJsonEvent& event);
72 				void			HandleError(status_t status, int32 line,
73 									const char* message);
74 				void			Complete();
75 
76 				status_t		ErrorStatus();
77 
78 				BJsonTextWriterStackedEventListener*
79 								Parent();
80 
81 protected:
82 
83 			status_t			StreamNumberNode(const BJsonEvent& event);
84 
85 			status_t			StreamStringVerbatim(const char* string);
86 			status_t			StreamStringVerbatim(const char* string,
87 									off_t offset, size_t length);
88 
89 			status_t			StreamStringEncoded(const char* string);
90 			status_t			StreamStringEncoded(const char* string,
91 									off_t offset, size_t length);
92 
93 			status_t			StreamQuotedEncodedString(const char* string);
94 			status_t			StreamQuotedEncodedString(const char* string,
95 									off_t offset, size_t length);
96 
97 			status_t			StreamChar(char c);
98 
99 		virtual	bool			WillAdd();
100 		virtual void			DidAdd();
101 
102 			void				SetStackedListenerOnWriter(
103 									BJsonTextWriterStackedEventListener*
104 									stackedListener);
105 
106 			BJsonTextWriter*
107 								fWriter;
108 			BJsonTextWriterStackedEventListener*
109 								fParent;
110 				uint32			fCount;
111 
112 };
113 
114 
115 class BJsonTextWriterArrayStackedEventListener
116 	: public BJsonTextWriterStackedEventListener {
117 public:
118 								BJsonTextWriterArrayStackedEventListener(
119 									BJsonTextWriter* writer,
120 									BJsonTextWriterStackedEventListener* parent);
121 								~BJsonTextWriterArrayStackedEventListener();
122 
123 				bool			Handle(const BJsonEvent& event);
124 
125 protected:
126 				bool			WillAdd();
127 };
128 
129 
130 class BJsonTextWriterObjectStackedEventListener
131 	: public BJsonTextWriterStackedEventListener {
132 public:
133 								BJsonTextWriterObjectStackedEventListener(
134 									BJsonTextWriter* writer,
135 									BJsonTextWriterStackedEventListener* parent);
136 								~BJsonTextWriterObjectStackedEventListener();
137 
138 				bool			Handle(const BJsonEvent& event);
139 };
140 
141 } // namespace BPrivate
142 
143 
144 using BPrivate::BJsonTextWriterStackedEventListener;
145 using BPrivate::BJsonTextWriterArrayStackedEventListener;
146 using BPrivate::BJsonTextWriterObjectStackedEventListener;
147 
148 
149 // #pragma mark - BJsonTextWriterStackedEventListener
150 
151 
152 BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener(
153 	BJsonTextWriter* writer,
154 	BJsonTextWriterStackedEventListener* parent)
155 {
156 	fWriter = writer;
157 	fParent = parent;
158 	fCount = 0;
159 }
160 
161 
162 BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener()
163 {
164 }
165 
166 
167 bool
168 BJsonTextWriterStackedEventListener::Handle(const BJsonEvent& event)
169 {
170 	status_t writeResult = B_OK;
171 
172 	if (fWriter->ErrorStatus() != B_OK)
173 		return false;
174 
175 	switch (event.EventType()) {
176 
177 		case B_JSON_NUMBER:
178 		case B_JSON_STRING:
179 		case B_JSON_TRUE:
180 		case B_JSON_FALSE:
181 		case B_JSON_NULL:
182 		case B_JSON_OBJECT_START:
183 		case B_JSON_ARRAY_START:
184 			if (!WillAdd())
185 				return false;
186 			break;
187 
188 		default:
189 			break;
190 	}
191 
192 	switch (event.EventType()) {
193 
194 		case B_JSON_NUMBER:
195 			writeResult = StreamNumberNode(event);
196 			break;
197 
198 		case B_JSON_STRING:
199 			writeResult = StreamQuotedEncodedString(event.Content());
200 			break;
201 
202 		case B_JSON_TRUE:
203 			writeResult = StreamStringVerbatim("true", 0, 4);
204 			break;
205 
206 		case B_JSON_FALSE:
207 			writeResult = StreamStringVerbatim("false", 0, 5);
208 			break;
209 
210 		case B_JSON_NULL:
211 			writeResult = StreamStringVerbatim("null", 0, 4);
212 			break;
213 
214 		case B_JSON_OBJECT_START:
215 		{
216 			writeResult = StreamChar('{');
217 
218 			if (writeResult == B_OK) {
219 				SetStackedListenerOnWriter(
220 					new BJsonTextWriterObjectStackedEventListener(
221 						fWriter, this));
222 			}
223 			break;
224 		}
225 
226 		case B_JSON_ARRAY_START:
227 		{
228 			writeResult = StreamChar('[');
229 
230 			if (writeResult == B_OK) {
231 				SetStackedListenerOnWriter(
232 					new BJsonTextWriterArrayStackedEventListener(
233 						fWriter, this));
234 			}
235 			break;
236 		}
237 
238 		default:
239 		{
240 			HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
241 				"unexpected type of json item to add to container");
242 			return false;
243 		}
244 	}
245 
246 	if (writeResult == B_OK)
247 		DidAdd();
248 	else {
249 		HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
250 			"error writing output");
251 	}
252 
253 	return ErrorStatus() == B_OK;
254 }
255 
256 
257 void
258 BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line,
259 	const char* message)
260 {
261 	fWriter->HandleError(status, line, message);
262 }
263 
264 
265 void
266 BJsonTextWriterStackedEventListener::Complete()
267 {
268 		// illegal state.
269 	HandleError(JSON_EVENT_LISTENER_ANY_LINE, B_NOT_ALLOWED,
270 		"Complete() called on stacked message listener");
271 }
272 
273 
274 status_t
275 BJsonTextWriterStackedEventListener::ErrorStatus()
276 {
277 	return fWriter->ErrorStatus();
278 }
279 
280 
281 BJsonTextWriterStackedEventListener*
282 BJsonTextWriterStackedEventListener::Parent()
283 {
284 	return fParent;
285 }
286 
287 
288 status_t
289 BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event)
290 {
291 	return fWriter->StreamNumberNode(event);
292 }
293 
294 
295 status_t
296 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string)
297 {
298 	return fWriter->StreamStringVerbatim(string);
299 }
300 
301 
302 status_t
303 BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string,
304 	off_t offset, size_t length)
305 {
306 	return fWriter->StreamStringVerbatim(string, offset, length);
307 }
308 
309 
310 status_t
311 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string)
312 {
313 	return fWriter->StreamStringEncoded(string);
314 }
315 
316 
317 status_t
318 BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string,
319 	off_t offset, size_t length)
320 {
321 	return fWriter->StreamStringEncoded(string, offset, length);
322 }
323 
324 
325 status_t
326 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
327 	const char* string)
328 {
329 	return fWriter->StreamQuotedEncodedString(string);
330 }
331 
332 
333 status_t
334 BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
335 	const char* string, off_t offset, size_t length)
336 {
337 	return fWriter->StreamQuotedEncodedString(string, offset, length);
338 }
339 
340 
341 status_t
342 BJsonTextWriterStackedEventListener::StreamChar(char c)
343 {
344 	return fWriter->StreamChar(c);
345 }
346 
347 
348 bool
349 BJsonTextWriterStackedEventListener::WillAdd()
350 {
351 	return true; // carry on
352 }
353 
354 
355 void
356 BJsonTextWriterStackedEventListener::DidAdd()
357 {
358 	fCount++;
359 }
360 
361 
362 void
363 BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter(
364 	BJsonTextWriterStackedEventListener* stackedListener)
365 {
366 	fWriter->SetStackedListener(stackedListener);
367 }
368 
369 
370 // #pragma mark - BJsonTextWriterArrayStackedEventListener
371 
372 
373 BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener(
374 	BJsonTextWriter* writer,
375 	BJsonTextWriterStackedEventListener* parent)
376 	:
377 	BJsonTextWriterStackedEventListener(writer, parent)
378 {
379 }
380 
381 
382 BJsonTextWriterArrayStackedEventListener
383 	::~BJsonTextWriterArrayStackedEventListener()
384 {
385 }
386 
387 
388 bool
389 BJsonTextWriterArrayStackedEventListener::Handle(const BJsonEvent& event)
390 {
391 	status_t writeResult = B_OK;
392 
393 	if (fWriter->ErrorStatus() != B_OK)
394 		return false;
395 
396 	switch (event.EventType()) {
397 		case B_JSON_ARRAY_END:
398 		{
399 			writeResult = StreamChar(']');
400 
401 			if (writeResult == B_OK) {
402 				SetStackedListenerOnWriter(fParent);
403 				delete this;
404 				return true; // must exit immediately after delete this.
405 			}
406 			break;
407 		}
408 
409 		default:
410 			return BJsonTextWriterStackedEventListener::Handle(event);
411 	}
412 
413 	if(writeResult != B_OK) {
414 		HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
415 			"error writing output");
416 	}
417 
418 	return ErrorStatus() == B_OK;
419 }
420 
421 
422 bool
423 BJsonTextWriterArrayStackedEventListener::WillAdd()
424 {
425 	status_t writeResult = B_OK;
426 
427 	if (writeResult == B_OK && fCount > 0)
428 		writeResult = StreamChar(',');
429 
430 	if (writeResult != B_OK) {
431 		HandleError(B_IO_ERROR, JSON_EVENT_LISTENER_ANY_LINE,
432 			"error writing data");
433 		return false;
434 	}
435 
436 	return BJsonTextWriterStackedEventListener::WillAdd();
437 }
438 
439 
440 // #pragma mark - BJsonTextWriterObjectStackedEventListener
441 
442 
443 BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener(
444 	BJsonTextWriter* writer,
445 	BJsonTextWriterStackedEventListener* parent)
446 	:
447 	BJsonTextWriterStackedEventListener(writer, parent)
448 {
449 }
450 
451 
452 BJsonTextWriterObjectStackedEventListener
453 	::~BJsonTextWriterObjectStackedEventListener()
454 {
455 }
456 
457 
458 bool
459 BJsonTextWriterObjectStackedEventListener::Handle(const BJsonEvent& event)
460 {
461 	status_t writeResult = B_OK;
462 
463 	if (fWriter->ErrorStatus() != B_OK)
464 		return false;
465 
466 	switch (event.EventType()) {
467 		case B_JSON_OBJECT_END:
468 		{
469 			writeResult = StreamChar('}');
470 
471 			if (writeResult == B_OK) {
472 				SetStackedListenerOnWriter(fParent);
473 				delete this;
474 				return true; // just exit after delete this.
475 			}
476 			break;
477 		}
478 
479 		case B_JSON_OBJECT_NAME:
480 		{
481 			if (writeResult == B_OK && fCount > 0)
482 				writeResult = StreamChar(',');
483 
484 			if (writeResult == B_OK)
485 				writeResult = StreamQuotedEncodedString(event.Content());
486 
487 			if (writeResult == B_OK)
488 				writeResult = StreamChar(':');
489 
490 			break;
491 		}
492 
493 		default:
494 			return BJsonTextWriterStackedEventListener::Handle(event);
495 	}
496 
497 	if (writeResult != B_OK) {
498 		HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
499 			"error writing data");
500 	}
501 
502 	return ErrorStatus() == B_OK;
503 }
504 
505 
506 // #pragma mark - BJsonTextWriter
507 
508 
509 BJsonTextWriter::BJsonTextWriter(
510 	BDataIO* dataIO)
511 	:
512 	fDataIO(dataIO)
513 {
514 
515 		// this is a preparation for this buffer to easily be used later
516 		// to efficiently output encoded unicode characters.
517 
518 	fUnicodeAssemblyBuffer[0] = '\\';
519 	fUnicodeAssemblyBuffer[1] = 'u';
520 
521 	fStackedListener = new BJsonTextWriterStackedEventListener(this, NULL);
522 }
523 
524 
525 BJsonTextWriter::~BJsonTextWriter()
526 {
527 	BJsonTextWriterStackedEventListener* listener = fStackedListener;
528 
529 	while (listener != NULL) {
530 		BJsonTextWriterStackedEventListener* nextListener = listener->Parent();
531 		delete listener;
532 		listener = nextListener;
533 	}
534 
535 	fStackedListener = NULL;
536 }
537 
538 
539 bool
540 BJsonTextWriter::Handle(const BJsonEvent& event)
541 {
542 	return fStackedListener->Handle(event);
543 }
544 
545 
546 void
547 BJsonTextWriter::Complete()
548 {
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
552 		// / object.
553 
554 	if (fStackedListener->Parent() != NULL) {
555 		HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE,
556 			"unexpected end of input data");
557 	}
558 }
559 
560 
561 void
562 BJsonTextWriter::SetStackedListener(
563 	BJsonTextWriterStackedEventListener* stackedListener)
564 {
565 	fStackedListener = stackedListener;
566 }
567 
568 
569 status_t
570 BJsonTextWriter::StreamNumberNode(const BJsonEvent& event)
571 {
572 	return StreamStringVerbatim(event.Content());
573 }
574 
575 
576 status_t
577 BJsonTextWriter::StreamStringVerbatim(const char* string)
578 {
579 	return StreamStringVerbatim(string, 0, strlen(string));
580 }
581 
582 
583 status_t
584 BJsonTextWriter::StreamStringVerbatim(const char* string,
585 	off_t offset, size_t length)
586 {
587 	return fDataIO->WriteExactly(&string[offset], length);
588 }
589 
590 
591 status_t
592 BJsonTextWriter::StreamStringEncoded(const char* string)
593 {
594 	return StreamStringEncoded(string, 0, strlen(string));
595 }
596 
597 
598 /*! Note that this method will expect a UTF-8 encoded string. */
599 
600 status_t
601 BJsonTextWriter::StreamStringEncoded(const char* string,
602 	off_t offset, size_t length)
603 {
604 	status_t writeResult = B_OK;
605 	uint8* string8bit = (uint8*)string;
606 
607 	while (writeResult == B_OK && length != 0) {
608 		uint8 c = string8bit[offset];
609 		const char* simpleEsc = b_json_simple_esc_sequence(c);
610 
611 			// simple escape sequence involving the backslash + one character.
612 
613 		if (simpleEsc != NULL) {
614 			writeResult = StreamStringVerbatim(simpleEsc, 0, 2);
615 
616 			if (writeResult == B_OK) {
617 				offset++;
618 				length--;
619 			}
620 		} else {
621 
622 			if (b_json_is_7bit_clean(c)) {
623 
624 					// roll forward while the characters are simple and then
625 					// output them at as a block verbatim.
626 
627 				uint32 count7BitClean = 1;
628 
629 				while (count7BitClean < length
630 					&& b_json_is_7bit_clean(
631 						string8bit[offset + count7BitClean])) {
632 					count7BitClean++;
633 				}
634 
635 				writeResult = StreamStringVerbatim(&string[offset], 0,
636 					count7BitClean);
637 
638 				if (writeResult == B_OK) {
639 					offset += count7BitClean;
640 					length -= count7BitClean;
641 				}
642 			} else {
643 				if (b_json_is_illegal(c)) {
644 					fprintf(stderr, "! string encoding error - illegal "
645 						"character [%" B_PRIu32 "]\n", static_cast<uint32>(c));
646 					offset++;
647 					length--;
648 				} else {
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(
654 						&stringInitial);
655 
656 					sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32,
657 						unicodeCharacter);
658 					writeResult = StreamStringVerbatim(fUnicodeAssemblyBuffer,
659 						0, 6);
660 
661 					if (writeResult == B_OK) {
662 						uint32 sequence_length
663 							= (uint32)(stringInitial - &string[offset]);
664 						offset += sequence_length;
665 						length -= sequence_length;
666 					}
667 				}
668 			}
669 		}
670 	}
671 
672 	return writeResult;
673 }
674 
675 
676 status_t
677 BJsonTextWriter::StreamQuotedEncodedString(const char* string)
678 {
679 	return StreamQuotedEncodedString(string, 0, strlen(string));
680 }
681 
682 
683 status_t
684 BJsonTextWriter::StreamQuotedEncodedString(const char* string,
685 	off_t offset, size_t length)
686 {
687 	status_t write_result = B_OK;
688 
689 	if (write_result == B_OK)
690 		write_result = StreamChar('\"');
691 
692 	if (write_result == B_OK)
693 		write_result = StreamStringEncoded(string, offset, length);
694 
695 	if (write_result == B_OK)
696 		write_result = StreamChar('\"');
697 
698 	return write_result;
699 }
700 
701 
702 status_t
703 BJsonTextWriter::StreamChar(char c)
704 {
705 	return fDataIO->WriteExactly(&c, 1);
706 }