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:
ctrlaltca 2019-01-24 00:17:10 +01:00 committed by Zach H
parent 19180243aa
commit ed70099e36
44 changed files with 1814 additions and 1360 deletions

View file

@ -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);
}

View file

@ -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

View file

@ -0,0 +1,3 @@
Eeli Reilin <eeli@emicode.fi>
Luis Gustavo S. Barreto <gustavosbarreto@gmail.com>
Stephen Kockentiedt <Stephen@Kockentiedt.name>

View 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
View 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
View 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
View 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