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 }