Smarter Clipboard Pasting/Parsing (#2706)
This commit is contained in:
parent
405a719412
commit
b53cd33eed
6 changed files with 254 additions and 52 deletions
|
@ -200,9 +200,19 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, const InnerDecklis
|
|||
QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName)
|
||||
{
|
||||
CardInfo *card = db->getCard(cardName);
|
||||
if(card && card->getIsToken())
|
||||
if (card && card->getIsToken())
|
||||
return DECK_ZONE_TOKENS;
|
||||
|
||||
return currentZoneName;
|
||||
}
|
||||
|
||||
QString DeckLoader::getCompleteCardName(const QString cardName) const
|
||||
{
|
||||
if (db) {
|
||||
CardInfo *temp = db->getCardBySimpleName(cardName);
|
||||
if (temp)
|
||||
return temp->getName();
|
||||
}
|
||||
|
||||
return cardName;
|
||||
}
|
|
@ -37,6 +37,7 @@ protected:
|
|||
void saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode);
|
||||
void saveToStream_DeckZoneCards(QTextStream &out, const InnerDecklistNode *zoneNode, QList <DecklistCardNode*> cards);
|
||||
virtual QString getCardZoneFromName(QString cardName, QString currentZoneName);
|
||||
virtual QString getCompleteCardName(const QString cardName) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -466,66 +466,111 @@ bool DeckList::saveToFile_Native(QIODevice *device)
|
|||
bool DeckList::loadFromStream_Plain(QTextStream &in)
|
||||
{
|
||||
cleanList();
|
||||
QVector<QString> inputs; // QTextStream -> QVector
|
||||
|
||||
bool inSideboard = false, isSideboard = false;
|
||||
bool priorEntryIsBlank = true, isAtBeginning = true;
|
||||
int blankLines = 0;
|
||||
while (!in.atEnd())
|
||||
{
|
||||
QString line = in.readLine().simplified().toLower();
|
||||
|
||||
/*
|
||||
* Removes all blank lines at start of inputs
|
||||
* Ex: ("", "", "", "Card1", "Card2") => ("Card1", "Card2")
|
||||
*
|
||||
* This will also concise multiple blank lines in a row to just one blank
|
||||
* Ex: ("Card1", "Card2", "", "", "", "Card3") => ("Card1", "Card2", "", "Card3")
|
||||
*/
|
||||
if (line.isEmpty()) {
|
||||
if (priorEntryIsBlank || isAtBeginning)
|
||||
continue;
|
||||
|
||||
priorEntryIsBlank = true;
|
||||
blankLines++;
|
||||
} else {
|
||||
isAtBeginning = false;
|
||||
priorEntryIsBlank = false;
|
||||
}
|
||||
|
||||
inputs.push_back(line);
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes blank line at end of inputs (if applicable)
|
||||
* Ex: ("Card1", "Card2", "") => ("Card1", "Card2")
|
||||
* NOTE: Any duplicates were taken care of above, so there can be
|
||||
* at most one blank line at the very end
|
||||
*/
|
||||
if (inputs.size() && inputs.last().isEmpty())
|
||||
{
|
||||
blankLines--;
|
||||
inputs.erase(inputs.end() - 1);
|
||||
}
|
||||
|
||||
// If "Sideboard" line appears in inputs, then blank lines mean nothing
|
||||
if (inputs.contains("sideboard"))
|
||||
blankLines = 2;
|
||||
|
||||
bool inSideboard = false, titleFound = false, isSideboard;
|
||||
int okRows = 0;
|
||||
bool titleFound = false;
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().simplified();
|
||||
|
||||
// skip comments
|
||||
foreach(QString line, inputs) {
|
||||
// This is a comment line, ignore it
|
||||
if (line.startsWith("//"))
|
||||
{
|
||||
if(!titleFound)
|
||||
{
|
||||
if (!titleFound) { // Set the title to the first comment
|
||||
name = line.mid(2).trimmed();
|
||||
titleFound = true;
|
||||
} else if(okRows == 0) {
|
||||
} else if (okRows == 0) { // We haven't processed any cards yet
|
||||
comments += line.mid(2).trimmed() + "\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for sideboard prefix
|
||||
if (line.startsWith("Sideboard", Qt::CaseInsensitive)) {
|
||||
// If we have a blank line and it's the _ONLY_ blank line in the paste
|
||||
// Then we assume it means to start the sideboard section of the paste.
|
||||
// If we have the word "Sideboard" appear on any line, then that will
|
||||
// also indicate the start of the sideboard.
|
||||
if ((line.isEmpty() && blankLines == 1) || line.startsWith("sideboard")) {
|
||||
inSideboard = true;
|
||||
continue;
|
||||
continue; // The line isn't actually a card
|
||||
}
|
||||
|
||||
isSideboard = inSideboard;
|
||||
|
||||
if (line.startsWith("SB:", Qt::CaseInsensitive)) {
|
||||
if (line.startsWith("sb:")) {
|
||||
line = line.mid(3).trimmed();
|
||||
isSideboard = true;
|
||||
}
|
||||
|
||||
if (line.trimmed().isEmpty())
|
||||
continue; // The line was " " instead of "\n"
|
||||
|
||||
// Filter out MWS edition symbols and basic land extras
|
||||
QRegExp rx("\\[.*\\]");
|
||||
line.remove(rx);
|
||||
rx.setPattern("\\(.*\\)");
|
||||
line.remove(rx);
|
||||
//Filter out post card name editions
|
||||
|
||||
// Filter out post card name editions
|
||||
rx.setPattern("\\|.*$");
|
||||
line.remove(rx);
|
||||
line = line.simplified();
|
||||
|
||||
int i = line.indexOf(' ');
|
||||
int cardNameStart = i + 1;
|
||||
|
||||
// If the count ends with an 'x', ignore it. For example,
|
||||
// "4x Storm Crow" will count 4 correctly.
|
||||
if (i > 0 && line[i - 1] == 'x') {
|
||||
if (i > 0 && line[i - 1] == 'x')
|
||||
i--;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
int number = line.left(i).toInt(&ok);
|
||||
if (!ok)
|
||||
continue;
|
||||
number = 1; // If input is "cardName" assume it's "1x cardName"
|
||||
|
||||
QString cardName = line.mid(cardNameStart);
|
||||
// Common differences between cockatrice's card names
|
||||
|
||||
// Common differences between Cockatrice's card names
|
||||
// and what's commonly used in decklists
|
||||
rx.setPattern("’");
|
||||
cardName.replace(rx, "'");
|
||||
|
@ -537,20 +582,26 @@ bool DeckList::loadFromStream_Plain(QTextStream &in)
|
|||
// Replace only if the ampersand is preceded by a non-capital letter,
|
||||
// as would happen with acronyms. So 'Fire & Ice' is replaced but not
|
||||
// 'R&D' or 'R & D'.
|
||||
//
|
||||
// Qt regexes don't support lookbehind so we capture and replace
|
||||
// instead.
|
||||
// Qt regexes don't support lookbehind so we capture and replace instead.
|
||||
rx.setPattern("([^A-Z])\\s*&\\s*");
|
||||
if (rx.indexIn(cardName) != -1) {
|
||||
if (rx.indexIn(cardName) != -1)
|
||||
cardName.replace(rx, QString("%1 // ").arg(rx.cap(1)));
|
||||
}
|
||||
|
||||
// Look for the correct card zone
|
||||
QString zoneName = getCardZoneFromName(cardName, isSideboard ? DECK_ZONE_SIDE: DECK_ZONE_MAIN);
|
||||
// We need to get the name of the card from the database,
|
||||
// but we can't do that until we get the "real" name
|
||||
// (name stored in database for the card)
|
||||
// and establish a card info that is of the card, then it's
|
||||
// a simple getting the _real_ name of the card
|
||||
// (i.e. "STOrm, CrOW" => "Storm Crow")
|
||||
cardName = getCompleteCardName(cardName);
|
||||
|
||||
++okRows;
|
||||
// Look for the correct card zone of where to place the new card
|
||||
QString zoneName = getCardZoneFromName(cardName, isSideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
|
||||
|
||||
okRows++;
|
||||
new DecklistCardNode(cardName, number, getZoneObjFromName(zoneName));
|
||||
}
|
||||
|
||||
updateDeckHash();
|
||||
return (okRows > 0);
|
||||
}
|
||||
|
|
|
@ -125,6 +125,7 @@ private:
|
|||
InnerDecklistNode *getZoneObjFromName(const QString zoneName);
|
||||
protected:
|
||||
virtual QString getCardZoneFromName(QString /* cardName */, QString currentZoneName) { return currentZoneName; };
|
||||
virtual QString getCompleteCardName(const QString cardName) const { return cardName; };
|
||||
signals:
|
||||
void deckHashChanged();
|
||||
public slots:
|
||||
|
|
|
@ -39,28 +39,33 @@ struct DecklistBuilder {
|
|||
};
|
||||
|
||||
namespace {
|
||||
TEST(LoadingFromClipboardTest, EmptyDeck) {
|
||||
TEST(LoadingFromClipboardTest, EmptyDeck)
|
||||
{
|
||||
DeckList *deckList = fromClipboard(new QString(""));
|
||||
ASSERT_TRUE(deckList->getCardList().isEmpty()) << "Deck should be empty";
|
||||
ASSERT_TRUE(deckList->getCardList().isEmpty());
|
||||
}
|
||||
|
||||
TEST(LoadingFromClipboardTest, EmptySideboard) {
|
||||
DeckList *deckList = fromClipboard(new QString("Sideboard"));
|
||||
ASSERT_TRUE(deckList->getCardList().isEmpty()) << "Deck should be empty";
|
||||
ASSERT_TRUE(deckList->getCardList().isEmpty());
|
||||
}
|
||||
|
||||
TEST(LoadingFromClipboardTest, QuantityPrefixed) {
|
||||
QString *clipboard = new QString(
|
||||
"1 Mountain\n"
|
||||
"2x Island\n"
|
||||
"3X FOREST\n"
|
||||
);
|
||||
DeckList *deckList = fromClipboard(clipboard);
|
||||
|
||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({{"Mountain", 1},
|
||||
{"Island", 2}});
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"mountain", 1},
|
||||
{"island", 2},
|
||||
{"forest", 3}
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({});
|
||||
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
|
@ -97,9 +102,13 @@ namespace {
|
|||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({{"Mountain", 1}});
|
||||
CardRows expectedSideboard = CardRows({{"Mountain", 1},
|
||||
{"Island", 2}});
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"mountain", 1}
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({
|
||||
{"mountain", 1},
|
||||
{"island", 2}
|
||||
});
|
||||
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
||||
|
@ -114,12 +123,142 @@ namespace {
|
|||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({{"CardThatDoesNotExistInCardsXml", 1}});
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"cardthatdoesnotexistincardsxml", 1}
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({});
|
||||
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
||||
}
|
||||
|
||||
TEST(LoadingFromClipboardTest, RemoveBlankEntriesFromBeginningAndEnd) {
|
||||
QString *clipboard = new QString(
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"1x Algae Gharial\n"
|
||||
"3x CardThatDoesNotExistInCardsXml\n"
|
||||
"2x Phelddagrif\n"
|
||||
"\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
DeckList *deckList = fromClipboard(clipboard);
|
||||
|
||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"algae gharial", 1},
|
||||
{"cardthatdoesnotexistincardsxml", 3},
|
||||
{"phelddagrif", 2}
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({});
|
||||
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
||||
}
|
||||
|
||||
TEST(LoadingFromClipboardTest, UseFirstBlankIfOnlyOneBlankToSplitSideboard) {
|
||||
QString *clipboard = new QString(
|
||||
"1x Algae Gharial\n"
|
||||
"3x CardThatDoesNotExistInCardsXml\n"
|
||||
"\n"
|
||||
"2x Phelddagrif\n"
|
||||
);
|
||||
|
||||
DeckList *deckList = fromClipboard(clipboard);
|
||||
|
||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"algae gharial", 1},
|
||||
{"cardthatdoesnotexistincardsxml", 3}
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({
|
||||
{"phelddagrif", 2}
|
||||
});
|
||||
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
||||
}
|
||||
|
||||
TEST(LoadingFromClipboardTest, IfMultipleScatteredBlanksAllMainBoard) {
|
||||
QString *clipboard = new QString(
|
||||
"1x Algae Gharial\n"
|
||||
"3x CardThatDoesNotExistInCardsXml\n"
|
||||
"\n"
|
||||
"2x Phelddagrif\n"
|
||||
"\n"
|
||||
"3 Giant Growth\n"
|
||||
);
|
||||
|
||||
DeckList *deckList = fromClipboard(clipboard);
|
||||
|
||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"algae gharial", 1},
|
||||
{"cardthatdoesnotexistincardsxml", 3},
|
||||
{"phelddagrif", 2},
|
||||
{"giant growth", 3}
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({});
|
||||
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
||||
}
|
||||
|
||||
TEST(LoadingFromClipboardTest, LotsOfStuffInBulkTesting) {
|
||||
QString *clipboard = new QString(
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"1x test1\n"
|
||||
"testNoValueMB\n"
|
||||
"2x test2\n"
|
||||
"SB: 10 testSB\n"
|
||||
"3 test3\n"
|
||||
"4X test4\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"5x test5\n"
|
||||
"6X test6\n"
|
||||
"testNoValueSB\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
DeckList *deckList = fromClipboard(clipboard);
|
||||
|
||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
||||
deckList->forEachCard(decklistBuilder);
|
||||
|
||||
CardRows expectedMainboard = CardRows({
|
||||
{"test1", 1},
|
||||
{"test2", 2},
|
||||
{"test3", 3},
|
||||
{"test4", 4},
|
||||
{"testnovaluemb", 1}
|
||||
|
||||
});
|
||||
CardRows expectedSideboard = CardRows({
|
||||
{"testsb", 10},
|
||||
{"test5", 5},
|
||||
{"test6", 6},
|
||||
{"testnovaluesb", 1}
|
||||
|
||||
});
|
||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
|
Loading…
Reference in a new issue