mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-13 01:24:46 -07:00
Turn Card, Deck_List, Protocol, RNG, Network (Client, Server), Settings and Utility into libraries and remove cockatrice_common. (#6212)
--------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: ebbit1q <ebbit1q@gmail.com>
This commit is contained in:
parent
be1403c920
commit
1ef07309d6
605 changed files with 3812 additions and 3408 deletions
|
|
@ -1,201 +0,0 @@
|
|||
#include "card_database.h"
|
||||
|
||||
#include "../card/card_relation.h"
|
||||
#include "../picture_loader/picture_loader.h"
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "parser/cockatrice_xml_3.h"
|
||||
#include "parser/cockatrice_xml_4.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded)
|
||||
{
|
||||
qRegisterMetaType<CardInfoPtr>("CardInfoPtr");
|
||||
qRegisterMetaType<CardInfoPtr>("CardSetPtr");
|
||||
|
||||
// create loader and wire it up
|
||||
loader = new CardDatabaseLoader(this, this);
|
||||
// re-emit loader signals (so other code doesn't need to know about internals)
|
||||
connect(loader, &CardDatabaseLoader::loadingFinished, this, &CardDatabase::cardDatabaseLoadingFinished);
|
||||
connect(loader, &CardDatabaseLoader::loadingFailed, this, &CardDatabase::cardDatabaseLoadingFailed);
|
||||
connect(loader, &CardDatabaseLoader::newSetsFound, this, &CardDatabase::cardDatabaseNewSetsFound);
|
||||
connect(loader, &CardDatabaseLoader::allNewSetsEnabled, this, &CardDatabase::cardDatabaseAllNewSetsEnabled);
|
||||
|
||||
querier = new CardDatabaseQuerier(this, this);
|
||||
}
|
||||
|
||||
CardDatabase::~CardDatabase()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void CardDatabase::clear()
|
||||
{
|
||||
QMutexLocker locker(clearDatabaseMutex);
|
||||
|
||||
for (const auto &card : cards.values()) {
|
||||
if (card) {
|
||||
removeCard(card);
|
||||
}
|
||||
}
|
||||
|
||||
cards.clear();
|
||||
simpleNameCards.clear();
|
||||
|
||||
sets.clear();
|
||||
ICardDatabaseParser::clearSetlist();
|
||||
|
||||
loadStatus = NotLoaded;
|
||||
}
|
||||
|
||||
void CardDatabase::loadCardDatabases()
|
||||
{
|
||||
loadStatus = loader->loadCardDatabases();
|
||||
}
|
||||
|
||||
bool CardDatabase::saveCustomTokensToFile()
|
||||
{
|
||||
return loader->saveCustomTokensToFile();
|
||||
}
|
||||
|
||||
void CardDatabase::refreshCachedReverseRelatedCards()
|
||||
{
|
||||
for (const auto &card : cards) {
|
||||
card->resetReverseRelatedCards2Me();
|
||||
}
|
||||
|
||||
for (const auto &card : cards) {
|
||||
for (auto *rel : card->getReverseRelatedCards()) {
|
||||
if (auto target = cards.value(rel->getName())) {
|
||||
auto *newRel = new CardRelation(card->getName(), rel->getAttachType(), rel->getIsCreateAllExclusion(),
|
||||
rel->getIsVariable(), rel->getDefaultCount(), rel->getIsPersistent());
|
||||
target->addReverseRelatedCards2Me(newRel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabase::addCard(CardInfoPtr card)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
qCWarning(CardDatabaseLog) << "CardDatabase::addCard(nullptr)";
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = card->getName();
|
||||
|
||||
// If a card already exists, just add the new set property.
|
||||
if (auto existing = cards.value(name)) {
|
||||
for (const auto &printings : card->getSets())
|
||||
for (const auto &printing : printings)
|
||||
existing->addToSet(printing.getSet(), printing);
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(addCardMutex);
|
||||
cards.insert(name, card);
|
||||
simpleNameCards.insert(card->getSimpleName(), card);
|
||||
|
||||
emit cardAdded(card);
|
||||
}
|
||||
|
||||
void CardDatabase::removeCard(CardInfoPtr card)
|
||||
{
|
||||
if (card.isNull()) {
|
||||
qCWarning(CardDatabaseLog) << "CardDatabase::removeCard(nullptr)";
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto *cardRelation : card->getRelatedCards())
|
||||
cardRelation->deleteLater();
|
||||
|
||||
for (auto *cardRelation : card->getReverseRelatedCards())
|
||||
cardRelation->deleteLater();
|
||||
|
||||
for (auto *cardRelation : card->getReverseRelatedCards2Me())
|
||||
cardRelation->deleteLater();
|
||||
|
||||
QMutexLocker locker(removeCardMutex);
|
||||
cards.remove(card->getName());
|
||||
simpleNameCards.remove(card->getSimpleName());
|
||||
emit cardRemoved(card);
|
||||
}
|
||||
|
||||
void CardDatabase::addSet(CardSetPtr set)
|
||||
{
|
||||
sets.insert(set->getShortName(), set);
|
||||
}
|
||||
|
||||
CardSetPtr CardDatabase::getSet(const QString &setName)
|
||||
{
|
||||
if (sets.contains(setName)) {
|
||||
return sets.value(setName);
|
||||
} else {
|
||||
CardSetPtr newSet = CardSet::newInstance(setName);
|
||||
sets.insert(setName, newSet);
|
||||
return newSet;
|
||||
}
|
||||
}
|
||||
|
||||
CardSetList CardDatabase::getSetList() const
|
||||
{
|
||||
CardSetList result;
|
||||
for (auto set : sets.values()) {
|
||||
result << set;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CardDatabase::checkUnknownSets()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
|
||||
if (_sets.getEnabledSetsNum()) {
|
||||
// if some sets are first found on this run, ask the user
|
||||
int numUnknownSets = _sets.getUnknownSetsNum();
|
||||
QStringList unknownSetNames = _sets.getUnknownSetsNames();
|
||||
if (numUnknownSets > 0) {
|
||||
emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames);
|
||||
} else {
|
||||
_sets.markAllAsKnown();
|
||||
}
|
||||
} else {
|
||||
// No set enabled. Probably this is the first time running trice
|
||||
_sets.guessSortKeys();
|
||||
_sets.sortByKey();
|
||||
_sets.enableAll();
|
||||
notifyEnabledSetsChanged();
|
||||
|
||||
emit cardDatabaseAllNewSetsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabase::enableAllUnknownSets()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
_sets.enableAllUnknown();
|
||||
}
|
||||
|
||||
void CardDatabase::markAllSetsAsKnown()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
_sets.markAllAsKnown();
|
||||
}
|
||||
|
||||
void CardDatabase::notifyEnabledSetsChanged()
|
||||
{
|
||||
// refresh the list of cached set names
|
||||
for (const CardInfoPtr &card : cards)
|
||||
card->refreshCachedSetNames();
|
||||
|
||||
// inform the carddatabasemodels that they need to re-check their list of cards
|
||||
emit cardDatabaseEnabledSetsChanged();
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/**
|
||||
* @file card_database.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabase is responsible for holding the card and set maps.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASE_H
|
||||
#define CARDDATABASE_H
|
||||
|
||||
#include "../card/card_set_list.h"
|
||||
#include "../card/exact_card.h"
|
||||
#include "../common/card_ref.h"
|
||||
#include "card_database_loader.h"
|
||||
#include "card_database_querier.h"
|
||||
|
||||
#include <QBasicMutex>
|
||||
#include <QDate>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <utility>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database");
|
||||
|
||||
class CardDatabase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
/*
|
||||
* The cards, indexed by name.
|
||||
*/
|
||||
CardNameMap cards;
|
||||
|
||||
/**
|
||||
* The cards, indexed by their simple name.
|
||||
*/
|
||||
CardNameMap simpleNameCards;
|
||||
|
||||
/*
|
||||
* The sets, indexed by short name.
|
||||
*/
|
||||
SetNameMap sets;
|
||||
|
||||
// loader responsible for file discovery & parsing
|
||||
CardDatabaseLoader *loader;
|
||||
|
||||
LoadStatus loadStatus;
|
||||
|
||||
CardDatabaseQuerier *querier;
|
||||
|
||||
private:
|
||||
void checkUnknownSets();
|
||||
void refreshCachedReverseRelatedCards();
|
||||
|
||||
QBasicMutex *clearDatabaseMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(),
|
||||
*removeCardMutex = new QBasicMutex();
|
||||
|
||||
public:
|
||||
explicit CardDatabase(QObject *parent = nullptr);
|
||||
~CardDatabase() override;
|
||||
|
||||
void removeCard(CardInfoPtr card);
|
||||
void clear();
|
||||
|
||||
const CardNameMap &getCardList() const
|
||||
{
|
||||
return cards;
|
||||
}
|
||||
CardSetPtr getSet(const QString &setName);
|
||||
CardSetList getSetList() const;
|
||||
LoadStatus getLoadStatus() const
|
||||
{
|
||||
return loadStatus;
|
||||
}
|
||||
CardDatabaseQuerier *query() const
|
||||
{
|
||||
return querier;
|
||||
}
|
||||
void enableAllUnknownSets();
|
||||
void markAllSetsAsKnown();
|
||||
void notifyEnabledSetsChanged();
|
||||
|
||||
public slots:
|
||||
void addCard(CardInfoPtr card);
|
||||
void addSet(CardSetPtr set);
|
||||
void loadCardDatabases();
|
||||
bool saveCustomTokensToFile();
|
||||
signals:
|
||||
void cardDatabaseLoadingFinished();
|
||||
void cardDatabaseLoadingFailed();
|
||||
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
||||
void cardDatabaseAllNewSetsEnabled();
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
void cardAdded(CardInfoPtr card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
|
||||
friend class CardDatabaseLoader;
|
||||
friend class CardDatabaseQuerier;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
#include "card_database_loader.h"
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "card_database.h"
|
||||
#include "parser/cockatrice_xml_3.h"
|
||||
#include "parser/cockatrice_xml_4.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QTime>
|
||||
|
||||
CardDatabaseLoader::CardDatabaseLoader(QObject *parent, CardDatabase *db) : QObject(parent), database(db)
|
||||
{
|
||||
// instantiate available parsers here and connect them to the database
|
||||
availableParsers << new CockatriceXml4Parser;
|
||||
availableParsers << new CockatriceXml3Parser;
|
||||
|
||||
for (auto *p : availableParsers) {
|
||||
// connect parser outputs to the database adders
|
||||
connect(p, &ICardDatabaseParser::addCard, database, &CardDatabase::addCard, Qt::DirectConnection);
|
||||
connect(p, &ICardDatabaseParser::addSet, database, &CardDatabase::addSet, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
// when SettingsCache's path changes, trigger reloads
|
||||
connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this,
|
||||
&CardDatabaseLoader::loadCardDatabases);
|
||||
}
|
||||
|
||||
CardDatabaseLoader::~CardDatabaseLoader()
|
||||
{
|
||||
qDeleteAll(availableParsers);
|
||||
availableParsers.clear();
|
||||
}
|
||||
|
||||
LoadStatus CardDatabaseLoader::loadFromFile(const QString &fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
if (!file.isOpen()) {
|
||||
return FileError;
|
||||
}
|
||||
|
||||
for (auto parser : availableParsers) {
|
||||
file.reset();
|
||||
if (parser->getCanParseFile(fileName, file)) {
|
||||
file.reset();
|
||||
parser->parseFile(file);
|
||||
return Ok;
|
||||
}
|
||||
}
|
||||
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
LoadStatus CardDatabaseLoader::loadCardDatabase(const QString &path)
|
||||
{
|
||||
auto startTime = QTime::currentTime();
|
||||
LoadStatus tempLoadStatus = NotLoaded;
|
||||
if (!path.isEmpty()) {
|
||||
QMutexLocker locker(loadFromFileMutex);
|
||||
tempLoadStatus = loadFromFile(path);
|
||||
}
|
||||
|
||||
int msecs = startTime.msecsTo(QTime::currentTime());
|
||||
qCInfo(CardDatabaseLoadingLog) << "Loaded card database: Path =" << path << "Status =" << tempLoadStatus
|
||||
<< "Cards =" << (database ? database->cards.size() : 0)
|
||||
<< "Sets =" << (database ? database->sets.size() : 0) << QString("%1ms").arg(msecs);
|
||||
|
||||
return tempLoadStatus;
|
||||
}
|
||||
|
||||
LoadStatus CardDatabaseLoader::loadCardDatabases()
|
||||
{
|
||||
QMutexLocker locker(reloadDatabaseMutex);
|
||||
|
||||
if (!database) {
|
||||
qCWarning(CardDatabaseLoadingLog) << "Loader has no database pointer";
|
||||
emit loadingFailed();
|
||||
return FileError;
|
||||
}
|
||||
emit loadingStarted();
|
||||
qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started";
|
||||
|
||||
database->clear(); // remove old db
|
||||
|
||||
LoadStatus loadStatus =
|
||||
loadCardDatabase(SettingsCache::instance().getCardDatabasePath()); // load main card database
|
||||
loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database
|
||||
loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database
|
||||
|
||||
// find all custom card databases, recursively & following symlinks
|
||||
// then load them alphabetically
|
||||
const QStringList customPaths = collectCustomDatabasePaths();
|
||||
for (int i = 0; i < customPaths.size(); ++i) {
|
||||
const auto &p = customPaths.at(i);
|
||||
qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")";
|
||||
loadCardDatabase(p);
|
||||
}
|
||||
|
||||
// AFTER all the cards have been loaded
|
||||
|
||||
// resolve the reverse-related tags
|
||||
|
||||
database->refreshCachedReverseRelatedCards();
|
||||
|
||||
if (loadStatus == Ok) {
|
||||
database->checkUnknownSets(); // update deck editors, etc
|
||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
|
||||
emit loadingFinished();
|
||||
} else {
|
||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
|
||||
emit loadingFailed(); // bring up the settings dialog
|
||||
}
|
||||
|
||||
return loadStatus;
|
||||
}
|
||||
|
||||
QStringList CardDatabaseLoader::collectCustomDatabasePaths() const
|
||||
{
|
||||
QDirIterator it(SettingsCache::instance().getCustomCardDatabasePath(), {"*.xml"}, QDir::Files,
|
||||
QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
|
||||
QStringList paths;
|
||||
while (it.hasNext())
|
||||
paths << it.next();
|
||||
paths.sort();
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool CardDatabaseLoader::saveCustomTokensToFile()
|
||||
{
|
||||
if (!database) {
|
||||
qCWarning(CardDatabaseLog) << "saveCustomTokensToFile: database pointer missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString fileName = SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardSet::TOKENS_SETNAME + ".xml";
|
||||
|
||||
SetNameMap tmpSets;
|
||||
CardSetPtr customTokensSet = database->getSet(CardSet::TOKENS_SETNAME);
|
||||
tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet);
|
||||
|
||||
CardNameMap tmpCards;
|
||||
for (const CardInfoPtr &card : database->cards) {
|
||||
if (card->getSets().contains(CardSet::TOKENS_SETNAME)) {
|
||||
tmpCards.insert(card->getName(), card);
|
||||
}
|
||||
}
|
||||
|
||||
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
* @file card_database_loader.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabaseLoader is responsible for populating the card database from files on disk.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_LOADER_H
|
||||
#define COCKATRICE_CARD_DATABASE_LOADER_H
|
||||
|
||||
#include <QBasicMutex>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading");
|
||||
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure");
|
||||
|
||||
class CardDatabase;
|
||||
class ICardDatabaseParser;
|
||||
|
||||
enum LoadStatus
|
||||
{
|
||||
Ok,
|
||||
VersionTooOld,
|
||||
Invalid,
|
||||
NotLoaded,
|
||||
FileError,
|
||||
NoCards
|
||||
};
|
||||
|
||||
class CardDatabaseLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardDatabaseLoader(QObject *parent, CardDatabase *db);
|
||||
~CardDatabaseLoader() override;
|
||||
|
||||
public slots:
|
||||
LoadStatus loadCardDatabases(); // discover & load the configured databases
|
||||
LoadStatus loadCardDatabase(const QString &path); // load a single file
|
||||
bool saveCustomTokensToFile(); // write tokens to custom DB path
|
||||
|
||||
signals:
|
||||
void loadingStarted();
|
||||
void loadingFinished();
|
||||
void loadingFailed();
|
||||
void newSetsFound(int numSets, const QStringList &setNames);
|
||||
void allNewSetsEnabled();
|
||||
|
||||
private:
|
||||
LoadStatus loadFromFile(const QString &fileName); // internal helper
|
||||
QStringList collectCustomDatabasePaths() const;
|
||||
|
||||
CardDatabase *database; // non-owning pointer to the container
|
||||
|
||||
// parsers
|
||||
QList<ICardDatabaseParser *> availableParsers;
|
||||
|
||||
QBasicMutex *loadFromFileMutex = new QBasicMutex();
|
||||
QBasicMutex *reloadDatabaseMutex = new QBasicMutex();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_LOADER_H
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#include "card_database_manager.h"
|
||||
|
||||
CardDatabase *CardDatabaseManager::getInstance()
|
||||
{
|
||||
static CardDatabase instance; // Created only once, on first access
|
||||
return &instance;
|
||||
}
|
||||
|
||||
CardDatabaseQuerier *CardDatabaseManager::query()
|
||||
{
|
||||
return getInstance()->query();
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* @file card_database_manager.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabaseManager is responsible for managing the global database singleton.
|
||||
*/
|
||||
|
||||
#ifndef CARD_DATABASE_ACCESSOR_H
|
||||
#define CARD_DATABASE_ACCESSOR_H
|
||||
|
||||
#pragma once
|
||||
#include "card_database.h"
|
||||
|
||||
class CardDatabaseManager
|
||||
{
|
||||
public:
|
||||
// Delete copy constructor and assignment operator to enforce singleton
|
||||
CardDatabaseManager(const CardDatabaseManager &) = delete;
|
||||
CardDatabaseManager &operator=(const CardDatabaseManager &) = delete;
|
||||
|
||||
// Static method to access the singleton instance
|
||||
static CardDatabase *getInstance();
|
||||
static CardDatabaseQuerier *query();
|
||||
|
||||
private:
|
||||
CardDatabaseManager() = default; // Private constructor
|
||||
~CardDatabaseManager() = default;
|
||||
};
|
||||
|
||||
#endif // CARD_DATABASE_ACCESSOR_H
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
#include "card_database_querier.h"
|
||||
|
||||
#include "../utility/card_set_comparator.h"
|
||||
#include "card_database.h"
|
||||
|
||||
#include <qrandom.h>
|
||||
|
||||
CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent, const CardDatabase *_db) : QObject(_parent), db(_db)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cardInfo corresponding to the cardName.
|
||||
*
|
||||
* @param cardName The card name to look up
|
||||
* @return A CardInfoPtr, or null if not corresponding CardInfo is found.
|
||||
*/
|
||||
CardInfoPtr CardDatabaseQuerier::getCardInfo(const QString &cardName) const
|
||||
{
|
||||
return db->cards.value(cardName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cardInfos for a list of card names.
|
||||
*
|
||||
* @param cardNames The card names to look up
|
||||
* @return A List of CardInfoPtr. Any failed lookups will be ignored and dropped from the resulting list
|
||||
*/
|
||||
QList<CardInfoPtr> CardDatabaseQuerier::getCardInfos(const QStringList &cardNames) const
|
||||
{
|
||||
QList<CardInfoPtr> cardInfos;
|
||||
for (const QString &cardName : cardNames) {
|
||||
CardInfoPtr ptr = db->cards.value(cardName);
|
||||
if (ptr)
|
||||
cardInfos.append(ptr);
|
||||
}
|
||||
|
||||
return cardInfos;
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabaseQuerier::getCardBySimpleName(const QString &cardName) const
|
||||
{
|
||||
return db->simpleNameCards.value(CardInfo::simplifyName(cardName));
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabaseQuerier::lookupCardByName(const QString &name) const
|
||||
{
|
||||
if (auto info = getCardInfo(name))
|
||||
return info;
|
||||
if (auto info = getCardBySimpleName(name))
|
||||
return info;
|
||||
return getCardBySimpleName(CardInfo::simplifyName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cards corresponding to the CardRefs.
|
||||
* If the providerId is empty, will default to the preferred printing.
|
||||
* If providerId is given but not found, the PrintingInfo will be empty.
|
||||
*
|
||||
* @param cardRefs The cards to look up. If providerId is empty for an entry, will default to the preferred printing for
|
||||
* that entry. If providerId is given but not found, the PrintingInfo will be empty for that entry.
|
||||
* @return A list of cards. Any failed lookups will be ignored and dropped from the resulting list.
|
||||
*/
|
||||
QList<ExactCard> CardDatabaseQuerier::getCards(const QList<CardRef> &cardRefs) const
|
||||
{
|
||||
QList<ExactCard> cards;
|
||||
for (const auto &cardRef : cardRefs) {
|
||||
ExactCard card = getCard(cardRef);
|
||||
if (card)
|
||||
cards.append(card);
|
||||
}
|
||||
|
||||
return cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the card corresponding to the CardRef.
|
||||
* If the providerId is empty, will default to the preferred printing.
|
||||
* If providerId is given but not found, the PrintingInfo will be empty.
|
||||
*
|
||||
* @param cardRef The card to look up.
|
||||
* @return A specific printing of a card, or empty if not found.
|
||||
*/
|
||||
ExactCard CardDatabaseQuerier::getCard(const CardRef &cardRef) const
|
||||
{
|
||||
auto info = getCardInfo(cardRef.name);
|
||||
if (info.isNull()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (cardRef.providerId.isEmpty() || cardRef.providerId.isNull()) {
|
||||
return ExactCard(info, getPreferredPrinting(info));
|
||||
}
|
||||
|
||||
return ExactCard(info, findPrintingWithId(info, cardRef.providerId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the card by CardRef, simplifying the name if required.
|
||||
* If the providerId is empty, will default to the preferred printing.
|
||||
* If providerId is given but not found, the PrintingInfo will be empty.
|
||||
*
|
||||
* @param cardRef The card to look up.
|
||||
* @return A specific printing of a card, or empty if not found.
|
||||
*/
|
||||
ExactCard CardDatabaseQuerier::guessCard(const CardRef &cardRef) const
|
||||
{
|
||||
auto card = lookupCardByName(cardRef.name);
|
||||
auto printing =
|
||||
cardRef.providerId.isEmpty() ? getPreferredPrinting(card) : findPrintingWithId(card, cardRef.providerId);
|
||||
|
||||
return ExactCard(card, printing);
|
||||
}
|
||||
|
||||
ExactCard CardDatabaseQuerier::getRandomCard() const
|
||||
{
|
||||
if (db->cards.isEmpty())
|
||||
return {};
|
||||
|
||||
const auto keys = db->cards.keys();
|
||||
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
|
||||
const QString &randomKey = keys.at(randomIndex);
|
||||
CardInfoPtr randomCard = getCardInfo(randomKey);
|
||||
|
||||
return ExactCard{randomCard, getPreferredPrinting(randomCard)};
|
||||
}
|
||||
|
||||
ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const
|
||||
{
|
||||
// The source card does not have a printing defined, which means we can't get a card from the same set.
|
||||
if (otherPrinting == PrintingInfo()) {
|
||||
return getCard({cardName});
|
||||
}
|
||||
|
||||
// The source card does have a printing defined, which means we can attempt to get a card from the same set.
|
||||
PrintingInfo relatedPrinting = getSpecificPrinting(cardName, otherPrinting.getSet()->getCorrectedShortName(), "");
|
||||
ExactCard relatedCard(guessCard({cardName}).getCardPtr(), relatedPrinting);
|
||||
|
||||
// If we didn't find a card from the same set, just try to find any card with the same name.
|
||||
return relatedCard ? relatedCard : getCard({cardName});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the PrintingInfo in the cardInfo that has the given uuid field.
|
||||
*
|
||||
* @param cardInfo The CardInfo to search
|
||||
* @param providerId The uuid to look for
|
||||
* @return The PrintingInfo, or a default-constructed PrintingInfo if not found.
|
||||
*/
|
||||
PrintingInfo CardDatabaseQuerier::findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId) const
|
||||
{
|
||||
for (const auto &printings : cardInfo->getSets()) {
|
||||
for (const auto &printing : printings) {
|
||||
if (printing.getUuid() == providerId) {
|
||||
return printing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PrintingInfo();
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const CardRef &cardRef) const
|
||||
{
|
||||
CardInfoPtr cardInfo = getCardInfo(cardRef.name);
|
||||
if (!cardInfo) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
return findPrintingWithId(cardInfo, cardRef.providerId);
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const QString &cardName,
|
||||
const QString &setShortName,
|
||||
const QString &collectorNumber) const
|
||||
{
|
||||
CardInfoPtr cardInfo = getCardInfo(cardName);
|
||||
if (!cardInfo) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
SetToPrintingsMap setMap = cardInfo->getSets();
|
||||
if (setMap.empty()) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
for (const auto &printings : setMap) {
|
||||
for (auto &cardInfoForSet : printings) {
|
||||
if (!collectorNumber.isEmpty()) {
|
||||
if (cardInfoForSet.getSet()->getShortName() == setShortName &&
|
||||
cardInfoForSet.getProperty("num") == collectorNumber) {
|
||||
return cardInfoForSet;
|
||||
}
|
||||
} else {
|
||||
if (cardInfoForSet.getSet()->getShortName() == setShortName) {
|
||||
return cardInfoForSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the card representing the preferred printing of the cardInfo
|
||||
*
|
||||
* @param cardInfo The cardInfo to find the preferred printing for
|
||||
* @return A specific printing of a card
|
||||
*/
|
||||
ExactCard CardDatabaseQuerier::getPreferredCard(const CardInfoPtr &cardInfo) const
|
||||
{
|
||||
return ExactCard(cardInfo, getPreferredPrinting(cardInfo));
|
||||
}
|
||||
|
||||
bool CardDatabaseQuerier::isPreferredPrinting(const CardRef &cardRef) const
|
||||
{
|
||||
if (cardRef.providerId.startsWith("card_")) {
|
||||
return cardRef.providerId ==
|
||||
QLatin1String("card_") + cardRef.name + QString("_") + getPreferredPrintingProviderId(cardRef.name);
|
||||
}
|
||||
return cardRef.providerId == getPreferredPrintingProviderId(cardRef.name);
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const QString &cardName) const
|
||||
{
|
||||
CardInfoPtr cardInfo = getCardInfo(cardName);
|
||||
return getPreferredPrinting(cardInfo);
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardInfo) const
|
||||
{
|
||||
if (!cardInfo) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
SetToPrintingsMap setMap = cardInfo->getSets();
|
||||
if (setMap.empty()) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
CardSetPtr preferredSet = nullptr;
|
||||
PrintingInfo preferredPrinting;
|
||||
SetPriorityComparator comparator;
|
||||
|
||||
for (const auto &printings : setMap) {
|
||||
for (auto &printing : printings) {
|
||||
CardSetPtr currentSet = printing.getSet();
|
||||
if (!preferredSet || comparator(currentSet, preferredSet)) {
|
||||
preferredSet = currentSet;
|
||||
preferredPrinting = printing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preferredSet) {
|
||||
return preferredPrinting;
|
||||
}
|
||||
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardName) const
|
||||
{
|
||||
PrintingInfo preferredPrinting = getPreferredPrinting(cardName);
|
||||
QString uuid = preferredPrinting.getUuid();
|
||||
if (!uuid.isEmpty()) {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
CardInfoPtr defaultCardInfo = getCardInfo(cardName);
|
||||
if (defaultCardInfo.isNull()) {
|
||||
return cardName;
|
||||
}
|
||||
return defaultCardInfo->getName();
|
||||
}
|
||||
|
||||
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
|
||||
{
|
||||
QSet<QString> types;
|
||||
for (const auto &card : db->cards.values()) {
|
||||
types.insert(card->getMainCardType());
|
||||
}
|
||||
return types.values();
|
||||
}
|
||||
|
||||
QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
|
||||
{
|
||||
QMap<QString, int> typeCounts;
|
||||
|
||||
for (const auto &card : db->cards.values()) {
|
||||
QString type = card->getMainCardType();
|
||||
typeCounts[type]++;
|
||||
}
|
||||
|
||||
return typeCounts;
|
||||
}
|
||||
|
||||
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
||||
{
|
||||
QMap<QString, int> typeCounts;
|
||||
|
||||
for (const auto &card : db->cards.values()) {
|
||||
QString type = card->getCardType();
|
||||
|
||||
QStringList parts = type.split(" — ");
|
||||
|
||||
if (parts.size() > 1) { // Ensure there are subtypes
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList subtypes = parts[1].split(" ", QString::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
for (const QString &subtype : subtypes) {
|
||||
typeCounts[subtype]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return typeCounts;
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* @file card_database_querier.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabaseQuerier is responsible for querying the database and returning data.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||
#define COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||
|
||||
#include "../card/exact_card.h"
|
||||
#include "../common/card_ref.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class CardDatabase;
|
||||
class CardDatabaseQuerier : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardDatabaseQuerier(QObject *parent, const CardDatabase *db);
|
||||
|
||||
[[nodiscard]] CardInfoPtr getCardInfo(const QString &cardName) const;
|
||||
[[nodiscard]] QList<CardInfoPtr> getCardInfos(const QStringList &cardNames) const;
|
||||
|
||||
/*
|
||||
* Get a card by its simple name. The name will be simplified in this
|
||||
* function, so you don't need to simplify it beforehand.
|
||||
*/
|
||||
[[nodiscard]] CardInfoPtr getCardBySimpleName(const QString &cardName) const;
|
||||
|
||||
[[nodiscard]] ExactCard guessCard(const CardRef &cardRef) const;
|
||||
[[nodiscard]] ExactCard getCard(const CardRef &cardRef) const;
|
||||
[[nodiscard]] QList<ExactCard> getCards(const QList<CardRef> &cardRefs) const;
|
||||
|
||||
[[nodiscard]] ExactCard getRandomCard() const;
|
||||
[[nodiscard]] ExactCard getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const;
|
||||
|
||||
[[nodiscard]] ExactCard getPreferredCard(const CardInfoPtr &card) const;
|
||||
[[nodiscard]] bool isPreferredPrinting(const CardRef &cardRef) const;
|
||||
[[nodiscard]] PrintingInfo getPreferredPrinting(const CardInfoPtr &card) const;
|
||||
[[nodiscard]] PrintingInfo getPreferredPrinting(const QString &cardName) const;
|
||||
[[nodiscard]] QString getPreferredPrintingProviderId(const QString &cardName) const;
|
||||
|
||||
[[nodiscard]] PrintingInfo getSpecificPrinting(const CardRef &cardRef) const;
|
||||
[[nodiscard]] PrintingInfo
|
||||
getSpecificPrinting(const QString &cardName, const QString &setCode, const QString &collectorNumber) const;
|
||||
[[nodiscard]] PrintingInfo findPrintingWithId(const CardInfoPtr &card, const QString &providerId) const;
|
||||
|
||||
[[nodiscard]] QStringList getAllMainCardTypes() const;
|
||||
[[nodiscard]] QMap<QString, int> getAllMainCardTypesWithCount() const;
|
||||
[[nodiscard]] QMap<QString, int> getAllSubCardTypesWithCount() const;
|
||||
|
||||
private:
|
||||
const CardDatabase *db;
|
||||
|
||||
CardInfoPtr lookupCardByName(const QString &name) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#include "card_completer_proxy_model.h"
|
||||
|
||||
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (filterRegularExpression().pattern().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
QString data = index.data(Qt::DisplayRole).toString();
|
||||
|
||||
// Ensure substring matching
|
||||
return data.contains(filterRegularExpression());
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* @file card_completer_proxy_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_COMPLETER_PROXY_MODEL_H
|
||||
#define CARD_COMPLETER_PROXY_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class CardCompleterProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardCompleterProxyModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // CARD_COMPLETER_PROXY_MODEL_H
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#include "card_search_model.h"
|
||||
|
||||
#include "../../../utility/levenshtein.h"
|
||||
#include "../card_database_model.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
|
||||
: QAbstractListModel(parent), sourceModel(sourceModel)
|
||||
{
|
||||
}
|
||||
|
||||
int CardSearchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return searchResults.size();
|
||||
}
|
||||
|
||||
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= searchResults.size())
|
||||
return QVariant();
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
return searchResults.at(index.row()).card->getName();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void CardSearchModel::updateSearchResults(const QString &query)
|
||||
{
|
||||
beginResetModel();
|
||||
searchResults.clear();
|
||||
|
||||
if (query.isEmpty() || !sourceModel)
|
||||
return;
|
||||
|
||||
// Set the filter for the display model
|
||||
sourceModel->setCardName(query);
|
||||
|
||||
// Collect matching cards and compute Levenshtein distance
|
||||
for (int i = 0; i < sourceModel->rowCount(); ++i) {
|
||||
QModelIndex modelIndex = sourceModel->index(i, 0);
|
||||
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
|
||||
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
|
||||
|
||||
if (!sourceDbModel || !sourceIndex.isValid())
|
||||
return;
|
||||
|
||||
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
|
||||
|
||||
if (!card)
|
||||
continue;
|
||||
|
||||
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
|
||||
searchResults.append({card, distance});
|
||||
}
|
||||
|
||||
// Sort by Levenshtein distance (lower distance = better match)
|
||||
std::sort(searchResults.begin(), searchResults.end(),
|
||||
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
|
||||
|
||||
// Keep only the top 5 results
|
||||
if (searchResults.size() > 10)
|
||||
searchResults = searchResults.mid(0, 10);
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
|
||||
emit layoutChanged();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* @file card_search_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_SEARCH_MODEL_H
|
||||
#define CARD_SEARCH_MODEL_H
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class CardSearchModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void updateSearchResults(const QString &query); // Update results based on input
|
||||
|
||||
private:
|
||||
struct SearchResult
|
||||
{
|
||||
CardInfoPtr card;
|
||||
int distance;
|
||||
};
|
||||
|
||||
CardDatabaseDisplayModel *sourceModel;
|
||||
QList<SearchResult> searchResults;
|
||||
};
|
||||
|
||||
#endif // CARD_SEARCH_MODEL_H
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
#include "card_database_display_model.h"
|
||||
|
||||
#include "card_database_model.h"
|
||||
|
||||
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
|
||||
{
|
||||
filterTree = nullptr;
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
dirtyTimer.setSingleShot(true);
|
||||
connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate);
|
||||
|
||||
loadedRowCount = 0;
|
||||
}
|
||||
|
||||
QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'},
|
||||
{L'”', L'\"'},
|
||||
{L'‘', L'\''},
|
||||
{L'’', L'\''}};
|
||||
|
||||
bool CardDatabaseDisplayModel::canFetchMore(const QModelIndex &index) const
|
||||
{
|
||||
return loadedRowCount < sourceModel()->rowCount(index);
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int remainder = sourceModel()->rowCount(index) - loadedRowCount;
|
||||
int itemsToFetch = qMin(100, remainder);
|
||||
|
||||
if (itemsToFetch == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount);
|
||||
beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1);
|
||||
|
||||
loadedRowCount += itemsToFetch;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
|
||||
QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
|
||||
|
||||
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
|
||||
bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
|
||||
// test for an exact match: isLeftType && leftString.size() == cardName.size()
|
||||
// or an exclusive start match: isLeftType && !isRightType
|
||||
if (isLeftType && (!isRightType || leftString.size() == cardName.size()))
|
||||
return true;
|
||||
|
||||
// same checks for the right string
|
||||
if (isRightType && (!isLeftType || rightString.size() == cardName.size()))
|
||||
return false;
|
||||
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
|
||||
QStringList leftList = leftString.split("/");
|
||||
QStringList rightList = rightString.split("/");
|
||||
|
||||
if (leftList.size() == 2 && rightList.size() == 2) {
|
||||
|
||||
// cool, have both P/T in list now
|
||||
int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0));
|
||||
if (lessThanNum != 0) {
|
||||
return lessThanNum < 0;
|
||||
} else {
|
||||
// power equal, check toughness
|
||||
return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right)
|
||||
{
|
||||
if (left == right) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool okLeft, okRight;
|
||||
float leftNum = left.toFloat(&okLeft);
|
||||
float rightNum = right.toFloat(&okRight);
|
||||
|
||||
if (okLeft && okRight) {
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else if (leftNum > rightNum) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// try and parsing again, for weird ones like "1+*"
|
||||
QString leftAfterNum = "";
|
||||
QString rightAfterNum = "";
|
||||
if (!okLeft) {
|
||||
int leftNumIndex = 0;
|
||||
for (; leftNumIndex < left.length(); leftNumIndex++) {
|
||||
if (!left.at(leftNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftNumIndex != 0) {
|
||||
leftNum = left.left(leftNumIndex).toFloat(&okLeft);
|
||||
leftAfterNum = left.right(leftNumIndex);
|
||||
}
|
||||
}
|
||||
if (!okRight) {
|
||||
int rightNumIndex = 0;
|
||||
for (; rightNumIndex < right.length(); rightNumIndex++) {
|
||||
if (!right.at(rightNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rightNumIndex != 0) {
|
||||
rightNum = right.left(rightNumIndex).toFloat(&okRight);
|
||||
rightAfterNum = right.right(rightNumIndex);
|
||||
}
|
||||
}
|
||||
if (okLeft && okRight) {
|
||||
|
||||
if (leftNum != rightNum) {
|
||||
// both parsed as numbers, but different number
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// both parsed, same number, but at least one has something else
|
||||
// so compare the part after the number - prefer nothing
|
||||
return QString::localeAwareCompare(leftAfterNum, rightAfterNum);
|
||||
}
|
||||
} else if (okLeft) {
|
||||
return -1;
|
||||
} else if (okRight) {
|
||||
return 1;
|
||||
}
|
||||
// couldn't parse it, just return String comparison
|
||||
return QString::localeAwareCompare(left, right);
|
||||
}
|
||||
bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
|
||||
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken()))
|
||||
return false;
|
||||
|
||||
if (filterString != nullptr) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
|
||||
return false;
|
||||
}
|
||||
return filterString->check(info);
|
||||
}
|
||||
|
||||
return rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
|
||||
{
|
||||
if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive))
|
||||
return false;
|
||||
|
||||
if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName()))
|
||||
return false;
|
||||
|
||||
if (filterTree != nullptr)
|
||||
return filterTree->acceptsCard(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::clearFilterAll()
|
||||
{
|
||||
cardName.clear();
|
||||
cardText.clear();
|
||||
cardTypes.clear();
|
||||
cardColors.clear();
|
||||
if (filterTree != nullptr)
|
||||
filterTree->clear();
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
|
||||
{
|
||||
if (this->filterTree != nullptr)
|
||||
disconnect(this->filterTree, nullptr, this, nullptr);
|
||||
|
||||
this->filterTree = _filterTree;
|
||||
connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::filterTreeChanged()
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QString CardDatabaseDisplayModel::sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table)
|
||||
{
|
||||
std::wstring toReturn = dirtyName.toStdWString();
|
||||
for (wchar_t &ch : toReturn) {
|
||||
if (table.contains(ch)) {
|
||||
ch = table.value(ch);
|
||||
}
|
||||
}
|
||||
return QString::fromStdWString(toReturn);
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* @file card_database_display_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief The CardDatabaseDisplayModel is a QSortFilterProxyModel that allows applying filters and sorting to a
|
||||
* CardDatabaseModel.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
#define COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
|
||||
#include "../../filters/filter_string.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
|
||||
class FilterTree;
|
||||
class CardDatabaseDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FilterBool
|
||||
{
|
||||
ShowTrue,
|
||||
ShowFalse,
|
||||
ShowAll
|
||||
};
|
||||
|
||||
private:
|
||||
FilterBool isToken;
|
||||
QString cardName, cardText;
|
||||
QSet<QString> cardNameSet, cardTypes, cardColors;
|
||||
FilterTree *filterTree;
|
||||
FilterString *filterString;
|
||||
int loadedRowCount;
|
||||
QTimer dirtyTimer;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
||||
public:
|
||||
explicit CardDatabaseDisplayModel(QObject *parent = nullptr);
|
||||
void setFilterTree(FilterTree *_filterTree);
|
||||
void setIsToken(FilterBool _isToken)
|
||||
{
|
||||
isToken = _isToken;
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
|
||||
void setCardName(const QString &_cardName)
|
||||
{
|
||||
if (filterString != nullptr) {
|
||||
delete filterString;
|
||||
filterString = nullptr;
|
||||
}
|
||||
cardName = sanitizeCardName(_cardName, characterTranslation);
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
void setStringFilter(const QString &_src)
|
||||
{
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
void setCardNameSet(const QSet<QString> &_cardNameSet)
|
||||
{
|
||||
cardNameSet = _cardNameSet;
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
|
||||
void dirty()
|
||||
{
|
||||
dirtyTimer.start(20);
|
||||
}
|
||||
void clearFilterAll();
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
signals:
|
||||
void modelDirty();
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
static int lessThanNumerically(const QString &left, const QString &right);
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
bool rowMatchesCardName(CardInfoPtr info) const;
|
||||
|
||||
private slots:
|
||||
void filterTreeChanged();
|
||||
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */
|
||||
const QString sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
#include "card_database_model.h"
|
||||
|
||||
#include <QMap>
|
||||
|
||||
#define CARDDBMODEL_COLUMNS 6
|
||||
|
||||
CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent)
|
||||
: QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets)
|
||||
{
|
||||
connect(db, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded);
|
||||
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved);
|
||||
connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this,
|
||||
&CardDatabaseModel::cardDatabaseEnabledSetsChanged);
|
||||
|
||||
cardDatabaseEnabledSetsChanged();
|
||||
}
|
||||
|
||||
CardDatabaseModel::~CardDatabaseModel() = default;
|
||||
|
||||
int CardDatabaseModel::rowCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return cardList.size();
|
||||
}
|
||||
|
||||
int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return CARDDBMODEL_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= cardList.size() || index.column() >= CARDDBMODEL_COLUMNS ||
|
||||
(role != Qt::DisplayRole && role != SortRole))
|
||||
return QVariant();
|
||||
|
||||
CardInfoPtr card = cardList.at(index.row());
|
||||
switch (index.column()) {
|
||||
case NameColumn:
|
||||
return card->getName();
|
||||
case SetListColumn:
|
||||
return card->getSetsNames();
|
||||
case ManaCostColumn:
|
||||
return role == SortRole ? QString("%1%2").arg(card->getCmc(), 4, QChar('0')).arg(card->getManaCost())
|
||||
: card->getManaCost();
|
||||
case CardTypeColumn:
|
||||
return card->getCardType();
|
||||
case PTColumn:
|
||||
return card->getPowTough();
|
||||
case ColorColumn:
|
||||
return card->getColors();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
if (orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return QString(tr("Name"));
|
||||
case SetListColumn:
|
||||
return QString(tr("Sets"));
|
||||
case ManaCostColumn:
|
||||
return QString(tr("Mana cost"));
|
||||
case CardTypeColumn:
|
||||
return QString(tr("Card type"));
|
||||
case PTColumn:
|
||||
return QString(tr("P/T"));
|
||||
case ColorColumn:
|
||||
return QString(tr("Color(s)"));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardInfoChanged(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1)
|
||||
return;
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, CARDDBMODEL_COLUMNS - 1));
|
||||
}
|
||||
|
||||
bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
|
||||
{
|
||||
if (!showOnlyCardsFromEnabledSets)
|
||||
return true;
|
||||
|
||||
for (const auto &printings : card->getSets()) {
|
||||
for (const auto &printing : printings) {
|
||||
if (printing.getSet()->getEnabled())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
|
||||
{
|
||||
// remove all the cards no more present in at least one enabled set
|
||||
for (const CardInfoPtr &card : cardList) {
|
||||
if (!checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
cardRemoved(card);
|
||||
}
|
||||
}
|
||||
|
||||
// re-check all the card currently not shown, maybe their part of a newly-enabled set
|
||||
for (const CardInfoPtr &card : db->getCardList()) {
|
||||
if (!cardListSet.contains(card)) {
|
||||
cardAdded(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardAdded(CardInfoPtr card)
|
||||
{
|
||||
if (checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
// add the card if it's present in at least one enabled set
|
||||
beginInsertRows(QModelIndex(), cardList.size(), cardList.size());
|
||||
cardList.append(card);
|
||||
cardListSet.insert(card);
|
||||
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardRemoved(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
disconnect(card.data(), nullptr, this, nullptr);
|
||||
cardListSet.remove(card);
|
||||
card.clear();
|
||||
cardList.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* @file card_database_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief The CardDatabaseModel maps the cardList contained in the CardDatabase as a QAbstractListModel.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASEMODEL_H
|
||||
#define CARDDATABASEMODEL_H
|
||||
|
||||
#include "../card_database.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
|
||||
class CardDatabaseModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn,
|
||||
SetListColumn,
|
||||
ManaCostColumn,
|
||||
PTColumn,
|
||||
CardTypeColumn,
|
||||
ColorColumn
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr);
|
||||
~CardDatabaseModel() override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
CardDatabase *getDatabase() const
|
||||
{
|
||||
return db;
|
||||
}
|
||||
CardInfoPtr getCard(int index) const
|
||||
{
|
||||
return cardList[index];
|
||||
}
|
||||
|
||||
private:
|
||||
QList<CardInfoPtr> cardList;
|
||||
QSet<CardInfoPtr> cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged()
|
||||
CardDatabase *db;
|
||||
bool showOnlyCardsFromEnabledSets;
|
||||
|
||||
inline bool checkCardHasAtLeastOneEnabledSet(CardInfoPtr card);
|
||||
private slots:
|
||||
void cardAdded(CardInfoPtr card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
void cardInfoChanged(CardInfoPtr card);
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#include "token_display_model.h"
|
||||
|
||||
#include "../card_database_model.h"
|
||||
|
||||
TokenDisplayModel::TokenDisplayModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* @file token_display_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
#define COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
|
||||
class TokenDisplayModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenDisplayModel(QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#include "token_edit_model.h"
|
||||
|
||||
#include "../card_database_model.h"
|
||||
|
||||
TokenEditModel::TokenEditModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenEditModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && info->getSets().contains(CardSet::TOKENS_SETNAME) && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenEditModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* @file token_edit_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
#define COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
|
||||
class TokenEditModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenEditModel(QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#include "card_database_parser.h"
|
||||
|
||||
SetNameMap ICardDatabaseParser::sets;
|
||||
|
||||
void ICardDatabaseParser::clearSetlist()
|
||||
{
|
||||
sets.clear();
|
||||
}
|
||||
|
||||
CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName,
|
||||
const QString &longName,
|
||||
const QString &setType,
|
||||
const QDate &releaseDate,
|
||||
const CardSet::Priority priority)
|
||||
{
|
||||
if (sets.contains(setName)) {
|
||||
return sets.value(setName);
|
||||
}
|
||||
|
||||
CardSetPtr newSet = CardSet::newInstance(setName);
|
||||
newSet->setLongName(longName);
|
||||
newSet->setSetType(setType);
|
||||
newSet->setReleaseDate(releaseDate);
|
||||
newSet->setPriority(priority);
|
||||
|
||||
sets.insert(setName, newSet);
|
||||
emit addSet(newSet);
|
||||
return newSet;
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* @file card_database_parser.h
|
||||
* @ingroup CardDatabaseParsers
|
||||
* @brief The ICardDatabaseParser defines the base interface for parser sub-classes.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASE_PARSER_H
|
||||
#define CARDDATABASE_PARSER_H
|
||||
|
||||
#include "../../card/card_info.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QString>
|
||||
|
||||
#define COCKATRICE_XML_XSI_NAMESPACE "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
class ICardDatabaseParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
~ICardDatabaseParser() override = default;
|
||||
|
||||
virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0;
|
||||
virtual void parseFile(QIODevice &device) = 0;
|
||||
virtual bool saveToFile(SetNameMap sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") = 0;
|
||||
static void clearSetlist();
|
||||
|
||||
protected:
|
||||
/*
|
||||
* A cached list of the available sets, needed to cross-reference sets from cards.
|
||||
* Shared between all parsers
|
||||
*/
|
||||
static SetNameMap sets;
|
||||
|
||||
CardSetPtr internalAddSet(const QString &setName,
|
||||
const QString &longName = "",
|
||||
const QString &setType = "",
|
||||
const QDate &releaseDate = QDate(),
|
||||
const CardSet::Priority priority = CardSet::PriorityFallback);
|
||||
signals:
|
||||
void addCard(CardInfoPtr card);
|
||||
void addSet(CardSetPtr set);
|
||||
};
|
||||
|
||||
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")
|
||||
|
||||
#endif
|
||||
|
|
@ -1,483 +0,0 @@
|
|||
#include "cockatrice_xml_3.h"
|
||||
|
||||
#include "../../card/card_relation.h"
|
||||
#include "../../card/card_relation_type.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <version_string.h>
|
||||
|
||||
#define COCKATRICE_XML3_TAGNAME "cockatrice_carddatabase"
|
||||
#define COCKATRICE_XML3_TAGVER 3
|
||||
#define COCKATRICE_XML3_SCHEMALOCATION \
|
||||
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v3/cards.xsd"
|
||||
|
||||
bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device)
|
||||
{
|
||||
qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName;
|
||||
|
||||
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
|
||||
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong extension";
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
if (xml.name().toString() == COCKATRICE_XML3_TAGNAME) {
|
||||
int version = xml.attributes().value("version").toString().toInt();
|
||||
if (version == COCKATRICE_XML3_TAGVER) {
|
||||
return true;
|
||||
} else {
|
||||
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong version" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong element tag" << xml.name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::parseFile(QIODevice &device)
|
||||
{
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto name = xml.name().toString();
|
||||
if (name == "sets") {
|
||||
loadSetsFromXml(xml);
|
||||
} else if (name == "cards") {
|
||||
loadCardsFromXml(xml);
|
||||
} else if (!name.isEmpty()) {
|
||||
qCInfo(CockatriceXml3Log) << "Unknown item" << name << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
|
||||
qCWarning(CockatriceXml3Log).noquote() << preamble << xml.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto name = xml.name().toString();
|
||||
if (name == "set") {
|
||||
QString shortName, longName, setType;
|
||||
QDate releaseDate;
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
name = xml.name().toString();
|
||||
|
||||
if (name == "name") {
|
||||
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "longname") {
|
||||
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "settype") {
|
||||
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "releasedate") {
|
||||
releaseDate =
|
||||
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
|
||||
} else if (!name.isEmpty()) {
|
||||
qCInfo(CockatriceXml3Log) << "Unknown set property" << name << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
internalAddSet(shortName, longName, setType, releaseDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString CockatriceXml3Parser::getMainCardType(QString &type)
|
||||
{
|
||||
QString result = type;
|
||||
/*
|
||||
Legendary Artifact Creature - Golem
|
||||
Instant // Instant
|
||||
*/
|
||||
|
||||
int pos;
|
||||
if ((pos = result.indexOf('-')) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
if ((pos = result.indexOf("—")) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
if ((pos = result.indexOf("//")) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
result = result.simplified();
|
||||
/*
|
||||
Legendary Artifact Creature
|
||||
Instant
|
||||
*/
|
||||
|
||||
if ((pos = result.lastIndexOf(' ')) != -1) {
|
||||
result = result.mid(pos + 1);
|
||||
}
|
||||
/*
|
||||
Creature
|
||||
Instant
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "card") {
|
||||
QString name = QString("");
|
||||
QString text = QString("");
|
||||
QVariantHash properties = QVariantHash();
|
||||
QString colors = QString("");
|
||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||
auto _sets = SetToPrintingsMap();
|
||||
int tableRow = 0;
|
||||
bool cipt = false;
|
||||
bool landscapeOrientation = false;
|
||||
bool isToken = false;
|
||||
bool upsideDown = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
// variable - assigned properties
|
||||
if (xmlName == "name") {
|
||||
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "text") {
|
||||
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "color" || xmlName == "colors") {
|
||||
colors.append(xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "token") {
|
||||
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
|
||||
// generic properties
|
||||
} else if (xmlName == "manacost") {
|
||||
properties.insert("manacost", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "cmc") {
|
||||
properties.insert("cmc", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "type") {
|
||||
QString type = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
properties.insert("type", type);
|
||||
properties.insert("maintype", getMainCardType(type));
|
||||
} else if (xmlName == "pt") {
|
||||
properties.insert("pt", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "loyalty") {
|
||||
properties.insert("loyalty", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
// positioning info
|
||||
} else if (xmlName == "tablerow") {
|
||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||
} else if (xmlName == "cipt") {
|
||||
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "landscapeOrientation") {
|
||||
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "upsidedown") {
|
||||
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
// sets
|
||||
} else if (xmlName == "set") {
|
||||
// NOTE: attributes must be read before readElementText()
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
PrintingInfo setInfo(internalAddSet(setName));
|
||||
if (attrs.hasAttribute("muId")) {
|
||||
setInfo.setProperty("muid", attrs.value("muId").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("muId")) {
|
||||
setInfo.setProperty("uuid", attrs.value("uuId").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("picURL")) {
|
||||
setInfo.setProperty("picurl", attrs.value("picURL").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("num")) {
|
||||
setInfo.setProperty("num", attrs.value("num").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("rarity")) {
|
||||
setInfo.setProperty("rarity", attrs.value("rarity").toString());
|
||||
}
|
||||
_sets[setName].append(setInfo);
|
||||
// related cards
|
||||
} else if (xmlName == "related" || xmlName == "reverse-related") {
|
||||
CardRelationType attach = CardRelationType::DoesNotAttach;
|
||||
bool exclude = false;
|
||||
bool variable = false;
|
||||
int count = 1;
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
if (attrs.hasAttribute("count")) {
|
||||
if (attrs.value("count").toString().indexOf("x=") == 0) {
|
||||
variable = true;
|
||||
count = attrs.value("count").toString().remove(0, 2).toInt();
|
||||
} else if (attrs.value("count").toString().indexOf("x") == 0) {
|
||||
variable = true;
|
||||
} else {
|
||||
count = attrs.value("count").toString().toInt();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("attach")) {
|
||||
attach = CardRelationType::AttachTo;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("exclude")) {
|
||||
exclude = true;
|
||||
}
|
||||
|
||||
auto *relation = new CardRelation(cardName, attach, exclude, variable, count);
|
||||
if (xmlName == "reverse-related") {
|
||||
reverseRelatedCards << relation;
|
||||
} else {
|
||||
relatedCards << relation;
|
||||
}
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml3Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (name.isEmpty()) {
|
||||
qCWarning(CockatriceXml3Log) << "Encountered card with empty name; skipping";
|
||||
continue;
|
||||
}
|
||||
|
||||
properties.insert("colors", colors);
|
||||
CardInfoPtr newCard =
|
||||
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
|
||||
landscapeOrientation, tableRow, upsideDown);
|
||||
emit addCard(newCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
|
||||
{
|
||||
if (set.isNull()) {
|
||||
qCWarning(CockatriceXml3Log) << "&operator<< set is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
xml.writeStartElement("set");
|
||||
xml.writeTextElement("name", set->getShortName());
|
||||
xml.writeTextElement("longname", set->getLongName());
|
||||
xml.writeTextElement("settype", set->getSetType());
|
||||
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
|
||||
xml.writeEndElement();
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
|
||||
{
|
||||
if (info.isNull()) {
|
||||
qCWarning(CockatriceXml3Log) << "operator<< info is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
QString tmpString;
|
||||
|
||||
xml.writeStartElement("card");
|
||||
|
||||
// variable - assigned properties
|
||||
xml.writeTextElement("name", info->getName());
|
||||
xml.writeTextElement("text", info->getText());
|
||||
if (info->getIsToken()) {
|
||||
xml.writeTextElement("token", "1");
|
||||
}
|
||||
|
||||
// generic properties
|
||||
xml.writeTextElement("manacost", info->getProperty("manacost"));
|
||||
xml.writeTextElement("cmc", info->getProperty("cmc"));
|
||||
xml.writeTextElement("type", info->getProperty("type"));
|
||||
|
||||
int colorSize = info->getColors().size();
|
||||
for (int i = 0; i < colorSize; ++i) {
|
||||
xml.writeTextElement("color", info->getColors().at(i));
|
||||
}
|
||||
|
||||
tmpString = info->getProperty("pt");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeTextElement("pt", tmpString);
|
||||
}
|
||||
|
||||
tmpString = info->getProperty("loyalty");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeTextElement("loyalty", tmpString);
|
||||
}
|
||||
|
||||
// sets
|
||||
const SetToPrintingsMap setMap = info->getSets();
|
||||
for (const auto &printings : setMap) {
|
||||
for (const PrintingInfo &set : printings) {
|
||||
xml.writeStartElement("set");
|
||||
xml.writeAttribute("rarity", set.getProperty("rarity"));
|
||||
xml.writeAttribute("muId", set.getProperty("muid"));
|
||||
xml.writeAttribute("uuId", set.getProperty("uuid"));
|
||||
|
||||
tmpString = set.getProperty("num");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeAttribute("num", tmpString);
|
||||
}
|
||||
|
||||
tmpString = set.getProperty("picurl");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeAttribute("picURL", tmpString);
|
||||
}
|
||||
|
||||
xml.writeCharacters(set.getSet()->getShortName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> related = info->getRelatedCards();
|
||||
for (auto i : related) {
|
||||
xml.writeStartElement("related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", "attach");
|
||||
}
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
|
||||
for (auto i : reverseRelated) {
|
||||
xml.writeStartElement("reverse-related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", "attach");
|
||||
}
|
||||
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// positioning
|
||||
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
||||
if (info->getCipt()) {
|
||||
xml.writeTextElement("cipt", "1");
|
||||
}
|
||||
if (info->getLandscapeOrientation()) {
|
||||
xml.writeTextElement("landscapeOrientation", "1");
|
||||
}
|
||||
if (info->getUpsideDownArt()) {
|
||||
xml.writeTextElement("upsidedown", "1");
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // card
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
bool CockatriceXml3Parser::saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl,
|
||||
const QString &sourceVersion)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter xml(&file);
|
||||
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
xml.writeStartElement(COCKATRICE_XML3_TAGNAME);
|
||||
xml.writeAttribute("version", QString::number(COCKATRICE_XML3_TAGVER));
|
||||
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
|
||||
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML3_SCHEMALOCATION);
|
||||
|
||||
xml.writeStartElement("info");
|
||||
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
|
||||
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
xml.writeTextElement("sourceUrl", sourceUrl);
|
||||
xml.writeTextElement("sourceVersion", sourceVersion);
|
||||
xml.writeEndElement();
|
||||
|
||||
if (_sets.count() > 0) {
|
||||
xml.writeStartElement("sets");
|
||||
for (CardSetPtr set : _sets) {
|
||||
xml << set;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
if (cards.count() > 0) {
|
||||
xml.writeStartElement("cards");
|
||||
for (CardInfoPtr card : cards) {
|
||||
xml << card;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // cockatrice_carddatabase
|
||||
xml.writeEndDocument();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* @file cockatrice_xml_3.h
|
||||
* @ingroup CardDatabaseParsers
|
||||
* @brief The CockatriceXml3Parser is capable of parsing version 3 of the Cockatrice XML Schema.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_XML3_H
|
||||
#define COCKATRICE_XML3_H
|
||||
|
||||
#include "card_database_parser.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CockatriceXml3Log, "cockatrice_xml.xml_3_parser");
|
||||
|
||||
class CockatriceXml3Parser : public ICardDatabaseParser
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(ICardDatabaseParser)
|
||||
public:
|
||||
CockatriceXml3Parser() = default;
|
||||
~CockatriceXml3Parser() override = default;
|
||||
bool getCanParseFile(const QString &name, QIODevice &device) override;
|
||||
void parseFile(QIODevice &device) override;
|
||||
bool saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") override;
|
||||
|
||||
private:
|
||||
void loadCardsFromXml(QXmlStreamReader &xml);
|
||||
void loadSetsFromXml(QXmlStreamReader &xml);
|
||||
QString getMainCardType(QString &type);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,441 +0,0 @@
|
|||
#include "cockatrice_xml_4.h"
|
||||
|
||||
#include "../../card/card_relation.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <version_string.h>
|
||||
|
||||
#define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase"
|
||||
#define COCKATRICE_XML4_TAGVER 4
|
||||
#define COCKATRICE_XML4_SCHEMALOCATION \
|
||||
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v4/cards.xsd"
|
||||
|
||||
bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &device)
|
||||
{
|
||||
qCInfo(CockatriceXml4Log) << "Trying to parse: " << fileName;
|
||||
|
||||
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
|
||||
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong extension";
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
if (xml.name().toString() == COCKATRICE_XML4_TAGNAME) {
|
||||
int version = xml.attributes().value("version").toString().toInt();
|
||||
if (version == COCKATRICE_XML4_TAGVER) {
|
||||
return true;
|
||||
} else {
|
||||
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong version" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong element tag" << xml.name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::parseFile(QIODevice &device)
|
||||
{
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "sets") {
|
||||
loadSetsFromXml(xml);
|
||||
} else if (xmlName == "cards") {
|
||||
loadCardsFromXml(xml);
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml4Log) << "Unknown item" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
|
||||
qCWarning(CockatriceXml4Log).noquote() << preamble << xml.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "set") {
|
||||
QString shortName, longName, setType;
|
||||
QDate releaseDate;
|
||||
short priority;
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
if (xmlName == "name") {
|
||||
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "longname") {
|
||||
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "settype") {
|
||||
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "releasedate") {
|
||||
releaseDate =
|
||||
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
|
||||
} else if (xmlName == "priority") {
|
||||
priority = xml.readElementText(QXmlStreamReader::IncludeChildElements).toShort();
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml4Log) << "Unknown set property" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
internalAddSet(shortName, longName, setType, releaseDate, static_cast<CardSet::Priority>(priority));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
QVariantHash properties = QVariantHash();
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (!xmlName.isEmpty()) {
|
||||
properties.insert(xmlName, xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
bool includeRebalancedCards = SettingsCache::instance().getIncludeRebalancedCards();
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
|
||||
if (xmlName == "card") {
|
||||
QString name = QString("");
|
||||
QString text = QString("");
|
||||
QVariantHash properties = QVariantHash();
|
||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||
auto _sets = SetToPrintingsMap();
|
||||
int tableRow = 0;
|
||||
bool cipt = false;
|
||||
bool landscapeOrientation = false;
|
||||
bool isToken = false;
|
||||
bool upsideDown = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
// variable - assigned properties
|
||||
if (xmlName == "name") {
|
||||
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "text") {
|
||||
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "token") {
|
||||
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
|
||||
// generic properties
|
||||
} else if (xmlName == "prop") {
|
||||
properties = loadCardPropertiesFromXml(xml);
|
||||
// positioning info
|
||||
} else if (xmlName == "tablerow") {
|
||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||
} else if (xmlName == "cipt") {
|
||||
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "landscapeOrientation") {
|
||||
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "upsidedown") {
|
||||
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
// sets
|
||||
} else if (xmlName == "set") {
|
||||
// NOTE: attributes but be read before readElementText()
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
auto set = internalAddSet(setName);
|
||||
if (set->getEnabled()) {
|
||||
PrintingInfo printingInfo(set);
|
||||
for (QXmlStreamAttribute attr : attrs) {
|
||||
QString attrName = attr.name().toString();
|
||||
if (attrName == "picURL")
|
||||
attrName = "picurl";
|
||||
printingInfo.setProperty(attrName, attr.value().toString());
|
||||
}
|
||||
|
||||
// This is very much a hack and not the right place to
|
||||
// put this check, as it requires a reload of Cockatrice
|
||||
// to be apply.
|
||||
//
|
||||
// However, this is also true of the `set->getEnabled()`
|
||||
// check above (which is currently bugged as well), so
|
||||
// we'll fix both at the same time.
|
||||
if (includeRebalancedCards || printingInfo.getProperty("isRebalanced") != "true") {
|
||||
_sets[setName].append(printingInfo);
|
||||
}
|
||||
}
|
||||
// related cards
|
||||
} else if (xmlName == "related" || xmlName == "reverse-related") {
|
||||
CardRelationType attachType = CardRelationType::DoesNotAttach;
|
||||
bool exclude = false;
|
||||
bool variable = false;
|
||||
bool persistent = false;
|
||||
int count = 1;
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
if (attrs.hasAttribute("count")) {
|
||||
if (attrs.value("count").toString().indexOf("x=") == 0) {
|
||||
variable = true;
|
||||
count = attrs.value("count").toString().remove(0, 2).toInt();
|
||||
} else if (attrs.value("count").toString().indexOf("x") == 0) {
|
||||
variable = true;
|
||||
} else {
|
||||
count = attrs.value("count").toString().toInt();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("attach")) {
|
||||
attachType = attrs.value("attach").toString() == "transform" ? CardRelationType::TransformInto
|
||||
: CardRelationType::AttachTo;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("exclude")) {
|
||||
exclude = true;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("persistent")) {
|
||||
persistent = true;
|
||||
}
|
||||
|
||||
auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
|
||||
if (xmlName == "reverse-related") {
|
||||
reverseRelatedCards << relation;
|
||||
} else {
|
||||
relatedCards << relation;
|
||||
}
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml4Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (name.isEmpty()) {
|
||||
qCWarning(CockatriceXml4Log) << "Encountered card with empty name; skipping";
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInfoPtr newCard =
|
||||
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
|
||||
landscapeOrientation, tableRow, upsideDown);
|
||||
emit addCard(newCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
|
||||
{
|
||||
if (set.isNull()) {
|
||||
qCWarning(CockatriceXml4Log) << "&operator<< set is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
xml.writeStartElement("set");
|
||||
xml.writeTextElement("name", set->getShortName());
|
||||
xml.writeTextElement("longname", set->getLongName());
|
||||
xml.writeTextElement("settype", set->getSetType());
|
||||
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
|
||||
xml.writeTextElement("priority", QString::number(set->getPriority()));
|
||||
xml.writeEndElement();
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
|
||||
{
|
||||
if (info.isNull()) {
|
||||
qCWarning(CockatriceXml4Log) << "operator<< info is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
QString tmpString;
|
||||
|
||||
xml.writeStartElement("card");
|
||||
|
||||
// variable - assigned properties
|
||||
xml.writeTextElement("name", info->getName());
|
||||
xml.writeTextElement("text", info->getText());
|
||||
if (info->getIsToken()) {
|
||||
xml.writeTextElement("token", "1");
|
||||
}
|
||||
|
||||
// generic properties
|
||||
xml.writeStartElement("prop");
|
||||
for (QString propName : info->getProperties()) {
|
||||
xml.writeTextElement(propName, info->getProperty(propName));
|
||||
}
|
||||
xml.writeEndElement();
|
||||
|
||||
// sets
|
||||
for (const auto &printings : info->getSets()) {
|
||||
for (const PrintingInfo &set : printings) {
|
||||
xml.writeStartElement("set");
|
||||
for (const QString &propName : set.getProperties()) {
|
||||
xml.writeAttribute(propName, set.getProperty(propName));
|
||||
}
|
||||
|
||||
xml.writeCharacters(set.getSet()->getShortName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> related = info->getRelatedCards();
|
||||
for (auto i : related) {
|
||||
xml.writeStartElement("related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", i->getAttachTypeAsString());
|
||||
}
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
if (i->getIsPersistent()) {
|
||||
xml.writeAttribute("persistent", "persistent");
|
||||
}
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
|
||||
for (auto i : reverseRelated) {
|
||||
xml.writeStartElement("reverse-related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", i->getAttachTypeAsString());
|
||||
}
|
||||
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsPersistent()) {
|
||||
xml.writeAttribute("persistent", "persistent");
|
||||
}
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// positioning
|
||||
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
||||
if (info->getCipt()) {
|
||||
xml.writeTextElement("cipt", "1");
|
||||
}
|
||||
if (info->getLandscapeOrientation()) {
|
||||
xml.writeTextElement("landscapeOrientation", "1");
|
||||
}
|
||||
if (info->getUpsideDownArt()) {
|
||||
xml.writeTextElement("upsidedown", "1");
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // card
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
bool CockatriceXml4Parser::saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl,
|
||||
const QString &sourceVersion)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter xml(&file);
|
||||
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
xml.writeStartElement(COCKATRICE_XML4_TAGNAME);
|
||||
xml.writeAttribute("version", QString::number(COCKATRICE_XML4_TAGVER));
|
||||
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
|
||||
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML4_SCHEMALOCATION);
|
||||
|
||||
xml.writeStartElement("info");
|
||||
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
|
||||
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
xml.writeTextElement("sourceUrl", sourceUrl);
|
||||
xml.writeTextElement("sourceVersion", sourceVersion);
|
||||
xml.writeEndElement();
|
||||
|
||||
if (_sets.count() > 0) {
|
||||
xml.writeStartElement("sets");
|
||||
for (CardSetPtr set : _sets) {
|
||||
xml << set;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
if (cards.count() > 0) {
|
||||
xml.writeStartElement("cards");
|
||||
for (CardInfoPtr card : cards) {
|
||||
xml << card;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // cockatrice_carddatabase
|
||||
xml.writeEndDocument();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* @file cockatrice_xml_4.h
|
||||
* @ingroup CardDatabaseParsers
|
||||
* @brief The CockatriceXml4Parser is capable of parsing version 4 of the Cockatrice XML Schema.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_XML4_H
|
||||
#define COCKATRICE_XML4_H
|
||||
|
||||
#include "card_database_parser.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CockatriceXml4Log, "cockatrice_xml.xml_4_parser");
|
||||
|
||||
class CockatriceXml4Parser : public ICardDatabaseParser
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(ICardDatabaseParser)
|
||||
public:
|
||||
CockatriceXml4Parser() = default;
|
||||
~CockatriceXml4Parser() override = default;
|
||||
bool getCanParseFile(const QString &name, QIODevice &device) override;
|
||||
void parseFile(QIODevice &device) override;
|
||||
bool saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") override;
|
||||
|
||||
private:
|
||||
QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml);
|
||||
void loadCardsFromXml(QXmlStreamReader &xml);
|
||||
void loadSetsFromXml(QXmlStreamReader &xml);
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue