This repository has been archived on 2025-12-24. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
yachat/third-party/JsonQt/lib/JsonToVariant.cpp

607 lines
11 KiB
C++
Raw Normal View History

2025-12-25 01:37:49 +05:00
/* 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;
}
}