diff --git a/DCPlugin.pro b/DCPlugin.pro index fece604..98002ff 100644 --- a/DCPlugin.pro +++ b/DCPlugin.pro @@ -76,14 +76,16 @@ HEADERS += \ src/ATBAPP/ATBHealthEvent.h \ src/ATBAPP/ATBMachineEvent.h \ src/ATBAPP/ATBDeviceControllerPlugin.h \ - src/ATBAPP/Utils.h + src/ATBAPP/Utils.h \ + src/ATBAPP/support/JSON.h SOURCES += \ src/ATBAPP/ATBHealthEvent.cpp \ src/ATBAPP/ATBMachineEvent.cpp \ src/ATBAPP/ATBDeviceControllerPlugin.cpp \ src/ATBAPP/DeviceControllerDiag.cpp \ - src/ATBAPP/Utils.cpp + src/ATBAPP/Utils.cpp \ + src/ATBAPP/support/JSON.cpp DISTFILES += \ generate-version.sh diff --git a/src/ATBAPP/support/JSON.cpp b/src/ATBAPP/support/JSON.cpp new file mode 100644 index 0000000..d3fc15b --- /dev/null +++ b/src/ATBAPP/support/JSON.cpp @@ -0,0 +1,735 @@ +#include +#include +#include "JSON.h" + + + +namespace JSON { + static QString dateFormat, dateTimeFormat; + static bool prettySerialize = false; + + static QString sanitizeString(QString str); + static QByteArray join(const QList &list, const QByteArray &sep); + static QVariant parseValue(const QString &json, int &index, bool &success); + static QVariant parseObject(const QString &json, int &index, bool &success); + static QVariant parseArray(const QString &json, int &index, bool &success); + static QVariant parseString(const QString &json, int &index, bool &success); + static QVariant parseNumber(const QString &json, int &index); + static int lastIndexOfNumber(const QString &json, int index); + static void eatWhitespace(const QString &json, int &index); + static int lookAhead(const QString &json, int index); + static int nextToken(const QString &json, int &index); + + + template + QByteArray serializeMap(const T &map, bool &success, int _level = 0) { + QByteArray newline; + QByteArray tabs; + QByteArray tabsFields; + if (prettySerialize && !map.isEmpty()) { + newline = "\n"; + for (int l=1; l<_level; l++) { + tabs += " "; + } + tabsFields = tabs + " "; + } + + QByteArray str = "{" + newline; + QList pairs; + for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { + bool otherSuccess = true; + QByteArray serializedValue = serialize(it.value(), otherSuccess, _level); + if (serializedValue.isNull()) { + success = false; + break; + } + pairs << tabsFields + sanitizeString(it.key()).toUtf8() + ":" + (prettySerialize ? " " : "") + serializedValue; + } + + str += join(pairs, "," + newline) + newline; + str += tabs + "}"; + return str; + } + + + void insert(QVariant &v, const QString &key, const QVariant &value); + void append(QVariant &v, const QVariant &value); + + template + void cloneMap(QVariant &json, const T &map) { + for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { + insert(json, it.key(), (*it)); + } + } + + template + void cloneList(QVariant &json, const T &list) { + for (typename T::const_iterator it = list.begin(), itend = list.end(); it != itend; ++it) { + append(json, (*it)); + } + } + + + /** + * parse + */ + QVariant parse(const QString &json) { + bool success = true; + return parse(json, success); + } + + /** + * parse + */ + QVariant parse(const QString &json, bool &success) { + success = true; + + // Return an empty QVariant if the JSON data is either null or empty + if (!json.isNull() || !json.isEmpty()) { + QString data = json; + // We'll start from index 0 + int index = 0; + + // Parse the first value + QVariant value = parseValue(data, index, success); + + // Return the parsed value + return value; + } else { + // Return the empty QVariant + return QVariant(); + } + } + + + + /** + * clone + */ + QVariant clone(const QVariant &data) { + QVariant v; + + if (data.type() == QVariant::Map) { + cloneMap(v, data.toMap()); + } else if (data.type() == QVariant::Hash) { + cloneMap(v, data.toHash()); + } else if (data.type() == QVariant::List) { + cloneList(v, data.toList()); + } else if (data.type() == QVariant::StringList) { + cloneList(v, data.toStringList()); + } else { + v = QVariant(data); + } + + return v; + } + + /** + * insert value (map case) + */ + void insert(QVariant &v, const QString &key, const QVariant &value) { + if (!v.canConvert()) v = QVariantMap(); + QVariantMap *p = (QVariantMap *)v.data(); + p->insert(key, clone(value)); + } + + /** + * append value (list case) + */ + void append(QVariant &v, const QVariant &value) { + if (!v.canConvert()) v = QVariantList(); + QVariantList *p = (QVariantList *)v.data(); + p->append(value); + } + + QByteArray serialize(const QVariant &data) { + bool success = true; + return serialize(data, success); + } + + + + QByteArray serialize(const QVariant &data, bool &success, int _level /*= 0*/) { + QByteArray newline; + QByteArray tabs; + QByteArray tabsFields; + if (prettySerialize) { + newline = "\n"; + for (int l=0; l<_level; l++) { + tabs += " "; + } + tabsFields = tabs + " "; + } + + QByteArray str; + success = true; + + if (!data.isValid()) { // invalid or null? + str = "null"; + } else if ((data.type() == QVariant::List) || + (data.type() == QVariant::StringList)) { // variant is a list? + QList values; + const QVariantList list = data.toList(); + Q_FOREACH(const QVariant& v, list) { + bool otherSuccess = true; + QByteArray serializedValue = serialize(v, otherSuccess, _level+1); + if (serializedValue.isNull()) { + success = false; + break; + } + values << tabsFields + serializedValue; + } + + if (!values.isEmpty()) { + str = "[" + newline + join( values, "," + newline ) + newline + tabs + "]"; + } else { + str = "[]"; + } + } else if (data.type() == QVariant::Hash) { // variant is a hash? + str = serializeMap<>(data.toHash(), success, _level+1); + } else if (data.type() == QVariant::Map) { // variant is a map? + str = serializeMap<>(data.toMap(), success, _level+1); + } else if ((data.type() == QVariant::String) || + (data.type() == QVariant::ByteArray)) {// a string or a byte array? + str = sanitizeString(data.toString()).toUtf8(); + } else if (data.type() == QVariant::Double) { // double? + double value = data.toDouble(&success); + if (success) { + str = QByteArray::number(value, 'g'); + if (!str.contains(".") && ! str.contains("e")) { + str += ".0"; + } + } + } else if (data.type() == QVariant::Bool) { // boolean value? + str = data.toBool() ? "true" : "false"; + } else if (data.type() == QVariant::ULongLong) { // large unsigned number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { // any signed number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { //TODO: this code is never executed because all smaller types can be converted to qlonglong + str = QString::number(data.value()).toUtf8(); + } else if (data.type() == QVariant::DateTime) { // datetime value? + str = sanitizeString(dateTimeFormat.isEmpty() + ? data.toDateTime().toString() + : data.toDateTime().toString(dateTimeFormat)).toUtf8(); + } else if (data.type() == QVariant::Date) { // date value? + str = sanitizeString(dateTimeFormat.isEmpty() + ? data.toDate().toString() + : data.toDate().toString(dateFormat)).toUtf8(); + } else if (data.canConvert()) { // can value be converted to string? + // this will catch QUrl, ... (all other types which can be converted to string) + str = sanitizeString(data.toString()).toUtf8(); + } else { + success = false; + } + + if (success) { + return str; + } + return QByteArray(); + } + + QString serializeStr(const QVariant &data) { + return QString::fromUtf8(serialize(data)); + } + + QString serializeStr(const QVariant &data, bool &success) { + return QString::fromUtf8(serialize(data, success)); + } + + + + + /** + * \enum JsonToken + */ + enum JsonToken { + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 + }; + + static QString sanitizeString(QString str) { + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); + } + + + + static QByteArray join(const QList &list, const QByteArray &sep) { + QByteArray res; + Q_FOREACH(const QByteArray &i, list) { + if (!res.isEmpty()) { + res += sep; + } + res += i; + } + return res; + } + + + + + /** + * parseValue + */ + static QVariant parseValue(const QString &json, int &index, bool &success) { + // Determine what kind of data we should parse by + // checking out the upcoming token + switch(lookAhead(json, index)) { + case JsonTokenString: + return parseString(json, index, success); + case JsonTokenNumber: + return parseNumber(json, index); + case JsonTokenCurlyOpen: + return parseObject(json, index, success); + case JsonTokenSquaredOpen: + return parseArray(json, index, success); + case JsonTokenTrue: + nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } + + // If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); + } + + + + + /** + * parseObject + */ + static QVariant parseObject(const QString &json, int &index, bool &success) { + QVariantMap map; + int token; + + // Get rid of the whitespace and increment index + nextToken(json, index); + + // Loop through all of the key/value pairs of the object + bool done = false; + while (!done) { + // Get the upcoming token + token = lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantMap(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenCurlyClose) { + nextToken(json, index); + return map; + } else { + // Parse the key/value pair's name + QString name = parseString(json, index, success).toString(); + + if (!success) { + return QVariantMap(); + } + + // Get the next token + token = nextToken(json, index); + + // If the next token is not a colon, flag the failure + // return an empty QVariant + if (token != JsonTokenColon) { + success = false; + return QVariant(QVariantMap()); + } + + // Parse the key/value pair's value + QVariant value = parseValue(json, index, success); + + if (!success) { + return QVariantMap(); + } + + // Assign the value to the key in the map + map[name] = value; + } + } + + // Return the map successfully + return QVariant(map); + } + + + /** + * parseArray + */ + static QVariant parseArray(const QString &json, int &index, bool &success) { + QVariantList list; + + nextToken(json, index); + + bool done = false; + while(!done) { + int token = lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantList(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenSquaredClose) { + nextToken(json, index); + break; + } else { + QVariant value = parseValue(json, index, success); + if (!success) { + return QVariantList(); + } + list.push_back(value); + } + } + + return QVariant(list); + } + + + + + /** + * parseString + */ + static QVariant parseString(const QString &json, int &index, bool &success) { + QString s; + QChar c; + + eatWhitespace(json, index); + + c = json[index++]; + + bool complete = false; + while(!complete) { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + complete = true; + break; + } else if (c == '\\') { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + s.append('\"'); + } else if (c == '\\') { + s.append('\\'); + } else if (c == '/') { + s.append('/'); + } else if (c == 'b') { + s.append('\b'); + } else if (c == 'f') { + s.append('\f'); + } else if (c == 'n') { + s.append('\n'); + } else if (c == 'r') { + s.append('\r'); + } else if (c == 't') { + s.append('\t'); + } else if (c == 'u') { + int remainingLength = json.size() - index; + if (remainingLength >= 4) { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } else { + break; + } + } + } else { + s.append(c); + } + } + + if (!complete) { + success = false; + return QVariant(); + } + + return QVariant(s); + } + + + + /** + * parseNumber + */ + static QVariant parseNumber(const QString &json, int &index) { + eatWhitespace(json, index); + + int lastIndex = lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; + + numberStr = json.mid(index, charLength); + + index = lastIndex + 1; + bool ok; + + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(NULL)); + } else if (numberStr.startsWith('-')) { + int i = numberStr.toInt(&ok); + if (!ok) { + qlonglong ll = numberStr.toLongLong(&ok); + return ok ? ll : QVariant(numberStr); + } + return i; + } else { + uint u = numberStr.toUInt(&ok); + if (!ok) { + qulonglong ull = numberStr.toULongLong(&ok); + return ok ? ull : QVariant(numberStr); + } + return u; + } + } + + /** + * lastIndexOfNumber + */ + static int lastIndexOfNumber(const QString &json, int index) { + int lastIndex; + + for(lastIndex = index; lastIndex < json.size(); lastIndex++) { + if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) { + break; + } + } + + return lastIndex -1; + } + + /** + * eatWhitespace + */ + static void eatWhitespace(const QString &json, int &index) { + for(; index < json.size(); index++) { + if (QString(" \t\n\r").indexOf(json[index]) == -1) { + break; + } + } + } + + /** + * lookAhead + */ + static int lookAhead(const QString &json, int index) { + int saveIndex = index; + return nextToken(json, saveIndex); + } + + /** + * nextToken + */ + static int nextToken(const QString &json, int &index) { + eatWhitespace(json, index); + + if (index == json.size()) { + return JsonTokenNone; + } + + QChar c = json[index]; + index++; + switch(c.toLatin1()) { + case '{': return JsonTokenCurlyOpen; + case '}': return JsonTokenCurlyClose; + case '[': return JsonTokenSquaredOpen; + case ']': return JsonTokenSquaredClose; + case ',': return JsonTokenComma; + case '"': return JsonTokenString; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': return JsonTokenNumber; + case ':': return JsonTokenColon; + } + index--; // ^ WTF? + + int remainingLength = json.size() - index; + + // True + if (remainingLength >= 4) { + if (json[index] == 't' && json[index + 1] == 'r' && + json[index + 2] == 'u' && json[index + 3] == 'e') { + index += 4; + return JsonTokenTrue; + } + } + + // False + if (remainingLength >= 5) { + if (json[index] == 'f' && json[index + 1] == 'a' && + json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JsonTokenFalse; + } + } + + // Null + if (remainingLength >= 4) { + if (json[index] == 'n' && json[index + 1] == 'u' && + json[index + 2] == 'l' && json[index + 3] == 'l') { + index += 4; + return JsonTokenNull; + } + } + + return JsonTokenNone; + } + + void setDateTimeFormat(const QString &format) { + dateTimeFormat = format; + } + + void setDateFormat(const QString &format) { + dateFormat = format; + } + + QString getDateTimeFormat() { + return dateTimeFormat; + } + + QString getDateFormat() { + return dateFormat; + } + + void setPrettySerialize(bool enabled) { + prettySerialize = enabled; + } + + bool isPrettySerialize() { + return prettySerialize; + } + + + + QQueue BuilderJsonObject::created_list; + + BuilderJsonObject::BuilderJsonObject() { + // clean objects previous "created" + while (!BuilderJsonObject::created_list.isEmpty()) { + delete BuilderJsonObject::created_list.dequeue(); + } + } + + BuilderJsonObject::BuilderJsonObject(JsonObject &json) { + BuilderJsonObject(); + + obj = json; + } + + BuilderJsonObject *BuilderJsonObject::set(const QString &key, const QVariant &value) { + obj[key] = value; + + return this; + } + + BuilderJsonObject *BuilderJsonObject::set(const QString &key, BuilderJsonObject *builder) { + return set(key, builder->create()); + } + + BuilderJsonObject *BuilderJsonObject::set(const QString &key, BuilderJsonArray *builder) { + return set(key, builder->create()); + } + + JsonObject BuilderJsonObject::create() { + BuilderJsonObject::created_list.enqueue(this); + + return obj; + } + + + QQueue BuilderJsonArray::created_list; + + BuilderJsonArray::BuilderJsonArray() { + // clean objects previous "created" + while (!BuilderJsonArray::created_list.isEmpty()) { + delete BuilderJsonArray::created_list.dequeue(); + } + } + + BuilderJsonArray::BuilderJsonArray(JsonArray &json) { + BuilderJsonArray(); + + array = json; + } + + BuilderJsonArray *BuilderJsonArray::add(const QVariant &element) { + array.append(element); + + return this; + } + + BuilderJsonArray *BuilderJsonArray::add(BuilderJsonObject *builder) { + return add(builder->create()); + } + + BuilderJsonArray *BuilderJsonArray::add(BuilderJsonArray *builder) { + return add(builder->create()); + } + + JsonArray BuilderJsonArray::create() { + BuilderJsonArray::created_list.enqueue(this); + + return array; + } + + + + + BuilderJsonObject *objectBuilder() { + return new BuilderJsonObject(); + } + + BuilderJsonObject *objectBuilder(JsonObject &json) { + return new BuilderJsonObject(json); + } + + BuilderJsonArray *arrayBuilder() { + return new BuilderJsonArray(); + } + + BuilderJsonArray *arrayBuilder(JsonArray &json) { + return new BuilderJsonArray(json); + } + +} //end namespace diff --git a/src/ATBAPP/support/JSON.h b/src/ATBAPP/support/JSON.h new file mode 100644 index 0000000..3017ca6 --- /dev/null +++ b/src/ATBAPP/support/JSON.h @@ -0,0 +1,250 @@ +#ifndef JSON_H +#define JSON_H + + +#include +#include +#include + +/********************************************** + * based on: https://github.com/qt-json/qt-json + */ + +/** + * \namespace JSON + * \brief A JSON data parser + * + * Json parses a JSON data into a QVariant hierarchy. + */ +namespace JSON { + typedef QVariantMap JsonObject; + typedef QVariantList JsonArray; + + + /** + * Clone a JSON object (makes a deep copy) + * + * \param data The JSON object + */ + QVariant clone(const QVariant &data); + + /** + * Insert value to JSON object (QVariantMap) + * + * \param v The JSON object + * \param key The key + * \param value The value + */ + void insert(QVariant &v, const QString &key, const QVariant &value); + + /** + * Append value to JSON array (QVariantList) + * + * \param v The JSON array + * \param value The value + */ + void append(QVariant &v, const QVariant &value); + + /** + * Parse a JSON string + * + * \param json The JSON data + */ + QVariant parse(const QString &json); + + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + QVariant parse(const QString &json, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data, bool &success, int _level = 0); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data, bool &success, int _level = 0); + + /** + * This method sets date(time) format to be used for QDateTime::toString + * If QString is empty, Qt::TextDate is used. + * + * \param format The JSON data generated by the parser. + */ + void setDateTimeFormat(const QString& format); + void setDateFormat(const QString& format); + + /** + * This method gets date(time) format to be used for QDateTime::toString + * If QString is empty, Qt::TextDate is used. + */ + QString getDateTimeFormat(); + QString getDateFormat(); + + /** + * @brief setPrettySerialize enable/disabled pretty-print when serialize() a json + * @param enabled + */ + void setPrettySerialize(bool enabled); + + /** + * @brief isPrettySerialize check if is enabled pretty-print when serialize() a json + * @return + */ + bool isPrettySerialize(); + + + + /** + * QVariant based Json object + */ + class Object : public QVariant { + template + Object& insertKey(Object* ptr, const QString& key) { + T* p = (T*)ptr->data(); + if (!p->contains(key)) p->insert(key, QVariant()); + return *reinterpret_cast(&p->operator[](key)); + } + template + void removeKey(Object *ptr, const QString& key) { + T* p = (T*)ptr->data(); + p->remove(key); + } + public: + Object() : QVariant() {} + Object(const Object& ref) : QVariant(ref) {} + + Object& operator=(const QVariant& rhs) { + /** It maybe more robust when running under Qt versions below 4.7 */ + QObject * obj = qvariant_cast(rhs); + // setValue(rhs); + setValue(obj); + return *this; + } + Object& operator[](const QString& key) { + if (type() == QVariant::Map) + return insertKey(this, key); + else if (type() == QVariant::Hash) + return insertKey(this, key); + + setValue(QVariantMap()); + + return insertKey(this, key); + } + const Object& operator[](const QString& key) const { + return const_cast(this)->operator[](key); + } + void remove(const QString& key) { + if (type() == QVariant::Map) + removeKey(this, key); + else if (type() == QVariant::Hash) + removeKey(this, key); + } + }; + + + class BuilderJsonArray; + + /** + * @brief The BuilderJsonObject class + */ + class BuilderJsonObject { + + public: + BuilderJsonObject(); + BuilderJsonObject(JsonObject &json); + + BuilderJsonObject *set(const QString &key, const QVariant &value); + BuilderJsonObject *set(const QString &key, BuilderJsonObject *builder); + BuilderJsonObject *set(const QString &key, BuilderJsonArray *builder); + JsonObject create(); + + private: + static QQueue created_list; + + JsonObject obj; + }; + + + /** + * @brief The BuilderJsonArray class + */ + class BuilderJsonArray { + + public: + BuilderJsonArray(); + BuilderJsonArray(JsonArray &json); + + BuilderJsonArray *add(const QVariant &element); + BuilderJsonArray *add(BuilderJsonObject *builder); + BuilderJsonArray *add(BuilderJsonArray *builder); + JsonArray create(); + + private: + static QQueue created_list; + + JsonArray array; + }; + + + /** + * @brief Create a BuilderJsonObject + * @return + */ + BuilderJsonObject *objectBuilder(); + + /** + * @brief Create a BuilderJsonObject starting from copy of another json + * @return + */ + BuilderJsonObject *objectBuilder(JsonObject &json); + + /** + * @brief Create a BuilderJsonArray + * @return + */ + BuilderJsonArray *arrayBuilder(); + + /** + * @brief Create a BuilderJsonArray starting from copy of another json + * @return + */ + BuilderJsonArray *arrayBuilder(JsonArray &json); +} + + + +#endif // JSON_H