From e4d2963e01eaa559c4cdc7165cd8ea0925e165dd Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Mon, 4 Aug 2014 13:00:03 -0400 Subject: [PATCH] libshared: Introduce JSON parser and MessageBuilder. Based on an earlier piece of source code of mine that parsed JSON into QObjects, this JSON parser creates a BMessage tree. Will be used by Stephan in HaikuDepot for communication with the web app. --- headers/private/shared/Json.h | 33 +++ headers/private/shared/MessageBuilder.h | 54 ++++ src/kits/shared/Jamfile | 2 + src/kits/shared/Json.cpp | 325 ++++++++++++++++++++++++ src/kits/shared/MessageBuilder.cpp | 208 +++++++++++++++ 5 files changed, 622 insertions(+) create mode 100644 headers/private/shared/Json.h create mode 100644 headers/private/shared/MessageBuilder.h create mode 100644 src/kits/shared/Json.cpp create mode 100644 src/kits/shared/MessageBuilder.cpp diff --git a/headers/private/shared/Json.h b/headers/private/shared/Json.h new file mode 100644 index 0000000000..a3eccbad80 --- /dev/null +++ b/headers/private/shared/Json.h @@ -0,0 +1,33 @@ +/* + * Copyright 2014, Augustin Cavalier (waddlesplash) + * Distributed under the terms of the MIT License. + */ +#ifndef JSON_H +#define JSON_H + +#include +#include + +namespace BPrivate { + +class BJson { +public: + enum JsonObjectType { + JSON_TYPE_MAP = '_JTM', + JSON_TYPE_ARRAY = '_JTA' + }; + +public: + static status_t Parse(BMessage& message, const char* JSON); + static status_t Parse(BMessage& message, BString& JSON); + +private: + static BString _Parser_ParseString(BString& JSON, int32& pos); + static double _Parser_ParseNumber(BString& JSON, int32& pos); + static bool _Parser_ParseConstant(BString& JSON, int32& pos, + const char* constant); +}; + +} // namespace BPrivate + +#endif // JSON_H diff --git a/headers/private/shared/MessageBuilder.h b/headers/private/shared/MessageBuilder.h new file mode 100644 index 0000000000..a5b55418fd --- /dev/null +++ b/headers/private/shared/MessageBuilder.h @@ -0,0 +1,54 @@ +/* + * Copyright 2014, Augustin Cavalier (waddlesplash) + * Distributed under the terms of the MIT License. + */ +#ifndef MESSAGE_BUILDER_H +#define MESSAGE_BUILDER_H + + +#include +#include +#include + +namespace BPrivate { + +class BMessageBuilder { +public: + BMessageBuilder(BMessage& message); + + status_t PushObject(const char* name); + status_t PushObject(uint32 name); + status_t PopObject(); + + uint32 What(); + void SetWhat(uint32 what); + + uint32 CountNames(type_code type = B_ANY_TYPE); + + // Copied from Message.h + status_t AddString(const char* name, const char* string); + status_t AddString(const char* name, + const BString& string); + status_t AddInt8(const char* name, int8 value); + status_t AddUInt8(const char* name, uint8 value); + status_t AddInt16(const char* name, int16 value); + status_t AddUInt16(const char* name, uint16 value); + status_t AddInt32(const char* name, int32 value); + status_t AddUInt32(const char* name, uint32 value); + status_t AddInt64(const char* name, int64 value); + status_t AddUInt64(const char* name, uint64 value); + status_t AddBool(const char* name, bool value); + status_t AddFloat(const char* name, float value); + status_t AddDouble(const char* name, double value); + status_t AddPointer(const char* name, + const void* pointer); + +private: + BObjectList fStack; + BObjectList fNameStack; + BMessage* fCurrentMessage; +}; + +} // namespace BPrivate + +#endif // MESSAGE_BUILDER_H diff --git a/src/kits/shared/Jamfile b/src/kits/shared/Jamfile index 212d499896..6867447673 100644 --- a/src/kits/shared/Jamfile +++ b/src/kits/shared/Jamfile @@ -33,8 +33,10 @@ for architectureObject in [ MultiArchSubDirSetup ] { HashString.cpp IconButton.cpp IconView.cpp + Json.cpp Keymap.cpp LongAndDragTrackingFilter.cpp + MessageBuilder.cpp NaturalCompare.cpp PromptWindow.cpp QueryFile.cpp diff --git a/src/kits/shared/Json.cpp b/src/kits/shared/Json.cpp new file mode 100644 index 0000000000..100e216313 --- /dev/null +++ b/src/kits/shared/Json.cpp @@ -0,0 +1,325 @@ +/* + * Copyright 2014, Augustin Cavalier (waddlesplash) + * Distributed under the terms of the MIT License. + */ + + +#include + +#include +#include + +#include +#include + + +// #pragma mark - Public methods + +namespace BPrivate { + +status_t +BJson::Parse(BMessage& message, const char* JSON) +{ + BString temp(JSON); + return Parse(message, temp); +} + + +status_t +BJson::Parse(BMessage& message, BString& JSON) +{ + BMessageBuilder builder(message); + int32 pos = 0; + int32 length = JSON.Length(); + + /* Locals used by the parser. */ + // Keeps track of the hierarchy (e.g. "{[{{") that has + // been read in. Allows the parser to verify that openbraces + // match up to closebraces and so on and so forth. + BString hierarchy(""); + // Stores the key that was just read by the string parser, + // in the case that we are parsing a map. + BString key(""); + + while (pos < length) { + switch (JSON[pos]) { + case '{': + hierarchy += "{"; + + if (hierarchy != "{") { + if (builder.What() == JSON_TYPE_ARRAY) + builder.PushObject(builder.CountNames()); + else { + builder.PushObject(key.String()); + key = ""; + } + } + + builder.SetWhat(JSON_TYPE_MAP); + break; + + case '}': + if (hierarchy.EndsWith("{") && (hierarchy.Length() != 1)) { + hierarchy.Truncate(hierarchy.Length() - 1); + builder.PopObject(); + } else if (hierarchy.Length() == 1) + return B_OK; // End of the JSON data + else + return B_BAD_DATA; // Unmatched closebrace + + break; + + case '[': + hierarchy += "["; + + if (builder.What() == JSON_TYPE_ARRAY) + builder.PushObject(builder.CountNames()); + else { + builder.PushObject(key.String()); + key = ""; + } + + builder.SetWhat(JSON_TYPE_ARRAY); + break; + + case ']': + if (hierarchy.EndsWith("[")) { + hierarchy.Truncate(hierarchy.Length() - 1); + builder.PopObject(); + } else + return B_BAD_DATA; // Unmatched closebracket + + break; + + case 't': + { + if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) + return B_BAD_DATA; + // 'true' cannot be a key, it can only be a value + + if (_Parser_ParseConstant(JSON, pos, "true")) { + if (builder.What() == JSON_TYPE_ARRAY) + key.SetToFormat("%zu", builder.CountNames()); + builder.AddBool(key.String(), true); + key = ""; + } else + return B_BAD_DATA; + // "t" out in the middle of nowhere!? + + break; + } + + case 'f': + { + if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) + return B_BAD_DATA; + // 'false' cannot be a key, it can only be a value + + if (_Parser_ParseConstant(JSON, pos, "false")) { + if (builder.What() == JSON_TYPE_ARRAY) + key.SetToFormat("%zu", builder.CountNames()); + builder.AddBool(key.String(), false); + key = ""; + } else + return B_BAD_DATA; + // "f" out in the middle of nowhere!? + + break; + } + + case 'n': + { + if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) + return B_BAD_DATA; + // 'null' cannot be a key, it can only be a value + + if (_Parser_ParseConstant(JSON, pos, "null")) { + if (builder.What() == JSON_TYPE_ARRAY) + key.SetToFormat("%zu", builder.CountNames()); + builder.AddPointer(key.String(), (void*)NULL); + key = ""; + } else + return B_BAD_DATA; + // "n" out in the middle of nowhere!? + + break; + } + + case '"': + if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) + key = _Parser_ParseString(JSON, pos); + else if (builder.What() != JSON_TYPE_ARRAY && key.Length() > 0) { + builder.AddString(key, _Parser_ParseString(JSON, pos)); + key = ""; + } else if (builder.What() == JSON_TYPE_ARRAY) { + key << builder.CountNames(); + builder.AddString(key, _Parser_ParseString(JSON, pos)); + key = ""; + } else + return B_BAD_DATA; + // Pretty sure it's impossible to get here, but you never know + + break; + + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) + return B_BAD_DATA; + // Numbers cannot be keys, they can only be values + + if (builder.What() == JSON_TYPE_ARRAY) + key << builder.CountNames(); + + double number = _Parser_ParseNumber(JSON, pos); + builder.AddDouble(key.String(), number); + + key = ""; + break; + } + + case ':': + case ',': + default: + // No need to do anything here. + break; + } + pos++; + } + + // Reached the end of the BString without reaching the end of the document + return B_BAD_DATA; +} + + +// #pragma mark - Private methods + + +BString +BJson::_Parser_ParseString(BString& JSON, int32& pos) +{ + if (JSON[pos] != '"') // Verify we're at the start of a string. + return BString(""); + pos++; + + BString str; + while (JSON[pos] != '"') { + if (JSON[pos] == '\\') { + pos++; + switch (JSON[pos]) { + case 'b': + str += "\b"; + break; + + case 'f': + str += "\f"; + break; + + case 'n': + str += "\n"; + break; + + case 'r': + str += "\r"; + break; + + case 't': + str += "\t"; + break; + + case 'u': // 4-byte hexadecimal Unicode char (e.g. "\uffff") + { + uint intValue; + BString substr; + JSON.CopyInto(substr, pos + 1, 4); + if (sscanf(substr.String(), "%4x", &intValue) != 1) + return str; + // We probably hit the end of the string. + // This probably should be counted as an error, + // but for now let's soft-fail instead of hard-fail. + + char character[20]; + char* ptr = character; + BUnicodeChar::ToUTF8(intValue, &ptr); + str.AppendChars(character, 1); + pos += 4; + break; + } + + default: + str += JSON[pos]; + break; + } + } else + str += JSON[pos]; + pos++; + } + + return str; +} + + +double +BJson::_Parser_ParseNumber(BString& JSON, int32& pos) +{ + BString value; + + while (true) { + switch (JSON[pos]) { + case '+': + case '-': + case 'e': + case 'E': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + value += JSON[pos]; + pos++; + continue; + + default: + // We've reached the end of the number, so decrement the + // "pos" value so that the parser picks up on the next char. + pos--; + break; + } + break; + } + + return strtod(value.String(), NULL); +} + + +bool +BJson::_Parser_ParseConstant(BString& JSON, int32& pos, const char* constant) +{ + BString value; + JSON.CopyInto(value, pos, strlen(constant)); + if (value == constant) { + pos += strlen(constant); + return true; + } else + return false; +} + + +} // namespace BPrivate + diff --git a/src/kits/shared/MessageBuilder.cpp b/src/kits/shared/MessageBuilder.cpp new file mode 100644 index 0000000000..d364abec46 --- /dev/null +++ b/src/kits/shared/MessageBuilder.cpp @@ -0,0 +1,208 @@ +/* + * Copyright 2014, Augustin Cavalier (waddlesplash) + * Distributed under the terms of the MIT License. + */ + + +#include + +#include + + +namespace BPrivate { + +// #pragma mark - BMessageBuilder + + +BMessageBuilder::BMessageBuilder(BMessage& message) + : + fCurrentMessage(&message), + fNameStack(20, true) +{ +} + + +/*! Creates a new BMessage, makes it a child of the + current one with "name", and then pushes the current + Message onto the stack and makes the new Message the + current one. +*/ +status_t +BMessageBuilder::PushObject(const char* name) +{ + BMessage* newMessage = new BMessage; + if (newMessage == NULL) + return B_NO_MEMORY; + + if (!fNameStack.AddItem(new BString(name))) + return B_ERROR; + if (!fStack.AddItem(fCurrentMessage)) + return B_ERROR; + + fCurrentMessage = newMessage; + return B_OK; +} + + +/*! Convenience function that converts "name" + to a string and calls PushObject(const char*) + with it. +*/ +status_t +BMessageBuilder::PushObject(uint32 name) +{ + BString nameString; + nameString.SetToFormat("%zu", name); + return PushObject(nameString.String()); +} + + +/*! Pops the last BMessage off the stack and makes it + the current one. +*/ +status_t +BMessageBuilder::PopObject() +{ + if (fStack.CountItems() < 1) + return B_ERROR; + + BMessage* previousMessage = fStack.LastItem(); + previousMessage->AddMessage(fNameStack.LastItem()->String(), + fCurrentMessage); + fCurrentMessage = previousMessage; + + fStack.RemoveItemAt(fStack.CountItems() - 1); + fNameStack.RemoveItemAt(fNameStack.CountItems() - 1); + return B_OK; +} + + +/*! Gets the "what" of the current message. +*/ +uint32 +BMessageBuilder::What() +{ + return fCurrentMessage->what; +} + + +/*! Sets the "what" of the current message. +*/ +void +BMessageBuilder::SetWhat(uint32 what) +{ + fCurrentMessage->what = what; +} + + +/*! Gets the value of CountNames() from the current message. +*/ +uint32 +BMessageBuilder::CountNames(type_code type) +{ + return fCurrentMessage->CountNames(type); +} + + +// #pragma mark - BMessageBuilder::Add (to fCurrentMessage) + + +status_t +BMessageBuilder::AddString(const char* name, const char* string) +{ + return fCurrentMessage->AddString(name, string); +} + + +status_t +BMessageBuilder::AddString(const char* name, const BString& string) +{ + return fCurrentMessage->AddString(name, string); +} + + +status_t +BMessageBuilder::AddInt8(const char* name, int8 value) +{ + return fCurrentMessage->AddInt8(name, value); +} + + +status_t +BMessageBuilder::AddUInt8(const char* name, uint8 value) +{ + return fCurrentMessage->AddUInt8(name, value); +} + + +status_t +BMessageBuilder::AddInt16(const char* name, int16 value) +{ + return fCurrentMessage->AddInt16(name, value); +} + + +status_t +BMessageBuilder::AddUInt16(const char* name, uint16 value) +{ + return fCurrentMessage->AddUInt16(name, value); +} + + +status_t +BMessageBuilder::AddInt32(const char* name, int32 value) +{ + return fCurrentMessage->AddInt32(name, value); +} + + +status_t +BMessageBuilder::AddUInt32(const char* name, uint32 value) +{ + return fCurrentMessage->AddUInt32(name, value); +} + + +status_t +BMessageBuilder::AddInt64(const char* name, int64 value) +{ + return fCurrentMessage->AddInt64(name, value); +} + + +status_t +BMessageBuilder::AddUInt64(const char* name, uint64 value) +{ + return fCurrentMessage->AddUInt64(name, value); +} + + +status_t +BMessageBuilder::AddBool(const char* name, bool value) +{ + return fCurrentMessage->AddBool(name, value); +} + + +status_t +BMessageBuilder::AddFloat(const char* name, float value) +{ + return fCurrentMessage->AddFloat(name, value); +} + + +status_t +BMessageBuilder::AddDouble(const char* name, double value) +{ + return fCurrentMessage->AddDouble(name, value); +} + + +status_t +BMessageBuilder::AddPointer(const char* name, const void* pointer) +{ + return fCurrentMessage->AddPointer(name, pointer); +} + + +} // namespace BPrivate