initial commit
This commit is contained in:
commit
9d20827c46
2469 changed files with 470994 additions and 0 deletions
606
third-party/JsonQt/lib/JsonToVariant.cpp
vendored
Normal file
606
third-party/JsonQt/lib/JsonToVariant.cpp
vendored
Normal file
|
|
@ -0,0 +1,606 @@
|
|||
/* LICENSE NOTICE
|
||||
Copyright (c) 2008, Frederick Emmott <mail@fredemmott.co.uk>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
#include "JsonToVariant.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#define FAIL(x) throw ParseException(*m_sym, x, remaining())
|
||||
|
||||
namespace JsonQt
|
||||
{
|
||||
JsonToVariant::JsonToVariant(){}
|
||||
|
||||
QVariant JsonToVariant::parse(const QString& json) throw(ParseException)
|
||||
{
|
||||
JsonToVariant parser;
|
||||
// Store the start and end of the string
|
||||
parser.m_next = json.constBegin();
|
||||
parser.m_sym = parser.m_next;
|
||||
parser.m_end = json.constEnd();
|
||||
// Parse any JSON value.
|
||||
return parser.parseValue();
|
||||
}
|
||||
|
||||
QList<QVariantMap> JsonToVariant::multiParse(const QString& raw) throw(ParseException)
|
||||
{
|
||||
QList<QVariantMap> objects;
|
||||
QString json(raw.trimmed());
|
||||
|
||||
JsonToVariant parser;
|
||||
// Store the start and end of the string
|
||||
parser.m_next = json.constBegin();
|
||||
parser.m_sym = parser.m_next;
|
||||
parser.m_end = json.constEnd();
|
||||
// A JSON Object is the top-level item in the parse tree
|
||||
do
|
||||
{
|
||||
objects.append(parser.parseObject());
|
||||
}
|
||||
while(parser.m_next != parser.m_end && parser.m_sym != parser.m_end);
|
||||
return objects;
|
||||
}
|
||||
|
||||
QVariantMap JsonToVariant::parseObject()
|
||||
{
|
||||
/*
|
||||
* object
|
||||
* {}
|
||||
* { members }
|
||||
*/
|
||||
|
||||
QVariantMap data;
|
||||
|
||||
consume('{');
|
||||
if(peekNext() != '}')
|
||||
data = parseMembers();
|
||||
consume('}');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QVariantMap JsonToVariant::parseMembers()
|
||||
{
|
||||
/*
|
||||
* members
|
||||
* pair
|
||||
* pair , members
|
||||
*/
|
||||
|
||||
QVariantMap data;
|
||||
QPair<QString, QVariant> pair;
|
||||
|
||||
// loop instead of recursing
|
||||
do
|
||||
{
|
||||
// Grab a pair
|
||||
pair = parsePair();
|
||||
|
||||
// Store it in our data
|
||||
data[pair.first] = pair.second;
|
||||
}
|
||||
while(tryConsume(',')); // Loop if we've got a list separator
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QPair<QString, QVariant> JsonToVariant::parsePair()
|
||||
{
|
||||
/*
|
||||
* pair
|
||||
* string : value
|
||||
*/
|
||||
|
||||
QString key = parseString();
|
||||
consume(':');
|
||||
QVariant value = parseValue();
|
||||
|
||||
return qMakePair(key, value);
|
||||
}
|
||||
|
||||
QVariantList JsonToVariant::parseArray()
|
||||
{
|
||||
/*
|
||||
* array
|
||||
* []
|
||||
* [ elements ]
|
||||
*/
|
||||
|
||||
QVariantList data;
|
||||
|
||||
consume('[');
|
||||
if(peekNext() != ']')
|
||||
data = parseElements();
|
||||
consume(']');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QVariantList JsonToVariant::parseElements()
|
||||
{
|
||||
/*
|
||||
* elements
|
||||
* value
|
||||
* value , elements
|
||||
*/
|
||||
QVariantList data;
|
||||
|
||||
// loop instead of recursing
|
||||
do
|
||||
{
|
||||
// Grab a value
|
||||
data += parseValue();
|
||||
}
|
||||
while(tryConsume(',')); // repeat if we've got a list separator
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QVariant JsonToVariant::parseValue()
|
||||
{
|
||||
/*
|
||||
* value
|
||||
* string
|
||||
* number
|
||||
* object
|
||||
* array
|
||||
* bool
|
||||
* null
|
||||
*/
|
||||
|
||||
tryConsume(':');
|
||||
|
||||
// Lookahead to work out the type of value
|
||||
switch(peekNext().toAscii())
|
||||
{
|
||||
case '"':
|
||||
return parseString();
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
return parseNumber();
|
||||
case '{':
|
||||
return parseObject();
|
||||
case '[':
|
||||
return parseArray();
|
||||
case 't': // true
|
||||
case 'f': // false
|
||||
return parseBool();
|
||||
case 'n': // null
|
||||
return parseNull();
|
||||
default:
|
||||
FAIL(QObject::tr("string, number, object, array, bool, or null"));
|
||||
}
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseString()
|
||||
{
|
||||
/*
|
||||
* string
|
||||
* ""
|
||||
* " chars "
|
||||
*/
|
||||
|
||||
QString data;
|
||||
|
||||
// Starting quotation marks
|
||||
consume('"');
|
||||
|
||||
// If it's a non-empty string, grab the contents
|
||||
if(*m_next != '"')
|
||||
data = parseChars();
|
||||
|
||||
// Ending quotation marks
|
||||
consume('"');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseChars()
|
||||
{
|
||||
/*
|
||||
* chars
|
||||
* char
|
||||
* char chars
|
||||
*/
|
||||
|
||||
QString data;
|
||||
|
||||
// chars contains at least one char
|
||||
data = parseChar();
|
||||
|
||||
while(peekNext() != '"')
|
||||
data.append( parseChar() );
|
||||
return data;
|
||||
}
|
||||
|
||||
QChar JsonToVariant::parseChar()
|
||||
{
|
||||
/*
|
||||
* char
|
||||
* any character except for ", \, or control characters
|
||||
* \"
|
||||
* \\
|
||||
* \/
|
||||
* \b
|
||||
* \f
|
||||
* \n
|
||||
* \r
|
||||
* \t
|
||||
* \u four-hex-digits
|
||||
*/
|
||||
|
||||
// Grab the next character, without skipping whitespace
|
||||
consume(false);
|
||||
|
||||
// We're not allowed unescaped quotation marks
|
||||
if(*m_sym == '"')
|
||||
FAIL(QObject::tr("Any unicode character except for \" or JSON escape sequences"));
|
||||
|
||||
// But some escape sequences are allowed
|
||||
if(*m_sym == '\\')
|
||||
{
|
||||
QString digits;
|
||||
switch(consume().toAscii())
|
||||
{
|
||||
case '"':
|
||||
return '"';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case '/':
|
||||
return '/';
|
||||
case 'b':
|
||||
return '\b';
|
||||
case 'f':
|
||||
return '\f';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 't':
|
||||
return '\t';
|
||||
case 'u':
|
||||
// Unicode 4-digit hex
|
||||
for(int i = 0; i < 4; ++i)
|
||||
{
|
||||
digits += parseHexDigit();
|
||||
}
|
||||
return QChar(digits.toInt(0, 16));
|
||||
default:
|
||||
FAIL("[\"\\/bfnrtu]");
|
||||
}
|
||||
}
|
||||
return *m_sym;
|
||||
}
|
||||
|
||||
QVariant JsonToVariant::parseNumber()
|
||||
{
|
||||
/*
|
||||
* number
|
||||
* int
|
||||
* int frac
|
||||
* int exp
|
||||
* int frac exp
|
||||
*/
|
||||
|
||||
// Every JSON number starts with an int
|
||||
QString data = parseInt();
|
||||
|
||||
// Lookahead for fractions and exponents
|
||||
|
||||
// Do we have a fraction?
|
||||
if(*m_next == '.') data += parseFrac();
|
||||
// Do we have an exponent?
|
||||
if(*m_next == 'e' || *m_next == 'E') data += parseExp();
|
||||
|
||||
// Try several return types...
|
||||
bool ok;
|
||||
QVariant ret;
|
||||
|
||||
ret = data.toInt(&ok); if(ok) return ret;
|
||||
ret = data.toLongLong(&ok); if(ok) return ret;
|
||||
ret = data.toDouble(&ok); if(ok) return ret;
|
||||
|
||||
// If this point is reached, don't know how to convert the string
|
||||
// to an integer.
|
||||
Q_ASSERT(false);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseInt()
|
||||
{
|
||||
/*
|
||||
* int
|
||||
* digit
|
||||
* digit1-9 digits
|
||||
* - digit
|
||||
* - digit1-9 digits
|
||||
*/
|
||||
|
||||
QString data;
|
||||
|
||||
// Match any negation mark
|
||||
if(tryConsume('-'))
|
||||
data = "-";
|
||||
|
||||
// Grab the first digit...
|
||||
QChar firstDigit = parseDigit();
|
||||
data += firstDigit;
|
||||
// ...if it's not zero...
|
||||
if(firstDigit != '0')
|
||||
{
|
||||
// ... try and add more digits.
|
||||
try { data += parseDigits(); }
|
||||
catch(ParseException)
|
||||
{
|
||||
// Catch, as more digits are entirely optional
|
||||
// Roll back.
|
||||
m_next = m_sym--;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseFrac()
|
||||
{
|
||||
/*
|
||||
* frac
|
||||
* . digits
|
||||
*/
|
||||
|
||||
consume('.');
|
||||
return QString(".%1").arg(parseDigits());
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseExp()
|
||||
{
|
||||
/*
|
||||
* exp
|
||||
* e digits
|
||||
*/
|
||||
|
||||
QString data;
|
||||
data = parseE();
|
||||
data += parseDigits();
|
||||
return data;
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseDigits()
|
||||
{
|
||||
/*
|
||||
* digits
|
||||
* digit
|
||||
* digit digits
|
||||
*/
|
||||
|
||||
QString data;
|
||||
|
||||
// Digits has at least one digit...
|
||||
data += parseDigit();
|
||||
|
||||
// ... try and get some more
|
||||
// Loop instead of recurse
|
||||
Q_FOREVER
|
||||
{
|
||||
try { data += parseDigit(); }
|
||||
catch(ParseException)
|
||||
{
|
||||
m_next = m_sym--; // roll back
|
||||
break;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
QString JsonToVariant::parseE()
|
||||
{
|
||||
/*
|
||||
* e
|
||||
* e
|
||||
* e+
|
||||
* e-
|
||||
* E
|
||||
* E+
|
||||
* E-
|
||||
*/
|
||||
|
||||
// Try and grab an 'e' or 'E'
|
||||
if(consume(false).toLower() == 'e')
|
||||
{
|
||||
// digits in follow[e]
|
||||
if(m_next->isDigit())
|
||||
return "e";
|
||||
|
||||
// Hopefully the next is a + or -
|
||||
// grab another chracter...
|
||||
consume(false);
|
||||
// If it's not + or -, fail
|
||||
if(*m_sym != '+' && *m_sym != '-')
|
||||
FAIL("+ | -");
|
||||
|
||||
// Otherwise, return e[+-]
|
||||
return QString("e%1").arg(*m_sym);
|
||||
}
|
||||
else
|
||||
FAIL("e | E");
|
||||
}
|
||||
|
||||
|
||||
QChar JsonToVariant::parseDigit()
|
||||
{
|
||||
/*
|
||||
* digit
|
||||
* [0-9]
|
||||
*/
|
||||
|
||||
if(!consume(false).isDigit())
|
||||
FAIL("[0-9]");
|
||||
return *m_sym;
|
||||
}
|
||||
|
||||
QChar JsonToVariant::parseHexDigit()
|
||||
{
|
||||
/*
|
||||
* hexdigit
|
||||
* [0-9a-fA-F]
|
||||
*/
|
||||
|
||||
QChar character = consume().toLower();
|
||||
if(character.isDigit() || (character >= 'a' && character <= 'f'))
|
||||
return character;
|
||||
FAIL("[0-9a-fA-F]");
|
||||
}
|
||||
|
||||
bool JsonToVariant::parseBool()
|
||||
{
|
||||
/*
|
||||
* bool
|
||||
* true
|
||||
* false
|
||||
*/
|
||||
|
||||
switch(peekNext().toAscii())
|
||||
{
|
||||
case 't':
|
||||
consume(QString("true"));
|
||||
return true;
|
||||
case 'f':
|
||||
consume(QString("false"));
|
||||
return false;
|
||||
default:
|
||||
consume(false);
|
||||
FAIL("true | false");
|
||||
}
|
||||
}
|
||||
|
||||
QVariant JsonToVariant::parseNull()
|
||||
{
|
||||
/*
|
||||
* null
|
||||
* null
|
||||
*/
|
||||
|
||||
consume(QString("null"));
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QString JsonToVariant::remaining()
|
||||
{
|
||||
QString data;
|
||||
|
||||
QString::ConstIterator it = m_sym;
|
||||
while(it != m_end) data += *it++;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QChar JsonToVariant::consume(bool skipWhitespace) throw(ParseException)
|
||||
{
|
||||
// Read a character...
|
||||
do
|
||||
{
|
||||
if(m_next == m_end)
|
||||
{
|
||||
throw ParseException("EOF", "symbol", remaining());
|
||||
}
|
||||
m_sym = m_next++;
|
||||
}
|
||||
//...and loop while we get whitespace, if it's being skipped
|
||||
while(skipWhitespace && m_sym->isSpace());
|
||||
|
||||
// Just for convenience...
|
||||
return *m_sym;
|
||||
}
|
||||
|
||||
void JsonToVariant::consume(QChar wanted) throw(ParseException)
|
||||
{
|
||||
// Grab a char(ignoring whitespace), and if it's not what's
|
||||
// expected, throw
|
||||
if(consume() != wanted)
|
||||
{
|
||||
FAIL(wanted);
|
||||
}
|
||||
}
|
||||
|
||||
void JsonToVariant::consume(char wanted) throw(ParseException)
|
||||
{
|
||||
// Convenience function for the above
|
||||
consume(QChar(wanted));
|
||||
}
|
||||
|
||||
void JsonToVariant::consume(QString wanted) throw(ParseException)
|
||||
{
|
||||
// Keep track of where we start...
|
||||
QString::ConstIterator it = m_sym;
|
||||
// Grab wanted.length() characters, and compare them to the
|
||||
// character in the appropriate position in the parameter
|
||||
for(int i = 0; i < wanted.length(); ++i)
|
||||
if(consume(false) != wanted[i])
|
||||
{
|
||||
// If it doesn't match, roll back, and throw a
|
||||
// parse exception
|
||||
m_sym = it;
|
||||
m_next = ++it;
|
||||
FAIL(wanted);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonToVariant::tryConsume(QChar wanted) throw()
|
||||
{
|
||||
// Grab the next character
|
||||
try
|
||||
{
|
||||
consume();
|
||||
}
|
||||
catch(ParseException)
|
||||
{
|
||||
// End-Of-String, rollback and return false
|
||||
m_next = m_sym--;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's what we want
|
||||
if(*m_sym != wanted)
|
||||
{
|
||||
// nope, something else, rollback and return false
|
||||
m_next = m_sym--;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QChar JsonToVariant::peekNext(bool skipWhitespace) throw(ParseException)
|
||||
{
|
||||
QString::ConstIterator it = m_sym;
|
||||
do
|
||||
{
|
||||
++it;
|
||||
if(it == m_end)
|
||||
{
|
||||
FAIL("symbol");
|
||||
}
|
||||
}
|
||||
while(skipWhitespace && it->isSpace());
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
Reference in a new issue