File indexing completed on 2024-05-12 05:10:06

0001 /***************************************************************************
0002     Copyright (C) 2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include <config.h>
0026 #include "tellicoreadtest.h"
0027 
0028 #include "../translators/tellicoimporter.h"
0029 #include "../collections/bookcollection.h"
0030 #include "../collections/bibtexcollection.h"
0031 #include "../collections/coincollection.h"
0032 #include "../collections/musiccollection.h"
0033 #include "../collectionfactory.h"
0034 #include "../translators/tellicoxmlexporter.h"
0035 #include "../translators/tellico_xml.h"
0036 #include "../translators/xslthandler.h"
0037 #include "../images/imagefactory.h"
0038 #include "../images/image.h"
0039 #include "../fieldformat.h"
0040 #include "../entry.h"
0041 #include "../document.h"
0042 #include "../utils/xmlhandler.h"
0043 #include "../utils/string_utils.h"
0044 
0045 #include <KLocalizedString>
0046 
0047 #include <QTest>
0048 #include <QNetworkInterface>
0049 #include <QDate>
0050 #include <QTextCodec>
0051 #include <QStandardPaths>
0052 
0053 QTEST_GUILESS_MAIN( TellicoReadTest )
0054 
0055 #define QSL(x) QStringLiteral(x)
0056 #define TELLICOREAD_NUMBER_OF_CASES 11
0057 
0058 static bool hasNetwork() {
0059 #ifdef ENABLE_NETWORK_TESTS
0060   foreach(const QNetworkInterface& net, QNetworkInterface::allInterfaces()) {
0061     if(net.flags().testFlag(QNetworkInterface::IsUp) && !net.flags().testFlag(QNetworkInterface::IsLoopBack)) {
0062       return true;
0063     }
0064   }
0065 #endif
0066   return false;
0067 }
0068 
0069 void TellicoReadTest::initTestCase() {
0070   QStandardPaths::setTestModeEnabled(true);
0071   KLocalizedString::setApplicationDomain("tellico");
0072   // need to register this first
0073   Tellico::RegisterCollection<Tellico::Data::BookCollection> registerBook(Tellico::Data::Collection::Book, "book");
0074   Tellico::RegisterCollection<Tellico::Data::BibtexCollection> registerBibtex(Tellico::Data::Collection::Bibtex, "bibtex");
0075   Tellico::RegisterCollection<Tellico::Data::CoinCollection> registerCoin(Tellico::Data::Collection::Coin, "coin");
0076   Tellico::RegisterCollection<Tellico::Data::Collection> registerBase(Tellico::Data::Collection::Base, "entry");
0077   Tellico::RegisterCollection<Tellico::Data::MusicCollection> registerAlbum(Tellico::Data::Collection::Album, "album");
0078 
0079   for(int i = 1; i <= TELLICOREAD_NUMBER_OF_CASES; ++i) {
0080     QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/books-format%1.bc").arg(i)));
0081 
0082     Tellico::Import::TellicoImporter importer(url);
0083     Tellico::Data::CollPtr coll = importer.collection();
0084     m_collections.append(coll);
0085   }
0086   Tellico::ImageFactory::init();
0087 }
0088 
0089 void TellicoReadTest::init() {
0090   Tellico::ImageFactory::clean(true);
0091 }
0092 
0093 void TellicoReadTest::testBookCollection() {
0094   Tellico::Data::CollPtr coll1 = m_collections[0];
0095   // skip the first one
0096   for(int i = 1; i < m_collections.count(); ++i) {
0097     Tellico::Data::CollPtr coll2 = m_collections[i];
0098     QVERIFY(coll2);
0099     QCOMPARE(coll1->type(), coll2->type());
0100     QCOMPARE(coll1->title(), coll2->title());
0101     QCOMPARE(coll1->entryCount(), coll2->entryCount());
0102   }
0103 }
0104 
0105 void TellicoReadTest::testEntries() {
0106   QFETCH(QString, fieldName);
0107 
0108   Tellico::Data::FieldPtr field1 = m_collections[0]->fieldByName(fieldName);
0109 
0110   // skip the first one
0111   for(int i = 1; i < m_collections.count(); ++i) {
0112     Tellico::Data::FieldPtr field2 = m_collections[i]->fieldByName(fieldName);
0113     if(field1 && field2) {
0114       QCOMPARE(field1->name(), field2->name());
0115       QCOMPARE(field1->title(), field2->title());
0116       QCOMPARE(field1->category(), field2->category());
0117       QCOMPARE(field1->type(), field2->type());
0118       QCOMPARE(field1->flags(), field2->flags());
0119       QCOMPARE(field1->propertyList(), field2->propertyList());
0120     }
0121 
0122     for(int j = 0; j < m_collections[0]->entryCount(); ++j) {
0123       // don't test id values since the initial value has changed from 0 to 1
0124       Tellico::Data::EntryPtr entry1 = m_collections[0]->entries().at(j);
0125       Tellico::Data::EntryPtr entry2 = m_collections[i]->entries().at(j);
0126       QVERIFY(entry1);
0127       QVERIFY(entry2);
0128       QCOMPARE(entry1->field(fieldName), entry2->field(fieldName));
0129     }
0130   }
0131 }
0132 
0133 void TellicoReadTest::testEntries_data() {
0134   QTest::addColumn<QString>("fieldName");
0135 
0136   QTest::newRow("title") << QSL("title");
0137   QTest::newRow("author") << QSL("author");
0138   QTest::newRow("publisher") << QSL("publisher");
0139   QTest::newRow("keywords") << QSL("keywords");
0140   QTest::newRow("keyword") << QSL("keyword");
0141   QTest::newRow("genre") << QSL("genre");
0142   QTest::newRow("isbn") << QSL("isbn");
0143   QTest::newRow("pub_year") << QSL("pub_year");
0144   QTest::newRow("rating") << QSL("rating");
0145   QTest::newRow("comments") << QSL("comments");
0146 }
0147 
0148 void TellicoReadTest::testCoinCollection() {
0149   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/coins-format9.tc"));
0150 
0151   Tellico::Import::TellicoImporter importer(url);
0152   Tellico::Data::CollPtr coll = importer.collection();
0153 
0154   QVERIFY(coll);
0155   QCOMPARE(coll->type(), Tellico::Data::Collection::Coin);
0156 
0157   Tellico::Data::FieldPtr field = coll->fieldByName(QStringLiteral("title"));
0158   // old field has Dependent value, now is Line
0159   QVERIFY(field);
0160   QCOMPARE(field->type(), Tellico::Data::Field::Line);
0161   QCOMPARE(field->title(), QSL("Title"));
0162   QVERIFY(field->hasFlag(Tellico::Data::Field::Derived));
0163 
0164   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0165   // test creating the derived title
0166   QCOMPARE(entry->title(), QSL("1974D Jefferson Nickel 0.05"));
0167 }
0168 
0169 void TellicoReadTest::testBibtexCollection() {
0170   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/bibtex-format11.tc"));
0171 
0172   Tellico::Import::TellicoImporter importer(url);
0173   Tellico::Data::CollPtr coll = importer.collection();
0174   Tellico::Data::BibtexCollection* bColl = dynamic_cast<Tellico::Data::BibtexCollection*>(coll.data());
0175 
0176   QVERIFY(coll);
0177   QCOMPARE(coll->type(), Tellico::Data::Collection::Bibtex);
0178   QVERIFY(bColl);
0179   QVERIFY(!bColl->preamble().isEmpty());
0180 
0181   auto macroList = bColl->macroList();
0182   QCOMPARE(macroList.count(), 13); // includes 12 months plus the one in the file
0183   QVERIFY(!macroList.value(QLatin1String("SPE")).isEmpty());
0184 
0185   auto borrowerList = coll->borrowers();
0186   QCOMPARE(borrowerList.count(), 1);
0187   auto borr1 = borrowerList.front();
0188   QCOMPARE(borr1->count(), 1);
0189   QCOMPARE(borr1->name(), QStringLiteral("кириллица"));
0190 
0191   auto filterList = coll->filters();
0192   QCOMPARE(filterList.count(), 1);
0193   auto filter1 = filterList.front();
0194   QCOMPARE(filter1->name(), QStringLiteral("1990"));
0195 
0196   Tellico::Export::TellicoXMLExporter exporter(coll);
0197   exporter.setEntries(coll->entries());
0198   exporter.setOptions(exporter.options() | Tellico::Export::ExportComplete);
0199   Tellico::Import::TellicoImporter importer2(exporter.text());
0200   Tellico::Data::CollPtr coll2 = importer2.collection();
0201   Tellico::Data::BibtexCollection* bColl2 = dynamic_cast<Tellico::Data::BibtexCollection*>(coll2.data());
0202 
0203   QVERIFY(coll2);
0204   QCOMPARE(coll2->type(), coll->type());
0205   QCOMPARE(coll2->entryCount(), coll->entryCount());
0206   QVERIFY(bColl2);
0207   QCOMPARE(bColl2->preamble(), bColl->preamble());
0208   QCOMPARE(bColl2->macroList(), bColl->macroList());
0209 
0210   QCOMPARE(coll2->filters().count(), coll->filters().count());
0211   auto filter2 = coll->filters().front();
0212   QCOMPARE(filter1->name(), filter2->name());
0213   QCOMPARE(filter1->count(), filter2->count());
0214   QCOMPARE(filter1->op(), filter2->op());
0215 
0216   QCOMPARE(coll2->borrowers().count(), coll->borrowers().count());
0217   auto borr2 = coll2->borrowers().front();
0218   QCOMPARE(borr1->name(), borr2->name());
0219   QCOMPARE(borr1->uid(), borr2->uid());
0220   QCOMPARE(borr1->count(), borr2->count());
0221   auto loan1 = borr1->loans().front();
0222   auto loan2 = borr2->loans().front();
0223   QCOMPARE(loan1->loanDate(), loan2->loanDate());
0224   QCOMPARE(loan1->borrower()->name(), borr1->name());
0225   QCOMPARE(loan1->dueDate(), loan2->dueDate());
0226   QCOMPARE(loan1->note(), loan2->note());
0227   QCOMPARE(loan1->uid(), loan2->uid());
0228   QCOMPARE(loan1->entry()->title(), loan2->entry()->title());
0229 }
0230 
0231 void TellicoReadTest::testTableData() {
0232   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/tabletest.tc"));
0233 
0234   Tellico::Import::TellicoImporter importer(url);
0235   Tellico::Data::CollPtr coll = importer.collection();
0236 
0237   QVERIFY(coll);
0238   QCOMPARE(coll->entryCount(), 3);
0239 
0240   Tellico::Export::TellicoXMLExporter exporter(coll);
0241   exporter.setEntries(coll->entries());
0242   Tellico::Import::TellicoImporter importer2(exporter.text());
0243   Tellico::Data::CollPtr coll2 = importer2.collection();
0244 
0245   QVERIFY(coll2);
0246   QCOMPARE(coll2->type(), coll->type());
0247   QCOMPARE(coll2->entryCount(), coll->entryCount());
0248 
0249   foreach(Tellico::Data::EntryPtr e1, coll->entries()) {
0250     Tellico::Data::EntryPtr e2 = coll2->entryById(e1->id());
0251     QVERIFY(e2);
0252     foreach(Tellico::Data::FieldPtr f, coll->fields()) {
0253       QCOMPARE(f->name() + e1->field(f), f->name() + e2->field(f));
0254     }
0255   }
0256 
0257   // test table value concatenation
0258   Tellico::Data::EntryPtr e3(new Tellico::Data::Entry(coll));
0259   coll->addEntries(e3);
0260   QString value = QSL("11a") + Tellico::FieldFormat::delimiterString() + QSL("11b")
0261                 + Tellico::FieldFormat::columnDelimiterString() + QSL("12")
0262                 + Tellico::FieldFormat::columnDelimiterString() + QSL("13")
0263                 + Tellico::FieldFormat::rowDelimiterString() + QSL("21")
0264                 + Tellico::FieldFormat::columnDelimiterString() + QSL("22")
0265                 + Tellico::FieldFormat::columnDelimiterString() + QSL("23");
0266   e3->setField(QSL("table"), value);
0267   QStringList groups = e3->groupNamesByFieldName(QStringLiteral("table"));
0268   QCOMPARE(groups.count(), 3);
0269   // the order of the group names is not stable (it uses QSet::toList)
0270   QCOMPARE(groups.size(), 3);
0271   QVERIFY(groups.contains(QSL("11a")));
0272   QVERIFY(groups.contains(QSL("11b")));
0273   QVERIFY(groups.contains(QSL("21")));
0274 
0275   // test having empty value in table
0276   Tellico::Data::EntryPtr e = coll2->entryById(2);
0277   QVERIFY(e);
0278   const QStringList rows = Tellico::FieldFormat::splitTable(e->field(QSL("table")));
0279   QCOMPARE(rows.count(), 1);
0280   const QStringList cols = Tellico::FieldFormat::splitRow(rows.at(0));
0281   QCOMPARE(cols.count(), 3);
0282 }
0283 
0284 void TellicoReadTest::testDuplicateLoans() {
0285   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/duplicate_loan.xml"));
0286 
0287   Tellico::Import::TellicoImporter importer(url);
0288   Tellico::Data::CollPtr coll = importer.collection();
0289 
0290   QVERIFY(coll);
0291 
0292   QCOMPARE(coll->borrowers().count(), 1);
0293 
0294   Tellico::Data::BorrowerPtr bor = coll->borrowers().first();
0295   QVERIFY(bor);
0296 
0297   QCOMPARE(bor->loans().count(), 1);
0298 }
0299 
0300 void TellicoReadTest::testDuplicateBorrowers() {
0301   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/duplicate_borrower.xml"));
0302 
0303   Tellico::Import::TellicoImporter importer(url);
0304   Tellico::Data::CollPtr coll = importer.collection();
0305 
0306   QVERIFY(coll);
0307 
0308   QCOMPARE(coll->borrowers().count(), 1);
0309 
0310   Tellico::Data::BorrowerPtr bor = coll->borrowers().first();
0311   QVERIFY(bor);
0312 
0313   QCOMPARE(bor->loans().count(), 2);
0314 }
0315 
0316 void TellicoReadTest::testLocalImage() {
0317   // this is the md5 hash of the tellico.png icon, used as an image id
0318   const QString imageId(QSL("dde5bf2cbd90fad8635a26dfb362e0ff.png"));
0319   // not yet loaded
0320   QVERIFY(!Tellico::ImageFactory::self()->hasImageInMemory(imageId));
0321   QVERIFY(!Tellico::ImageFactory::self()->hasImageInfo(imageId));
0322 
0323   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/image_test.xml"));
0324   QFile f(url.toLocalFile());
0325   QVERIFY(f.exists());
0326   QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
0327 
0328   QTextStream in(&f);
0329   QString fileText = in.readAll();
0330   // replace %COVER% with image file location
0331   fileText.replace(QSL("%COVER%"),
0332                    QFINDTESTDATA("../../icons/tellico.png"));
0333 
0334   Tellico::Import::TellicoImporter importer(fileText);
0335   Tellico::Data::CollPtr coll = importer.collection();
0336   QVERIFY(coll);
0337   QCOMPARE(coll->entries().count(), 1);
0338 
0339   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0340   QVERIFY(entry);
0341   QCOMPARE(entry->field(QStringLiteral("cover")), imageId);
0342 
0343   // the image should be in local memory now
0344   QVERIFY(Tellico::ImageFactory::self()->hasImageInMemory(imageId));
0345   QVERIFY(Tellico::ImageFactory::self()->hasImageInfo(imageId));
0346 
0347   const Tellico::Data::Image& img = Tellico::ImageFactory::imageById(imageId);
0348   QVERIFY(!img.isNull());
0349 }
0350 
0351 void TellicoReadTest::testRemoteImage() {
0352   if(!hasNetwork()) QSKIP("This test requires network access", SkipSingle);
0353 
0354   // this is the md5 hash of the logo.png icon, used as an image id
0355   const QString imageId(QSL("ecaf5185c4016881aaabb4933211d5d6.png"));
0356   // not yet loaded
0357   QVERIFY(!Tellico::ImageFactory::self()->hasImageInMemory(imageId));
0358   QVERIFY(!Tellico::ImageFactory::self()->hasImageInfo(imageId));
0359 
0360   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/image_test.xml"));
0361   QFile f(url.toLocalFile());
0362   QVERIFY(f.exists());
0363   QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
0364 
0365   QTextStream in(&f);
0366   QString fileText = in.readAll();
0367   // replace %COVER% with image file location
0368   fileText.replace(QSL("%COVER%"),
0369                    QSL("https://tellico-project.org/wp-content/uploads/96-tellico.png"));
0370 
0371   Tellico::Import::TellicoImporter importer(fileText);
0372   Tellico::Data::CollPtr coll = importer.collection();
0373   QVERIFY(coll);
0374   QCOMPARE(coll->entries().count(), 1);
0375 
0376   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0377   QVERIFY(entry);
0378   QCOMPARE(entry->field(QStringLiteral("cover")), imageId);
0379 
0380   // the image should be in local memory now
0381   QVERIFY(Tellico::ImageFactory::self()->hasImageInMemory(imageId));
0382   QVERIFY(Tellico::ImageFactory::self()->hasImageInfo(imageId));
0383 
0384   const Tellico::Data::Image& img = Tellico::ImageFactory::imageById(imageId);
0385   QVERIFY(!img.isNull());
0386 }
0387 
0388 void TellicoReadTest::testDataImage() {
0389   // this is the md5 hash of the tellico.png icon, used as an image id
0390   const QString imageId(QSL("dde5bf2cbd90fad8635a26dfb362e0ff.png"));
0391   // not yet loaded
0392   QVERIFY(!Tellico::ImageFactory::self()->hasImageInMemory(imageId));
0393   QVERIFY(!Tellico::ImageFactory::self()->hasImageInfo(imageId));
0394 
0395   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("/data/image_test.xml"));
0396   QFile f(url.toLocalFile());
0397   QVERIFY(f.exists());
0398   QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
0399 
0400   Tellico::Data::Image testImage(QFINDTESTDATA("../../icons/tellico.png"));
0401   QVERIFY(!testImage.isNull());
0402   const QByteArray imgData = testImage.byteArray().toBase64();
0403 
0404   QTextStream in(&f);
0405   QString fileText = in.readAll();
0406   // replace %COVER% with image file location
0407   fileText.replace(QSL("%COVER%"),
0408                    QString::fromUtf8("data:image/png;base64,") +
0409                    QLatin1String(imgData));
0410 
0411   Tellico::Import::TellicoImporter importer(fileText);
0412   Tellico::Data::CollPtr coll = importer.collection();
0413   QVERIFY(coll);
0414   QCOMPARE(coll->entries().count(), 1);
0415 
0416   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0417   QVERIFY(entry);
0418   QCOMPARE(entry->field(QStringLiteral("cover")), imageId);
0419 
0420   // the image should be in local memory now
0421   QVERIFY(Tellico::ImageFactory::self()->hasImageInMemory(imageId));
0422   QVERIFY(Tellico::ImageFactory::self()->hasImageInfo(imageId));
0423 
0424   const Tellico::Data::Image& img = Tellico::ImageFactory::imageById(imageId);
0425   QVERIFY(!img.isNull());
0426 }
0427 
0428 void TellicoReadTest::testXMLHandler() {
0429   QFETCH(QByteArray, data);
0430   QFETCH(QString, expectedString);
0431   QFETCH(bool, changeEncoding);
0432 
0433   QString origString = QString::fromUtf8(data);
0434   QCOMPARE(Tellico::XMLHandler::readXMLData(data), expectedString);
0435   QCOMPARE(Tellico::XMLHandler::setUtf8XmlEncoding(origString), changeEncoding);
0436 }
0437 
0438 void TellicoReadTest::testXMLHandler_data() {
0439   QTest::addColumn<QByteArray>("data");
0440   QTest::addColumn<QString>("expectedString");
0441   QTest::addColumn<bool>("changeEncoding");
0442 
0443   QTest::newRow("basic") << QByteArray("<x>value</x>") << QStringLiteral("<x>value</x>") << false;
0444   QTest::newRow("utf8") << QByteArray("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<x>value</x>")
0445                         << QStringLiteral("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<x>value</x>") << false;
0446   QTest::newRow("UTF8") << QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<x>value</x>")
0447                         << QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<x>value</x>") << false;
0448   QTest::newRow("latin1") << QByteArray("<?xml version=\"1.0\" encoding=\"latin1\"?>\n<x>value</x>")
0449                           << QStringLiteral("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<x>value</x>") << true;
0450   QTest::newRow("LATIN1") << QByteArray("<?xml version=\"1.0\" encoding=\"LATIN1\"?>\n<x>value</x>")
0451                           << QStringLiteral("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<x>value</x>") << true;
0452 
0453   QString usa = QString::fromUtf8("США");
0454   QTextCodec* cp1251 = QTextCodec::codecForName("cp1251");
0455   QByteArray usaBytes = QByteArray("<?xml version=\"1.0\" encoding=\"cp1251\"?>\n<x>")
0456                       + cp1251->fromUnicode(usa)
0457                       + QByteArray("</x>");
0458   QString usaString = QStringLiteral("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<x>")
0459                     + usa
0460                     + QStringLiteral("</x>");
0461   QTest::newRow("cp1251") << usaBytes << usaString << true;
0462 }
0463 
0464 void TellicoReadTest::testXmlName() {
0465   QFETCH(bool, valid);
0466   QFETCH(QString, input);
0467   QFETCH(QString, modified);
0468 
0469   QCOMPARE(Tellico::XML::validXMLElementName(input), valid);
0470   QCOMPARE(Tellico::XML::elementName(input), modified);
0471 }
0472 
0473 void TellicoReadTest::testXmlName_data() {
0474   QTest::addColumn<bool>("valid");
0475   QTest::addColumn<QString>("input");
0476   QTest::addColumn<QString>("modified");
0477 
0478   QTest::newRow("start")  << true  << QSL("start")  << QSL("start");
0479   QTest::newRow("_start") << true  << QSL("_start") << QSL("_start");
0480   QTest::newRow("n42")    << true  << QSL("n42")    << QSL("n42");
0481   // an empty string is handled in CollectionFieldsDialog when creating the field name
0482   QTest::newRow("42")     << false << QSL("42")     << QString();
0483   QTest::newRow("she is") << false << QSL("she is") << QSL("she-is");
0484   QTest::newRow("colon:") << false << QSL("colon:") << QSL("colon");
0485   QTest::newRow("Svět")   << true  << QSL("Svět")   << QSL("Svět");
0486   QTest::newRow("<test>") << false << QSL("<test>") << QSL("test");
0487   QTest::newRow("is-€:")  << false << QSL("is-€:")  << QSL("is-");
0488 }
0489 
0490 void TellicoReadTest::testRecoverXmlName() {
0491   QFETCH(QByteArray, input);
0492   QFETCH(QByteArray, modified);
0493 
0494   QCOMPARE(Tellico::XML::recoverFromBadXMLName(input), modified);
0495 }
0496 
0497 void TellicoReadTest::testRecoverXmlName_data() {
0498   QTest::addColumn<QByteArray>("input");
0499   QTest::addColumn<QByteArray>("modified");
0500 
0501   QTest::newRow("<nr:>")   << QByteArray("<fields><field name=\"nr:\"/></fields><nr:>x</nr:>")
0502                            << QByteArray("<fields><field name=\"nr\"/></fields><nr>x</nr>");
0503   QTest::newRow("<nr:>2")  << QByteArray("<fields><field name=\"nr:\" d=\"d\"/></fields><nr:>x</nr:>")
0504                            << QByteArray("<fields><field name=\"nr\" d=\"d\"/></fields><nr>x</nr>");
0505   QTest::newRow("<nr:>3")  << QByteArray("<fields><field name=\"nr:\"/></fields><nr:s><nr:>x</nr:></nr:s>")
0506                            << QByteArray("<fields><field name=\"nr\"/></fields><nrs><nr>x</nr></nrs>");
0507   QTest::newRow("<nr:>4")  << QByteArray("<fields><field d=\"nr:\" name=\"nr:\" d=\"nr:\"/></fields><nr:>x</nr:>")
0508                            << QByteArray("<fields><field d=\"nr:\" name=\"nr\" d=\"nr:\"/></fields><nr>x</nr>");
0509   QTest::newRow("<is-€:>") << QByteArray("<fields><field name=\"is-€:\"/></fields><is-€:>x</is-€:>")
0510                            << QByteArray("<fields><field name=\"is-\"/></fields><is->x</is->");
0511 }
0512 
0513 void TellicoReadTest::testBug418067() {
0514   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/bug418067.xml")));
0515 
0516   Tellico::Import::TellicoImporter importer(url);
0517   Tellico::Data::CollPtr coll = importer.collection();
0518 
0519   QVERIFY(coll);
0520   QVERIFY(coll->hasField(QSL("lc-no.")));
0521   QVERIFY(coll->hasField(QSL("mein-wunschpreis-")));
0522 }
0523 
0524 void TellicoReadTest::testNoCreationDate() {
0525   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/no_cdate.xml")));
0526 
0527   Tellico::Import::TellicoImporter importer(url);
0528   Tellico::Data::CollPtr coll = importer.collection();
0529 
0530   QVERIFY(coll);
0531   QVERIFY(coll->hasField(QStringLiteral("cdate")));
0532   QVERIFY(coll->hasField(QStringLiteral("mdate")));
0533   QCOMPARE(coll->entries().count(), 1);
0534 
0535   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0536   QVERIFY(entry);
0537   // entry data has an mdate but no cdate
0538   // cdate should be set to same as mdate
0539   QString mdate(QStringLiteral("2020-05-30"));
0540   QCOMPARE(entry->field(QStringLiteral("cdate")), mdate);
0541   QCOMPARE(entry->field(QStringLiteral("mdate")), mdate);
0542 }
0543 
0544 void TellicoReadTest::testFutureVersion() {
0545   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/future_version.xml")));
0546 
0547   Tellico::Import::TellicoImporter importer(url);
0548   Tellico::Data::CollPtr coll = importer.collection();
0549 
0550   QVERIFY(!coll);
0551   QVERIFY(!importer.statusMessage().isEmpty());
0552 }
0553 
0554 void TellicoReadTest::testRelativeLink() {
0555   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/relative-link.xml")));
0556 
0557   Tellico::Import::TellicoImporter importer(url);
0558   Tellico::Data::CollPtr coll = importer.collection();
0559 
0560   QVERIFY(coll);
0561   QVERIFY(coll->hasField(QStringLiteral("url")));
0562   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0563   QVERIFY(entry);
0564   QCOMPARE(entry->field(QStringLiteral("url")), QLatin1String("collectorz/image.png"));
0565 
0566   Tellico::XSLTHandler handler(QFile::encodeName(QFINDTESTDATA("data/output-url.xsl")));
0567   QVERIFY(handler.isValid());
0568 
0569   Tellico::Data::Document::self()->setURL(url); // set the base url
0570   QUrl expected = url.resolved(QUrl(QLatin1String("collectorz/image.png")));
0571 
0572   Tellico::Export::TellicoXMLExporter exp(coll);
0573   exp.setEntries(coll->entries());
0574   exp.setURL(url);
0575   QString output = handler.applyStylesheet(exp.text());
0576   // first, the link should remain completely relative
0577   QVERIFY(output.contains(QLatin1String("href=\"collectorz/image.png")));
0578 
0579   exp.setOptions(exp.options() | Tellico::Export::ExportAbsoluteLinks);
0580   output = handler.applyStylesheet(exp.text());
0581   // now, the link should be absolute
0582   QVERIFY(output.contains(expected.url()));
0583 }
0584 
0585 void TellicoReadTest::testEmptyFirstTableRow() {
0586   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/table-empty-first-row.xml")));
0587 
0588   Tellico::Import::TellicoImporter importer(url);
0589   Tellico::Data::CollPtr coll = importer.collection();
0590 
0591   QVERIFY(coll);
0592   QVERIFY(coll->hasField(QStringLiteral("table")));
0593   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0594   QVERIFY(entry);
0595   const QStringList rows = Tellico::FieldFormat::splitTable(entry->field(QSL("table")));
0596   QCOMPARE(rows.count(), 2);
0597 
0598   Tellico::Export::TellicoXMLExporter exporter(coll);
0599   exporter.setEntries(coll->entries());
0600   Tellico::Import::TellicoImporter importer2(exporter.text());
0601   Tellico::Data::CollPtr coll2 = importer2.collection();
0602   QVERIFY(coll2);
0603   Tellico::Data::EntryPtr entry2 = coll2->entries().at(0);
0604   QVERIFY(entry2);
0605   const QStringList rows2 = Tellico::FieldFormat::splitTable(entry2->field(QSL("table")));
0606   QCOMPARE(rows2.count(), 2);
0607 }
0608 
0609 void TellicoReadTest::testBug443845() {
0610   // Tellico allowed data in a paragraph field that included an invalid control character
0611   // and Tellico 3.3 could load the resulting file, but Tellico 3.4 couldn't
0612   // first verify that we don't write an invalid character to the XML
0613   QString badTitle = QStringLiteral("title with control") + QChar(0x0C);
0614   Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); // add default fields
0615   QVERIFY(coll->hasField(QStringLiteral("title")));
0616   Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll));
0617   entry1->setField(QStringLiteral("title"), badTitle);
0618   coll->addEntries(entry1);
0619   Tellico::Export::TellicoXMLExporter exporter(coll);
0620   exporter.setEntries(coll->entries());
0621   // exported XML should not contain the illegal control character
0622   QVERIFY(!exporter.text().contains(QChar(0x0C)));
0623 
0624   // now since we used to allow these characters, need to make the parser robust for them
0625   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/bug443845.xml")));
0626 
0627   Tellico::Import::TellicoImporter importer(url);
0628   coll = importer.collection();
0629 
0630   QVERIFY(coll);
0631   Tellico::Data::EntryPtr entry = coll->entries().at(0);
0632   QVERIFY(entry);
0633 }
0634 
0635 void TellicoReadTest::testEmoji() {
0636   // https://www.fileformat.info/info/unicode/char/1f3e1/index.htm
0637   QString textWithEmoji = QString::fromUtf8("Title 🏡️");
0638   // stripping control codes should not affect the emoji
0639   QCOMPARE(Tellico::removeControlCodes(textWithEmoji), textWithEmoji);
0640 
0641   Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); // add default fields
0642   QVERIFY(coll->hasField(QStringLiteral("title")));
0643   Tellico::Data::FieldPtr field = coll->fieldByName(QStringLiteral("title"));
0644   QVERIFY(field);
0645   field->setTitle(textWithEmoji);
0646   Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll));
0647   entry1->setField(QStringLiteral("title"), textWithEmoji);
0648   QCOMPARE(entry1->title(), textWithEmoji);
0649   coll->addEntries(entry1);
0650   Tellico::Export::TellicoXMLExporter exporter(coll);
0651   exporter.setEntries(coll->entries());
0652 
0653   Tellico::Import::TellicoImporter importer(exporter.text());
0654   Tellico::Data::CollPtr coll2 = importer.collection();
0655   QVERIFY(coll2);
0656 
0657   Tellico::Data::FieldPtr field2 = coll2->fieldByName(QStringLiteral("title"));
0658   QVERIFY(field2);
0659   QCOMPARE(field2->title(), textWithEmoji);
0660 
0661   Tellico::Data::EntryPtr entry2 = coll2->entries().at(0);
0662   QVERIFY(entry2);
0663   QCOMPARE(entry2->title(), textWithEmoji);
0664 }
0665 
0666 void TellicoReadTest::testXmlWithJunk() {
0667   QUrl url = QUrl::fromLocalFile(QFINDTESTDATA(QSL("data/xml-with-junk.xml")));
0668 
0669   Tellico::Import::TellicoImporter importer(url);
0670   Tellico::Data::CollPtr coll = importer.collection();
0671   QVERIFY(!coll);
0672 
0673   QFile f(url.toLocalFile());
0674   QVERIFY(f.exists());
0675   QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
0676 
0677   QTextStream in(&f);
0678   QString fileText = in.readAll();
0679   Tellico::Import::TellicoImporter importer2(fileText);
0680   Tellico::Data::CollPtr coll2 = importer2.collection();
0681   QVERIFY(!coll2);
0682 }