File indexing completed on 2024-05-19 05:05:53

0001 /***************************************************************************
0002  *   SPDX-License-Identifier: GPL-2.0-or-later
0003  *                                                                         *
0004  *   SPDX-FileCopyrightText: 2004-2023 Thomas Fischer <fischer@unix-ag.uni-kl.de>
0005  *                                                                         *
0006  *   This program is free software; you can redistribute it and/or modify  *
0007  *   it under the terms of the GNU General Public License as published by  *
0008  *   the Free Software Foundation; either version 2 of the License, or     *
0009  *   (at your option) any later version.                                   *
0010  *                                                                         *
0011  *   This program is distributed in the hope that it will be useful,       *
0012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0014  *   GNU General Public License for more details.                          *
0015  *                                                                         *
0016  *   You should have received a copy of the GNU General Public License     *
0017  *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
0018  ***************************************************************************/
0019 
0020 #include <QtTest>
0021 
0022 #include <QStandardPaths>
0023 
0024 #include <KBibTeX>
0025 #include <Preferences>
0026 #include <Value>
0027 #include <Entry>
0028 #include <Comment>
0029 #include <File>
0030 #include <FileInfo>
0031 #include <EncoderXML>
0032 #include <EncoderLaTeX>
0033 #include <BibUtils>
0034 #include <FileImporter>
0035 #include <FileImporterBibTeX>
0036 #include <FileImporterRIS>
0037 #include <FileImporterBibUtils>
0038 #include <FileExporterBibTeX>
0039 #include <FileExporterRIS>
0040 #include <FileExporterBibUtils>
0041 #include <FileExporterXML>
0042 
0043 #include "logging_test.h"
0044 // Provides definition of KDESRCDIR
0045 #include "test-config.h"
0046 
0047 typedef QHash<FileExporterXML::OutputStyle, QString> MapFileExporterXMLOutputStyleToQString;
0048 
0049 Q_DECLARE_METATYPE(MapFileExporterXMLOutputStyleToQString)
0050 Q_DECLARE_METATYPE(QMimeType)
0051 Q_DECLARE_METATYPE(QSharedPointer<Element>)
0052 
0053 class KBibTeXIOTest : public QObject
0054 {
0055     Q_OBJECT
0056 
0057 private:
0058     File *mobyDickBibliography();
0059     File *latinUmlautBibliography();
0060     File *onlyComments(int numPercentSigns, int numLines);
0061     File *koreanBibliography();
0062     File *russianBibliography();
0063     QVector<QPair<const char *, File *> > fileImporterExporterTestCases();
0064 
0065 private Q_SLOTS:
0066     void initTestCase();
0067     void encoderConvertToPlainAscii_data();
0068     void encoderConvertToPlainAscii();
0069     void encoderXMLdecode_data();
0070     void encoderXMLdecode();
0071     void encoderXMLencode_data();
0072     void encoderXMLencode();
0073     void encoderLaTeXdecode_data();
0074     void encoderLaTeXdecode();
0075     void encoderLaTeXencode_data();
0076     void encoderLaTeXencode();
0077     void encoderLaTeXencodeHash();
0078     void fileImporterSplitName_data();
0079     void fileImporterSplitName();
0080     void fileInfoMimeTypeForUrl_data();
0081     void fileInfoMimeTypeForUrl();
0082     void fileInfoUrlsInText_data();
0083     void fileInfoUrlsInText();
0084     void fileExporterXMLsave_data();
0085     void fileExporterXMLsave();
0086     void fileExporterRISsave_data();
0087     void fileExporterRISsave();
0088     void fileExporterBibTeXsave_data();
0089     void fileExporterBibTeXsave();
0090     void fileImporterRISload_data();
0091     void fileImporterRISload();
0092     void fileImporterBibTeXload_data();
0093     void fileImporterBibTeXload();
0094     void fileExporterBibTeXEncoding_data();
0095     void fileExporterBibTeXEncoding();
0096     void fileImportExportBibTeXroundtrip_data();
0097     void fileImportExportBibTeXroundtrip();
0098     void protectiveCasingEntryGeneratedOnTheFly();
0099     void protectiveCasingEntryFromData();
0100     void partialBibTeXInput_data();
0101     void partialBibTeXInput();
0102     void partialRISInput_data();
0103     void partialRISInput();
0104     void jabRefFieldFile_data();
0105     void jabRefFieldFile();
0106 
0107 private:
0108 };
0109 
0110 void KBibTeXIOTest::encoderConvertToPlainAscii_data()
0111 {
0112     QTest::addColumn<QString>("unicodestring");
0113     /// Depending on the chosen implementation for Encoder::instance().convertToPlainAscii(),
0114     /// the ASCII variant may slightly differ (both alternatives are considered valid).
0115     /// If both implementations produce the same ASCII output, 'asciialternative2' is
0116     /// to be set to be empty.
0117     QTest::addColumn<QString>("asciialternative1");
0118     QTest::addColumn<QString>("asciialternative2");
0119 
0120     QTest::newRow("Just 'A'") << QString(QChar(0x00c0)) + QChar(0x00c2) + QChar(0x00c5) << QStringLiteral("AAA") << QString();
0121     QTest::newRow("Just ASCII letters and numbers") << QStringLiteral("qwertyuiopASDFGHJKLzxcvbnm1234567890") << QStringLiteral("qwertyuiopASDFGHJKLzxcvbnm1234567890") << QString();
0122     QTest::newRow("Latin text") << QStringLiteral("Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.") << QStringLiteral("Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.") << QString();
0123     QTest::newRow("ASCII low and high bytes") << QStringLiteral("\x00\x01\x09\x0a\x10\x11\x19\x1a\x1f\x20\x7e\x7f") << QStringLiteral(" ~") << QString();
0124     QTest::newRow("European Scripts/Latin-1 Supplement") << QString::fromUtf8("\xc3\x80\xc3\x82\xc3\x84\xc3\x92\xc3\x94\xc3\x96\xc3\xac\xc3\xad\xc3\xae\xc3\xaf") << QStringLiteral("AAAOOOiiii") << QStringLiteral("AAAEOOOEiiii");
0125     QTest::newRow("European Scripts/Latin Extended-A") << QString::fromUtf8("\xc4\x8a\xc4\x8b\xc4\xae\xc4\xaf\xc5\x9c\xc5\x9d\xc5\xbb\xc5\xbc") << QStringLiteral("CcIiSsZz") << QString();
0126     QTest::newRow("European Scripts/Latin Extended-B") << QString::fromUtf8("\xc7\x8a\xc7\x8b\xc7\x8c") << QStringLiteral("NJNjnj") << QString();
0127     QTest::newRow("European Scripts/Latin Extended Additional") << QString::fromUtf8("\xe1\xb8\xbe\xe1\xb8\xbf\xe1\xb9\xa4\xe1\xb9\xa5\xe1\xbb\xae\xe1\xbb\xaf") << QStringLiteral("MmSsUu") << QString();
0128     QTest::newRow("European Scripts/Cyrillic") << QString::fromUtf8("\xd0\x90\xd0\x9e\xd0\x9f") << QStringLiteral("AOP") << QString();
0129     QTest::newRow("European Scripts/Greek and Coptic") << QString::fromUtf8("\xce\xba\xce\xb1\xce\xa4\xcf\xba\xce\x9d") << QStringLiteral("kaTSN") << QStringLiteral("kappaalphaTauSanNu");
0130     QTest::newRow("East Asian Scripts/Katakana") << QString::fromUtf8("\xe3\x82\xb7\xe3\x83\x84") << QStringLiteral("shitsu") << QStringLiteral("situ");
0131     QTest::newRow("East Asian Scripts/Hangul Syllables") << QString::fromUtf8("\xea\xb9\x80\xec\xa0\x95\xec\x9d\x80") << QStringLiteral("gimjeongeun") << QStringLiteral("gimjeong-eun");
0132     QTest::newRow("Non-BMP characters (stay unchanged)") << QString::fromUtf8(/* U+10437 */ "\xf0\x90\x90\xb7" /* U+10E6D */ "\xf0\x90\xb9\xad" /* U+1D11E */ "\xf0\x9d\x84\x9e" /* U+10FFFF */ "") << QString::fromUtf8("\xf0\x90\x90\xb7\xf0\x90\xb9\xad\xf0\x9d\x84\x9e") << QString();
0133     QTest::newRow("Base symbols followed by combining symbols") << QString::fromUtf8("123" /* COMBINING GRAVE ACCENT */ "A\xcc\x80" /* COMBINING DIAERESIS */ "A\xcc\x88" /* COMBINING LOW LINE */ "A\xcc\xb2" "123") << QStringLiteral("123AAA123") << QString();
0134 }
0135 
0136 void KBibTeXIOTest::encoderConvertToPlainAscii()
0137 {
0138     QFETCH(QString, unicodestring);
0139     QFETCH(QString, asciialternative1);
0140     QFETCH(QString, asciialternative2);
0141 
0142     const QString converted = Encoder::instance().convertToPlainAscii(unicodestring);
0143     /// Depending on the chosen implementation for Encoder::instance().convertToPlainAscii(),
0144     /// the ASCII variant may slightly differ (both alternatives are considered valid).
0145     if (converted != asciialternative1 && converted != asciialternative2)
0146         qCWarning(LOG_KBIBTEX_TEST) << "converted=" << converted << "  asciialternative1=" << asciialternative1 << "  asciialternative2=" << asciialternative2;
0147     QVERIFY(converted == asciialternative1 || converted == asciialternative2);
0148 }
0149 
0150 void KBibTeXIOTest::encoderXMLdecode_data()
0151 {
0152     QTest::addColumn<QString>("xml");
0153     QTest::addColumn<QString>("unicode");
0154 
0155     QTest::newRow("Just ASCII") << QStringLiteral("Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.") << QStringLiteral("Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.");
0156     QTest::newRow("Quotation marks") << QStringLiteral("Caesar said: &quot;Veni, vidi, vici&quot;") << QStringLiteral("Caesar said: \"Veni, vidi, vici\"");
0157     QTest::newRow("Characters from EncoderXMLCharMapping") << QStringLiteral("&quot;&amp;&lt;&gt;") << QStringLiteral("\"\\&<>");
0158     QTest::newRow("Characters from backslashSymbols") << QStringLiteral("&amp;%_") << QStringLiteral("\\&\\%\\_");
0159 
0160     for (int start = 0; start < 16; ++start) {
0161         QString xmlString, unicodeString;
0162         for (int offset = 1561; offset < 6791; offset += 621) {
0163             const ushort unicode = static_cast<ushort>((start * 3671 + offset) & 0x7fff);
0164             xmlString += QStringLiteral("&#") + QString::number(unicode) + QStringLiteral(";");
0165             unicodeString += QChar(unicode);
0166         }
0167         QTest::newRow(QString(QStringLiteral("Some arbitrary Unicode characters (%1): %2")).arg(start).arg(xmlString).toLatin1().constData()) << xmlString << unicodeString;
0168     }
0169 }
0170 
0171 void KBibTeXIOTest::encoderXMLdecode()
0172 {
0173     QFETCH(QString, xml);
0174     QFETCH(QString, unicode);
0175 
0176     QCOMPARE(EncoderXML::instance().decode(xml), unicode);
0177 }
0178 
0179 void KBibTeXIOTest::encoderXMLencode_data()
0180 {
0181     encoderXMLdecode_data();
0182 }
0183 
0184 void KBibTeXIOTest::encoderXMLencode()
0185 {
0186     QFETCH(QString, xml);
0187     QFETCH(QString, unicode);
0188 
0189     QCOMPARE(EncoderXML::instance().encode(unicode, Encoder::TargetEncoding::ASCII), xml);
0190 }
0191 
0192 void KBibTeXIOTest::encoderLaTeXdecode_data()
0193 {
0194     QTest::addColumn<QString>("latex");
0195     QTest::addColumn<QString>("unicode");
0196     QTest::addColumn<QString>("alternativelatex");
0197 
0198     QTest::newRow("Just ASCII") << QStringLiteral("Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.") << QStringLiteral("Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.") << QString();
0199     QTest::newRow("Dotless i and j characters") << QStringLiteral("{\\`\\i}{\\`{\\i}}{\\'\\i}{\\^\\i}{\\\"\\i}{\\~\\i}{\\=\\i}{\\u\\i}{\\k\\i}{\\^\\j}{\\m\\i}{\\v\\i}{\\v\\j}\\m\\i") << QString(QChar(0x00EC)) + QChar(0x00EC) + QChar(0x00ED) + QChar(0x00EE) + QChar(0x00EF) + QChar(0x0129) + QChar(0x012B) + QChar(0x012D) + QChar(0x012F) + QChar(0x0135) + QStringLiteral("{\\m\\i}")  + QChar(0x01D0) + QChar(0x01F0) + QStringLiteral("\\m\\i") <<  QStringLiteral("{\\`\\i}{\\`\\i}{\\'\\i}{\\^\\i}{\\\"\\i}{\\~\\i}{\\=\\i}{\\u\\i}{\\k\\i}{\\^\\j}{\\m\\i}{\\v\\i}{\\v\\j}\\m\\i");
0200     QTest::newRow("\\l and \\ldots") << QStringLiteral("\\l\\ldots\\l\\ldots") << QString(QChar(0x0142)) + QChar(0x2026) + QChar(0x0142) + QChar(0x2026) << QStringLiteral("{\\l}{\\ldots}{\\l}{\\ldots}");
0201     QTest::newRow("Various two-letter commands (1)") << QStringLiteral("\\AA\\textmu") << QString(QChar(0x00c5)) + QChar(0x03bc) << QStringLiteral("{\\AA}{\\textmu}");
0202     QTest::newRow("Various two-letter commands (2)") << QStringLiteral("{\\AA}{\\textmu}") << QString(QChar(0x00c5)) + QChar(0x03bc) << QStringLiteral("{\\AA}{\\textmu}");
0203     QTest::newRow("Various two-letter commands (3)") << QStringLiteral("\\AA \\textmu") << QString(QChar(0x00c5)) + QChar(0x03bc) << QStringLiteral("{\\AA}{\\textmu}");
0204     QTest::newRow("Inside curly brackets: modifier plus letter") << QStringLiteral("aa{\\\"A}bb{\\\"T}") << QStringLiteral("aa") + QChar(0x00c4) + QStringLiteral("bb{\\\"T}") << QString();
0205     QTest::newRow("Inside curly brackets: modifier plus, inside curly brackets, letter") << QStringLiteral("aa{\\\"{A}}bb{\\\"{T}}") << QStringLiteral("aa") + QChar(0x00c4) + QStringLiteral("bb{\\\"{T}}") <<  QStringLiteral("aa{\\\"A}bb{\\\"{T}}");
0206     QTest::newRow("Modifier plus letter") << QStringLiteral("\\\"A aa\\\"Abb\\\"T") << QChar(0x00c4) + QStringLiteral(" aa") + QChar(0x00c4) + QStringLiteral("bb\\\"T") << QStringLiteral("{\\\"A} aa{\\\"A}bb\\\"T");
0207     QTest::newRow("Modifier plus, inside curly brackets, letter") << QStringLiteral("\\\"{A} aa\\\"{A}bb\\\"{T}") << QChar(0x00c4) + QStringLiteral(" aa") + QChar(0x00c4) + QStringLiteral("bb\\\"{T}") << QStringLiteral("{\\\"A} aa{\\\"A}bb\\\"{T}");
0208     QTest::newRow("Single-letter commands") << QStringLiteral("\\,\\&\\_\\#\\%") << QChar(0x2009) + QStringLiteral("&_#%") << QString();
0209     QTest::newRow("\\noopsort{\\noopsort}") << QStringLiteral("\\noopsort{\\noopsort}") << QStringLiteral("\\noopsort{\\noopsort}") << QString();
0210     QTest::newRow("\\ensuremath") << QStringLiteral("\\ensuremath{${\\alpha}$}${\\ensuremath{\\delta}}$-spot \\ensuremath{26^{\\mathrm{th}}} {\\ensuremath{-}}") << QStringLiteral("\\ensuremath{$") + QChar(0x03b1) + QStringLiteral("$}${\\ensuremath{") + QChar(0x03b4) + QStringLiteral("}}$-spot \\ensuremath{26^{\\mathrm{th}}} {\\ensuremath{-}}") << QStringLiteral("\\ensuremath{$\\alpha$}${\\ensuremath{\\delta}}$-spot \\ensuremath{26^{\\mathrm{th}}} {\\ensuremath{-}}");
0211     QTest::newRow("Greek mu with 'Dollar' math") << QString(QStringLiteral("%1\\mu\\textmu$%1\\mu$")).arg(QChar(0x03bc)) << QString(QStringLiteral("%1%1%1$%1%1$")).arg(QChar(0x03bc)) << QStringLiteral("{\\textmu}{\\textmu}{\\textmu}$\\mu{}\\mu$");
0212     QTest::newRow("Greek mu with '\\ensuremath'") << QString(QStringLiteral("%1\\mu\\textmu\\ensuremath{%1\\mu}")).arg(QChar(0x03bc)) << QString(QStringLiteral("%1%1%1\\ensuremath{%1%1}")).arg(QChar(0x03bc)) << QString(QStringLiteral("{\\textmu}{\\textmu}{\\textmu}\\ensuremath{\\mu{}\\mu}"));
0213     QTest::newRow("Micro sign with 'Dollar' math") << QString(QStringLiteral("%1$%1$")).arg(QChar(0x00b5)) << QString(QStringLiteral("%1$%1$")).arg(QChar(0x00b5)) << QStringLiteral("{\\textmu}$\\mu$");
0214     QTest::newRow("Micro sign with '\\ensuremath'") << QString(QStringLiteral("%1\\ensuremath{%1}")).arg(QChar(0x00b5)) << QString(QStringLiteral("%1\\ensuremath{%1}")).arg(QChar(0x00b5)) << QString(QStringLiteral("{\\textmu}\\ensuremath{\\mu}"));
0215     QTest::newRow("\\uu") << QStringLiteral("u\\uu\\u uu\\u u u u{\\uu}{\\u u}u{\\u u} u") << QString(QStringLiteral("u\\uu%1u%1 u u{\\uu}%1u%1 u")).arg(QChar(0x016d)) << QStringLiteral("u\\uu{\\u u}u{\\u u} u u{\\uu}{\\u u}u{\\u u} u");
0216     QTest::newRow("mhchem") << QStringLiteral("\\ce{H2O}") << QStringLiteral("\\ce{H2O}") << QString();
0217     QTest::newRow("Latin small letter e with ogonek versus mhchem") << QStringLiteral("{\\c e} vs \\ce{H2O}") << QString(QStringLiteral("%1 vs \\ce{H2O}")).arg(QChar(0x0229)) << QStringLiteral("\\c{e} vs \\ce{H2O}");
0218 }
0219 
0220 void KBibTeXIOTest::encoderLaTeXdecode()
0221 {
0222     QFETCH(QString, latex);
0223     QFETCH(QString, unicode);
0224 
0225     QCOMPARE(EncoderLaTeX::instance().decode(latex), unicode);
0226 }
0227 
0228 void KBibTeXIOTest::encoderLaTeXencode_data()
0229 {
0230     encoderLaTeXdecode_data();
0231 }
0232 
0233 void KBibTeXIOTest::encoderLaTeXencode()
0234 {
0235     QFETCH(QString, latex);
0236     QFETCH(QString, unicode);
0237     QFETCH(QString, alternativelatex);
0238 
0239     const QString generatedLatex = EncoderLaTeX::instance().encode(unicode, Encoder::TargetEncoding::ASCII);
0240     if (generatedLatex != latex && !alternativelatex.isEmpty())
0241         QCOMPARE(generatedLatex, alternativelatex);
0242     else
0243         QCOMPARE(generatedLatex, latex);
0244 }
0245 
0246 void KBibTeXIOTest::encoderLaTeXencodeHash()
0247 {
0248     /// File with single entry, inspired by 'Moby Dick'
0249     QScopedPointer<File> file(new File());
0250     QSharedPointer<Entry> entry(new Entry(Entry::etBook, QStringLiteral("latex-encoder-test")));
0251     file->append(entry);
0252     static const QString titleText(QStringLiteral("{This is a Title with a \\#}"));
0253     entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(titleText)));
0254     static const QString authorLastNameText(QStringLiteral("{Flash the \\#}"));
0255     entry->insert(Entry::ftAuthor, Value() << QSharedPointer<Person>(new Person(QString(), authorLastNameText)));
0256     static const QString urlText(QStringLiteral("https://127.0.0.1/#hashtag"));
0257     entry->insert(Entry::ftUrl, Value() << QSharedPointer<VerbatimText>(new VerbatimText(urlText)));
0258     file->setProperty(File::ProtectCasing, Qt::Checked);
0259     file->setProperty(File::Encoding, QStringLiteral("latex"));
0260 
0261     QBuffer fileBuffer;
0262     fileBuffer.open(QBuffer::WriteOnly);
0263     FileExporterBibTeX exporter(this);
0264     exporter.save(&fileBuffer, file.data());
0265     fileBuffer.close();
0266 
0267     fileBuffer.open(QBuffer::ReadOnly);
0268     FileImporterBibTeX importer(this);
0269     QScopedPointer<File> importedFile(importer.load(&fileBuffer));
0270     fileBuffer.close();
0271 
0272     QVERIFY(!importedFile.isNull());
0273     QVERIFY(importedFile->count() == 1);
0274     QSharedPointer<Entry> importedEntry = importedFile->first().dynamicCast<Entry>();
0275     QVERIFY(!importedEntry.isNull());
0276     QVERIFY(importedEntry->count() == 3);
0277     QVERIFY(importedEntry->contains(Entry::ftTitle));
0278     QVERIFY(importedEntry->value(Entry::ftTitle).count() == 1);
0279     const QSharedPointer<PlainText> titlePlainText = importedEntry->value(Entry::ftTitle).first().dynamicCast<PlainText>();
0280     QVERIFY(!titlePlainText.isNull());
0281     const QString importedTitleText = titlePlainText->text();
0282     QVERIFY(!importedTitleText.isEmpty());
0283     QVERIFY(importedEntry->contains(Entry::ftAuthor));
0284     QVERIFY(importedEntry->value(Entry::ftAuthor).count() == 1);
0285     const QSharedPointer<Person> authorPerson = importedEntry->value(Entry::ftAuthor).first().dynamicCast<Person>();
0286     QVERIFY(!authorPerson.isNull());
0287     const QString importedAuthorLastNameText = authorPerson->lastName();
0288     QVERIFY(!importedAuthorLastNameText.isEmpty());
0289     QVERIFY(importedEntry->contains(Entry::ftUrl));
0290     QVERIFY(importedEntry->value(Entry::ftUrl).count() == 1);
0291     const QSharedPointer<VerbatimText> urlVerbatimText = importedEntry->value(Entry::ftUrl).first().dynamicCast<VerbatimText>();
0292     QVERIFY(!urlVerbatimText.isNull());
0293     const QString importedUrlText = urlVerbatimText->text();
0294     QVERIFY(!importedUrlText.isEmpty());
0295 
0296     QVERIFY(importedTitleText == titleText);
0297     QVERIFY(importedAuthorLastNameText == authorLastNameText);
0298     QVERIFY(importedUrlText == urlText);
0299 }
0300 
0301 void KBibTeXIOTest::fileImporterSplitName_data()
0302 {
0303     QTest::addColumn<QString>("name");
0304     QTest::addColumn<Person *>("person");
0305 
0306     QTest::newRow("Empty name") << QString() << new Person(QString(), QString(), QString());
0307     QTest::newRow("PubMed style") << QStringLiteral("Jones A B C") << new Person(QStringLiteral("A B C"), QStringLiteral("Jones"), QString());
0308     QTest::newRow("Just last name") << QStringLiteral("Dido") << new Person(QString(), QStringLiteral("Dido"), QString());
0309     QTest::newRow("Name with 'von'") << QStringLiteral("Theodor von Sickel") << new Person(QStringLiteral("Theodor"), QStringLiteral("von Sickel"), QString());
0310     QTest::newRow("Name with 'von', reversed") << QStringLiteral("von Sickel, Theodor") << new Person(QStringLiteral("Theodor"), QStringLiteral("von Sickel"), QString());
0311     QTest::newRow("Name with 'van der'") << QStringLiteral("Adriaen van der Werff") << new Person(QStringLiteral("Adriaen"), QStringLiteral("van der Werff"), QString());
0312     QTest::newRow("Name with 'van der', reversed") << QStringLiteral("van der Werff, Adriaen") << new Person(QStringLiteral("Adriaen"), QStringLiteral("van der Werff"), QString());
0313     QTest::newRow("Name with suffix") << QStringLiteral("Anna Eleanor Roosevelt Jr.") << new Person(QStringLiteral("Anna Eleanor"), QStringLiteral("Roosevelt"), QStringLiteral("Jr."));
0314 }
0315 
0316 void KBibTeXIOTest::fileImporterSplitName()
0317 {
0318     QFETCH(QString, name);
0319     QFETCH(Person *, person);
0320 
0321     Person *computedPerson = FileImporter::splitName(name);
0322     QCOMPARE(*computedPerson, *person);
0323 
0324     delete person;
0325     delete computedPerson;
0326 }
0327 
0328 void KBibTeXIOTest::fileInfoMimeTypeForUrl_data()
0329 {
0330     QTest::addColumn<QUrl>("url");
0331     QTest::addColumn<QMimeType>("mimetype");
0332 
0333     static const QMimeDatabase db;
0334     QTest::newRow("Invalid URL") << QUrl() << QMimeType();
0335     QTest::newRow("Generic URL") << QUrl(QStringLiteral("https://www.example.com")) << db.mimeTypeForName(QStringLiteral("text/html"));
0336     QTest::newRow("Generic local file") << QUrl(QStringLiteral("/usr/bin/who")) << db.mimeTypeForName(QStringLiteral("application/octet-stream"));
0337     QTest::newRow("Generic Samba URL") << QUrl(QStringLiteral("smb://fileserver.local/file")) << db.mimeTypeForName(QStringLiteral("application/octet-stream"));
0338     QTest::newRow("URL to .bib file") << QUrl(QStringLiteral("https://www.example.com/references.bib")) << db.mimeTypeForName(QStringLiteral("text/x-bibtex"));
0339     QTest::newRow("Local .bib file") << QUrl(QStringLiteral("/home/user/references.bib")) << db.mimeTypeForName(QStringLiteral("text/x-bibtex"));
0340     QTest::newRow("URL to .pdf file") << QUrl(QStringLiteral("https://www.example.com/references.pdf")) << db.mimeTypeForName(QStringLiteral("application/pdf"));
0341     QTest::newRow("Local .pdf file") << QUrl(QStringLiteral("/home/user/references.pdf")) << db.mimeTypeForName(QStringLiteral("application/pdf"));
0342 }
0343 
0344 void KBibTeXIOTest::fileInfoMimeTypeForUrl()
0345 {
0346     QFETCH(QUrl, url);
0347     QFETCH(QMimeType, mimetype);
0348 
0349     QCOMPARE(FileInfo::mimeTypeForUrl(url), mimetype);
0350 }
0351 
0352 void KBibTeXIOTest::fileInfoUrlsInText_data()
0353 {
0354     QTest::addColumn<QString>("text");
0355     QTest::addColumn<QSet<QUrl>>("expectedUrls");
0356 
0357     QTest::newRow("Empty text") << QString() << QSet<QUrl>();
0358     QTest::newRow("Lore ipsum with DOI (without URL)") << QStringLiteral("Lore ipsum 10.1000/38-abc Lore ipsum") << QSet<QUrl> {QUrl(KBibTeX::doiUrlPrefix + QStringLiteral("10.1000/38-abc"))};
0359     QTest::newRow("Lore ipsum with DOI (with HTTP URL)") << QStringLiteral("Lore ipsum http://doi.example.org/10.1000/38-abc Lore ipsum") << QSet<QUrl> {QUrl(KBibTeX::doiUrlPrefix + QStringLiteral("10.1000/38-abc"))};
0360     QTest::newRow("Lore ipsum with DOI (with HTTPS URL)") << QStringLiteral("Lore ipsum https://doi.example.org/10.1000/42-XYZ Lore ipsum") << QSet<QUrl> {QUrl(KBibTeX::doiUrlPrefix + QStringLiteral("10.1000/42-XYZ"))};
0361     QTest::newRow("URLs and DOI (without URL), all semicolon-separated") << QStringLiteral("http://www.example.com;10.1000/38-abc   ;\nhttps://www.example.com") << QSet<QUrl> {QUrl(QStringLiteral("http://www.example.com")), QUrl(KBibTeX::doiUrlPrefix + QStringLiteral("10.1000/38-abc")), QUrl(QStringLiteral("https://www.example.com"))};
0362     QTest::newRow("URLs and DOI (with URL), all semicolon-separated") << QStringLiteral("http://www.example.com\n;   10.1000/38-abc;https://www.example.com") << QSet<QUrl> {QUrl(QStringLiteral("http://www.example.com")), QUrl(KBibTeX::doiUrlPrefix + QStringLiteral("10.1000/38-abc")), QUrl(QStringLiteral("https://www.example.com"))};
0363     QTest::newRow("URLs with various separators") << QStringLiteral("http://www.example.com/def.pdf https://www.example.com\nhttp://download.example.com/abc") << QSet<QUrl> {QUrl(QStringLiteral("http://www.example.com/def.pdf")), QUrl(QStringLiteral("https://www.example.com")), QUrl(QStringLiteral("http://download.example.com/abc"))};
0364     QTest::newRow("URLs with query strings and anchors") << QStringLiteral("http://www.example.com/def.pdf?a=3&b=1 https://www.example.com#1581584\nhttp://download.example.com/abc,7352,A#abc?gh=352&ghi=1254") << QSet<QUrl> {QUrl(QStringLiteral("http://www.example.com/def.pdf?a=3&b=1")), QUrl(QStringLiteral("https://www.example.com#1581584")), QUrl(QStringLiteral("http://download.example.com/abc,7352,A#abc?gh=352&ghi=1254"))};
0365 }
0366 
0367 void KBibTeXIOTest::fileInfoUrlsInText()
0368 {
0369     QFETCH(QString, text);
0370     QFETCH(QSet<QUrl>, expectedUrls);
0371 
0372     QSet<QUrl> extractedUrls;
0373     FileInfo::urlsInText(text, FileInfo::TestExistence::No, QString(), extractedUrls);
0374 
0375     QCOMPARE(extractedUrls.count(), expectedUrls.count());
0376     for (const QUrl &expectedUrl : const_cast<const QSet<QUrl> &>(expectedUrls))
0377         QCOMPARE(extractedUrls.contains(expectedUrl), true);
0378 }
0379 
0380 static const char *fileImporterExporterTestCases_Label_Empty_file = "Empty file";
0381 static const char *fileImporterExporterTestCases_Label_Moby_Dick = "Moby Dick";
0382 
0383 File *KBibTeXIOTest::mobyDickBibliography()
0384 {
0385     static File *mobyDickFile = nullptr;
0386     if (mobyDickFile == nullptr) {
0387         /// File with single entry, inspired by 'Moby Dick'
0388         mobyDickFile = new File();
0389         QSharedPointer<Entry> entry(new Entry(Entry::etBook, QStringLiteral("the-whale-1851")));
0390         mobyDickFile->append(entry);
0391         entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(QStringLiteral("{Call me Ishmael}"))));
0392         entry->insert(Entry::ftAuthor, Value() << QSharedPointer<Person>(new Person(QStringLiteral("Herman"), QStringLiteral("Melville"))) << QSharedPointer<Person>(new Person(QStringLiteral("Moby"), QStringLiteral("Dick"))));
0393         entry->insert(Entry::ftMonth, Value() << QSharedPointer<MacroKey>(new MacroKey(QStringLiteral("jun"))));
0394         entry->insert(Entry::ftYear, Value() << QSharedPointer<PlainText>(new PlainText(QStringLiteral("1851"))));
0395         mobyDickFile->setProperty(File::ProtectCasing, Qt::Checked);
0396     }
0397     return mobyDickFile;
0398 }
0399 
0400 File *KBibTeXIOTest::latinUmlautBibliography()
0401 {
0402     static File *latinUmlautFile = nullptr;
0403     if (latinUmlautFile == nullptr) {
0404         latinUmlautFile = new File();
0405         QSharedPointer<Entry> entry(new Entry(Entry::etArticle, QStringLiteral("einstein1907relativitaetsprinzip")));
0406         latinUmlautFile->append(entry);
0407         entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("{%1ber das Relativit%2tsprinzip und die aus demselben gezogenen Folgerungen}")).arg(QChar(0x00DC)).arg(QChar(0x00E4)))));
0408         entry->insert(Entry::ftAuthor, Value() << QSharedPointer<Person>(new Person(QStringLiteral("Albert"), QStringLiteral("Einstein"))));
0409         entry->insert(Entry::ftYear, Value() << QSharedPointer<PlainText>(new PlainText(QStringLiteral("1907/08"))));
0410         entry->insert(Entry::ftPages, Value() << QSharedPointer<PlainText>(new PlainText(QStringLiteral("23") + QChar(0x2013) + QStringLiteral("42"))));
0411         latinUmlautFile->setProperty(File::ProtectCasing, Qt::Checked);
0412     }
0413     return latinUmlautFile;
0414 }
0415 
0416 File *KBibTeXIOTest::onlyComments(int numPercentSigns, int numLines)
0417 {
0418     const int key = numPercentSigns * 1024 + numLines;
0419     static QHash<int, File *> commentFiles;
0420     if (!commentFiles.contains(key)) {
0421         File *commentFile = new File();
0422         const QString percentSigns {numPercentSigns > 0 ? QString(numPercentSigns, QLatin1Char('%')) + QStringLiteral(" ") : QString()};
0423         QString rawText {numPercentSigns < 0 ? QStringLiteral("Inside command") : (numPercentSigns > 0 ? QString(QStringLiteral("%1 percent symbol(s), %2 line(s)")).arg(QString::number(numPercentSigns), QString::number(numLines)) : QStringLiteral("Direct comment"))};
0424         const QString moreLinesRawText {QStringLiteral("\nOne more line")};
0425         for (int i = 1; i < numLines; ++i)
0426             rawText.append(moreLinesRawText);
0427         QSharedPointer<Comment> comment(new Comment(rawText, numPercentSigns < 0 ? Preferences::CommentContext::Command : (numPercentSigns > 0 ? Preferences::CommentContext::Prefix : Preferences::CommentContext::Verbatim), percentSigns));
0428         commentFile->append(comment);
0429         commentFiles.insert(key, commentFile);
0430     }
0431     return commentFiles[key];
0432 }
0433 
0434 File *KBibTeXIOTest::koreanBibliography()
0435 {
0436     static File *koreanFile = nullptr;
0437     if (koreanFile == nullptr) {
0438         koreanFile = new File();
0439         QSharedPointer<Entry> entry(new Entry(Entry::etMastersThesis, QStringLiteral("korean-text-copied-from-dailynk")));
0440         koreanFile->append(entry);
0441         entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(QString::fromUtf8("\xeb\xb3\xb4\xec\x9c\x84\xea\xb5\xad \xec\x88\x98\xec\x82\xac\xeb\xb6\x80\xc2\xb7\xec\x98\x81\xec\xb0\xbd\xea\xb4\x80\xeb\xa6\xac\xeb\xb6\x80 \xec\x8a\xb9\xea\xb2\xa9\xe2\x80\xa6\xea\xb9\x80\xec\xa0\x95\xec\x9d\x80, \xeb\xb6\x80\xec\xa0\x95\xeb\xb6\x80\xed\x8c\xa8\xec\x99\x80 \xec\xa0\x84\xec\x9f\x81 \xec\x98\x88\xea\xb3\xa0"))));
0442         entry->insert(Entry::ftAuthor, Value() << QSharedPointer<Person>(new Person(QString::fromUtf8("\xec\xa0\x95\xec\x9d\x80"), QString::fromUtf8("\xea\xb9\x80"))));
0443         entry->insert(Entry::ftYear, Value() << QSharedPointer<PlainText>(new PlainText(QStringLiteral("1921"))));
0444         koreanFile->setProperty(File::NameFormatting, Preferences::personNameFormatLastFirst);
0445         koreanFile->setProperty(File::ProtectCasing, Qt::Unchecked);
0446     }
0447     return koreanFile;
0448 }
0449 
0450 File *KBibTeXIOTest::russianBibliography()
0451 {
0452     static File *russianFile = nullptr;
0453     if (russianFile == nullptr) {
0454         russianFile = new File();
0455         QSharedPointer<Entry> entry(new Entry(Entry::etBook, QStringLiteral("war-and-peace")));
0456         russianFile->append(entry);
0457         entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(QString::fromUtf8("\xd0\x92\xd0\xbe\xd0\xb9\xd0\xbd\xd0\xb0 \xd0\xb8 \xd0\xbc\xd0\xb8\xd1\x80"))));
0458         entry->insert(Entry::ftAuthor, Value() << QSharedPointer<Person>(new Person(QString::fromUtf8("\xd0\x9b\xd0\xb5\xd0\xb2"), QString::fromUtf8("{\xd0\x9d\xd0\xb8\xd0\xba\xd0\xbe\xd0\xbb\xd0\xb0\xd0\xb5\xd0\xb2\xd0\xb8\xd1\x87 \xd0\xa2\xd0\xbe\xd0\xbb\xd1\x81\xd1\x82\xd0\xbe\xd0\xb9}"))));
0459         entry->insert(Entry::ftYear, Value() << QSharedPointer<PlainText>(new PlainText(QStringLiteral("1869"))));
0460         russianFile->setProperty(File::NameFormatting, Preferences::personNameFormatLastFirst);
0461         russianFile->setProperty(File::ProtectCasing, Qt::Unchecked);
0462     }
0463     return russianFile;
0464 }
0465 
0466 QVector<QPair<const char *, File *> > KBibTeXIOTest::fileImporterExporterTestCases()
0467 {
0468     /// The vector 'result' is static so that if this function is invoked multiple
0469     /// times, the vector will be initialized and filled with File objects only upon
0470     /// the function's first invocation.
0471     static QVector<QPair<const char *, File *> > result;
0472 
0473     if (result.isEmpty()) {
0474         /// Empty file without any entries
0475         result.append(QPair<const char *, File *>(fileImporterExporterTestCases_Label_Empty_file, new File()));
0476 
0477         /// File with single entry, inspired by 'Moby Dick'
0478         result.append(QPair<const char *, File *>(fileImporterExporterTestCases_Label_Moby_Dick, mobyDickBibliography()));
0479 
0480         // TODO add more file objects to result vector
0481 
0482         /// Set various properties to guarantee reproducible results irrespective of local settings
0483         for (auto it = result.constBegin(); it != result.constEnd(); ++it) {
0484             File *file = it->second;
0485             file->setProperty(File::NameFormatting, Preferences::personNameFormatLastFirst);
0486             file->setProperty(File::ProtectCasing, static_cast<int>(Qt::Checked));
0487             // TODO more file properties to set?
0488         }
0489     }
0490 
0491     return result;
0492 }
0493 
0494 void KBibTeXIOTest::fileExporterXMLsave_data()
0495 {
0496     QTest::addColumn<File *>("bibTeXfile");
0497     QTest::addColumn<MapFileExporterXMLOutputStyleToQString>("expectedData");
0498 
0499     static const QHash<const char *, MapFileExporterXMLOutputStyleToQString> keyToXmlData {
0500         {   fileImporterExporterTestCases_Label_Empty_file, {
0501                 {FileExporterXML::OutputStyle::XML_KBibTeX, QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>|<!-- XML document written by KBibTeXIO as part of KBibTeX -->|<!-- https://userbase.kde.org/KBibTeX -->|<bibliography>|</bibliography>|")},
0502                 {FileExporterXML::OutputStyle::HTML_Standard, QStringLiteral("<!DOCTYPE html>|<!-- HTML document written by KBibTeXIO as part of KBibTeX -->|<!-- https://userbase.kde.org/KBibTeX -->|<html xmlns=\"http://www.w3.org/1999/xhtml\">|<head><title>Bibliography</title><meta charset=\"utf-8\"></head>|<body>|</body></html>||")},
0503                 {FileExporterXML::OutputStyle::HTML_AbstractOnly, QStringLiteral("<!DOCTYPE html>|<!-- HTML document written by KBibTeXIO as part of KBibTeX -->|<!-- https://userbase.kde.org/KBibTeX -->|<html xmlns=\"http://www.w3.org/1999/xhtml\">|<head><title>Bibliography</title><meta charset=\"utf-8\"></head>|<body>|</body></html>||")},
0504                 {FileExporterXML::OutputStyle::Plain_WikipediaCite, QString()}
0505             }
0506         },
0507         {   fileImporterExporterTestCases_Label_Moby_Dick, {
0508                 {FileExporterXML::OutputStyle::XML_KBibTeX, QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>|<!-- XML document written by KBibTeXIO as part of KBibTeX -->|<!-- https://userbase.kde.org/KBibTeX -->|<bibliography>| <entry id=\"the-whale-1851\" type=\"book\">|  <authors>|<person><firstname>Herman</firstname><lastname>Melville</lastname></person><person><firstname>Moby</firstname><lastname>Dick</lastname></person>|  </authors>|  <month triple=\"jun\" number=\"6\"><text>June</text></month>|  <title><text>Call me Ishmael</text></title>|  <year number=\"1851\"><text>1851</text></year>| </entry>|</bibliography>|")},
0509                 {FileExporterXML::OutputStyle::HTML_Standard, QStringLiteral("<!DOCTYPE html>|<!-- HTML document written by KBibTeXIO as part of KBibTeX -->|<!-- https://userbase.kde.org/KBibTeX -->|<html xmlns=\"http://www.w3.org/1999/xhtml\">|<head><title>Bibliography</title><meta charset=\"utf-8\"></head>|<body>|<p>Melville<span style=\"opacity:0.75;\">, Herman</span>, Dick<span style=\"opacity:0.75;\">, Moby</span>: <strong>Call me Ishmael</strong>, June 1851</p>|</body></html>||")},
0510                 {FileExporterXML::OutputStyle::HTML_AbstractOnly, QString()},
0511                 {FileExporterXML::OutputStyle::Plain_WikipediaCite, QStringLiteral("{{Citation|| title = Call me Ishmael|| year = 1851||last1 = Melville||first1 = Herman||last2 = Dick||first2 = Moby|}}|")}
0512             }
0513         }
0514     };
0515     static const QVector<QPair<const char *, File *> > keyFileTable = fileImporterExporterTestCases();
0516 
0517     for (auto it = keyFileTable.constBegin(); it != keyFileTable.constEnd(); ++it)
0518         if (keyToXmlData.contains(it->first))
0519             QTest::newRow(it->first) << it->second << keyToXmlData.value(it->first);
0520 }
0521 
0522 void KBibTeXIOTest::fileExporterXMLsave()
0523 {
0524     QFETCH(File *, bibTeXfile);
0525     QFETCH(MapFileExporterXMLOutputStyleToQString, expectedData);
0526 
0527     FileExporterXML fileExporterXML(this);
0528 
0529     for (auto it = expectedData.constBegin(); it != expectedData.constEnd(); ++it) {
0530         fileExporterXML.setOutputStyle(it.key());
0531         const QString &expectedText = it.value();
0532         const QString generatedText = fileExporterXML.toString(bibTeXfile).remove(QLatin1Char('\r')).replace(QLatin1Char('\n'), QLatin1Char('|'));
0533         QCOMPARE(generatedText, expectedText);
0534     }
0535 }
0536 
0537 void KBibTeXIOTest::fileExporterRISsave_data()
0538 {
0539     QTest::addColumn<File *>("bibTeXfile");
0540     QTest::addColumn<QString>("risData");
0541 
0542     static const QHash<const char *, QString> keyToRisData {
0543         {fileImporterExporterTestCases_Label_Empty_file, QString()},
0544         {fileImporterExporterTestCases_Label_Moby_Dick, QStringLiteral("TY  - BOOK|ID  - the-whale-1851|AU  - Melville, Herman|AU  - Dick, Moby|TI  - Call me Ishmael|PY  - 1851/06//|ER  - ||")}
0545     };
0546     static const QVector<QPair<const char *, File *> > keyFileTable = fileImporterExporterTestCases();
0547 
0548     for (auto it = keyFileTable.constBegin(); it != keyFileTable.constEnd(); ++it)
0549         if (keyToRisData.contains(it->first))
0550             QTest::newRow(it->first) << it->second << keyToRisData.value(it->first);
0551 }
0552 
0553 void KBibTeXIOTest::fileExporterRISsave()
0554 {
0555     QFETCH(File *, bibTeXfile);
0556     QFETCH(QString, risData);
0557 
0558     FileExporterRIS fileExporterRIS(this);
0559     const QString generatedData = fileExporterRIS.toString(bibTeXfile).remove(QLatin1Char('\r')).replace(QLatin1Char('\n'), QLatin1Char('|'));
0560 
0561     QCOMPARE(generatedData, risData);
0562 }
0563 
0564 void KBibTeXIOTest::fileExporterBibTeXsave_data()
0565 {
0566     QTest::addColumn<File *>("bibTeXfile");
0567     QTest::addColumn<QString>("bibTeXdata");
0568 
0569     static const QHash<const char *, QString> keyToBibTeXData {
0570         {fileImporterExporterTestCases_Label_Empty_file, QString()},
0571         {fileImporterExporterTestCases_Label_Moby_Dick, QStringLiteral("@book{the-whale-1851,|\tauthor = {Melville, Herman and Dick, Moby},|\tmonth = jun,|\ttitle = {{Call me Ishmael}},|\tyear = {1851}|}||")}
0572     };
0573     static const QVector<QPair<const char *, File *> > keyFileTable = fileImporterExporterTestCases();
0574 
0575     for (auto it = keyFileTable.constBegin(); it != keyFileTable.constEnd(); ++it)
0576         if (keyToBibTeXData.contains(it->first))
0577             QTest::newRow(it->first) << it->second << keyToBibTeXData.value(it->first);
0578 }
0579 
0580 void KBibTeXIOTest::fileExporterBibTeXsave()
0581 {
0582     QFETCH(File *, bibTeXfile);
0583     QFETCH(QString, bibTeXdata);
0584 
0585     FileExporterBibTeX fileExporterBibTeX(this);
0586     const QString generatedData = fileExporterBibTeX.toString(bibTeXfile).remove(QLatin1Char('\r')).replace(QLatin1Char('\n'), QLatin1Char('|'));
0587 
0588     QCOMPARE(generatedData, bibTeXdata);
0589 }
0590 
0591 void KBibTeXIOTest::fileImporterRISload_data()
0592 {
0593     QTest::addColumn<QByteArray>("risData");
0594     QTest::addColumn<File *>("bibTeXfile");
0595 
0596     static const QHash<const char *, QString> keyToRisData {
0597         {fileImporterExporterTestCases_Label_Empty_file, QString()},
0598         {fileImporterExporterTestCases_Label_Moby_Dick, QStringLiteral("TY  - BOOK|ID  - the-whale-1851|AU  - Melville, Herman|AU  - Dick, Moby|TI  - Call me Ishmael|PY  - 1851/06//|ER  - ||")}
0599     };
0600     static const QVector<QPair<const char *, File *> > keyFileTable = fileImporterExporterTestCases();
0601 
0602     for (auto it = keyFileTable.constBegin(); it != keyFileTable.constEnd(); ++it)
0603         if (keyToRisData.contains(it->first))
0604             QTest::newRow(it->first) << keyToRisData.value(it->first).toUtf8().replace('|', '\n') << it->second;
0605 }
0606 
0607 void KBibTeXIOTest::fileImporterRISload()
0608 {
0609     QFETCH(QByteArray, risData);
0610     QFETCH(File *, bibTeXfile);
0611 
0612     FileImporterRIS fileImporterRIS(this);
0613     fileImporterRIS.setProtectCasing(true);
0614     QBuffer buffer(&risData);
0615     buffer.open(QBuffer::ReadOnly);
0616     QScopedPointer<File> generatedFile(fileImporterRIS.load(&buffer));
0617 
0618     QVERIFY(generatedFile->operator ==(*bibTeXfile));
0619 }
0620 
0621 void KBibTeXIOTest::fileImporterBibTeXload_data()
0622 {
0623     QTest::addColumn<QByteArray>("bibTeXdata");
0624     QTest::addColumn<File *>("bibTeXfile");
0625 
0626     static const QHash<const char *, QString> keyToBibTeXData {
0627         {fileImporterExporterTestCases_Label_Empty_file, QString()},
0628         {fileImporterExporterTestCases_Label_Moby_Dick, QStringLiteral("@book{the-whale-1851,|\tauthor = {Melville, Herman and Dick, Moby},|\tmonth = jun,|\ttitle = {{Call me Ishmael}},|\tyear = {1851}|}||")}
0629     };
0630     static const QVector<QPair<const char *, File *> > keyFileTable = fileImporterExporterTestCases();
0631 
0632     for (auto it = keyFileTable.constBegin(); it != keyFileTable.constEnd(); ++it)
0633         if (keyToBibTeXData.contains(it->first))
0634             QTest::newRow(it->first) << keyToBibTeXData.value(it->first).toUtf8().replace('|', '\n') << it->second ;
0635 }
0636 
0637 void KBibTeXIOTest::fileImporterBibTeXload()
0638 {
0639     QFETCH(QByteArray, bibTeXdata);
0640     QFETCH(File *, bibTeXfile);
0641 
0642     FileImporterBibTeX fileImporterBibTeX(this);
0643     QBuffer buffer(&bibTeXdata);
0644     buffer.open(QBuffer::ReadOnly);
0645     QScopedPointer<File> generatedFile(fileImporterBibTeX.load(&buffer));
0646 
0647     QVERIFY(generatedFile->operator ==(*bibTeXfile));
0648 }
0649 
0650 void KBibTeXIOTest::protectiveCasingEntryGeneratedOnTheFly()
0651 {
0652     static const QString titleText = QStringLiteral("Some Title for a Journal Article");
0653     static const QString singleCurleyBracketTitle = QStringLiteral("{") + titleText + QStringLiteral("}");
0654     static const QString doubleCurleyBracketTitle = QStringLiteral("{{") + titleText + QStringLiteral("}}");
0655 
0656     FileExporterBibTeX fileExporterBibTeX(this);
0657 
0658     /// Create a simple File object with a title field
0659     File file;
0660     file.setProperty(File::StringDelimiter, QStringLiteral("{}"));
0661     QSharedPointer<Entry> entry {new Entry(Entry::etArticle, QStringLiteral("SomeId"))};
0662     Value titleValue = Value() << QSharedPointer<PlainText>(new PlainText(titleText));
0663     entry->insert(Entry::ftTitle, titleValue);
0664     file.append(entry);
0665 
0666     file.setProperty(File::ProtectCasing, Qt::Checked);
0667     const QString textWithProtectiveCasing = fileExporterBibTeX.toString(&file);
0668     QVERIFY(textWithProtectiveCasing.contains(doubleCurleyBracketTitle));
0669 
0670     file.setProperty(File::ProtectCasing, Qt::Unchecked);
0671     const QString textWithoutProtectiveCasing = fileExporterBibTeX.toString(&file);
0672     QVERIFY(textWithoutProtectiveCasing.contains(singleCurleyBracketTitle)
0673             && !textWithoutProtectiveCasing.contains(doubleCurleyBracketTitle));
0674 }
0675 
0676 void KBibTeXIOTest::fileExporterBibTeXEncoding_data()
0677 {
0678     QTest::addColumn<File *>("bibTeXfile");
0679     QTest::addColumn<QString>("encoding");
0680     QTest::addColumn<QSet<QByteArray>>("expectedOutputOptionalBOMs");
0681     QTest::addColumn<QByteArray>("expectedOutput");
0682 
0683     static const QSet<QByteArray> bomUTF8 {{"\xEF\xBB\xBF"}};
0684     static const QByteArray mobyDickEntryOutput {"@book{the-whale-1851,\n\tauthor = {Melville, Herman and Dick, Moby},\n\tmonth = jun,\n\ttitle = {{Call me Ishmael}},\n\tyear = {1851}\n}\n\n"};
0685     static const QStringList listOfASCIIcompatibleEncodings {QStringLiteral("LaTeX"), QStringLiteral("UTF-8"), QStringLiteral("ISO-8859-1"), QStringLiteral("Windows-1254"), QStringLiteral("ISO 2022-JP") /** technically not ASCII-compatible, but works for this case here */};
0686     for (const QString &encoding : listOfASCIIcompatibleEncodings) {
0687         const QByteArray encodingOutput{QByteArray{"@comment{x-kbibtex-encoding="} +encoding.toLatin1() + QByteArray{"}\n\n"}};
0688         const QByteArray mobyDickExpectedOutput{encoding == QStringLiteral("LaTeX") ? mobyDickEntryOutput : encodingOutput + mobyDickEntryOutput};
0689         const QSet<QByteArray> bom {encoding == QStringLiteral("UTF-8") ? bomUTF8 : QSet<QByteArray>()};
0690         QTest::newRow(QString(QStringLiteral("Moby Dick (ASCII only) encoded in '%1'")).arg(encoding).toLatin1().constData()) << mobyDickBibliography() << encoding << bom << mobyDickExpectedOutput;
0691     }
0692     static const QSet<QByteArray> bomUTF16 {{"\xFF\xFE"}, {"\xFE\xFF"}};
0693     static const QByteArray mobyDickExpectedOutputUTF16 {"@\0""b\0o\0o\0k\0{\0t\0h\0""e\0-\0w\0h\0""a\0l\0""e\0-\0""1\0""8\0""5\0""1\0,\0\n\0\x09\0""a\0u\0t\0h\0o\0r\0 \0=\0 \0{\0M\0""e\0l\0v\0i\0l\0l\0""e\0,\0 \0H\0""e\0r\0m\0""a\0n\0 \0""a\0n\0""d\0 \0""D\0i\0""c\0k\0,\0 \0M\0o\0""b\0y\0}\0,\0\n\0\x09\0m\0o\0n\0t\0h\0 \0=\0 \0j\0u\0n\0,\0\n\0\x09\0t\0i\0t\0l\0""e\0 \0=\0 \0{\0{\0""C\0""a\0l\0l\0 \0m\0""e\0 \0I\0s\0h\0m\0""a\0""e\0l\0}\0}\0,\0\n\0\x09\0y\0""e\0""a\0r\0 \0=\0 \0{\0""1\0""8\0""5\0""1\0}\0\n\0}\0\n\0\n\0", 258};
0694     QTest::newRow("Moby Dick (ASCII only) encoded in 'UTF-16'") << mobyDickBibliography() << QStringLiteral("UTF-16") << bomUTF16 << mobyDickExpectedOutputUTF16;
0695     static const QSet<QByteArray> bomUTF32 {QByteArray("\xFF\xFE\x00\x00", 4), {"\x00\x00\xFE\xFF"}};
0696     static const QByteArray mobyDickExpectedOutputUTF32 {"@\0\0\0""b\0\0\0o\0\0\0o\0\0\0k\0\0\0{\0\0\0t\0\0\0h\0\0\0""e\0\0\0-\0\0\0w\0\0\0h\0\0\0""a\0\0\0l\0\0\0""e\0\0\0-\0\0\0""1\0\0\0""8\0\0\0""5\0\0\0""1\0\0\0,\0\0\0\n\0\0\0\x09\0\0\0""a\0\0\0u\0\0\0t\0\0\0h\0\0\0o\0\0\0r\0\0\0 \0\0\0=\0\0\0 \0\0\0{\0\0\0M\0\0\0""e\0\0\0l\0\0\0v\0\0\0i\0\0\0l\0\0\0l\0\0\0""e\0\0\0,\0\0\0 \0\0\0H\0\0\0""e\0\0\0r\0\0\0m\0\0\0""a\0\0\0n\0\0\0 \0\0\0""a\0\0\0n\0\0\0""d\0\0\0 \0\0\0""D\0\0\0i\0\0\0""c\0\0\0k\0\0\0,\0\0\0 \0\0\0M\0\0\0o\0\0\0""b\0\0\0y\0\0\0}\0\0\0,\0\0\0\n\0\0\0\x09\0\0\0m\0\0\0o\0\0\0n\0\0\0t\0\0\0h\0\0\0 \0\0\0=\0\0\0 \0\0\0j\0\0\0u\0\0\0n\0\0\0,\0\0\0\n\0\0\0\x09\0\0\0t\0\0\0i\0\0\0t\0\0\0l\0\0\0""e\0\0\0 \0\0\0=\0\0\0 \0\0\0{\0\0\0{\0\0\0""C\0\0\0""a\0\0\0l\0\0\0l\0\0\0 \0\0\0m\0\0\0""e\0\0\0 \0\0\0I\0\0\0s\0\0\0h\0\0\0m\0\0\0""a\0\0\0""e\0\0\0l\0\0\0}\0\0\0}\0\0\0,\0\0\0\n\0\0\0\x09\0\0\0y\0\0\0""e\0\0\0""a\0\0\0r\0\0\0 \0\0\0=\0\0\0 \0\0\0{\0\0\0""1\0\0\0""8\0\0\0""5\0\0\0""1\0\0\0}\0\0\0\n\0\0\0}\0\0\0\n\0\0\0\n\0\0\0", 516};
0697     QTest::newRow("Moby Dick (ASCII only) encoded in 'UTF-32'") << mobyDickBibliography() << QStringLiteral("UTF-32") << bomUTF32 << mobyDickExpectedOutputUTF32;
0698     static const QByteArray latinUmlautExpectedOutputLaTeX {"@article{einstein1907relativitaetsprinzip,\n\tauthor = {Einstein, Albert},\n\tpages = {23--42},\n\ttitle = {{{\\\"U}ber das Relativit{\\\"a}tsprinzip und die aus demselben gezogenen Folgerungen}},\n\tyear = {1907/08}\n}\n\n"};
0699     QTest::newRow("Albert Einstein (latin umlaut) data encoded in 'LaTeX'") << latinUmlautBibliography() << QStringLiteral("LaTeX") << bomUTF8 << latinUmlautExpectedOutputLaTeX;
0700     static const QByteArray latinUmlautExpectedOutputUTF8 {"@comment{x-kbibtex-encoding=UTF-8}\n\n@article{einstein1907relativitaetsprinzip,\n\tauthor = {Einstein, Albert},\n\tpages = {23\xE2\x80\x93""42},\n\ttitle = {{\xC3\x9C""ber das Relativit\xC3\xA4tsprinzip und die aus demselben gezogenen Folgerungen}},\n\tyear = {1907/08}\n}\n\n"};
0701     QTest::newRow("Albert Einstein (latin umlaut) encoded in 'UTF-8'") << latinUmlautBibliography() << QStringLiteral("UTF-8") << bomUTF8 << latinUmlautExpectedOutputUTF8;
0702 }
0703 
0704 void KBibTeXIOTest::fileExporterBibTeXEncoding()
0705 {
0706     QFETCH(File *, bibTeXfile);
0707     QFETCH(QString, encoding);
0708     QFETCH(QSet<QByteArray>, expectedOutputOptionalBOMs);
0709     QFETCH(QByteArray, expectedOutput);
0710 
0711     FileExporterBibTeX exporter(this);
0712     exporter.setEncoding(encoding);
0713     QByteArray generatedOutput;
0714     generatedOutput.reserve(8192);
0715     QBuffer buffer(&generatedOutput);
0716     buffer.open(QBuffer::WriteOnly);
0717     QVERIFY(exporter.save(&buffer, bibTeXfile));
0718     buffer.close();
0719 
0720     bool anyMatch = generatedOutput == expectedOutput;
0721     for (const QByteArray &bom : expectedOutputOptionalBOMs) {
0722         if (anyMatch) break;
0723         const QByteArray merged{bom + expectedOutput};
0724         anyMatch |= merged == generatedOutput;
0725     }
0726     QVERIFY2(anyMatch, "generatedOutput does not match expectedOutput (even with BOM)");
0727 }
0728 
0729 void KBibTeXIOTest::fileImportExportBibTeXroundtrip_data() {
0730     struct TestCase {
0731         QString label;
0732         File *bibliography;
0733         QStringList encodingsToBeTested;
0734         FileImporterBibTeX::CommentHandling fileImporterCommentHandling;
0735     };
0736     static const QVector<struct TestCase> testCases {/*
0737 {QStringLiteral("Moby Dick"), mobyDickBibliography(), Preferences::availableBibTeXEncodings, FileImporterBibTeX::CommentHandling::Ignore},
0738 {QStringLiteral("Albert Einstein"), latinUmlautBibliography(), Preferences::availableBibTeXEncodings, FileImporterBibTeX::CommentHandling::Ignore},
0739 {QStringLiteral("Kim Jong-un"), koreanBibliography(), {QStringLiteral("LaTeX"), QStringLiteral("UTF-8"), QStringLiteral("UTF-16"), QStringLiteral("UTF-16BE"), QStringLiteral("UTF-16LE"), QStringLiteral("UTF-32"), QStringLiteral("UTF-32BE"), QStringLiteral("UTF-32LE"), QStringLiteral("GB18030"), QStringLiteral("EUC-KR"), QStringLiteral("Windows-949")}, FileImporterBibTeX::CommentHandling::Ignore},
0740 {QStringLiteral("L. Tolstoy"), russianBibliography(), {QStringLiteral("LaTeX"), QStringLiteral("ISO-8859-5"), QStringLiteral("UTF-8"), QStringLiteral("UTF-16"), QStringLiteral("UTF-16BE"), QStringLiteral("UTF-16LE"), QStringLiteral("UTF-32"), QStringLiteral("UTF-32BE"), QStringLiteral("UTF-32LE"), QStringLiteral("KOI8-R"), QStringLiteral("KOI8-U"), QStringLiteral("Big5-HKSCS"), QStringLiteral("GB18030"), QStringLiteral("EUC-JP"), QStringLiteral("EUC-KR"), QStringLiteral("ISO 2022-JP"), QStringLiteral("Shift-JIS"), QStringLiteral("Windows-949"), QStringLiteral("Windows-1251")}, FileImporterBibTeX::CommentHandling::Ignore},*/
0741         {QStringLiteral("Only comments (Command, 1 line)"), onlyComments(-1, 1), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep}/*,
0742         {QStringLiteral("Only comments (Direct, 1 line)"), onlyComments(0, 1), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0743         {QStringLiteral("Only comments ('%', 1 line)"), onlyComments(1, 1), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0744         {QStringLiteral("Only comments ('%%', 1 line)"), onlyComments(2, 1), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0745         {QStringLiteral("Only comments ('%%%', 1 line)"), onlyComments(3, 1), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0746         {QStringLiteral("Only comments (Command, 2 lines)"), onlyComments(-1, 2), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0747         {QStringLiteral("Only comments (Direct, 2 lines)"), onlyComments(0, 2), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0748         {QStringLiteral("Only comments ('%', 2 lines)"), onlyComments(1, 2), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0749         {QStringLiteral("Only comments ('%%', 2 lines)"), onlyComments(2, 2), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0750         {QStringLiteral("Only comments ('%%%', 2 lines)"), onlyComments(3, 2), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0751         {QStringLiteral("Only comments (Command, 3 lines)"), onlyComments(-1, 3), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0752         {QStringLiteral("Only comments (Direct, 3 lines)"), onlyComments(0, 3), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0753         {QStringLiteral("Only comments ('%', 3 lines)"), onlyComments(1, 3), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0754         {QStringLiteral("Only comments ('%%', 3 lines)"), onlyComments(2, 3), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep},
0755         {QStringLiteral("Only comments ('%%%', 3 lines)"), onlyComments(3, 3), {QStringLiteral("UTF-8")}, FileImporterBibTeX::CommentHandling::Keep}*/
0756     };
0757 
0758     QTest::addColumn<File *>("bibliography");
0759     QTest::addColumn<QString>("encoding");
0760     QTest::addColumn<FileImporterBibTeX::CommentHandling>("fileImporterCommentHandling");
0761 
0762     for (const TestCase &testCase : testCases) {
0763         for (const QString &encoding : testCase.encodingsToBeTested) {
0764             const QString testLabel = QString(QStringLiteral("Round-trip of '%1' encoded in '%2'")).arg(testCase.label).arg(encoding);
0765             QTest::newRow(testLabel.toLatin1().constData()) << testCase.bibliography << encoding << testCase.fileImporterCommentHandling;
0766         }
0767     }
0768 }
0769 
0770 void KBibTeXIOTest::fileImportExportBibTeXroundtrip()
0771 {
0772     QFETCH(File *, bibliography);
0773     QFETCH(QString, encoding);
0774     QFETCH(FileImporterBibTeX::CommentHandling, fileImporterCommentHandling);
0775 
0776     FileImporterBibTeX importer(this);
0777     importer.setCommentHandling(fileImporterCommentHandling);
0778 
0779     // The point with this test is to apply various encodings (e.g. 'UTF-8', 'ISO 2022-JP')
0780     // to example bibliography files when writing to a buffer (a in-memory representation of
0781     // a real .bib file).
0782     // The encoding can by set in two different ways:
0783     // 1. As a property of the File object
0784     // 2. Enforced upon a FileExporterBibTeX instance, ignoring the File's encoding property
0785 
0786     // Fist, the forced-upon case will be executed
0787 
0788     QByteArray ba(1 << 12, '\0');
0789     QBuffer buffer(&ba);
0790 
0791     buffer.open(QBuffer::WriteOnly);
0792     FileExporterBibTeX exporterWithForcedEncoding(this);
0793     exporterWithForcedEncoding.setEncoding(encoding); //< Force the encoding on the FileExporterBibTeX instance
0794     QVERIFY(exporterWithForcedEncoding.save(&buffer, bibliography));
0795     const qint64 bytesWrittenWithForcedEncoding = buffer.pos();
0796     QVERIFY(bytesWrittenWithForcedEncoding > 32); //< All bibliographies in test have a certain minimum size
0797     buffer.close();
0798 
0799     ba.resize(static_cast<int>(bytesWrittenWithForcedEncoding & 0x7fffffff));
0800 
0801     buffer.open(QBuffer::ReadOnly);
0802     File *loadedFile = importer.load(&buffer);
0803     buffer.close();
0804 
0805     QVERIFY(loadedFile != nullptr);
0806     QVERIFY(loadedFile->length() == 1);
0807     QVERIFY(bibliography->operator ==(*loadedFile));
0808     delete loadedFile;
0809 
0810     // Second, the File encoding property case will be executed
0811 
0812     ba.fill('\0', 1 << 12); //< reset and clear buffer from above execution
0813 
0814     buffer.open(QBuffer::WriteOnly);
0815     FileExporterBibTeX exporterWithFileEncoding(this);
0816     bibliography->setProperty(File::Encoding, encoding); //< set the File's encoding property
0817     QVERIFY(exporterWithFileEncoding.save(&buffer, bibliography));
0818     const qint64 bytesWrittenWithFileEncoding = buffer.pos();
0819     QVERIFY(bytesWrittenWithFileEncoding > 32); //< All bibliographies in test have a certain minimum size
0820     buffer.close();
0821 
0822     ba.resize(static_cast<int>(bytesWrittenWithFileEncoding & 0x7fffffff));
0823 
0824     buffer.open(QBuffer::ReadOnly);
0825     loadedFile = importer.load(&buffer);
0826     buffer.close();
0827 
0828     QVERIFY(loadedFile != nullptr);
0829     QVERIFY(loadedFile->length() == 1);
0830     QVERIFY(bibliography->operator ==(*loadedFile));
0831     delete loadedFile;
0832 }
0833 
0834 void KBibTeXIOTest::protectiveCasingEntryFromData()
0835 {
0836     static const QString titleText = QStringLiteral("Some Title for a Journal Article");
0837     static const QString singleCurleyBracketTitle = QStringLiteral("{") + titleText + QStringLiteral("}");
0838     static const QString doubleCurleyBracketTitle = QStringLiteral("{{") + titleText + QStringLiteral("}}");
0839     static const QString bibTeXDataDoubleCurleyBracketTitle = QStringLiteral("@articl{doubleCurleyBracketTitle,\ntitle={{") + titleText + QStringLiteral("}}\n}\n");
0840     static const QString bibTeXDataSingleCurleyBracketTitle = QStringLiteral("@articl{singleCurleyBracketTitle,\ntitle={") + titleText + QStringLiteral("}\n}\n");
0841 
0842     FileImporterBibTeX fileImporterBibTeX(this);
0843     FileExporterBibTeX fileExporterBibTeX(this);
0844 
0845     QByteArray b1(bibTeXDataDoubleCurleyBracketTitle.toUtf8());
0846     QBuffer bufferDoubleCurleyBracketTitle(&b1, this);
0847     QByteArray b2(bibTeXDataSingleCurleyBracketTitle.toUtf8());
0848     QBuffer bufferSingleCurleyBracketTitle(&b2, this);
0849 
0850     bufferDoubleCurleyBracketTitle.open(QBuffer::ReadOnly);
0851     QScopedPointer<File> fileDoubleCurleyBracketTitle(fileImporterBibTeX.load(&bufferDoubleCurleyBracketTitle));
0852     bufferDoubleCurleyBracketTitle.close();
0853     fileDoubleCurleyBracketTitle->setProperty(File::StringDelimiter, QStringLiteral("{}"));
0854     bufferSingleCurleyBracketTitle.open(QBuffer::ReadOnly);
0855     QScopedPointer<File> fileSingleCurleyBracketTitle(fileImporterBibTeX.load(&bufferSingleCurleyBracketTitle));
0856     bufferSingleCurleyBracketTitle.close();
0857     fileSingleCurleyBracketTitle->setProperty(File::StringDelimiter, QStringLiteral("{}"));
0858 
0859     fileDoubleCurleyBracketTitle->setProperty(File::ProtectCasing, Qt::PartiallyChecked);
0860     const QString textDoubleCurleyBracketTitlePartialProtectiveCasing = fileExporterBibTeX.toString(fileDoubleCurleyBracketTitle.data());
0861     QVERIFY(textDoubleCurleyBracketTitlePartialProtectiveCasing.contains(doubleCurleyBracketTitle));
0862 
0863     fileSingleCurleyBracketTitle->setProperty(File::ProtectCasing, Qt::PartiallyChecked);
0864     const QString textSingleCurleyBracketTitlePartialProtectiveCasing = fileExporterBibTeX.toString(fileSingleCurleyBracketTitle.data());
0865     QVERIFY(textSingleCurleyBracketTitlePartialProtectiveCasing.contains(singleCurleyBracketTitle)
0866             && !textSingleCurleyBracketTitlePartialProtectiveCasing.contains(doubleCurleyBracketTitle));
0867 
0868     fileDoubleCurleyBracketTitle->setProperty(File::ProtectCasing, Qt::Checked);
0869     const QString textDoubleCurleyBracketTitleWithProtectiveCasing = fileExporterBibTeX.toString(fileDoubleCurleyBracketTitle.data());
0870     QVERIFY(textDoubleCurleyBracketTitleWithProtectiveCasing.contains(doubleCurleyBracketTitle));
0871 
0872     fileSingleCurleyBracketTitle->setProperty(File::ProtectCasing, Qt::Checked);
0873     const QString textSingleCurleyBracketTitleWithProtectiveCasing = fileExporterBibTeX.toString(fileSingleCurleyBracketTitle.data());
0874     QVERIFY(textSingleCurleyBracketTitleWithProtectiveCasing.contains(doubleCurleyBracketTitle));
0875 
0876     fileDoubleCurleyBracketTitle->setProperty(File::ProtectCasing, Qt::Unchecked);
0877     const QString textDoubleCurleyBracketTitleWithoutProtectiveCasing = fileExporterBibTeX.toString(fileDoubleCurleyBracketTitle.data());
0878     QVERIFY(textDoubleCurleyBracketTitleWithoutProtectiveCasing.contains(singleCurleyBracketTitle)
0879             && !textDoubleCurleyBracketTitleWithoutProtectiveCasing.contains(doubleCurleyBracketTitle));
0880 
0881     fileSingleCurleyBracketTitle->setProperty(File::ProtectCasing, Qt::Unchecked);
0882     const QString textSingleCurleyBracketTitleWithoutProtectiveCasing = fileExporterBibTeX.toString(fileSingleCurleyBracketTitle.data());
0883     QVERIFY(textSingleCurleyBracketTitleWithoutProtectiveCasing.contains(singleCurleyBracketTitle)
0884             && !textSingleCurleyBracketTitleWithoutProtectiveCasing.contains(doubleCurleyBracketTitle));
0885 }
0886 
0887 void KBibTeXIOTest::partialBibTeXInput_data()
0888 {
0889     QTest::addColumn<bool>("isValid");
0890     QTest::addColumn<QString>("text");
0891 
0892     static const struct BibTeXDataTable {
0893         const char *label;
0894         const bool isValid;
0895         const QString text;
0896     }
0897     bibTeXDataTable[] = {
0898         {"Empty string", false, QString()},
0899         {"Only 'at' sign", false, QStringLiteral("@")},
0900         {"Only 'at' sign followed by element type", false, QStringLiteral("@entry")},
0901         {"Only up to opening curly bracket", false, QStringLiteral("@entry{")},
0902         {"Complete entry but without id", true, QStringLiteral("@entry{,\n  title=\"{Abc Def}\",\n  month = jan\n}")},
0903         {"Entry without any data", true, QStringLiteral("@entry{}")},
0904         {"Entry up to entry id, but no closing curly bracket", false, QStringLiteral("@entry{test")},
0905         {"Entry up to entry id with opening curly bracket", false, QStringLiteral("@entry{test{")},
0906         {"Entry up to entry id with closing curly bracket", true, QStringLiteral("@entry{test}")},
0907         {"Entry up to comma after entry id", false, QStringLiteral("@entry{test,")},
0908         {"Entry up to comma after entry id, followed by closing curly bracket", true, QStringLiteral("@entry{test,}")},
0909         {"Entry up to first field's key, but nothing more, not even an assign char", false, QStringLiteral("@entry{test,title")},
0910         {"Entry up to first field's key, but nothing more, just a closing curly bracket", false, QStringLiteral("@entry{test,title}")},
0911         {"Entry up to first field's assign char, but nothing more", false, QStringLiteral("@entry{test,title=")},
0912         {"Entry up to first field's assign char, but nothing more, just a closing curly bracket", false, QStringLiteral("@entry{test,title=}")},
0913         {"Invalid combination of curly bracket in a field's value (1)", false, QStringLiteral("@entry{test,title={}")},
0914         {"Invalid combination of curly bracket in a field's value (2)", false, QStringLiteral("@entry{test,title={{}}")},
0915         {"Invalid combination of curly bracket in a field's value (3)", false, QStringLiteral("@entry{test,title={}{}")},
0916         {"Invalid combination of curly bracket in a field's value (4)", false, QStringLiteral("@entry{test,title={}{}}")},
0917         {"Complete entry with empty title (1)", true, QStringLiteral("@entry{test,\n  title=\"{}\"\n}")},
0918         {"Complete entry with empty title (2)", true, QStringLiteral("@entry{test,\n  title=\"\"\n}")},
0919         {"Complete entry with empty title (3)", true, QStringLiteral("@entry{test,\n  title={{}}\n}")},
0920         {"Complete entry with empty title (4)", true, QStringLiteral("@entry{test,\n  title={}\n}")},
0921         {"Entry abruptly ending at macro key as field value (1)", false, QStringLiteral("@entry{test,\n  month = jan")},
0922         {"Entry abruptly ending at macro key as field value (2)", false, QStringLiteral("@entry{test,\n  month = jan\n")},
0923         // TODO more tests
0924         {"Complete entry", true, QStringLiteral("@entry{test,\n  title=\"{Abc Def}\",\n  month = jan\n}")}
0925     };
0926 
0927     for (const auto &bibTeXDataRow : bibTeXDataTable)
0928         QTest::newRow(bibTeXDataRow.label) << bibTeXDataRow.isValid << bibTeXDataRow.text;
0929 }
0930 
0931 void KBibTeXIOTest::partialBibTeXInput()
0932 {
0933     QFETCH(bool, isValid);
0934     QFETCH(QString, text);
0935 
0936     bool gotErrors = false;
0937     FileImporterBibTeX importer(this);
0938     connect(&importer, &FileImporter::message, [&gotErrors](const FileImporter::MessageSeverity messageSeverity, const QString & messageText) {
0939         gotErrors |= messageSeverity >= FileImporter::MessageSeverity::Error;
0940         Q_UNUSED(messageText)
0941         //qCDebug(LOG_KBIBTEX_TEST)<<"FileImporterBibTeX issues message during 'partialBibTeXInput' test: "<<messageText;
0942     });
0943     QScopedPointer<File> bibTeXfile(importer.fromString(text));
0944 
0945     QVERIFY(text.isEmpty() || isValid != gotErrors);
0946     QVERIFY(isValid ? (!bibTeXfile.isNull() && bibTeXfile->count() == 1) : (bibTeXfile.isNull() || bibTeXfile->count() == 0));
0947 }
0948 
0949 void KBibTeXIOTest::partialRISInput_data()
0950 {
0951     QTest::addColumn<bool>("isValid");
0952     QTest::addColumn<QString>("text");
0953 
0954     static const struct RISDataTable {
0955         const char *label;
0956         const bool isValid;
0957         const QString text;
0958     }
0959     risDataTable[] = {
0960         //{"Empty string", false, QString()},
0961         {"Incorrect year", true, QStringLiteral("TY  - JOUR\nAU  - Shannon, Claude E.\nPY  - 5555/07//\nTI  - A Mathematical Theory of Communication\nT2  - Bell System Technical Journal\nSP  - 379\nEP  - 423\nVL  - 27\nER  -")},
0962         {"Incorrect month", true, QStringLiteral("TY  - JOUR\nAU  - Shannon, Claude E.\nPY  - 1948/17//\nTI  - A Mathematical Theory of Communication\nT2  - Bell System Technical Journal\nSP  - 379\nEP  - 423\nVL  - 27\nER  -")},
0963         {"Entry does not end with 'ER'", true, QStringLiteral("TY  - JOUR\nAU  - Shannon, Claude E.\nPY  - 1948/07//\nTI  - A Mathematical Theory of Communication\nT2  - Bell System Technical Journal\nSP  - 379\nEP  - 423\nVL  - 27")},
0964         // TODO more tests
0965         //{"Complete entry", true, QStringLiteral("TY  - JOUR\nAU  - Shannon, Claude E.\nPY  - 1948/07//\nTI  - A Mathematical Theory of Communication\nT2  - Bell System Technical Journal\nSP  - 379\nEP  - 423\nVL  - 27\nER  -")}
0966     };
0967 
0968     for (const auto &risDataRow : risDataTable)
0969         QTest::newRow(risDataRow.label) << risDataRow.isValid << risDataRow.text;
0970 }
0971 
0972 void KBibTeXIOTest::partialRISInput()
0973 {
0974     QFETCH(bool, isValid);
0975     QFETCH(QString, text);
0976 
0977     bool gotErrors = false;
0978     FileImporterRIS importer(this);
0979     connect(&importer, &FileImporter::message, [&gotErrors](const FileImporter::MessageSeverity messageSeverity, const QString & messageText) {
0980         gotErrors |= messageSeverity >= FileImporter::MessageSeverity::Error;
0981         Q_UNUSED(messageText)
0982         //qCDebug(LOG_KBIBTEX_TEST)<<"FileImporterRIS issues message during 'partialBibTeXInput' test: "<<messageText;
0983     });
0984     QScopedPointer<File> bibTeXfile(importer.fromString(text));
0985 
0986     QVERIFY(text.isEmpty() || isValid != gotErrors);
0987     QVERIFY(isValid ? (!bibTeXfile.isNull() && bibTeXfile->count() == 1) : (bibTeXfile.isNull() || bibTeXfile->count() == 0));
0988 }
0989 
0990 void KBibTeXIOTest::jabRefFieldFile_data()
0991 {
0992     QTest::addColumn<FileExporter *>("exporter");
0993     QTest::addColumn<FileImporter *>("importer");
0994     QTest::addColumn<File *>("inputBibliography");
0995 
0996     static const size_t buffersize = 1024;
0997     char buffer[buffersize], bibutilsbuffer[buffersize];
0998     QVector<QPair<FileExporter *, FileImporter *>> listOfExImporter {
0999         qMakePair(new FileExporterBibTeX(this), new FileImporterBibTeX(this))
1000     };
1001     if (BibUtils::available()) {
1002         /// BibUtils does not (yet) support all variations like JabRef (and KBibTeX's own BibTeX exporter/importer) does
1003         static const QVector<BibUtils::Format> bibUtilFormats{/* BibUtils::Format::BibTeX */};
1004         for (const auto format : bibUtilFormats) {
1005             FileExporterBibUtils *exporter = new FileExporterBibUtils(this);
1006             exporter->setFormat(format);
1007             FileImporterBibUtils *importer = new  FileImporterBibUtils(this);
1008             importer->setFormat(format);
1009             listOfExImporter.append(qMakePair(exporter, importer));
1010         }
1011     }
1012     for (auto &pair : listOfExImporter) {
1013         auto exporter = pair.first;
1014         auto importer = pair.second;
1015         const BibUtils::Format bibutilsformat = strncmp(exporter->metaObject()->className() + 12, "BibUtils", 8) == 0 ? qobject_cast<FileExporterBibUtils *>(exporter)->format() : BibUtils::Format::InvalidFormat;
1016         if (bibutilsformat == BibUtils::Format::InvalidFormat)
1017             bibutilsbuffer[0] = '\0';
1018         else
1019             snprintf(bibutilsbuffer, buffersize, ", format=%s", bibutilsformat == BibUtils::Format::BibTeX ? "BibTeX" : bibutilsformat == BibUtils::Format::BibLaTeX ? "BibLaTeX" : bibutilsformat == BibUtils::Format::RIS ? "RIS" : bibutilsformat == BibUtils::Format::WordBib ? "WordBib" : "???");
1020 
1021         File *file = new File();
1022         QSharedPointer<Entry> entry = QSharedPointer<Entry>(new Entry(Entry::etArticle, QStringLiteral("jabRefFieldFile")));
1023         Value value;
1024         value.append(QSharedPointer<VerbatimText>(new VerbatimText(QStringLiteral("file.pdf"))));
1025         entry->insert(Entry::ftFile, value);
1026         file->append(entry);
1027         int ret = snprintf(buffer, buffersize, "Field 'file' with just a filename (exporter=%s, importer=%s%s)", exporter->metaObject()->className(), importer->metaObject()->className(), bibutilsbuffer);
1028         if (ret > 0)
1029             QTest::newRow(buffer) << exporter << importer << file;
1030 
1031         file = new File();
1032         entry = QSharedPointer<Entry>(new Entry(Entry::etArticle, QStringLiteral("jabRefFieldFile")));
1033         value.clear();
1034         VerbatimText *verbatimText = new VerbatimText(QStringLiteral("file.pdf"));
1035         verbatimText->setComment(QStringLiteral("Some PDF file"));
1036         value.append(QSharedPointer<VerbatimText>(verbatimText));
1037         entry->insert(Entry::ftFile, value);
1038         file->append(entry);
1039         ret = snprintf(buffer, buffersize, "Field 'file' with a JabRef-like value (exporter=%s, importer=%s%s)", exporter->metaObject()->className(), importer->metaObject()->className(), bibutilsbuffer);
1040         if (ret > 0)
1041             QTest::newRow(buffer) << exporter << importer << file;
1042 
1043         file = new File();
1044         entry = QSharedPointer<Entry>(new Entry(Entry::etArticle, QStringLiteral("jabRefFieldFile")));
1045         value.clear();
1046         verbatimText = new VerbatimText(QStringLiteral("file.pdf"));
1047         verbatimText->setComment();
1048         value.append(QSharedPointer<VerbatimText>(verbatimText));
1049         entry->insert(Entry::ftFile, value);
1050         file->append(entry);
1051         ret = snprintf(buffer, buffersize, "Field 'file' with a JabRef-like value and empty comment (exporter=%s, importer=%s%s)", exporter->metaObject()->className(), importer->metaObject()->className(), bibutilsbuffer);
1052         if (ret > 0)
1053             QTest::newRow(buffer) << exporter << importer << file;
1054 
1055     }
1056 }
1057 
1058 void KBibTeXIOTest::jabRefFieldFile()
1059 {
1060     QFETCH(FileExporter *, exporter);
1061     QFETCH(FileImporter *, importer);
1062     QFETCH(File *, inputBibliography);
1063 
1064     const QString inputAsString = exporter->toString(inputBibliography);
1065     QVERIFY(!inputAsString.isEmpty());
1066 
1067     File *reimportedBibliography = importer->fromString(inputAsString);
1068     QVERIFY(reimportedBibliography != nullptr);
1069 
1070     /// Thorough check if both files contain the same elements/entries and if those are identical
1071     QVERIFY(reimportedBibliography->operator ==(*inputBibliography));
1072 }
1073 
1074 void KBibTeXIOTest::initTestCase()
1075 {
1076     qRegisterMetaType<FileImporter::MessageSeverity>();
1077 }
1078 
1079 QTEST_MAIN(KBibTeXIOTest)
1080 
1081 #include "kbibtexiotest.moc"