mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 00:04:48 -07:00
Rework of the card database, xml format and oracle parser (#3511)
* CardDB: merge all card properties in a new structure * Pre Json parser changes * Cockatrice: use qt's builtin json support * Move qt-json src dir from cockatrice to oracle * Add dummy cockatricexml4 parser (yet to be implemented) * Implement a new parser and xml format * cockatricexml4: new xml parser following the "generic properties hash" pattern; * oracleimporter: refactor the parsing code to better adapt to cockatricexml4; rewrote split cards parsing * carddb: change "colors" from a stringlist to a string * carddb: move the getMainCardType() method to the cockatricexml3 parser * * CardInfo: show all properties (stil missing: nice name + translation) * Rework the "add related card" feature so that it doesn't change the card name in the carddb Also, fix token count display * Picture loader: Added support for transform cards * Fix side information for flip cards Mtgjson uses side a/b for flip cards, while scryfall doesn't * Pictureloader: dynamic tag resolution from card properties Examples old => new * !cardid! => !set:muid! * !uuid! => !set:uuid! * !collectornumber! => !set:num! New examples: * !prop:type! * !prop:manacost! * Start moving mtg-related property names to a specific file * Clangify * Fix tests * Make gcc an happy puppy * Revert "Make gcc an happy puppy" This reverts commit 446ec5f27516c4d3b32dbfc79557f4827c5c5bdf. * Some gcc fixes * Share set list between different db parsers, so they won't overwrite one each other * All glory to the hypnoclangifier! * Fix test compilation * Cleanup edited files in the prior PR. (#3519) * Cleanup edited files in the prior PR. Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com> * Fix includes Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com> * Update carddatabase.h
This commit is contained in:
parent
19180243aa
commit
ed70099e36
44 changed files with 1814 additions and 1360 deletions
|
|
@ -1,5 +1,5 @@
|
|||
#include "oracleimporter.h"
|
||||
#include "carddbparser/cockatricexml3.h"
|
||||
#include "carddbparser/cockatricexml4.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtWidgets>
|
||||
|
|
@ -7,6 +7,14 @@
|
|||
|
||||
#include "qt-json/json.h"
|
||||
|
||||
SplitCardPart::SplitCardPart(const int _index,
|
||||
const QString &_text,
|
||||
const QVariantHash &_properties,
|
||||
const CardInfoPerSet _setInfo)
|
||||
: index(_index), text(_text), properties(_properties), setInfo(_setInfo)
|
||||
{
|
||||
}
|
||||
|
||||
OracleImporter::OracleImporter(const QString &_dataDir, QObject *parent) : CardDatabase(parent), dataDir(_dataDir)
|
||||
{
|
||||
}
|
||||
|
|
@ -25,24 +33,23 @@ bool OracleImporter::readSetsFromByteArray(const QByteArray &data)
|
|||
QListIterator<QVariant> it(setsMap.values());
|
||||
QVariantMap map;
|
||||
|
||||
QString edition;
|
||||
QString editionLong;
|
||||
QVariant editionCards;
|
||||
QString shortName;
|
||||
QString longName;
|
||||
QList<QVariant> setCards;
|
||||
QString setType;
|
||||
QDate releaseDate;
|
||||
|
||||
while (it.hasNext()) {
|
||||
map = it.next().toMap();
|
||||
edition = map.value("code").toString().toUpper();
|
||||
editionLong = map.value("name").toString();
|
||||
editionCards = map.value("cards");
|
||||
shortName = map.value("code").toString().toUpper();
|
||||
longName = map.value("name").toString();
|
||||
setCards = map.value("cards").toList();
|
||||
setType = map.value("type").toString();
|
||||
// capitalize set type
|
||||
if (setType.length() > 0)
|
||||
setType[0] = setType[0].toUpper();
|
||||
releaseDate = map.value("releaseDate").toDate();
|
||||
|
||||
newSetList.append(SetToDownload(edition, editionLong, editionCards, setType, releaseDate));
|
||||
newSetList.append(SetToDownload(shortName, longName, setCards, setType, releaseDate));
|
||||
}
|
||||
|
||||
qSort(newSetList);
|
||||
|
|
@ -53,37 +60,27 @@ bool OracleImporter::readSetsFromByteArray(const QByteArray &data)
|
|||
return true;
|
||||
}
|
||||
|
||||
CardInfoPtr OracleImporter::addCard(const QString &setName,
|
||||
QString cardName,
|
||||
CardInfoPtr OracleImporter::addCard(QString name,
|
||||
QString text,
|
||||
bool isToken,
|
||||
int cardId,
|
||||
QString &cardUuId,
|
||||
QString &setNumber,
|
||||
QString &cardCost,
|
||||
QString &cmc,
|
||||
const QString &cardType,
|
||||
const QString &cardPT,
|
||||
const QString &cardLoyalty,
|
||||
const QString &cardText,
|
||||
const QStringList &colors,
|
||||
const QList<CardRelation *> &relatedCards,
|
||||
const QList<CardRelation *> &reverseRelatedCards,
|
||||
bool upsideDown,
|
||||
QString &rarity)
|
||||
QVariantHash properties,
|
||||
QList<CardRelation *> &relatedCards,
|
||||
CardInfoPerSet setInfo)
|
||||
{
|
||||
QStringList cardTextRows = cardText.split("\n");
|
||||
|
||||
// Workaround for card name weirdness
|
||||
cardName = cardName.replace("Æ", "AE");
|
||||
cardName = cardName.replace("’", "'");
|
||||
name = name.replace("Æ", "AE");
|
||||
name = name.replace("’", "'");
|
||||
if (cards.contains(name)) {
|
||||
CardInfoPtr card = cards.value(name);
|
||||
card->addToSet(setInfo.getPtr(), setInfo);
|
||||
return card;
|
||||
}
|
||||
|
||||
CardInfoPtr card;
|
||||
if (cards.contains(cardName)) {
|
||||
card = cards.value(cardName);
|
||||
} else {
|
||||
// Remove {} around mana costs, except if it's split cost
|
||||
QStringList symbols = cardCost.split("}");
|
||||
QString formattedCardCost = QString();
|
||||
// Remove {} around mana costs, except if it's split cost
|
||||
QString manacost = properties.value("manacost").toString();
|
||||
if (!manacost.isEmpty()) {
|
||||
QStringList symbols = manacost.split("}");
|
||||
QString formattedCardCost;
|
||||
for (QString symbol : symbols) {
|
||||
if (symbol.contains(QRegExp("[0-9WUBGRP]/[0-9WUBGRP]"))) {
|
||||
symbol.append("}");
|
||||
|
|
@ -92,240 +89,251 @@ CardInfoPtr OracleImporter::addCard(const QString &setName,
|
|||
}
|
||||
formattedCardCost.append(symbol);
|
||||
}
|
||||
|
||||
// detect mana generator artifacts
|
||||
bool mArtifact = false;
|
||||
if (cardType.endsWith("Artifact")) {
|
||||
for (int i = 0; i < cardTextRows.size(); ++i) {
|
||||
cardTextRows[i].remove(QRegularExpression(R"(\".*?\")"));
|
||||
if (cardTextRows[i].contains("{T}") && cardTextRows[i].contains("to your mana pool")) {
|
||||
mArtifact = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detect cards that enter the field tapped
|
||||
bool cipt =
|
||||
cardText.contains("Hideaway") || (cardText.contains(cardName + " enters the battlefield tapped") &&
|
||||
!cardText.contains(cardName + " enters the battlefield tapped unless"));
|
||||
|
||||
// insert the card and its properties
|
||||
card = CardInfo::newInstance(cardName, isToken, formattedCardCost, cmc, cardType, cardPT, cardText, colors,
|
||||
relatedCards, reverseRelatedCards, upsideDown, cardLoyalty, cipt);
|
||||
int tableRow = 1;
|
||||
QString mainCardType = card->getMainCardType();
|
||||
if ((mainCardType == "Land") || mArtifact)
|
||||
tableRow = 0;
|
||||
else if ((mainCardType == "Sorcery") || (mainCardType == "Instant"))
|
||||
tableRow = 3;
|
||||
else if (mainCardType == "Creature")
|
||||
tableRow = 2;
|
||||
card->setTableRow(tableRow);
|
||||
|
||||
cards.insert(cardName, card);
|
||||
properties.insert("manacost", formattedCardCost);
|
||||
}
|
||||
|
||||
card->setMuId(setName, cardId);
|
||||
card->setUuId(setName, cardUuId);
|
||||
card->setSetNumber(setName, setNumber);
|
||||
card->setRarity(setName, rarity);
|
||||
// fix colors
|
||||
QString allColors = properties.value("colors").toString();
|
||||
if (allColors.size() > 1) {
|
||||
sortAndReduceColors(allColors);
|
||||
properties.insert("colors", allColors);
|
||||
}
|
||||
|
||||
return card;
|
||||
// DETECT CARD POSITIONING INFO
|
||||
|
||||
// cards that enter the field tapped
|
||||
bool cipt = text.contains("Hideaway") || (text.contains(name + " enters the battlefield tapped") &&
|
||||
!text.contains(name + " enters the battlefield tapped unless"));
|
||||
|
||||
// detect mana generator artifacts
|
||||
QStringList cardTextRows = text.split("\n");
|
||||
bool mArtifact = false;
|
||||
QString cardType = properties.value("type").toString();
|
||||
if (cardType.endsWith("Artifact")) {
|
||||
for (int i = 0; i < cardTextRows.size(); ++i) {
|
||||
cardTextRows[i].remove(QRegularExpression(R"(\".*?\")"));
|
||||
if (cardTextRows[i].contains("{T}") && cardTextRows[i].contains("to your mana pool")) {
|
||||
mArtifact = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// table row
|
||||
int tableRow = 1;
|
||||
QString mainCardType = properties.value("maintype").toString();
|
||||
if ((mainCardType == "Land") || mArtifact)
|
||||
tableRow = 0;
|
||||
else if ((mainCardType == "Sorcery") || (mainCardType == "Instant"))
|
||||
tableRow = 3;
|
||||
else if (mainCardType == "Creature")
|
||||
tableRow = 2;
|
||||
|
||||
// card side
|
||||
QString side = properties.value("side").toString() == "b" ? "back" : "front";
|
||||
properties.insert("side", side);
|
||||
|
||||
// upsideDown (flip cards)
|
||||
bool upsideDown = false;
|
||||
QStringList additionalNames = properties.value("names").toStringList();
|
||||
QString layout = properties.value("layout").toString();
|
||||
if (layout == "flip") {
|
||||
if (properties.value("side").toString() != "front") {
|
||||
upsideDown = true;
|
||||
}
|
||||
// reset the side property, since the card has no back image
|
||||
properties.insert("side", "front");
|
||||
}
|
||||
|
||||
// insert the card and its properties
|
||||
QList<CardRelation *> reverseRelatedCards;
|
||||
CardInfoPerSetMap setsInfo;
|
||||
setsInfo.insert(setInfo.getPtr()->getShortName(), setInfo);
|
||||
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards,
|
||||
setsInfo, cipt, tableRow, upsideDown);
|
||||
|
||||
cards.insert(name, newCard);
|
||||
return newCard;
|
||||
}
|
||||
|
||||
int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data)
|
||||
QString OracleImporter::getStringPropertyFromMap(QVariantMap card, QString propertyName)
|
||||
{
|
||||
int cards = 0;
|
||||
return card.contains(propertyName) ? card.value(propertyName).toString() : QString("");
|
||||
}
|
||||
|
||||
QListIterator<QVariant> it(data.toList());
|
||||
QVariantMap map;
|
||||
QString cardName;
|
||||
QString cardCost;
|
||||
QString cmc;
|
||||
QString cardType;
|
||||
QString cardPT;
|
||||
QString cardText;
|
||||
QStringList colors;
|
||||
int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVariant> &cardsList)
|
||||
{
|
||||
static const QMap<QString, QString> cardProperties{
|
||||
// mtgjson name => xml name
|
||||
{"manaCost", "manacost"}, {"convertedManaCost", "cmc"}, {"type", "type"},
|
||||
{"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"},
|
||||
};
|
||||
|
||||
static const QMap<QString, QString> setInfoProperties{// mtgjson name => xml name
|
||||
{"multiverseId", "muid"},
|
||||
{"scryfallId", "uuid"},
|
||||
{"number", "num"},
|
||||
{"rarity", "rarity"}};
|
||||
|
||||
int numCards = 0;
|
||||
QMap<QString, SplitCardPart> splitCards;
|
||||
QString ptSeparator("/");
|
||||
QVariantMap card;
|
||||
QString layout, name, text, colors, maintype, power, toughness;
|
||||
bool isToken;
|
||||
QStringList additionalNames;
|
||||
QVariantHash properties;
|
||||
CardInfoPerSet setInfo;
|
||||
QList<CardRelation *> relatedCards;
|
||||
QList<CardRelation *> reverseRelatedCards; // dummy
|
||||
int cardId;
|
||||
QString cardUuId;
|
||||
QString setNumber;
|
||||
QString rarity;
|
||||
QString cardLoyalty;
|
||||
bool upsideDown;
|
||||
QMap<int, QVariantMap> splitCards;
|
||||
|
||||
while (it.hasNext()) {
|
||||
map = it.next().toMap();
|
||||
for (const QVariant &cardVar : cardsList) {
|
||||
card = cardVar.toMap();
|
||||
|
||||
/* Currently used layouts are:
|
||||
* augment, double_faced_token, flip, host, leveler, meld, normal, planar,
|
||||
* saga, scheme, split, token, transform, vanguard
|
||||
*/
|
||||
QString layout = map.value("layout").toString();
|
||||
layout = getStringPropertyFromMap(card, "layout");
|
||||
|
||||
// don't import tokens from the json file
|
||||
isToken = false;
|
||||
if (layout == "token")
|
||||
continue;
|
||||
|
||||
// Aftermath card layout seems to have been integrated in "split"
|
||||
if (layout == "split") {
|
||||
// Enqueue split card for later handling
|
||||
cardId = map.contains("multiverseId") ? map.value("multiverseId").toInt() : 0;
|
||||
if (cardId)
|
||||
splitCards.insertMulti(cardId, map);
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal cards handling
|
||||
cardName = map.contains("name") ? map.value("name").toString() : QString("");
|
||||
cardCost = map.contains("manaCost") ? map.value("manaCost").toString() : QString("");
|
||||
cmc = map.contains("convertedManaCost") ? map.value("convertedManaCost").toString() : QString("0");
|
||||
cardType = map.contains("type") ? map.value("type").toString() : QString("");
|
||||
cardPT = map.contains("power") || map.contains("toughness")
|
||||
? map.value("power").toString() + QString('/') + map.value("toughness").toString()
|
||||
: QString("");
|
||||
cardText = map.contains("text") ? map.value("text").toString() : QString("");
|
||||
cardId = map.contains("multiverseId") ? map.value("multiverseId").toInt() : 0;
|
||||
cardUuId = map.contains("scryfallId") ? map.value("scryfallId").toString() : QString("");
|
||||
setNumber = map.contains("number") ? map.value("number").toString() : QString("");
|
||||
rarity = map.contains("rarity") ? map.value("rarity").toString() : QString("");
|
||||
cardLoyalty = map.contains("loyalty") ? map.value("loyalty").toString() : QString("");
|
||||
colors = map.contains("colors") ? map.value("colors").toStringList() : QStringList();
|
||||
relatedCards = QList<CardRelation *>();
|
||||
if (map.contains("names"))
|
||||
for (const QString &name : map.value("names").toStringList()) {
|
||||
if (name != cardName)
|
||||
relatedCards.append(new CardRelation(name, true));
|
||||
}
|
||||
name = getStringPropertyFromMap(card, "name");
|
||||
text = getStringPropertyFromMap(card, "text");
|
||||
|
||||
if (0 == QString::compare(map.value("layout").toString(), QString("flip"), Qt::CaseInsensitive)) {
|
||||
QStringList cardNames = map.contains("names") ? map.value("names").toStringList() : QStringList();
|
||||
upsideDown = (cardNames.indexOf(cardName) > 0);
|
||||
// card properties
|
||||
properties.clear();
|
||||
QMapIterator<QString, QString> it(cardProperties);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
QString mtgjsonProperty = it.key();
|
||||
QString xmlPropertyName = it.value();
|
||||
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
|
||||
if (!propertyValue.isEmpty())
|
||||
properties.insert(xmlPropertyName, propertyValue);
|
||||
}
|
||||
|
||||
// per-set properties
|
||||
setInfo = CardInfoPerSet(currentSet);
|
||||
QMapIterator<QString, QString> it2(setInfoProperties);
|
||||
while (it2.hasNext()) {
|
||||
it2.next();
|
||||
QString mtgjsonProperty = it2.key();
|
||||
QString xmlPropertyName = it2.value();
|
||||
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
|
||||
if (!propertyValue.isEmpty())
|
||||
setInfo.setProperty(xmlPropertyName, propertyValue);
|
||||
}
|
||||
|
||||
// special handling properties
|
||||
colors = card.value("colors").toStringList().join("");
|
||||
if (!colors.isEmpty())
|
||||
properties.insert("colors", colors);
|
||||
|
||||
maintype = card.value("types").toStringList().first();
|
||||
if (!maintype.isEmpty())
|
||||
properties.insert("maintype", maintype);
|
||||
|
||||
power = getStringPropertyFromMap(card, "power");
|
||||
toughness = getStringPropertyFromMap(card, "toughness");
|
||||
if (!(power.isEmpty() && toughness.isEmpty()))
|
||||
properties.insert("pt", power + ptSeparator + toughness);
|
||||
|
||||
additionalNames = card.value("names").toStringList();
|
||||
// split cards are considered a single card, enqueue for later merging
|
||||
if (layout == "split") {
|
||||
// get the position of this card part
|
||||
int index = additionalNames.indexOf(name);
|
||||
// construct full card name
|
||||
name = additionalNames.join(QString(" // "));
|
||||
SplitCardPart split(index, text, properties, setInfo);
|
||||
splitCards.insertMulti(name, split);
|
||||
} else {
|
||||
upsideDown = false;
|
||||
}
|
||||
// relations
|
||||
relatedCards.clear();
|
||||
if (additionalNames.size() > 1) {
|
||||
for (const QString &additionalName : additionalNames) {
|
||||
if (additionalName != name)
|
||||
relatedCards.append(new CardRelation(additionalName, true));
|
||||
}
|
||||
}
|
||||
|
||||
CardInfoPtr card =
|
||||
addCard(set->getShortName(), cardName, false, cardId, cardUuId, setNumber, cardCost, cmc, cardType, cardPT,
|
||||
cardLoyalty, cardText, colors, relatedCards, reverseRelatedCards, upsideDown, rarity);
|
||||
|
||||
if (!set->contains(card)) {
|
||||
card->addToSet(set);
|
||||
cards++;
|
||||
CardInfoPtr newCard = addCard(name, text, isToken, properties, relatedCards, setInfo);
|
||||
numCards++;
|
||||
}
|
||||
}
|
||||
|
||||
// split cards handling - get all unique card muids
|
||||
QList<int> muids = splitCards.uniqueKeys();
|
||||
for (int muid : muids) {
|
||||
// get all cards for this specific muid
|
||||
QList<QVariantMap> maps = splitCards.values(muid);
|
||||
QStringList names;
|
||||
// now, reorder the cards using the ordered list of names
|
||||
QMap<int, QVariantMap> orderedMaps;
|
||||
for (const QVariantMap &inner_map : maps) {
|
||||
if (names.isEmpty())
|
||||
names = inner_map.contains("names") ? inner_map.value("names").toStringList() : QStringList();
|
||||
QString name = inner_map.value("name").toString();
|
||||
int index = names.indexOf(name);
|
||||
orderedMaps.insertMulti(index, inner_map);
|
||||
// split cards handling
|
||||
QString splitCardPropSeparator = QString(" // ");
|
||||
QString splitCardTextSeparator = QString("\n\n---\n\n");
|
||||
for (const QString &nameSplit : splitCards.uniqueKeys()) {
|
||||
// get all parts for this specific card
|
||||
QList<SplitCardPart> splitCardParts = splitCards.values(nameSplit);
|
||||
// sort them by index (aka position)
|
||||
qSort(splitCardParts.begin(), splitCardParts.end(),
|
||||
[](const SplitCardPart &a, const SplitCardPart &b) -> bool { return a.getIndex() < b.getIndex(); });
|
||||
|
||||
text = QString("");
|
||||
isToken = false;
|
||||
properties.clear();
|
||||
relatedCards.clear();
|
||||
|
||||
int lastIndex = -1;
|
||||
for (const SplitCardPart &tmp : splitCardParts) {
|
||||
// some sets have 2 different variations of the same split card,
|
||||
// eg. Fire // Ice in WC02. Avoid adding duplicates.
|
||||
if (lastIndex == tmp.getIndex())
|
||||
continue;
|
||||
lastIndex = tmp.getIndex();
|
||||
|
||||
if (!text.isEmpty())
|
||||
text.append(splitCardTextSeparator);
|
||||
text.append(tmp.getText());
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
properties = tmp.getProperties();
|
||||
setInfo = tmp.getSetInfo();
|
||||
} else {
|
||||
const QVariantHash &props = tmp.getProperties();
|
||||
for (const QString &prop : props.keys()) {
|
||||
QString originalPropertyValue = properties.value(prop).toString();
|
||||
QString thisCardPropertyValue = props.value(prop).toString();
|
||||
if (originalPropertyValue != thisCardPropertyValue) {
|
||||
if (prop == "colors") {
|
||||
properties.insert(prop, originalPropertyValue + thisCardPropertyValue);
|
||||
} else {
|
||||
properties.insert(prop,
|
||||
originalPropertyValue + splitCardPropSeparator + thisCardPropertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clean variables
|
||||
cardName = "";
|
||||
cardCost = "";
|
||||
cmc = "";
|
||||
cardType = "";
|
||||
cardPT = "";
|
||||
cardText = "";
|
||||
cardUuId = "";
|
||||
setNumber = "";
|
||||
rarity = "";
|
||||
cardLoyalty = "";
|
||||
colors.clear();
|
||||
|
||||
// loop cards and merge their contents
|
||||
QString prefix = QString(" // ");
|
||||
QString prefix2 = QString("\n\n---\n\n");
|
||||
for (const QVariantMap &inner_map : orderedMaps.values()) {
|
||||
if (inner_map.contains("name")) {
|
||||
if (!cardName.isEmpty())
|
||||
cardName += (orderedMaps.count() > 2) ? QString("/") : prefix;
|
||||
cardName += inner_map.value("name").toString();
|
||||
}
|
||||
if (inner_map.contains("manaCost")) {
|
||||
if (!cardCost.isEmpty())
|
||||
cardCost += prefix;
|
||||
cardCost += inner_map.value("manaCost").toString();
|
||||
}
|
||||
if (inner_map.contains("convertedManaCost")) {
|
||||
if (!cmc.isEmpty())
|
||||
cmc += prefix;
|
||||
cmc += inner_map.value("convertedManaCost").toString();
|
||||
}
|
||||
if (inner_map.contains("type")) {
|
||||
if (!cardType.isEmpty())
|
||||
cardType += prefix;
|
||||
cardType += inner_map.value("type").toString();
|
||||
}
|
||||
if (inner_map.contains("power") || inner_map.contains("toughness")) {
|
||||
if (!cardPT.isEmpty())
|
||||
cardPT += prefix;
|
||||
cardPT += inner_map.value("power").toString() + QString('/') + inner_map.value("toughness").toString();
|
||||
}
|
||||
if (inner_map.contains("text")) {
|
||||
if (!cardText.isEmpty())
|
||||
cardText += prefix2;
|
||||
cardText += inner_map.value("text").toString();
|
||||
}
|
||||
if (inner_map.contains("uuid")) {
|
||||
if (cardUuId.isEmpty())
|
||||
cardUuId = inner_map.value("uuid").toString();
|
||||
}
|
||||
if (inner_map.contains("number")) {
|
||||
if (setNumber.isEmpty())
|
||||
setNumber = inner_map.value("number").toString();
|
||||
}
|
||||
if (inner_map.contains("rarity")) {
|
||||
if (rarity.isEmpty())
|
||||
rarity = inner_map.value("rarity").toString();
|
||||
}
|
||||
|
||||
colors << inner_map.value("colors").toStringList();
|
||||
}
|
||||
|
||||
colors.removeDuplicates();
|
||||
if (colors.length() > 1) {
|
||||
sortColors(colors);
|
||||
}
|
||||
|
||||
// Fortunately, there are no split cards that flip, transform or meld.
|
||||
relatedCards = QList<CardRelation *>();
|
||||
reverseRelatedCards = QList<CardRelation *>();
|
||||
upsideDown = false;
|
||||
|
||||
// add the card
|
||||
CardInfoPtr card =
|
||||
addCard(set->getShortName(), cardName, false, muid, cardUuId, setNumber, cardCost, cmc, cardType, cardPT,
|
||||
cardLoyalty, cardText, colors, relatedCards, reverseRelatedCards, upsideDown, rarity);
|
||||
|
||||
if (!set->contains(card)) {
|
||||
card->addToSet(set);
|
||||
cards++;
|
||||
}
|
||||
CardInfoPtr newCard = addCard(name, text, isToken, properties, relatedCards, setInfo);
|
||||
numCards++;
|
||||
}
|
||||
|
||||
return cards;
|
||||
return numCards;
|
||||
}
|
||||
|
||||
void OracleImporter::sortColors(QStringList &colors)
|
||||
void OracleImporter::sortAndReduceColors(QString &colors)
|
||||
{
|
||||
const QHash<QString, unsigned int> colorOrder{{"W", 0}, {"U", 1}, {"B", 2}, {"R", 3}, {"G", 4}};
|
||||
std::sort(colors.begin(), colors.end(), [&colorOrder](const QString a, const QString b) {
|
||||
// sort
|
||||
const QHash<QChar, unsigned int> colorOrder{{'W', 0}, {'U', 1}, {'B', 2}, {'R', 3}, {'G', 4}};
|
||||
std::sort(colors.begin(), colors.end(), [&colorOrder](const QChar a, const QChar b) {
|
||||
return colorOrder.value(a, INT_MAX) < colorOrder.value(b, INT_MAX);
|
||||
});
|
||||
// reduce
|
||||
QChar lastChar = '\0';
|
||||
for (int i = 0; i < colors.size(); ++i) {
|
||||
if (colors.at(i) == lastChar)
|
||||
colors.remove(i, 1);
|
||||
else
|
||||
lastChar = colors.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
int OracleImporter::startImport()
|
||||
|
|
@ -333,25 +341,21 @@ int OracleImporter::startImport()
|
|||
clear();
|
||||
|
||||
int setCards = 0, setIndex = 0;
|
||||
QListIterator<SetToDownload> it(allSets);
|
||||
const SetToDownload *curSet;
|
||||
|
||||
// add an empty set for tokens
|
||||
CardSetPtr tokenSet = CardSet::newInstance(TOKENS_SETNAME, tr("Dummy set containing tokens"), "Tokens");
|
||||
sets.insert(TOKENS_SETNAME, tokenSet);
|
||||
|
||||
while (it.hasNext()) {
|
||||
curSet = &it.next();
|
||||
CardSetPtr set = CardSet::newInstance(curSet->getShortName(), curSet->getLongName(), curSet->getSetType(),
|
||||
curSet->getReleaseDate());
|
||||
if (!sets.contains(set->getShortName()))
|
||||
sets.insert(set->getShortName(), set);
|
||||
for (const SetToDownload &curSetToParse : allSets) {
|
||||
CardSetPtr newSet = CardSet::newInstance(curSetToParse.getShortName(), curSetToParse.getLongName(),
|
||||
curSetToParse.getSetType(), curSetToParse.getReleaseDate());
|
||||
if (!sets.contains(newSet->getShortName()))
|
||||
sets.insert(newSet->getShortName(), newSet);
|
||||
|
||||
int setCardsHere = importTextSpoiler(set, curSet->getCards());
|
||||
int numCardsInSet = importCardsFromSet(newSet, curSetToParse.getCards());
|
||||
|
||||
++setIndex;
|
||||
|
||||
emit setIndexChanged(setCardsHere, setIndex, curSet->getLongName());
|
||||
emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName());
|
||||
}
|
||||
|
||||
emit setIndexChanged(setCards, setIndex, QString());
|
||||
|
|
@ -362,6 +366,6 @@ int OracleImporter::startImport()
|
|||
|
||||
bool OracleImporter::saveToFile(const QString &fileName)
|
||||
{
|
||||
CockatriceXml3Parser parser;
|
||||
CockatriceXml4Parser parser;
|
||||
return parser.saveToFile(sets, cards, fileName);
|
||||
}
|
||||
|
|
@ -3,14 +3,14 @@
|
|||
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
|
||||
#include <carddatabase.h>
|
||||
#include <utility>
|
||||
|
||||
class SetToDownload
|
||||
{
|
||||
private:
|
||||
QString shortName, longName;
|
||||
QVariant cards;
|
||||
QList<QVariant> cards;
|
||||
QDate releaseDate;
|
||||
QString setType;
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ public:
|
|||
{
|
||||
return longName;
|
||||
}
|
||||
const QVariant &getCards() const
|
||||
const QList<QVariant> &getCards() const
|
||||
{
|
||||
return cards;
|
||||
}
|
||||
|
|
@ -35,12 +35,13 @@ public:
|
|||
{
|
||||
return releaseDate;
|
||||
}
|
||||
SetToDownload(const QString &_shortName,
|
||||
const QString &_longName,
|
||||
const QVariant &_cards,
|
||||
const QString &_setType = QString(),
|
||||
SetToDownload(QString _shortName,
|
||||
QString _longName,
|
||||
QList<QVariant> _cards,
|
||||
QString _setType = QString(),
|
||||
const QDate &_releaseDate = QDate())
|
||||
: shortName(_shortName), longName(_longName), cards(_cards), releaseDate(_releaseDate), setType(_setType)
|
||||
: shortName(std::move(_shortName)), longName(std::move(_longName)), cards(std::move(_cards)),
|
||||
releaseDate(_releaseDate), setType(std::move(_setType))
|
||||
{
|
||||
}
|
||||
bool operator<(const SetToDownload &set) const
|
||||
|
|
@ -49,6 +50,34 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class SplitCardPart
|
||||
{
|
||||
public:
|
||||
SplitCardPart(int _index, const QString &_text, const QVariantHash &_properties, CardInfoPerSet setInfo);
|
||||
inline const int &getIndex() const
|
||||
{
|
||||
return index;
|
||||
}
|
||||
inline const QString &getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
inline const QVariantHash &getProperties() const
|
||||
{
|
||||
return properties;
|
||||
}
|
||||
inline const CardInfoPerSet &getSetInfo() const
|
||||
{
|
||||
return setInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
int index;
|
||||
QString text;
|
||||
QVariantHash properties;
|
||||
CardInfoPerSet setInfo;
|
||||
};
|
||||
|
||||
class OracleImporter : public CardDatabase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
@ -57,33 +86,22 @@ private:
|
|||
QVariantMap setsMap;
|
||||
QString dataDir;
|
||||
|
||||
CardInfoPtr addCard(const QString &setName,
|
||||
QString cardName,
|
||||
CardInfoPtr addCard(QString name,
|
||||
QString text,
|
||||
bool isToken,
|
||||
int cardId,
|
||||
QString &cardUuId,
|
||||
QString &setNumber,
|
||||
QString &cardCost,
|
||||
QString &cmc,
|
||||
const QString &cardType,
|
||||
const QString &cardPT,
|
||||
const QString &cardLoyalty,
|
||||
const QString &cardText,
|
||||
const QStringList &colors,
|
||||
const QList<CardRelation *> &relatedCards,
|
||||
const QList<CardRelation *> &reverseRelatedCards,
|
||||
bool upsideDown,
|
||||
QString &rarity);
|
||||
QVariantHash properties,
|
||||
QList<CardRelation *> &relatedCards,
|
||||
CardInfoPerSet setInfo);
|
||||
signals:
|
||||
void setIndexChanged(int cardsImported, int setIndex, const QString &setName);
|
||||
void dataReadProgress(int bytesRead, int totalBytes);
|
||||
|
||||
public:
|
||||
OracleImporter(const QString &_dataDir, QObject *parent = 0);
|
||||
explicit OracleImporter(const QString &_dataDir, QObject *parent = nullptr);
|
||||
bool readSetsFromByteArray(const QByteArray &data);
|
||||
int startImport();
|
||||
bool saveToFile(const QString &fileName);
|
||||
int importTextSpoiler(CardSetPtr set, const QVariant &data);
|
||||
int importCardsFromSet(CardSetPtr currentSet, const QList<QVariant> &cards);
|
||||
QList<SetToDownload> &getSets()
|
||||
{
|
||||
return allSets;
|
||||
|
|
@ -94,7 +112,8 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
void sortColors(QStringList &colors);
|
||||
inline QString getStringPropertyFromMap(QVariantMap card, QString propertyName);
|
||||
void sortAndReduceColors(QString &colors);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
3
oracle/src/qt-json/AUTHORS
Normal file
3
oracle/src/qt-json/AUTHORS
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Eeli Reilin <eeli@emicode.fi>
|
||||
Luis Gustavo S. Barreto <gustavosbarreto@gmail.com>
|
||||
Stephen Kockentiedt <Stephen@Kockentiedt.name>
|
||||
27
oracle/src/qt-json/LICENSE
Normal file
27
oracle/src/qt-json/LICENSE
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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.
|
||||
|
||||
96
oracle/src/qt-json/README
Normal file
96
oracle/src/qt-json/README
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
########################################################################
|
||||
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!
|
||||
614
oracle/src/qt-json/json.cpp
Normal file
614
oracle/src/qt-json/json.cpp
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
/* 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 <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;
|
||||
Q_FOREACH(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())
|
||||
{
|
||||
QString data = json;
|
||||
//We'll start from index 0
|
||||
int index = 0;
|
||||
|
||||
//Parse the first value
|
||||
QVariant value = Json::parseValue(data, 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.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list?
|
||||
{
|
||||
QList<QByteArray> values;
|
||||
const QVariantList list = data.toList();
|
||||
Q_FOREACH(const QVariant& v, list)
|
||||
{
|
||||
QByteArray serializedValue = serialize(v);
|
||||
if(serializedValue.isNull())
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
values << serializedValue;
|
||||
}
|
||||
|
||||
str = "[ " + join( values, ", " ) + " ]";
|
||||
}
|
||||
else if(data.type() == QVariant::Hash) // 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.type() == QVariant::Map) // 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.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array?
|
||||
{
|
||||
str = sanitizeString(data.toString()).toUtf8();
|
||||
}
|
||||
else if(data.type() == QVariant::Double) // double?
|
||||
{
|
||||
str = QByteArray::number(data.toDouble(), 'g', 20);
|
||||
if(!str.contains(".") && ! str.contains("e"))
|
||||
{
|
||||
str += ".0";
|
||||
}
|
||||
}
|
||||
else if (data.type() == QVariant::Bool) // boolean value?
|
||||
{
|
||||
str = data.toBool() ? "true" : "false";
|
||||
}
|
||||
else if (data.type() == QVariant::ULongLong) // large unsigned number?
|
||||
{
|
||||
str = QByteArray::number(data.value<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;
|
||||
}
|
||||
|
||||
|
||||
} //end namespace
|
||||
204
oracle/src/qt-json/json.h
Normal file
204
oracle/src/qt-json/json.h
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/* 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