2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
3 * Copyright 2014-2017, Augustin Cavalier (waddlesplash)
4 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>
5 * Distributed under the terms of the MIT License.
16 #include <AutoDeleter.h>
18 #include <UnicodeChar.h>
20 #include "JsonEventListener.h"
21 #include "JsonMessageWriter.h"
24 // #pragma mark - Public methods
30 b_jsonparse_is_hex(char c
)
33 || (c
> 0x41 && c
<= 0x46)
34 || (c
> 0x61 && c
<= 0x66);
39 b_jsonparse_all_hex(const char* c
)
41 for (int i
= 0; i
< 4; i
++) {
42 if (!b_jsonparse_is_hex(c
[i
]))
50 /*! This class carries state around the parsing process. */
52 class JsonParseContext
{
54 JsonParseContext(BDataIO
* data
, BJsonEventListener
* listener
)
58 fLineNumber(1), // 1 is the first line
60 fHasPushbackChar(false)
65 BJsonEventListener
* Listener() const
77 int LineNumber() const
83 void IncrementLineNumber()
89 // TODO; there is considerable opportunity for performance improvements
90 // here by buffering the input and then feeding it into the parse
91 // algorithm character by character.
93 status_t
NextChar(char* buffer
)
96 if (fHasPushbackChar
) {
97 buffer
[0] = fPushbackChar
;
98 fHasPushbackChar
= false;
102 return Data()->ReadExactly(buffer
, 1);
106 void PushbackChar(char c
)
109 fHasPushbackChar
= true;
113 BJsonEventListener
* fListener
;
117 bool fHasPushbackChar
;
122 BJson::Parse(const BString
& JSON
, BMessage
& message
)
124 return Parse(JSON
.String(), message
);
129 BJson::Parse(const char* JSON
, BMessage
& message
)
131 BMemoryIO
* input
= new BMemoryIO(JSON
, strlen(JSON
));
132 ObjectDeleter
<BMemoryIO
> inputDeleter(input
);
133 BJsonMessageWriter
* writer
= new BJsonMessageWriter(message
);
134 ObjectDeleter
<BJsonMessageWriter
> writerDeleter(writer
);
136 Parse(input
, writer
);
137 status_t result
= writer
->ErrorStatus();
143 /*! The data is read as a stream of JSON data. As the JSON is read, events are
150 Each event is sent to the listener to process as required.
154 BJson::Parse(BDataIO
* data
, BJsonEventListener
* listener
)
156 JsonParseContext
context(data
, listener
);
158 listener
->Complete();
162 // #pragma mark - Specific parse logic.
166 BJson::NextChar(JsonParseContext
& jsonParseContext
, char* c
)
168 status_t result
= jsonParseContext
.NextChar(c
);
176 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
177 jsonParseContext
.LineNumber(), "unexpected end of input");
183 jsonParseContext
.Listener()->HandleError(result
, -1,
184 "io related read error");
192 BJson::NextNonWhitespaceChar(JsonParseContext
& jsonParseContext
, char* c
)
195 if (!NextChar(jsonParseContext
, c
))
199 case 0x0a: // newline
201 jsonParseContext
.IncrementLineNumber();
203 // swallow whitespace as it is not syntactically
215 BJson::ParseAny(JsonParseContext
& jsonParseContext
)
219 if (!NextNonWhitespaceChar(jsonParseContext
, &c
))
224 return ParseExpectedVerbatimStringAndRaiseEvent(
225 jsonParseContext
, "alse", 4, 'f', B_JSON_FALSE
);
228 return ParseExpectedVerbatimStringAndRaiseEvent(
229 jsonParseContext
, "rue", 3, 't', B_JSON_TRUE
);
232 return ParseExpectedVerbatimStringAndRaiseEvent(
233 jsonParseContext
, "ull", 3, 'n', B_JSON_NULL
);
236 return ParseString(jsonParseContext
, B_JSON_STRING
);
239 return ParseObject(jsonParseContext
);
242 return ParseArray(jsonParseContext
);
256 jsonParseContext
.PushbackChar(c
); // keeps the parse simple
257 return ParseNumber(jsonParseContext
);
261 BString errorMessage
;
262 if (c
>= 0x20 && c
< 0x7f) {
263 errorMessage
.SetToFormat("unexpected character [%" B_PRIu8
"]"
264 " (%c) when parsing element", static_cast<uint8
>(c
), c
);
266 errorMessage
.SetToFormat("unexpected character [%" B_PRIu8
"]"
267 " when parsing element", (uint8
) c
);
269 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
270 jsonParseContext
.LineNumber(), errorMessage
.String());
279 /*! This method captures an object name, a separator ':' and then any value. */
282 BJson::ParseObjectNameValuePair(JsonParseContext
& jsonParseContext
)
284 bool didParseName
= false;
288 if (!NextNonWhitespaceChar(jsonParseContext
, &c
))
292 case '\"': // name of the object
295 if (!ParseString(jsonParseContext
, B_JSON_OBJECT_NAME
))
300 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
301 jsonParseContext
.LineNumber(), "unexpected"
302 " [\"] character when parsing object name-"
309 case ':': // separator
312 if (!ParseAny(jsonParseContext
))
316 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
317 jsonParseContext
.LineNumber(), "unexpected"
318 " [:] character when parsing object name-"
326 BString errorMessage
;
327 errorMessage
.SetToFormat(
328 "unexpected character [%c] when parsing object"
331 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
332 jsonParseContext
.LineNumber(), errorMessage
.String());
341 BJson::ParseObject(JsonParseContext
& jsonParseContext
)
343 if (!jsonParseContext
.Listener()->Handle(
344 BJsonEvent(B_JSON_OBJECT_START
))) {
349 bool firstItem
= true;
352 if (!NextNonWhitespaceChar(jsonParseContext
, &c
))
356 case '}': // terminate the object
358 if (!jsonParseContext
.Listener()->Handle(
359 BJsonEvent(B_JSON_OBJECT_END
))) {
365 case ',': // next value.
368 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
369 jsonParseContext
.LineNumber(), "unexpected"
370 " item separator when parsing start of"
375 if (!ParseObjectNameValuePair(jsonParseContext
))
383 jsonParseContext
.PushbackChar(c
);
384 if (!ParseObjectNameValuePair(jsonParseContext
))
388 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
389 jsonParseContext
.LineNumber(), "expected"
390 " separator when parsing an object");
401 BJson::ParseArray(JsonParseContext
& jsonParseContext
)
403 if (!jsonParseContext
.Listener()->Handle(
404 BJsonEvent(B_JSON_ARRAY_START
))) {
409 bool firstItem
= true;
412 if (!NextNonWhitespaceChar(jsonParseContext
, &c
))
416 case ']': // terminate the array
418 if (!jsonParseContext
.Listener()->Handle(
419 BJsonEvent(B_JSON_ARRAY_END
))) {
425 case ',': // next value.
428 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
429 jsonParseContext
.LineNumber(), "unexpected"
430 " item separator when parsing start of"
434 if (!ParseAny(jsonParseContext
))
442 jsonParseContext
.PushbackChar(c
);
443 if (!ParseAny(jsonParseContext
))
447 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
448 jsonParseContext
.LineNumber(), "expected"
449 " separator when parsing an array");
460 BJson::ParseEscapeUnicodeSequence(JsonParseContext
& jsonParseContext
,
461 BString
& stringResult
)
466 if (!NextChar(jsonParseContext
, &buffer
[0])
467 || !NextChar(jsonParseContext
, &buffer
[1])
468 || !NextChar(jsonParseContext
, &buffer
[2])
469 || !NextChar(jsonParseContext
, &buffer
[3])) {
473 if (!b_jsonparse_all_hex(buffer
)) {
474 BString errorMessage
;
475 errorMessage
.SetToFormat(
476 "malformed unicode sequence [%s] in string parsing",
478 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
479 jsonParseContext
.LineNumber(), errorMessage
.String());
485 if (sscanf(buffer
, "%4x", &intValue
) != 1) {
486 BString errorMessage
;
487 errorMessage
.SetToFormat(
488 "unable to process unicode sequence [%s] in string "
490 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
491 jsonParseContext
.LineNumber(), errorMessage
.String());
496 char* ptr
= character
;
497 BUnicodeChar::ToUTF8(intValue
, &ptr
);
498 int32 sequenceLength
= ptr
- character
;
499 stringResult
.Append(character
, sequenceLength
);
506 BJson::ParseStringEscapeSequence(JsonParseContext
& jsonParseContext
,
507 BString
& stringResult
)
511 if (!NextChar(jsonParseContext
, &c
))
516 stringResult
+= "\n";
519 stringResult
+= "\r";
522 stringResult
+= "\b";
525 stringResult
+= "\f";
528 stringResult
+= "\\";
534 stringResult
+= "\t";
537 stringResult
+= "\"";
541 // unicode escape sequence.
542 if (!ParseEscapeUnicodeSequence(jsonParseContext
,
550 BString errorMessage
;
551 errorMessage
.SetToFormat(
552 "unexpected escaped character [%c] in string parsing",
554 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
555 jsonParseContext
.LineNumber(), errorMessage
.String());
565 BJson::ParseString(JsonParseContext
& jsonParseContext
,
566 json_event_type eventType
)
569 BString stringResult
;
572 if (!NextChar(jsonParseContext
, &c
))
578 // terminates the string assembled so far.
579 jsonParseContext
.Listener()->Handle(
580 BJsonEvent(eventType
, stringResult
.String()));
586 if (!ParseStringEscapeSequence(jsonParseContext
,
595 uint8 uc
= static_cast<uint8
>(c
);
597 if(uc
< 0x20) { // control characters are not allowed
598 BString errorMessage
;
599 errorMessage
.SetToFormat("illegal control character"
600 " [%" B_PRIu8
"] when parsing a string", uc
);
601 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
602 jsonParseContext
.LineNumber(),
603 errorMessage
.String());
607 stringResult
.Append(&c
, 1);
616 BJson::ParseExpectedVerbatimStringAndRaiseEvent(
617 JsonParseContext
& jsonParseContext
, const char* expectedString
,
618 size_t expectedStringLength
, char leadingChar
,
619 json_event_type jsonEventType
)
621 if (ParseExpectedVerbatimString(jsonParseContext
, expectedString
,
622 expectedStringLength
, leadingChar
)) {
623 if (!jsonParseContext
.Listener()->Handle(BJsonEvent(jsonEventType
)))
630 /*! This will make sure that the constant string is available at the input. */
633 BJson::ParseExpectedVerbatimString(JsonParseContext
& jsonParseContext
,
634 const char* expectedString
, size_t expectedStringLength
, char leadingChar
)
639 while (offset
< expectedStringLength
) {
640 if (!NextChar(jsonParseContext
, &c
))
643 if (c
!= expectedString
[offset
]) {
644 BString errorMessage
;
645 errorMessage
.SetToFormat("malformed json primative literal; "
646 "expected [%c%s], but got [%c] at position %" B_PRIdSSIZE
,
647 leadingChar
, expectedString
, c
, offset
);
648 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
649 jsonParseContext
.LineNumber(), errorMessage
.String());
660 /*! This function checks to see that the supplied string is a well formed
661 JSON number. It does this from a string rather than a stream for
662 convenience. This is not anticipated to impact performance because
663 the string values are short.
667 BJson::IsValidNumber(BString
& number
)
670 int32 len
= number
.Length();
672 if (offset
< len
&& number
[offset
] == '-')
678 if (isdigit(number
[offset
]) && number
[offset
] != '0') {
679 while (offset
< len
&& isdigit(number
[offset
]))
682 if (number
[offset
] == '0')
688 if (offset
< len
&& number
[offset
] == '.') {
694 while (offset
< len
&& isdigit(number
[offset
]))
698 if (offset
< len
&& (number
[offset
] == 'E' || number
[offset
] == 'e')) {
701 if(offset
< len
&& (number
[offset
] == '+' || number
[offset
] == '-'))
707 while (offset
< len
&& isdigit(number
[offset
]))
711 return offset
== len
;
715 /*! Note that this method hits the 'NextChar' method on the context directly
716 and handles any end-of-file state itself because it is feasible that the
717 entire JSON payload is a number and because (unlike other structures, the
718 number can take the end-of-file to signify the end of the number.
722 BJson::ParseNumber(JsonParseContext
& jsonParseContext
)
728 status_t result
= jsonParseContext
.NextChar(&c
);
738 if (NULL
!= strchr("+-eE.", c
)) {
743 jsonParseContext
.PushbackChar(c
);
744 // intentional fall through
750 if (!IsValidNumber(value
)) {
751 jsonParseContext
.Listener()->HandleError(B_BAD_DATA
,
752 jsonParseContext
.LineNumber(), "malformed number");
756 jsonParseContext
.Listener()->Handle(BJsonEvent(B_JSON_NUMBER
,
763 jsonParseContext
.Listener()->HandleError(result
, -1,
764 "io related read error");
771 } // namespace BPrivate