mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 00:55:09 -07:00
[Oracle] Replace vendored qt-json with native QJson for set import (#7214)
* [Oracle] Replace vendored QtJson with native QJson for set import
- Drop the vendored oracle/src/qt-json/json.{h,cpp} implementation
- Switch SetToDownload and importCardsFromSet from QList<QVariant> to
native QJsonArray/QJsonObject
- Release set JSON data after import in the save sets page
Took 20 minutes
* [Oracle] Restore property coercion and legality merge in native JSON import
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
d6fbfb32a1
commit
4d4ddd4278
10 changed files with 97 additions and 949 deletions
|
|
@ -22,7 +22,6 @@ libcockatrice_* \
|
||||||
exclude=("libcockatrice_rng/libcockatrice/rng/sfmt/" \
|
exclude=("libcockatrice_rng/libcockatrice/rng/sfmt/" \
|
||||||
"libcockatrice_utility/libcockatrice/utility/peglib.h" \
|
"libcockatrice_utility/libcockatrice/utility/peglib.h" \
|
||||||
"oracle/src/lzma/" \
|
"oracle/src/lzma/" \
|
||||||
"oracle/src/qt-json/" \
|
|
||||||
"oracle/src/zip/" \
|
"oracle/src/zip/" \
|
||||||
"servatrice/src/smtp/")
|
"servatrice/src/smtp/")
|
||||||
exts=("cpp" "h" "proto")
|
exts=("cpp" "h" "proto")
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ set(oracle_SOURCES
|
||||||
src/pages.cpp
|
src/pages.cpp
|
||||||
src/pagetemplates.cpp
|
src/pagetemplates.cpp
|
||||||
src/parsehelpers.cpp
|
src/parsehelpers.cpp
|
||||||
src/qt-json/json.cpp
|
|
||||||
../cockatrice/src/client/settings/cache_settings.cpp
|
../cockatrice/src/client/settings/cache_settings.cpp
|
||||||
../cockatrice/src/client/settings/card_counter_settings.cpp
|
../cockatrice/src/client/settings/card_counter_settings.cpp
|
||||||
../cockatrice/src/client/settings/shortcuts_settings.cpp
|
../cockatrice/src/client/settings/shortcuts_settings.cpp
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
#include "libcockatrice/interfaces/noop_card_preference_provider.h"
|
#include "libcockatrice/interfaces/noop_card_preference_provider.h"
|
||||||
#include "libcockatrice/interfaces/noop_card_set_priority_controller.h"
|
#include "libcockatrice/interfaces/noop_card_set_priority_controller.h"
|
||||||
#include "parsehelpers.h"
|
#include "parsehelpers.h"
|
||||||
#include "qt-json/json.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
@ -44,24 +45,24 @@ static CardSet::Priority getSetPriority(const QString &setType, const QString &s
|
||||||
|
|
||||||
bool OracleImporter::readSetsFromByteArray(const QByteArray &data)
|
bool OracleImporter::readSetsFromByteArray(const QByteArray &data)
|
||||||
{
|
{
|
||||||
bool ok;
|
QJsonParseError error;
|
||||||
auto setsMap = QtJson::Json::parse(QString(data), ok).toMap().value("data").toMap();
|
auto doc = QJsonDocument::fromJson(data, &error);
|
||||||
if (!ok) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qDebug() << "error: QtJson::Json::parse()";
|
qDebug() << "error: QJsonDocument::fromJson():" << error.errorString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto setsObj = doc.object().value("data").toObject();
|
||||||
|
|
||||||
QList<SetToDownload> newSetList;
|
QList<SetToDownload> newSetList;
|
||||||
|
|
||||||
QListIterator it(setsMap.values());
|
for (auto it = setsObj.constBegin(); it != setsObj.constEnd(); ++it) {
|
||||||
|
QJsonObject setObj = it.value().toObject();
|
||||||
while (it.hasNext()) {
|
QString shortName = setObj.value("code").toString().toUpper();
|
||||||
QVariantMap map = it.next().toMap();
|
QString longName = setObj.value("name").toString();
|
||||||
QString shortName = map.value("code").toString().toUpper();
|
QJsonArray setCards = setObj.value("cards").toArray();
|
||||||
QString longName = map.value("name").toString();
|
QString setType = setObj.value("type").toString();
|
||||||
QList<QVariant> setCards = map.value("cards").toList();
|
QDate releaseDate = QDate::fromString(setObj.value("releaseDate").toString(), Qt::ISODate);
|
||||||
QString setType = map.value("type").toString();
|
|
||||||
QDate releaseDate = map.value("releaseDate").toDate();
|
|
||||||
CardSet::Priority priority = getSetPriority(setType, shortName);
|
CardSet::Priority priority = getSetPriority(setType, shortName);
|
||||||
// capitalize set type
|
// capitalize set type
|
||||||
if (setType.length() > 0) {
|
if (setType.length() > 0) {
|
||||||
|
|
@ -142,9 +143,12 @@ CardInfoPtr OracleImporter::addCard(QString name,
|
||||||
// Workaround for card name weirdness
|
// Workaround for card name weirdness
|
||||||
name = name.replace("Æ", "AE");
|
name = name.replace("Æ", "AE");
|
||||||
name = name.replace("’", "'");
|
name = name.replace("’", "'");
|
||||||
if (cards.contains(name)) {
|
auto existingIt = cards.constFind(name);
|
||||||
CardInfoPtr card = cards.value(name);
|
if (existingIt != cards.constEnd()) {
|
||||||
|
CardInfoPtr card = existingIt.value();
|
||||||
card->addToSet(printingInfo.getSet(), printingInfo);
|
card->addToSet(printingInfo.getSet(), printingInfo);
|
||||||
|
// Only merge legalities when the card has none yet, so multi-format
|
||||||
|
// printings don't overwrite each other's legality lists.
|
||||||
if (card->getProperties().filter(formatRegex).empty()) {
|
if (card->getProperties().filter(formatRegex).empty()) {
|
||||||
card->combineLegalities(properties);
|
card->combineLegalities(properties);
|
||||||
}
|
}
|
||||||
|
|
@ -222,12 +226,15 @@ CardInfoPtr OracleImporter::addCard(QString name,
|
||||||
return newCard;
|
return newCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName)
|
static QString getJsonString(const QJsonObject &obj, const QString &key)
|
||||||
{
|
{
|
||||||
return card.contains(propertyName) ? card.value(propertyName).toString() : QString("");
|
// QVariant coerces numbers and booleans to text, while QJsonValue::toString()
|
||||||
|
// returns a null string for them — some MTGJSON fields (manaValue,
|
||||||
|
// convertedManaCost, isOnlineOnly, isRebalanced) carry those types.
|
||||||
|
return obj.value(key).toVariant().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList<QVariant> &cardsList)
|
int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList)
|
||||||
{
|
{
|
||||||
// mtgjson name => xml name
|
// mtgjson name => xml name
|
||||||
static const QMap<QString, QString> cardProperties{
|
static const QMap<QString, QString> cardProperties{
|
||||||
|
|
@ -248,7 +255,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
|
|
||||||
static const QString ptSeparator = "/";
|
static const QString ptSeparator = "/";
|
||||||
static constexpr bool isToken = false;
|
static constexpr bool isToken = false;
|
||||||
static const QList<QString> setsWithCardsWithSameNameButDifferentText = {"UST"};
|
static const QSet<QString> setsWithCardsWithSameNameButDifferentText = {"UST"};
|
||||||
|
|
||||||
int numCards = 0;
|
int numCards = 0;
|
||||||
|
|
||||||
|
|
@ -256,16 +263,16 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
QMap<QString, QPair<QList<SplitCardPart>, QString>> splitCards;
|
QMap<QString, QPair<QList<SplitCardPart>, QString>> splitCards;
|
||||||
|
|
||||||
// Keeps track of all names encountered so far
|
// Keeps track of all names encountered so far
|
||||||
QList<QString> allNameProps;
|
QSet<QString> allNameProps;
|
||||||
|
|
||||||
for (const QVariant &cardVar : cardsList) {
|
for (const QJsonValue &cardVal : cardsList) {
|
||||||
QVariantMap card = cardVar.toMap();
|
QJsonObject card = cardVal.toObject();
|
||||||
|
|
||||||
/* Currently used layouts are:
|
/* Currently used layouts are:
|
||||||
* augment, double_faced_token, flip, host, leveler, meld, normal, planar,
|
* augment, double_faced_token, flip, host, leveler, meld, normal, planar,
|
||||||
* saga, scheme, split, token, transform, vanguard
|
* saga, scheme, split, token, transform, vanguard
|
||||||
*/
|
*/
|
||||||
QString layout = getStringPropertyFromMap(card, "layout");
|
QString layout = getJsonString(card, "layout");
|
||||||
|
|
||||||
// don't import tokens from the json file
|
// don't import tokens from the json file
|
||||||
if (layout == "token") {
|
if (layout == "token") {
|
||||||
|
|
@ -273,9 +280,9 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal cards handling
|
// normal cards handling
|
||||||
QString name = getStringPropertyFromMap(card, "name");
|
QString name = getJsonString(card, "name");
|
||||||
QString text = getStringPropertyFromMap(card, "text");
|
QString text = getJsonString(card, "text");
|
||||||
QString faceName = getStringPropertyFromMap(card, "faceName");
|
QString faceName = getJsonString(card, "faceName");
|
||||||
if (faceName.isEmpty()) {
|
if (faceName.isEmpty()) {
|
||||||
faceName = name;
|
faceName = name;
|
||||||
}
|
}
|
||||||
|
|
@ -283,39 +290,34 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
// card properties
|
// card properties
|
||||||
QHash<QString, QString> properties;
|
QHash<QString, QString> properties;
|
||||||
for (auto i = cardProperties.cbegin(), end = cardProperties.cend(); i != end; ++i) {
|
for (auto i = cardProperties.cbegin(), end = cardProperties.cend(); i != end; ++i) {
|
||||||
QString mtgjsonProperty = i.key();
|
QString propertyValue = getJsonString(card, i.key());
|
||||||
QString xmlPropertyName = i.value();
|
|
||||||
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
|
|
||||||
if (!propertyValue.isEmpty()) {
|
if (!propertyValue.isEmpty()) {
|
||||||
properties.insert(xmlPropertyName, propertyValue);
|
properties.insert(i.value(), propertyValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// per-set properties
|
// per-set properties
|
||||||
QHash<QString, QString> printingProps;
|
QHash<QString, QString> printingProps;
|
||||||
for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) {
|
for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) {
|
||||||
QString mtgjsonProperty = i.key();
|
QString propertyValue = getJsonString(card, i.key());
|
||||||
QString xmlPropertyName = i.value();
|
|
||||||
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
|
|
||||||
if (!propertyValue.isEmpty()) {
|
if (!propertyValue.isEmpty()) {
|
||||||
printingProps.insert(xmlPropertyName, propertyValue);
|
printingProps.insert(i.value(), propertyValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle flavorNames specially due to double-faced cards
|
// handle flavorNames specially due to double-faced cards
|
||||||
QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName");
|
QString faceFlavorName = getJsonString(card, "faceFlavorName");
|
||||||
QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName");
|
QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getJsonString(card, "flavorName");
|
||||||
if (!flavorName.isEmpty()) {
|
if (!flavorName.isEmpty()) {
|
||||||
printingProps.insert("flavorName", flavorName);
|
printingProps.insert("flavorName", flavorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifiers
|
// Identifiers
|
||||||
|
QJsonObject identifiers = card.value("identifiers").toObject();
|
||||||
for (auto i = identifierProperties.cbegin(), end = identifierProperties.cend(); i != end; ++i) {
|
for (auto i = identifierProperties.cbegin(), end = identifierProperties.cend(); i != end; ++i) {
|
||||||
QString mtgjsonProperty = i.key();
|
QString propertyValue = getJsonString(identifiers, i.key());
|
||||||
QString xmlPropertyName = i.value();
|
|
||||||
QString propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty);
|
|
||||||
if (!propertyValue.isEmpty()) {
|
if (!propertyValue.isEmpty()) {
|
||||||
printingProps.insert(xmlPropertyName, propertyValue);
|
printingProps.insert(i.value(), propertyValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,21 +333,26 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
allNameProps.contains(faceName) && layout == "normal" && lastChar.isLetter()) {
|
allNameProps.contains(faceName) && layout == "normal" && lastChar.isLetter()) {
|
||||||
numComponent = " (" + QString(lastChar).toLower() + ")";
|
numComponent = " (" + QString(lastChar).toLower() + ")";
|
||||||
}
|
}
|
||||||
allNameProps.append(faceName);
|
allNameProps.insert(faceName);
|
||||||
|
|
||||||
// special handling properties
|
// special handling properties
|
||||||
QString colors = card.value("colors").toStringList().join("");
|
QString colors;
|
||||||
|
for (const QJsonValue &color : card.value("colors").toArray()) {
|
||||||
|
colors += color.toString();
|
||||||
|
}
|
||||||
if (!colors.isEmpty()) {
|
if (!colors.isEmpty()) {
|
||||||
properties.insert("colors", colors);
|
properties.insert("colors", colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// special handling properties
|
QString colorIdentity;
|
||||||
QString colorIdentity = card.value("colorIdentity").toStringList().join("");
|
for (const QJsonValue &color : card.value("colorIdentity").toArray()) {
|
||||||
|
colorIdentity += color.toString();
|
||||||
|
}
|
||||||
if (!colorIdentity.isEmpty()) {
|
if (!colorIdentity.isEmpty()) {
|
||||||
properties.insert("coloridentity", colorIdentity);
|
properties.insert("coloridentity", colorIdentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &mainCardType = getMainCardType(card.value("types").toStringList());
|
const auto &mainCardType = getMainCardType(card.value("types").toVariant().toStringList());
|
||||||
if (mainCardType.isEmpty()) {
|
if (mainCardType.isEmpty()) {
|
||||||
qDebug() << "warning: no mainCardType for card:" << name;
|
qDebug() << "warning: no mainCardType for card:" << name;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -354,22 +361,22 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
|
|
||||||
// Depending on whether power and/or toughness are present, the format
|
// Depending on whether power and/or toughness are present, the format
|
||||||
// is either P/T (most common), P (no toughness), or /T (no power).
|
// is either P/T (most common), P (no toughness), or /T (no power).
|
||||||
QString power = getStringPropertyFromMap(card, "power");
|
QString power = getJsonString(card, "power");
|
||||||
QString toughness = getStringPropertyFromMap(card, "toughness");
|
QString toughness = getJsonString(card, "toughness");
|
||||||
if (toughness.isEmpty() && !power.isEmpty()) {
|
if (toughness.isEmpty() && !power.isEmpty()) {
|
||||||
properties.insert("pt", power);
|
properties.insert("pt", power);
|
||||||
} else if (!toughness.isEmpty()) {
|
} else if (!toughness.isEmpty()) {
|
||||||
properties.insert("pt", power + ptSeparator + toughness);
|
properties.insert("pt", power + ptSeparator + toughness);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto legalities = card.value("legalities").toMap();
|
auto legalities = card.value("legalities").toObject();
|
||||||
for (auto i = legalities.cbegin(), end = legalities.cend(); i != end; ++i) {
|
for (auto i = legalities.constBegin(), end = legalities.constEnd(); i != end; ++i) {
|
||||||
properties.insert(QString("format-%1").arg(i.key()), i.value().toString().toLower());
|
properties.insert(QString("format-%1").arg(i.key()), i.value().toString().toLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
// split cards are considered a single card, enqueue for later merging
|
// split cards are considered a single card, enqueue for later merging
|
||||||
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
||||||
auto _faceName = getStringPropertyFromMap(card, "faceName");
|
auto _faceName = getJsonString(card, "faceName");
|
||||||
SplitCardPart split(_faceName, text, properties, printingInfo);
|
SplitCardPart split(_faceName, text, properties, printingInfo);
|
||||||
auto found_iter = splitCards.find(name + numProperty);
|
auto found_iter = splitCards.find(name + numProperty);
|
||||||
if (found_iter == splitCards.end()) {
|
if (found_iter == splitCards.end()) {
|
||||||
|
|
@ -382,11 +389,11 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
QList<CardRelation *> relatedCards;
|
QList<CardRelation *> relatedCards;
|
||||||
|
|
||||||
// add other face for split cards as card relation
|
// add other face for split cards as card relation
|
||||||
if (!getStringPropertyFromMap(card, "side").isEmpty()) {
|
if (!getJsonString(card, "side").isEmpty()) {
|
||||||
auto faceManaValue = getStringPropertyFromMap(card, "faceManaValue");
|
auto faceManaValue = getJsonString(card, "faceManaValue");
|
||||||
if (faceManaValue.isEmpty()) {
|
if (faceManaValue.isEmpty()) {
|
||||||
// check the old name for the property, for backwards compatibility purposes
|
// check the old name for the property, for backwards compatibility purposes
|
||||||
faceManaValue = getStringPropertyFromMap(card, "faceConvertedManaCost");
|
faceManaValue = getJsonString(card, "faceConvertedManaCost");
|
||||||
}
|
}
|
||||||
properties["cmc"] = faceManaValue;
|
properties["cmc"] = faceManaValue;
|
||||||
|
|
||||||
|
|
@ -406,15 +413,15 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
name = faceName;
|
name = faceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mtgjon related cards
|
// mtgjson related cards
|
||||||
if (card.contains("relatedCards")) {
|
QJsonObject givenRelated = card.value("relatedCards").toObject();
|
||||||
QVariantMap givenRelated = card.value("relatedCards").toMap();
|
if (!givenRelated.isEmpty()) {
|
||||||
// conjured cards from a spellbook
|
// conjured cards from a spellbook
|
||||||
if (givenRelated.contains("spellbook")) {
|
QJsonArray spellbook = givenRelated.value("spellbook").toArray();
|
||||||
auto spbk = givenRelated.value("spellbook").toStringList();
|
if (!spellbook.isEmpty()) {
|
||||||
for (const QString &spbkName : spbk) {
|
for (const QJsonValue &spbkVal : spellbook) {
|
||||||
relatedCards.append(
|
relatedCards.append(new CardRelation(spbkVal.toString(), CardRelationType::DoesNotAttach, false,
|
||||||
new CardRelation(spbkName, CardRelationType::DoesNotAttach, false, false, 1, true));
|
false, 1, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -427,7 +434,6 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
// split cards handling
|
// split cards handling
|
||||||
static const QString splitCardPropSeparator = QString(" // ");
|
static const QString splitCardPropSeparator = QString(" // ");
|
||||||
static const QString splitCardTextSeparator = QString("\n\n---\n\n");
|
static const QString splitCardTextSeparator = QString("\n\n---\n\n");
|
||||||
static const QList<CardRelation *> noRelatedCards = {};
|
|
||||||
|
|
||||||
QList<QPair<QList<SplitCardPart>, QString>> partsAndNames = splitCards.values();
|
QList<QPair<QList<SplitCardPart>, QString>> partsAndNames = splitCards.values();
|
||||||
for (auto [splitCardParts, name] : partsAndNames) {
|
for (auto [splitCardParts, name] : partsAndNames) {
|
||||||
|
|
@ -465,20 +471,20 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CardInfoPtr newCard = addCard(name, text, isToken, properties, noRelatedCards, printingInfo);
|
CardInfoPtr newCard = addCard(name, text, isToken, properties, {}, printingInfo);
|
||||||
numCards++;
|
numCards++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return numCards;
|
return numCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatRulesNameMap OracleImporter::createDefaultMagicFormats()
|
static FormatRulesNameMap buildDefaultMagicFormats()
|
||||||
{
|
{
|
||||||
// Predefined common exceptions
|
// Predefined common exceptions
|
||||||
CardCondition superTypeIsBasic;
|
CardCondition superTypeIsBasic;
|
||||||
superTypeIsBasic.field = "type";
|
superTypeIsBasic.field = "type";
|
||||||
superTypeIsBasic.matchType = "regex";
|
superTypeIsBasic.matchType = "regex";
|
||||||
superTypeIsBasic.value = "\bBasic\b[^—]+\bLand\b";
|
superTypeIsBasic.value = R"(\bBasic\b[^—]+\bLand\b)";
|
||||||
|
|
||||||
ExceptionRule basicLands;
|
ExceptionRule basicLands;
|
||||||
basicLands.conditions.append(superTypeIsBasic);
|
basicLands.conditions.append(superTypeIsBasic);
|
||||||
|
|
@ -491,7 +497,6 @@ FormatRulesNameMap OracleImporter::createDefaultMagicFormats()
|
||||||
ExceptionRule mayContainAnyNumber;
|
ExceptionRule mayContainAnyNumber;
|
||||||
mayContainAnyNumber.conditions.append(anyNumberAllowed);
|
mayContainAnyNumber.conditions.append(anyNumberAllowed);
|
||||||
|
|
||||||
// Map to store default rules
|
|
||||||
FormatRulesNameMap defaultFormatRulesNameMap;
|
FormatRulesNameMap defaultFormatRulesNameMap;
|
||||||
|
|
||||||
// ----------------- Helper lambda to create format -----------------
|
// ----------------- Helper lambda to create format -----------------
|
||||||
|
|
@ -537,6 +542,12 @@ FormatRulesNameMap OracleImporter::createDefaultMagicFormats()
|
||||||
return defaultFormatRulesNameMap;
|
return defaultFormatRulesNameMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FormatRulesNameMap &OracleImporter::createDefaultMagicFormats()
|
||||||
|
{
|
||||||
|
static const FormatRulesNameMap cached = buildDefaultMagicFormats();
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
int OracleImporter::startImport()
|
int OracleImporter::startImport()
|
||||||
{
|
{
|
||||||
static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController();
|
static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController();
|
||||||
|
|
@ -576,6 +587,11 @@ bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUr
|
||||||
return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion);
|
return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OracleImporter::releaseSetData()
|
||||||
|
{
|
||||||
|
allSets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void OracleImporter::clear()
|
void OracleImporter::clear()
|
||||||
{
|
{
|
||||||
sets.clear();
|
sets.clear();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef ORACLEIMPORTER_H
|
#ifndef ORACLEIMPORTER_H
|
||||||
#define ORACLEIMPORTER_H
|
#define ORACLEIMPORTER_H
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
@ -44,7 +46,7 @@ class SetToDownload
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
QString shortName, longName;
|
QString shortName, longName;
|
||||||
QList<QVariant> cards;
|
QJsonArray cards;
|
||||||
QDate releaseDate;
|
QDate releaseDate;
|
||||||
QString setType;
|
QString setType;
|
||||||
CardSet::Priority priority;
|
CardSet::Priority priority;
|
||||||
|
|
@ -58,7 +60,7 @@ public:
|
||||||
{
|
{
|
||||||
return longName;
|
return longName;
|
||||||
}
|
}
|
||||||
const QList<QVariant> &getCards() const
|
const QJsonArray &getCards() const
|
||||||
{
|
{
|
||||||
return cards;
|
return cards;
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +78,7 @@ public:
|
||||||
}
|
}
|
||||||
SetToDownload(QString _shortName,
|
SetToDownload(QString _shortName,
|
||||||
QString _longName,
|
QString _longName,
|
||||||
QList<QVariant> _cards,
|
QJsonArray _cards,
|
||||||
CardSet::Priority _priority,
|
CardSet::Priority _priority,
|
||||||
QString _setType = QString(),
|
QString _setType = QString(),
|
||||||
const QDate &_releaseDate = QDate())
|
const QDate &_releaseDate = QDate())
|
||||||
|
|
@ -154,8 +156,11 @@ public:
|
||||||
bool readSetsFromByteArray(const QByteArray &data);
|
bool readSetsFromByteArray(const QByteArray &data);
|
||||||
int startImport();
|
int startImport();
|
||||||
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
||||||
int importCardsFromSet(const CardSetPtr ¤tSet, const QList<QVariant> &cardsList);
|
int importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList);
|
||||||
FormatRulesNameMap createDefaultMagicFormats();
|
/**
|
||||||
|
* @brief Returns the default format rules. The result is memoized on first use and must be treated as immutable.
|
||||||
|
*/
|
||||||
|
const FormatRulesNameMap &createDefaultMagicFormats();
|
||||||
const CardNameMap &getCardList() const
|
const CardNameMap &getCardList() const
|
||||||
{
|
{
|
||||||
return cards;
|
return cards;
|
||||||
|
|
@ -164,6 +169,7 @@ public:
|
||||||
{
|
{
|
||||||
return allSets;
|
return allSets;
|
||||||
}
|
}
|
||||||
|
void releaseSetData();
|
||||||
void clear();
|
void clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -560,6 +560,9 @@ void SaveSetsPage::initializePage()
|
||||||
|
|
||||||
int setsImported = wizard()->importer->startImport();
|
int setsImported = wizard()->importer->startImport();
|
||||||
|
|
||||||
|
// JSON data no longer needed after CardInfo objects are built
|
||||||
|
wizard()->importer->releaseSetData();
|
||||||
|
|
||||||
if (setsImported == 0) {
|
if (setsImported == 0) {
|
||||||
QMessageBox::critical(this, tr("Error"), tr("No set has been imported."));
|
QMessageBox::critical(this, tr("Error"), tr("No set has been imported."));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Eeli Reilin <eeli@emicode.fi>
|
|
||||||
Luis Gustavo S. Barreto <gustavosbarreto@gmail.com>
|
|
||||||
Stephen Kockentiedt <Stephen@Kockentiedt.name>
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright 2011 Eeli Reilin. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
|
|
||||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
||||||
EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
||||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
||||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
The views and conclusions contained in the software and documentation
|
|
||||||
are those of the authors and should not be interpreted as representing
|
|
||||||
official policies, either expressed or implied, of Eeli Reilin.
|
|
||||||
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
########################################################################
|
|
||||||
1. INTRODUCTION
|
|
||||||
|
|
||||||
The Json class is a simple class for parsing JSON data into a QVariant
|
|
||||||
hierarchies. Now, we can also reverse the process and serialize
|
|
||||||
QVariant hierarchies into valid JSON data.
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
2. HOW TO USE
|
|
||||||
|
|
||||||
The parser is really easy to use. Let's say we have the following
|
|
||||||
QString of JSON data:
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
{
|
|
||||||
"encoding" : "UTF-8",
|
|
||||||
"plug-ins" : [
|
|
||||||
"python",
|
|
||||||
"c++",
|
|
||||||
"ruby"
|
|
||||||
],
|
|
||||||
"indent" : {
|
|
||||||
"length" : 3,
|
|
||||||
"use_space" : true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
We would first call the parse-method:
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
//Say that we're using the QtJson namespace
|
|
||||||
using namespace QtJson;
|
|
||||||
bool ok;
|
|
||||||
//json is a QString containing the JSON data
|
|
||||||
QVariantMap result = Json::parse(json, ok).toMap();
|
|
||||||
|
|
||||||
if(!ok) {
|
|
||||||
qFatal("An error occurred during parsing");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Assuming the parsing process completed without errors, we would then
|
|
||||||
go through the hierarchy:
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
qDebug() << "encoding:" << result["encoding"].toString();
|
|
||||||
qDebug() << "plugins:";
|
|
||||||
|
|
||||||
foreach(QVariant plugin, result["plug-ins"].toList()) {
|
|
||||||
qDebug() << "\t-" << plugin.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap nestedMap = result["indent"].toMap();
|
|
||||||
qDebug() << "length:" << nestedMap["length"].toInt();
|
|
||||||
qDebug() << "use_space:" << nestedMap["use_space"].toBool();
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
The previous code would print out the following:
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
encoding: "UTF-8"
|
|
||||||
plugins:
|
|
||||||
- "python"
|
|
||||||
- "c++"
|
|
||||||
- "ruby"
|
|
||||||
length: 3
|
|
||||||
use_space: true
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
To write JSON data from Qt object is as simple as parsing:
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
QVariantMap map;
|
|
||||||
map["name"] = "Name";
|
|
||||||
map["age"] = 22;
|
|
||||||
|
|
||||||
QByteArray data = Json::serialize(map);
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
The byte array 'data' contains valid JSON data:
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
{
|
|
||||||
name: "Luis Gustavo",
|
|
||||||
age: 22,
|
|
||||||
}
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
4. CONTRIBUTING
|
|
||||||
|
|
||||||
The code is available to download at GitHub. Contribute if you dare!
|
|
||||||
|
|
@ -1,545 +0,0 @@
|
||||||
/* Copyright 2011 Eeli Reilin. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
|
|
||||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
||||||
* EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
||||||
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
||||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* The views and conclusions contained in the software and documentation
|
|
||||||
* are those of the authors and should not be interpreted as representing
|
|
||||||
* official policies, either expressed or implied, of Eeli Reilin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \file json.cpp
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "json.h"
|
|
||||||
|
|
||||||
#include <QMetaType>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace QtJson
|
|
||||||
{
|
|
||||||
|
|
||||||
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<QByteArray> &list, const QByteArray &sep)
|
|
||||||
{
|
|
||||||
QByteArray res;
|
|
||||||
for (const QByteArray &i : list) {
|
|
||||||
if (!res.isEmpty()) {
|
|
||||||
res += sep;
|
|
||||||
}
|
|
||||||
res += i;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parse
|
|
||||||
*/
|
|
||||||
QVariant Json::parse(const QString &json)
|
|
||||||
{
|
|
||||||
bool success = true;
|
|
||||||
return Json::parse(json, success);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parse
|
|
||||||
*/
|
|
||||||
QVariant Json::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()) {
|
|
||||||
// We'll start from index 0
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
// Parse the first value
|
|
||||||
QVariant value = Json::parseValue(json, index, success);
|
|
||||||
|
|
||||||
// Return the parsed value
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
// Return the empty QVariant
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Json::serialize(const QVariant &data)
|
|
||||||
{
|
|
||||||
bool success = true;
|
|
||||||
return Json::serialize(data, success);
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Json::serialize(const QVariant &data, bool &success)
|
|
||||||
{
|
|
||||||
QByteArray str;
|
|
||||||
success = true;
|
|
||||||
|
|
||||||
if (!data.isValid()) // invalid or null?
|
|
||||||
{
|
|
||||||
str = "null";
|
|
||||||
}
|
|
||||||
else if ((data.typeId() == QMetaType::Type::QVariantList) ||
|
|
||||||
(data.typeId() == QMetaType::Type::QStringList)) // variant is a list?
|
|
||||||
{
|
|
||||||
QList<QByteArray> values;
|
|
||||||
const QVariantList list = data.toList();
|
|
||||||
for (const QVariant &v : list) {
|
|
||||||
QByteArray serializedValue = serialize(v);
|
|
||||||
if (serializedValue.isNull()) {
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
values << serializedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
str = "[ " + join(values, ", ") + " ]";
|
|
||||||
}
|
|
||||||
else if ((data.typeId() == QMetaType::Type::QVariantHash)) // variant is a hash?
|
|
||||||
{
|
|
||||||
const QVariantHash vhash = data.toHash();
|
|
||||||
QHashIterator<QString, QVariant> it(vhash);
|
|
||||||
str = "{ ";
|
|
||||||
QList<QByteArray> pairs;
|
|
||||||
|
|
||||||
while (it.hasNext()) {
|
|
||||||
it.next();
|
|
||||||
QByteArray serializedValue = serialize(it.value());
|
|
||||||
|
|
||||||
if (serializedValue.isNull()) {
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
str += join(pairs, ", ");
|
|
||||||
str += " }";
|
|
||||||
}
|
|
||||||
else if ((data.typeId() == QMetaType::Type::QVariantMap)) // variant is a map?
|
|
||||||
{
|
|
||||||
const QVariantMap vmap = data.toMap();
|
|
||||||
QMapIterator<QString, QVariant> it(vmap);
|
|
||||||
str = "{ ";
|
|
||||||
QList<QByteArray> pairs;
|
|
||||||
while (it.hasNext()) {
|
|
||||||
it.next();
|
|
||||||
QByteArray serializedValue = serialize(it.value());
|
|
||||||
if (serializedValue.isNull()) {
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
|
|
||||||
}
|
|
||||||
str += join(pairs, ", ");
|
|
||||||
str += " }";
|
|
||||||
}
|
|
||||||
else if ((data.typeId() == QMetaType::Type::QString) ||
|
|
||||||
(data.typeId() == QMetaType::Type::QByteArray)) // a string or a byte array?
|
|
||||||
{
|
|
||||||
str = sanitizeString(data.toString()).toUtf8();
|
|
||||||
}
|
|
||||||
else if (data.typeId() == QMetaType::Type::Double) // double?
|
|
||||||
{
|
|
||||||
str = QByteArray::number(data.toDouble(), 'g', 20);
|
|
||||||
if (!str.contains(".") && !str.contains("e")) {
|
|
||||||
str += ".0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (data.typeId() == QMetaType::Type::Bool) // boolean value?
|
|
||||||
{
|
|
||||||
str = data.toBool() ? "true" : "false";
|
|
||||||
}
|
|
||||||
else if (data.typeId() == QMetaType::Type::ULongLong) // large unsigned number?
|
|
||||||
{
|
|
||||||
str = QByteArray::number(data.value<qulonglong>());
|
|
||||||
} else if (data.canConvert<qlonglong>()) // any signed number?
|
|
||||||
{
|
|
||||||
str = QByteArray::number(data.value<qlonglong>());
|
|
||||||
} else if (data.canConvert<long>()) {
|
|
||||||
str = QString::number(data.value<long>()).toUtf8();
|
|
||||||
} else if (data.canConvert<QString>()) // can value be converted to string?
|
|
||||||
{
|
|
||||||
// this will catch QDate, QDateTime, QUrl, ...
|
|
||||||
str = sanitizeString(data.toString()).toUtf8();
|
|
||||||
} else {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
return str;
|
|
||||||
} else {
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parseValue
|
|
||||||
*/
|
|
||||||
QVariant Json::parseValue(const QString &json, int &index, bool &success)
|
|
||||||
{
|
|
||||||
// Determine what kind of data we should parse by
|
|
||||||
// checking out the upcoming token
|
|
||||||
switch (Json::lookAhead(json, index)) {
|
|
||||||
case JsonTokenString:
|
|
||||||
return Json::parseString(json, index, success);
|
|
||||||
case JsonTokenNumber:
|
|
||||||
return Json::parseNumber(json, index);
|
|
||||||
case JsonTokenCurlyOpen:
|
|
||||||
return Json::parseObject(json, index, success);
|
|
||||||
case JsonTokenSquaredOpen:
|
|
||||||
return Json::parseArray(json, index, success);
|
|
||||||
case JsonTokenTrue:
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
return QVariant(true);
|
|
||||||
case JsonTokenFalse:
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
return QVariant(false);
|
|
||||||
case JsonTokenNull:
|
|
||||||
Json::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
|
|
||||||
*/
|
|
||||||
QVariant Json::parseObject(const QString &json, int &index, bool &success)
|
|
||||||
{
|
|
||||||
QVariantMap map;
|
|
||||||
int token;
|
|
||||||
|
|
||||||
// Get rid of the whitespace and increment index
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
|
|
||||||
// Loop through all of the key/value pairs of the object
|
|
||||||
bool done = false;
|
|
||||||
while (!done) {
|
|
||||||
// Get the upcoming token
|
|
||||||
token = Json::lookAhead(json, index);
|
|
||||||
|
|
||||||
if (token == JsonTokenNone) {
|
|
||||||
success = false;
|
|
||||||
return QVariantMap();
|
|
||||||
} else if (token == JsonTokenComma) {
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
} else if (token == JsonTokenCurlyClose) {
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
return map;
|
|
||||||
} else {
|
|
||||||
// Parse the key/value pair's name
|
|
||||||
QString name = Json::parseString(json, index, success).toString();
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
return QVariantMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the next token
|
|
||||||
token = Json::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 = Json::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
|
|
||||||
*/
|
|
||||||
QVariant Json::parseArray(const QString &json, int &index, bool &success)
|
|
||||||
{
|
|
||||||
QVariantList list;
|
|
||||||
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
while (!done) {
|
|
||||||
int token = Json::lookAhead(json, index);
|
|
||||||
|
|
||||||
if (token == JsonTokenNone) {
|
|
||||||
success = false;
|
|
||||||
return QVariantList();
|
|
||||||
} else if (token == JsonTokenComma) {
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
} else if (token == JsonTokenSquaredClose) {
|
|
||||||
Json::nextToken(json, index);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
QVariant value = Json::parseValue(json, index, success);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
return QVariantList();
|
|
||||||
}
|
|
||||||
|
|
||||||
list.push_back(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QVariant(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parseString
|
|
||||||
*/
|
|
||||||
QVariant Json::parseString(const QString &json, int &index, bool &success)
|
|
||||||
{
|
|
||||||
QString s;
|
|
||||||
QChar c;
|
|
||||||
|
|
||||||
Json::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
|
|
||||||
*/
|
|
||||||
QVariant Json::parseNumber(const QString &json, int &index)
|
|
||||||
{
|
|
||||||
Json::eatWhitespace(json, index);
|
|
||||||
|
|
||||||
int lastIndex = Json::lastIndexOfNumber(json, index);
|
|
||||||
int charLength = (lastIndex - index) + 1;
|
|
||||||
QString numberStr;
|
|
||||||
|
|
||||||
numberStr = json.mid(index, charLength);
|
|
||||||
|
|
||||||
index = lastIndex + 1;
|
|
||||||
|
|
||||||
if (numberStr.contains('.')) {
|
|
||||||
return QVariant(numberStr.toDouble(NULL));
|
|
||||||
} else if (numberStr.startsWith('-')) {
|
|
||||||
return QVariant(numberStr.toLongLong(NULL));
|
|
||||||
} else {
|
|
||||||
return QVariant(numberStr.toULongLong(NULL));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* lastIndexOfNumber
|
|
||||||
*/
|
|
||||||
int Json::lastIndexOfNumber(const QString &json, int index)
|
|
||||||
{
|
|
||||||
static const QString numericCharacters("0123456789+-.eE");
|
|
||||||
int lastIndex;
|
|
||||||
|
|
||||||
for (lastIndex = index; lastIndex < json.size(); lastIndex++) {
|
|
||||||
if (numericCharacters.indexOf(json[lastIndex]) == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastIndex - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* eatWhitespace
|
|
||||||
*/
|
|
||||||
void Json::eatWhitespace(const QString &json, int &index)
|
|
||||||
{
|
|
||||||
static const QString whitespaceChars(" \t\n\r");
|
|
||||||
for (; index < json.size(); index++) {
|
|
||||||
if (whitespaceChars.indexOf(json[index]) == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* lookAhead
|
|
||||||
*/
|
|
||||||
int Json::lookAhead(const QString &json, int index)
|
|
||||||
{
|
|
||||||
int saveIndex = index;
|
|
||||||
return Json::nextToken(json, saveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* nextToken
|
|
||||||
*/
|
|
||||||
int Json::nextToken(const QString &json, int &index)
|
|
||||||
{
|
|
||||||
Json::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--;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QtJson
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
/* Copyright 2011 Eeli Reilin. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
|
|
||||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
||||||
* EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
||||||
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
||||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* The views and conclusions contained in the software and documentation
|
|
||||||
* are those of the authors and should not be interpreted as representing
|
|
||||||
* official policies, either expressed or implied, of Eeli Reilin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \file json.h
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef JSON_H
|
|
||||||
#define JSON_H
|
|
||||||
|
|
||||||
#include <QVariant>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace QtJson
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \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
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \class Json
|
|
||||||
* \brief A JSON data parser
|
|
||||||
*
|
|
||||||
* Json parses a JSON data into a QVariant hierarchy.
|
|
||||||
*/
|
|
||||||
class Json
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Parse a JSON string
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
*/
|
|
||||||
static QVariant parse(const QString &json);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a JSON string
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param success The success of the parsing
|
|
||||||
*/
|
|
||||||
static QVariant parse(const QString &json, bool &success);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method generates a textual JSON representation
|
|
||||||
*
|
|
||||||
* \param data The JSON data generated by the parser.
|
|
||||||
* \param success The success of the serialization
|
|
||||||
*/
|
|
||||||
static 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
|
|
||||||
*/
|
|
||||||
static QByteArray serialize(const QVariant &data, bool &success);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Parses a value starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The start index
|
|
||||||
* \param success The success of the parse process
|
|
||||||
*
|
|
||||||
* \return QVariant The parsed value
|
|
||||||
*/
|
|
||||||
static QVariant parseValue(const QString &json, int &index,
|
|
||||||
bool &success);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an object starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The start index
|
|
||||||
* \param success The success of the object parse
|
|
||||||
*
|
|
||||||
* \return QVariant The parsed object map
|
|
||||||
*/
|
|
||||||
static QVariant parseObject(const QString &json, int &index,
|
|
||||||
bool &success);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an array starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The starting index
|
|
||||||
* \param success The success of the array parse
|
|
||||||
*
|
|
||||||
* \return QVariant The parsed variant array
|
|
||||||
*/
|
|
||||||
static QVariant parseArray(const QString &json, int &index,
|
|
||||||
bool &success);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a string starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The starting index
|
|
||||||
* \param success The success of the string parse
|
|
||||||
*
|
|
||||||
* \return QVariant The parsed string
|
|
||||||
*/
|
|
||||||
static QVariant parseString(const QString &json, int &index,
|
|
||||||
bool &success);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a number starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The starting index
|
|
||||||
*
|
|
||||||
* \return QVariant The parsed number
|
|
||||||
*/
|
|
||||||
static QVariant parseNumber(const QString &json, int &index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the last index of a number starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The starting index
|
|
||||||
*
|
|
||||||
* \return The last index of the number
|
|
||||||
*/
|
|
||||||
static int lastIndexOfNumber(const QString &json, int index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip unwanted whitespace symbols starting from index
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The start index
|
|
||||||
*/
|
|
||||||
static void eatWhitespace(const QString &json, int &index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check what token lies ahead
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The starting index
|
|
||||||
*
|
|
||||||
* \return int The upcoming token
|
|
||||||
*/
|
|
||||||
static int lookAhead(const QString &json, int index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next JSON token
|
|
||||||
*
|
|
||||||
* \param json The JSON data
|
|
||||||
* \param index The starting index
|
|
||||||
*
|
|
||||||
* \return int The next JSON token
|
|
||||||
*/
|
|
||||||
static int nextToken(const QString &json, int &index);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} //end namespace
|
|
||||||
|
|
||||||
#endif //JSON_H
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue