| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| #include <qv4jsonobject_p.h> |
| #include <qv4objectproto_p.h> |
| #include <qv4numberobject_p.h> |
| #include <qv4stringobject_p.h> |
| #include <qv4booleanobject_p.h> |
| #include <qv4objectiterator_p.h> |
| #include <qv4scopedvalue_p.h> |
| #include <qv4runtime_p.h> |
| #include <qv4variantobject_p.h> |
| #include "qv4string_p.h" |
| #include "qv4jscall_p.h" |
| #include <qv4symbol_p.h> |
| |
| #include <qstack.h> |
| #include <qstringlist.h> |
| |
| #include <wtf/MathExtras.h> |
| |
| using namespace QV4; |
| |
| //#define PARSER_DEBUG |
| #ifdef PARSER_DEBUG |
| static int indent = 0; |
| #define BEGIN qDebug() << QByteArray(4*indent++, ' ').constData() |
| #define END --indent |
| #define DEBUG qDebug() << QByteArray(4*indent, ' ').constData() |
| #else |
| #define BEGIN if (1) ; else qDebug() |
| #define END do {} while (0) |
| #define DEBUG if (1) ; else qDebug() |
| #endif |
| |
| |
| DEFINE_OBJECT_VTABLE(JsonObject); |
| |
| static const int nestingLimit = 1024; |
| |
| |
| JsonParser::JsonParser(ExecutionEngine *engine, const QChar *json, int length) |
| : engine(engine), head(json), json(json), nestingLevel(0), lastError(QJsonParseError::NoError) |
| { |
| end = json + length; |
| } |
| |
| |
| |
| /* |
| |
| begin-array = ws %x5B ws ; [ left square bracket |
| |
| begin-object = ws %x7B ws ; { left curly bracket |
| |
| end-array = ws %x5D ws ; ] right square bracket |
| |
| end-object = ws %x7D ws ; } right curly bracket |
| |
| name-separator = ws %x3A ws ; : colon |
| |
| value-separator = ws %x2C ws ; , comma |
| |
| Insignificant whitespace is allowed before or after any of the six |
| structural characters. |
| |
| ws = *( |
| %x20 / ; Space |
| %x09 / ; Horizontal tab |
| %x0A / ; Line feed or New line |
| %x0D ; Carriage return |
| ) |
| |
| */ |
| |
| enum { |
| Space = 0x20, |
| Tab = 0x09, |
| LineFeed = 0x0a, |
| Return = 0x0d, |
| BeginArray = 0x5b, |
| BeginObject = 0x7b, |
| EndArray = 0x5d, |
| EndObject = 0x7d, |
| NameSeparator = 0x3a, |
| ValueSeparator = 0x2c, |
| Quote = 0x22 |
| }; |
| |
| bool JsonParser::eatSpace() |
| { |
| while (json < end) { |
| if (*json > Space) |
| break; |
| if (*json != Space && |
| *json != Tab && |
| *json != LineFeed && |
| *json != Return) |
| break; |
| ++json; |
| } |
| return (json < end); |
| } |
| |
| QChar JsonParser::nextToken() |
| { |
| if (!eatSpace()) |
| return 0; |
| QChar token = *json++; |
| switch (token.unicode()) { |
| case BeginArray: |
| case BeginObject: |
| case NameSeparator: |
| case ValueSeparator: |
| case EndArray: |
| case EndObject: |
| eatSpace(); |
| case Quote: |
| break; |
| default: |
| token = 0; |
| break; |
| } |
| return token; |
| } |
| |
| /* |
| JSON-text = object / array |
| */ |
| ReturnedValue JsonParser::parse(QJsonParseError *error) |
| { |
| #ifdef PARSER_DEBUG |
| indent = 0; |
| qDebug() << ">>>>> parser begin"; |
| #endif |
| |
| eatSpace(); |
| |
| Scope scope(engine); |
| ScopedValue v(scope); |
| if (!parseValue(v)) { |
| #ifdef PARSER_DEBUG |
| qDebug() << ">>>>> parser error"; |
| #endif |
| if (lastError == QJsonParseError::NoError) |
| lastError = QJsonParseError::IllegalValue; |
| error->offset = json - head; |
| error->error = lastError; |
| return Encode::undefined(); |
| } |
| |
| // some input left... |
| if (eatSpace()) { |
| lastError = QJsonParseError::IllegalValue; |
| error->offset = json - head; |
| error->error = lastError; |
| return Encode::undefined(); |
| } |
| |
| END; |
| error->offset = 0; |
| error->error = QJsonParseError::NoError; |
| return v->asReturnedValue(); |
| } |
| |
| /* |
| object = begin-object [ member *( value-separator member ) ] |
| end-object |
| */ |
| |
| ReturnedValue JsonParser::parseObject() |
| { |
| if (++nestingLevel > nestingLimit) { |
| lastError = QJsonParseError::DeepNesting; |
| return Encode::undefined(); |
| } |
| |
| BEGIN << "parseObject pos=" << json; |
| Scope scope(engine); |
| |
| ScopedObject o(scope, engine->newObject()); |
| |
| QChar token = nextToken(); |
| while (token == Quote) { |
| if (!parseMember(o)) |
| return Encode::undefined(); |
| token = nextToken(); |
| if (token != ValueSeparator) |
| break; |
| token = nextToken(); |
| if (token == EndObject) { |
| lastError = QJsonParseError::MissingObject; |
| return Encode::undefined(); |
| } |
| } |
| |
| DEBUG << "end token=" << token; |
| if (token != EndObject) { |
| lastError = QJsonParseError::UnterminatedObject; |
| return Encode::undefined(); |
| } |
| |
| END; |
| |
| --nestingLevel; |
| return o.asReturnedValue(); |
| } |
| |
| /* |
| member = string name-separator value |
| */ |
| bool JsonParser::parseMember(Object *o) |
| { |
| BEGIN << "parseMember"; |
| Scope scope(engine); |
| |
| QString key; |
| if (!parseString(&key)) |
| return false; |
| QChar token = nextToken(); |
| if (token != NameSeparator) { |
| lastError = QJsonParseError::MissingNameSeparator; |
| return false; |
| } |
| ScopedValue val(scope); |
| if (!parseValue(val)) |
| return false; |
| |
| ScopedString s(scope, engine->newString(key)); |
| PropertyKey skey = s->toPropertyKey(); |
| if (skey.isArrayIndex()) { |
| o->put(skey.asArrayIndex(), val); |
| } else { |
| // avoid trouble with properties named __proto__ |
| o->insertMember(s, val); |
| } |
| |
| END; |
| return true; |
| } |
| |
| /* |
| array = begin-array [ value *( value-separator value ) ] end-array |
| */ |
| ReturnedValue JsonParser::parseArray() |
| { |
| Scope scope(engine); |
| BEGIN << "parseArray"; |
| ScopedArrayObject array(scope, engine->newArrayObject()); |
| |
| if (++nestingLevel > nestingLimit) { |
| lastError = QJsonParseError::DeepNesting; |
| return Encode::undefined(); |
| } |
| |
| if (!eatSpace()) { |
| lastError = QJsonParseError::UnterminatedArray; |
| return Encode::undefined(); |
| } |
| if (*json == EndArray) { |
| nextToken(); |
| } else { |
| uint index = 0; |
| while (1) { |
| ScopedValue val(scope); |
| if (!parseValue(val)) |
| return Encode::undefined(); |
| array->arraySet(index, val); |
| QChar token = nextToken(); |
| if (token == EndArray) |
| break; |
| else if (token != ValueSeparator) { |
| if (!eatSpace()) |
| lastError = QJsonParseError::UnterminatedArray; |
| else |
| lastError = QJsonParseError::MissingValueSeparator; |
| return Encode::undefined(); |
| } |
| ++index; |
| } |
| } |
| |
| DEBUG << "size =" << array->getLength(); |
| END; |
| |
| --nestingLevel; |
| return array.asReturnedValue(); |
| } |
| |
| /* |
| value = false / null / true / object / array / number / string |
| |
| */ |
| |
| bool JsonParser::parseValue(Value *val) |
| { |
| BEGIN << "parse Value" << *json; |
| |
| switch ((json++)->unicode()) { |
| case 'n': |
| if (end - json < 3) { |
| lastError = QJsonParseError::IllegalValue; |
| return false; |
| } |
| if (*json++ == 'u' && |
| *json++ == 'l' && |
| *json++ == 'l') { |
| *val = Value::nullValue(); |
| DEBUG << "value: null"; |
| END; |
| return true; |
| } |
| lastError = QJsonParseError::IllegalValue; |
| return false; |
| case 't': |
| if (end - json < 3) { |
| lastError = QJsonParseError::IllegalValue; |
| return false; |
| } |
| if (*json++ == 'r' && |
| *json++ == 'u' && |
| *json++ == 'e') { |
| *val = Value::fromBoolean(true); |
| DEBUG << "value: true"; |
| END; |
| return true; |
| } |
| lastError = QJsonParseError::IllegalValue; |
| return false; |
| case 'f': |
| if (end - json < 4) { |
| lastError = QJsonParseError::IllegalValue; |
| return false; |
| } |
| if (*json++ == 'a' && |
| *json++ == 'l' && |
| *json++ == 's' && |
| *json++ == 'e') { |
| *val = Value::fromBoolean(false); |
| DEBUG << "value: false"; |
| END; |
| return true; |
| } |
| lastError = QJsonParseError::IllegalValue; |
| return false; |
| case Quote: { |
| QString value; |
| if (!parseString(&value)) |
| return false; |
| DEBUG << "value: string"; |
| END; |
| *val = Value::fromHeapObject(engine->newString(value)); |
| return true; |
| } |
| case BeginArray: { |
| *val = parseArray(); |
| if (val->isUndefined()) |
| return false; |
| DEBUG << "value: array"; |
| END; |
| return true; |
| } |
| case BeginObject: { |
| *val = parseObject(); |
| if (val->isUndefined()) |
| return false; |
| DEBUG << "value: object"; |
| END; |
| return true; |
| } |
| case EndArray: |
| lastError = QJsonParseError::MissingObject; |
| return false; |
| default: |
| --json; |
| if (!parseNumber(val)) |
| return false; |
| DEBUG << "value: number"; |
| END; |
| } |
| |
| return true; |
| } |
| |
| |
| |
| |
| |
| /* |
| number = [ minus ] int [ frac ] [ exp ] |
| decimal-point = %x2E ; . |
| digit1-9 = %x31-39 ; 1-9 |
| e = %x65 / %x45 ; e E |
| exp = e [ minus / plus ] 1*DIGIT |
| frac = decimal-point 1*DIGIT |
| int = zero / ( digit1-9 *DIGIT ) |
| minus = %x2D ; - |
| plus = %x2B ; + |
| zero = %x30 ; 0 |
| |
| */ |
| |
| bool JsonParser::parseNumber(Value *val) |
| { |
| BEGIN << "parseNumber" << *json; |
| |
| const QChar *start = json; |
| bool isInt = true; |
| |
| // minus |
| if (json < end && *json == '-') |
| ++json; |
| |
| // int = zero / ( digit1-9 *DIGIT ) |
| if (json < end && *json == '0') { |
| ++json; |
| } else { |
| while (json < end && *json >= '0' && *json <= '9') |
| ++json; |
| } |
| |
| // frac = decimal-point 1*DIGIT |
| if (json < end && *json == '.') { |
| isInt = false; |
| ++json; |
| while (json < end && *json >= '0' && *json <= '9') |
| ++json; |
| } |
| |
| // exp = e [ minus / plus ] 1*DIGIT |
| if (json < end && (*json == 'e' || *json == 'E')) { |
| isInt = false; |
| ++json; |
| if (json < end && (*json == '-' || *json == '+')) |
| ++json; |
| while (json < end && *json >= '0' && *json <= '9') |
| ++json; |
| } |
| |
| QString number(start, json - start); |
| DEBUG << "numberstring" << number; |
| |
| if (isInt) { |
| bool ok; |
| int n = number.toInt(&ok); |
| if (ok && n < (1<<25) && n > -(1<<25)) { |
| *val = Value::fromInt32(n); |
| END; |
| return true; |
| } |
| } |
| |
| bool ok; |
| double d; |
| d = number.toDouble(&ok); |
| |
| if (!ok) { |
| lastError = QJsonParseError::IllegalNumber; |
| return false; |
| } |
| |
| * val = Value::fromDouble(d); |
| |
| END; |
| return true; |
| } |
| |
| /* |
| |
| string = quotation-mark *char quotation-mark |
| |
| char = unescaped / |
| escape ( |
| %x22 / ; " quotation mark U+0022 |
| %x5C / ; \ reverse solidus U+005C |
| %x2F / ; / solidus U+002F |
| %x62 / ; b backspace U+0008 |
| %x66 / ; f form feed U+000C |
| %x6E / ; n line feed U+000A |
| %x72 / ; r carriage return U+000D |
| %x74 / ; t tab U+0009 |
| %x75 4HEXDIG ) ; uXXXX U+XXXX |
| |
| escape = %x5C ; \ |
| |
| quotation-mark = %x22 ; " |
| |
| unescaped = %x20-21 / %x23-5B / %x5D-10FFFF |
| */ |
| static inline bool addHexDigit(QChar digit, uint *result) |
| { |
| ushort d = digit.unicode(); |
| *result <<= 4; |
| if (d >= '0' && d <= '9') |
| *result |= (d - '0'); |
| else if (d >= 'a' && d <= 'f') |
| *result |= (d - 'a') + 10; |
| else if (d >= 'A' && d <= 'F') |
| *result |= (d - 'A') + 10; |
| else |
| return false; |
| return true; |
| } |
| |
| static inline bool scanEscapeSequence(const QChar *&json, const QChar *end, uint *ch) |
| { |
| ++json; |
| if (json >= end) |
| return false; |
| |
| DEBUG << "scan escape"; |
| uint escaped = (json++)->unicode(); |
| switch (escaped) { |
| case '"': |
| *ch = '"'; break; |
| case '\\': |
| *ch = '\\'; break; |
| case '/': |
| *ch = '/'; break; |
| case 'b': |
| *ch = 0x8; break; |
| case 'f': |
| *ch = 0xc; break; |
| case 'n': |
| *ch = 0xa; break; |
| case 'r': |
| *ch = 0xd; break; |
| case 't': |
| *ch = 0x9; break; |
| case 'u': { |
| *ch = 0; |
| if (json > end - 4) |
| return false; |
| for (int i = 0; i < 4; ++i) { |
| if (!addHexDigit(*json, ch)) |
| return false; |
| ++json; |
| } |
| return true; |
| } |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| |
| bool JsonParser::parseString(QString *string) |
| { |
| BEGIN << "parse string stringPos=" << json; |
| |
| while (json < end) { |
| if (*json == '"') |
| break; |
| else if (*json == '\\') { |
| uint ch = 0; |
| if (!scanEscapeSequence(json, end, &ch)) { |
| lastError = QJsonParseError::IllegalEscapeSequence; |
| return false; |
| } |
| if (QChar::requiresSurrogates(ch)) { |
| *string += QChar(QChar::highSurrogate(ch)) + QChar(QChar::lowSurrogate(ch)); |
| } else { |
| *string += QChar(ch); |
| } |
| } else { |
| if (json->unicode() <= 0x1f) { |
| lastError = QJsonParseError::IllegalEscapeSequence; |
| return false; |
| } |
| *string += *json; |
| ++json; |
| } |
| } |
| ++json; |
| |
| if (json > end) { |
| lastError = QJsonParseError::UnterminatedString; |
| return false; |
| } |
| |
| END; |
| return true; |
| } |
| |
| |
| struct Stringify |
| { |
| ExecutionEngine *v4; |
| FunctionObject *replacerFunction; |
| QV4::String *propertyList; |
| int propertyListSize; |
| QString gap; |
| QString indent; |
| QStack<Object *> stack; |
| |
| bool stackContains(Object *o) { |
| for (int i = 0; i < stack.size(); ++i) |
| if (stack.at(i)->d() == o->d()) |
| return true; |
| return false; |
| } |
| |
| Stringify(ExecutionEngine *e) : v4(e), replacerFunction(nullptr), propertyList(nullptr), propertyListSize(0) {} |
| |
| QString Str(const QString &key, const Value &v); |
| QString JA(Object *a); |
| QString JO(Object *o); |
| |
| QString makeMember(const QString &key, const Value &v); |
| }; |
| |
| static QString quote(const QString &str) |
| { |
| QString product; |
| const int length = str.length(); |
| product.reserve(length + 2); |
| product += QLatin1Char('"'); |
| for (int i = 0; i < length; ++i) { |
| QChar c = str.at(i); |
| switch (c.unicode()) { |
| case '"': |
| product += QLatin1String("\\\""); |
| break; |
| case '\\': |
| product += QLatin1String("\\\\"); |
| break; |
| case '\b': |
| product += QLatin1String("\\b"); |
| break; |
| case '\f': |
| product += QLatin1String("\\f"); |
| break; |
| case '\n': |
| product += QLatin1String("\\n"); |
| break; |
| case '\r': |
| product += QLatin1String("\\r"); |
| break; |
| case '\t': |
| product += QLatin1String("\\t"); |
| break; |
| default: |
| if (c.unicode() <= 0x1f) { |
| product += QLatin1String("\\u00"); |
| product += (c.unicode() > 0xf ? QLatin1Char('1') : QLatin1Char('0')) + |
| QLatin1Char("0123456789abcdef"[c.unicode() & 0xf]); |
| } else { |
| product += c; |
| } |
| } |
| } |
| product += QLatin1Char('"'); |
| return product; |
| } |
| |
| QString Stringify::Str(const QString &key, const Value &v) |
| { |
| Scope scope(v4); |
| |
| ScopedValue value(scope, v); |
| ScopedObject o(scope, value); |
| if (o) { |
| ScopedString s(scope, v4->newString(QStringLiteral("toJSON"))); |
| ScopedFunctionObject toJSON(scope, o->get(s)); |
| if (!!toJSON) { |
| JSCallData jsCallData(scope, 1); |
| *jsCallData->thisObject = value; |
| jsCallData->args[0] = v4->newString(key); |
| value = toJSON->call(jsCallData); |
| } |
| } |
| |
| if (replacerFunction) { |
| ScopedObject holder(scope, v4->newObject()); |
| holder->put(scope.engine->id_empty(), value); |
| JSCallData jsCallData(scope, 2); |
| jsCallData->args[0] = v4->newString(key); |
| jsCallData->args[1] = value; |
| *jsCallData->thisObject = holder; |
| value = replacerFunction->call(jsCallData); |
| } |
| |
| o = value->asReturnedValue(); |
| if (o) { |
| if (NumberObject *n = o->as<NumberObject>()) |
| value = Encode(n->value()); |
| else if (StringObject *so = o->as<StringObject>()) |
| value = so->d()->string; |
| else if (BooleanObject *b = o->as<BooleanObject>()) |
| value = Encode(b->value()); |
| } |
| |
| if (value->isNull()) |
| return QStringLiteral("null"); |
| if (value->isBoolean()) |
| return value->booleanValue() ? QStringLiteral("true") : QStringLiteral("false"); |
| if (value->isString()) |
| return quote(value->stringValue()->toQString()); |
| |
| if (value->isNumber()) { |
| double d = value->toNumber(); |
| return std::isfinite(d) ? value->toQString() : QStringLiteral("null"); |
| } |
| |
| if (const QV4::VariantObject *v = value->as<QV4::VariantObject>()) { |
| return quote(v->d()->data().toString()); |
| } |
| |
| o = value->asReturnedValue(); |
| if (o) { |
| if (!o->as<FunctionObject>()) { |
| if (o->isArrayLike()) { |
| return JA(o.getPointer()); |
| } else { |
| return JO(o); |
| } |
| } |
| } |
| |
| return QString(); |
| } |
| |
| QString Stringify::makeMember(const QString &key, const Value &v) |
| { |
| QString strP = Str(key, v); |
| if (!strP.isEmpty()) { |
| QString member = quote(key) + QLatin1Char(':'); |
| if (!gap.isEmpty()) |
| member += QLatin1Char(' '); |
| member += strP; |
| return member; |
| } |
| return QString(); |
| } |
| |
| QString Stringify::JO(Object *o) |
| { |
| if (stackContains(o)) { |
| v4->throwTypeError(); |
| return QString(); |
| } |
| |
| Scope scope(v4); |
| |
| QString result; |
| stack.push(o); |
| QString stepback = indent; |
| indent += gap; |
| |
| QStringList partial; |
| if (!propertyListSize) { |
| ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); |
| ScopedValue name(scope); |
| |
| ScopedValue val(scope); |
| while (1) { |
| name = it.nextPropertyNameAsString(val); |
| if (name->isNull()) |
| break; |
| QString key = name->toQString(); |
| QString member = makeMember(key, val); |
| if (!member.isEmpty()) |
| partial += member; |
| } |
| } else { |
| ScopedValue v(scope); |
| for (int i = 0; i < propertyListSize; ++i) { |
| bool exists; |
| String *s = propertyList + i; |
| if (!s) |
| continue; |
| v = o->get(s, &exists); |
| if (!exists) |
| continue; |
| QString member = makeMember(s->toQString(), v); |
| if (!member.isEmpty()) |
| partial += member; |
| } |
| } |
| |
| if (partial.isEmpty()) { |
| result = QStringLiteral("{}"); |
| } else if (gap.isEmpty()) { |
| result = QLatin1Char('{') + partial.join(QLatin1Char(',')) + QLatin1Char('}'); |
| } else { |
| QString separator = QLatin1String(",\n") + indent; |
| result = QLatin1String("{\n") + indent + partial.join(separator) + QLatin1Char('\n') |
| + stepback + QLatin1Char('}'); |
| } |
| |
| indent = stepback; |
| stack.pop(); |
| return result; |
| } |
| |
| QString Stringify::JA(Object *a) |
| { |
| if (stackContains(a)) { |
| v4->throwTypeError(); |
| return QString(); |
| } |
| |
| Scope scope(a->engine()); |
| |
| QString result; |
| stack.push(a); |
| QString stepback = indent; |
| indent += gap; |
| |
| QStringList partial; |
| uint len = a->getLength(); |
| ScopedValue v(scope); |
| for (uint i = 0; i < len; ++i) { |
| bool exists; |
| v = a->get(i, &exists); |
| if (!exists) { |
| partial += QStringLiteral("null"); |
| continue; |
| } |
| QString strP = Str(QString::number(i), v); |
| if (!strP.isEmpty()) |
| partial += strP; |
| else |
| partial += QStringLiteral("null"); |
| } |
| |
| if (partial.isEmpty()) { |
| result = QStringLiteral("[]"); |
| } else if (gap.isEmpty()) { |
| result = QLatin1Char('[') + partial.join(QLatin1Char(',')) + QLatin1Char(']'); |
| } else { |
| QString separator = QLatin1String(",\n") + indent; |
| result = QLatin1String("[\n") + indent + partial.join(separator) + QLatin1Char('\n') + stepback + QLatin1Char(']'); |
| } |
| |
| indent = stepback; |
| stack.pop(); |
| return result; |
| } |
| |
| |
| void Heap::JsonObject::init() |
| { |
| Object::init(); |
| Scope scope(internalClass->engine); |
| ScopedObject o(scope, this); |
| |
| o->defineDefaultProperty(QStringLiteral("parse"), QV4::JsonObject::method_parse, 2); |
| o->defineDefaultProperty(QStringLiteral("stringify"), QV4::JsonObject::method_stringify, 3); |
| ScopedString json(scope, scope.engine->newString(QStringLiteral("JSON"))); |
| o->defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), json); |
| } |
| |
| |
| ReturnedValue JsonObject::method_parse(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| ExecutionEngine *v4 = b->engine(); |
| QString jtext; |
| if (argc > 0) |
| jtext = argv[0].toQString(); |
| |
| DEBUG << "parsing source = " << jtext; |
| JsonParser parser(v4, jtext.constData(), jtext.length()); |
| QJsonParseError error; |
| ReturnedValue result = parser.parse(&error); |
| if (error.error != QJsonParseError::NoError) { |
| DEBUG << "parse error" << error.errorString(); |
| RETURN_RESULT(v4->throwSyntaxError(QStringLiteral("JSON.parse: Parse error"))); |
| } |
| |
| return result; |
| } |
| |
| ReturnedValue JsonObject::method_stringify(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| Scope scope(b); |
| Stringify stringify(scope.engine); |
| |
| ScopedObject o(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
| if (o) { |
| stringify.replacerFunction = o->as<FunctionObject>(); |
| if (o->isArrayObject()) { |
| uint arrayLen = o->getLength(); |
| stringify.propertyList = static_cast<QV4::String *>(scope.alloc(arrayLen)); |
| for (uint i = 0; i < arrayLen; ++i) { |
| Value *v = stringify.propertyList + i; |
| *v = o->get(i); |
| if (v->as<NumberObject>() || v->as<StringObject>() || v->isNumber()) |
| *v = v->toString(scope.engine); |
| if (!v->isString()) { |
| v->setM(nullptr); |
| } else { |
| for (uint j = 0; j <i; ++j) { |
| if (stringify.propertyList[j].m() == v->m()) { |
| v->setM(nullptr); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| ScopedValue s(scope, argc > 2 ? argv[2] : Value::undefinedValue()); |
| if (NumberObject *n = s->as<NumberObject>()) |
| s = Encode(n->value()); |
| else if (StringObject *so = s->as<StringObject>()) |
| s = so->d()->string; |
| |
| if (s->isNumber()) { |
| stringify.gap = QString(qMin(10, (int)s->toInteger()), ' '); |
| } else if (String *str = s->stringValue()) { |
| stringify.gap = str->toQString().left(10); |
| } |
| |
| |
| ScopedValue arg0(scope, argc ? argv[0] : Value::undefinedValue()); |
| QString result = stringify.Str(QString(), arg0); |
| if (result.isEmpty() || scope.engine->hasException) |
| RETURN_UNDEFINED(); |
| return Encode(scope.engine->newString(result)); |
| } |
| |
| |
| |
| ReturnedValue JsonObject::fromJsonValue(ExecutionEngine *engine, const QJsonValue &value) |
| { |
| if (value.isString()) |
| return engine->newString(value.toString())->asReturnedValue(); |
| else if (value.isDouble()) |
| return Encode(value.toDouble()); |
| else if (value.isBool()) |
| return Encode(value.toBool()); |
| else if (value.isArray()) |
| return fromJsonArray(engine, value.toArray()); |
| else if (value.isObject()) |
| return fromJsonObject(engine, value.toObject()); |
| else if (value.isNull()) |
| return Encode::null(); |
| else |
| return Encode::undefined(); |
| } |
| |
| QJsonValue JsonObject::toJsonValue(const Value &value, V4ObjectSet &visitedObjects) |
| { |
| if (value.isNumber()) |
| return QJsonValue(value.toNumber()); |
| else if (value.isBoolean()) |
| return QJsonValue((bool)value.booleanValue()); |
| else if (value.isNull()) |
| return QJsonValue(QJsonValue::Null); |
| else if (value.isUndefined()) |
| return QJsonValue(QJsonValue::Undefined); |
| else if (String *s = value.stringValue()) |
| return QJsonValue(s->toQString()); |
| |
| Q_ASSERT(value.isObject()); |
| Scope scope(value.as<Object>()->engine()); |
| ScopedArrayObject a(scope, value); |
| if (a) |
| return toJsonArray(a, visitedObjects); |
| ScopedObject o(scope, value); |
| if (o) |
| return toJsonObject(o, visitedObjects); |
| return QJsonValue(value.toQString()); |
| } |
| |
| QV4::ReturnedValue JsonObject::fromJsonObject(ExecutionEngine *engine, const QJsonObject &object) |
| { |
| Scope scope(engine); |
| ScopedObject o(scope, engine->newObject()); |
| ScopedString s(scope); |
| ScopedValue v(scope); |
| for (QJsonObject::const_iterator it = object.begin(), cend = object.end(); it != cend; ++it) { |
| v = fromJsonValue(engine, it.value()); |
| o->put((s = engine->newString(it.key())), v); |
| } |
| return o.asReturnedValue(); |
| } |
| |
| QJsonObject JsonObject::toJsonObject(const Object *o, V4ObjectSet &visitedObjects) |
| { |
| QJsonObject result; |
| if (!o || o->as<FunctionObject>()) |
| return result; |
| |
| Scope scope(o->engine()); |
| |
| if (visitedObjects.contains(ObjectItem(o))) { |
| // Avoid recursion. |
| // For compatibility with QVariant{List,Map} conversion, we return an |
| // empty object (and no error is thrown). |
| return result; |
| } |
| |
| visitedObjects.insert(ObjectItem(o)); |
| |
| ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); |
| ScopedValue name(scope); |
| QV4::ScopedValue val(scope); |
| while (1) { |
| name = it.nextPropertyNameAsString(val); |
| if (name->isNull()) |
| break; |
| |
| QString key = name->toQStringNoThrow(); |
| if (!val->as<FunctionObject>()) |
| result.insert(key, toJsonValue(val, visitedObjects)); |
| } |
| |
| visitedObjects.remove(ObjectItem(o)); |
| |
| return result; |
| } |
| |
| QV4::ReturnedValue JsonObject::fromJsonArray(ExecutionEngine *engine, const QJsonArray &array) |
| { |
| Scope scope(engine); |
| int size = array.size(); |
| ScopedArrayObject a(scope, engine->newArrayObject()); |
| a->arrayReserve(size); |
| ScopedValue v(scope); |
| for (int i = 0; i < size; i++) |
| a->arrayPut(i, (v = fromJsonValue(engine, array.at(i)))); |
| a->setArrayLengthUnchecked(size); |
| return a.asReturnedValue(); |
| } |
| |
| QJsonArray JsonObject::toJsonArray(const ArrayObject *a, V4ObjectSet &visitedObjects) |
| { |
| QJsonArray result; |
| if (!a) |
| return result; |
| |
| Scope scope(a->engine()); |
| |
| if (visitedObjects.contains(ObjectItem(a))) { |
| // Avoid recursion. |
| // For compatibility with QVariant{List,Map} conversion, we return an |
| // empty array (and no error is thrown). |
| return result; |
| } |
| |
| visitedObjects.insert(ObjectItem(a)); |
| |
| ScopedValue v(scope); |
| quint32 length = a->getLength(); |
| for (quint32 i = 0; i < length; ++i) { |
| v = a->get(i); |
| if (v->as<FunctionObject>()) |
| v = Encode::null(); |
| result.append(toJsonValue(v, visitedObjects)); |
| } |
| |
| visitedObjects.remove(ObjectItem(a)); |
| |
| return result; |
| } |