File indexing completed on 2024-05-19 05:05:32
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 "fileexporter.h" 0021 0022 #include <QBuffer> 0023 #include <QTextStream> 0024 #include <QStandardPaths> 0025 #include <QRegularExpression> 0026 0027 #include <Element> 0028 #include "fileexporterbibtex.h" 0029 #include "fileexporterbibtexoutput.h" 0030 #include "fileexporterwordbibxml.h" 0031 #include "fileexporterxml.h" 0032 #include "fileexporterris.h" 0033 #include "fileexporterpdf.h" 0034 #include "fileexporterps.h" 0035 #include "fileexporterrtf.h" 0036 #include "fileexporterbibtex2html.h" 0037 #include "fileexporterbibutils.h" 0038 #include "logging_io.h" 0039 0040 FileExporter::FileExporter(QObject *parent) 0041 : QObject(parent) 0042 { 0043 /// nothing 0044 } 0045 0046 FileExporter::~FileExporter() 0047 { 0048 /// nothing 0049 } 0050 0051 0052 FileExporter *FileExporter::factory(const QFileInfo &fileInfo, const QString &exporterClassHint, QObject *parent) 0053 { 0054 const QString ending = fileInfo.completeSuffix().toLower(); 0055 0056 if (ending.endsWith(QStringLiteral("html")) || ending.endsWith(QStringLiteral("htm"))) { 0057 if (!QStandardPaths::findExecutable(QStringLiteral("bibtex2html")).isEmpty() && exporterClassHint.contains(QStringLiteral("FileExporterBibTeX2HTML"))) 0058 return new FileExporterBibTeX2HTML(parent); 0059 // else // TODO anything? 0060 } else if (ending.endsWith(QStringLiteral("xml"))) { 0061 if (BibUtils::available() && exporterClassHint.contains(QStringLiteral("FileExporterBibUtils"))) { 0062 FileExporterBibUtils *fileExporterBibUtils = new FileExporterBibUtils(parent); 0063 fileExporterBibUtils->setFormat(BibUtils::Format::WordBib); 0064 return fileExporterBibUtils; 0065 } else if (exporterClassHint.contains(QStringLiteral("FileExporterWordBibXML"))) 0066 return new FileExporterWordBibXML(parent); 0067 else 0068 return new FileExporterXML(parent); 0069 } else if (ending.endsWith(QStringLiteral("ris"))) { 0070 if (BibUtils::available() && exporterClassHint.contains(QStringLiteral("FileExporterBibUtils"))) { 0071 FileExporterBibUtils *fileExporterBibUtils = new FileExporterBibUtils(parent); 0072 fileExporterBibUtils->setFormat(BibUtils::Format::RIS); 0073 return fileExporterBibUtils; 0074 } else 0075 return new FileExporterRIS(parent); 0076 } else if (ending.endsWith(QStringLiteral("pdf"))) { 0077 return new FileExporterPDF(parent); 0078 } else if (ending.endsWith(QStringLiteral("ps"))) { 0079 return new FileExporterPS(parent); 0080 } else if (BibUtils::available() && ending.endsWith(QStringLiteral("isi"))) { 0081 FileExporterBibUtils *fileExporterBibUtils = new FileExporterBibUtils(parent); 0082 fileExporterBibUtils->setFormat(BibUtils::Format::ISI); 0083 return fileExporterBibUtils; 0084 } else if (ending.endsWith(QStringLiteral("rtf"))) { 0085 return new FileExporterRTF(parent); 0086 } else if (ending.endsWith(QStringLiteral("bbl"))) { 0087 return new FileExporterBibTeXOutput(FileExporterBibTeXOutput::OutputType::BibTeXBlockList, parent); 0088 } 0089 0090 return new FileExporterBibTeX(parent); 0091 } 0092 0093 FileExporter *FileExporter::factory(const QUrl &url, const QString &exporterClassHint, QObject *parent) 0094 { 0095 const QFileInfo inputFileInfo(url.fileName()); 0096 return factory(inputFileInfo, exporterClassHint, parent); 0097 } 0098 0099 QVector<QString> FileExporter::exporterClasses(const QFileInfo &fileInfo) 0100 { 0101 const QString ending = fileInfo.completeSuffix().toLower(); 0102 0103 if (ending.endsWith(QStringLiteral("html")) || ending.endsWith(QStringLiteral("htm"))) { 0104 if (!QStandardPaths::findExecutable(QStringLiteral("bibtex2html")).isEmpty()) 0105 return {QStringLiteral("FileExporterHTML"), QStringLiteral("FileExporterBibTeX2HTML")}; 0106 else 0107 return {QStringLiteral("FileExporterHTML")}; 0108 } else if (ending.endsWith(QStringLiteral("xml"))) { 0109 if (BibUtils::available()) 0110 return {QStringLiteral("FileExporterXML"), QStringLiteral("FileExporterWordBibXML"), QStringLiteral("FileExporterBibUtils")}; 0111 else 0112 return {QStringLiteral("FileExporterXML"), QStringLiteral("FileExporterWordBibXML")}; 0113 } else if (ending.endsWith(QStringLiteral("ris"))) { 0114 if (BibUtils::available()) 0115 return {QStringLiteral("FileExporterRIS"), QStringLiteral("FileExporterBibUtils")}; 0116 else 0117 return {QStringLiteral("FileExporterRIS")}; 0118 } else if (ending.endsWith(QStringLiteral("pdf"))) { 0119 return{QStringLiteral("FileExporterPDF")}; 0120 } else if (ending.endsWith(QStringLiteral("ps"))) { 0121 return {QStringLiteral("FileExporterPS")}; 0122 } else if (BibUtils::available() && ending.endsWith(QStringLiteral("isi"))) { 0123 return {QStringLiteral("FileExporterBibUtils")}; 0124 } else if (ending.endsWith(QStringLiteral("rtf"))) { 0125 return {QStringLiteral("FileExporterRTF")}; 0126 } else if (ending.endsWith(QStringLiteral("bbl"))) { 0127 return {QStringLiteral("FileExporterBibTeXOutput")}; 0128 } else { 0129 return {QStringLiteral("FileExporterBibTeX")}; 0130 } 0131 } 0132 0133 QVector<QString> FileExporter::exporterClasses(const QUrl &url) 0134 { 0135 const QFileInfo inputFileInfo(url.fileName()); 0136 return exporterClasses(inputFileInfo); 0137 } 0138 0139 QString FileExporter::toString(const QSharedPointer<const Element> &element, const File *bibtexfile) 0140 { 0141 QBuffer buffer; 0142 buffer.open(QBuffer::WriteOnly); 0143 if (save(&buffer, element, bibtexfile)) { 0144 buffer.close(); 0145 if (buffer.open(QBuffer::ReadOnly)) { 0146 QTextStream ts(&buffer); 0147 // https://forum.qt.io/topic/135724/qt-6-replacement-for-qtextcodec 0148 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 0149 ts.setCodec("UTF-8"); 0150 #else 0151 ts.setEncoding(QStringConverter::Utf8); 0152 #endif 0153 return ts.readAll(); 0154 } 0155 } 0156 0157 return QString(); 0158 } 0159 0160 QString FileExporter::toString(const File *bibtexfile) 0161 { 0162 QBuffer buffer; 0163 buffer.open(QBuffer::WriteOnly); 0164 if (save(&buffer, bibtexfile)) { 0165 buffer.close(); 0166 if (buffer.open(QBuffer::ReadOnly)) { 0167 QTextStream ts(&buffer); 0168 // https://forum.qt.io/topic/135724/qt-6-replacement-for-qtextcodec 0169 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 0170 ts.setCodec("utf-8"); 0171 #else 0172 ts.setEncoding(QStringConverter::Utf8); 0173 #endif 0174 return ts.readAll(); 0175 } 0176 } 0177 0178 return QString(); 0179 } 0180 0181 QString FileExporter::numberToOrdinal(const int number, bool onlyText) 0182 { 0183 if (number < 1) { 0184 // Unsupported editions, like -23 0185 qCWarning(LOG_KBIBTEX_IO) << "Don't know how to convert number" << number << "into an ordinal string for edition"; 0186 return QString(); 0187 } else if (onlyText && number == 1) 0188 return QStringLiteral("First"); 0189 else if (onlyText && number == 2) 0190 return QStringLiteral("Second"); 0191 else if (onlyText && number == 3) 0192 return QStringLiteral("Third"); 0193 else if (onlyText && number == 4) 0194 return QStringLiteral("Fourth"); 0195 else if (onlyText && number == 5) 0196 return QStringLiteral("Fifth"); 0197 else if (onlyText && number >= 20 && number % 10 == 1) { 0198 // 21, 31, 41, ... 0199 return QString(QStringLiteral("%1st")).arg(number); 0200 } else if (number >= 20 && number % 10 == 2) { 0201 // 22, 32, 42, ... 0202 return QString(QStringLiteral("%1nd")).arg(number); 0203 } else if (number >= 20 && number % 10 == 3) { 0204 // 23, 33, 43, ... 0205 return QString(QStringLiteral("%1rd")).arg(number); 0206 } else { 0207 // Remaining editions: 6, 7, ..., 19, 20, 24, 25, ... if onlyText is true 0208 // ... or 1, 2, 3, ... 19 if onlyText is false 0209 return QString(QStringLiteral("%1th")).arg(number); 0210 } 0211 } 0212 0213 int FileExporter::editionStringToNumber(const QString &editionString, bool *ok) 0214 { 0215 *ok = true; // Assume the best for now as this function only returns if successful (except for last return) 0216 0217 // Test if string is just digits that can be converted into a positive int 0218 bool toIntOk = false; 0219 int edition = editionString.toInt(&toIntOk); 0220 if (toIntOk && edition >= 1) 0221 return edition; 0222 0223 const QString editionStringLower = editionString.toLower().trimmed(); 0224 0225 // Test if string starts with digits, followed by English ordinal suffices 0226 static const QRegularExpression englishOrdinal(QStringLiteral("^(?<number>[1-9][0-9]*)(st|nd|rd|th|[.])($| edition)"), QRegularExpression::CaseInsensitiveOption); 0227 const QRegularExpressionMatch englishOrdinalMatch = englishOrdinal.match(editionStringLower); 0228 if (englishOrdinalMatch.hasMatch()) { 0229 bool toIntOk = false; 0230 int edition = englishOrdinalMatch.captured(QStringLiteral("number")).toInt(&toIntOk); 0231 if (toIntOk && edition >= 1) 0232 return edition; 0233 } 0234 0235 // Test if string is a spelled-out English ordinal (in some cases consider mis-spellings) 0236 if (editionStringLower == QLatin1String("first")) 0237 return 1; 0238 else if (editionStringLower == QLatin1String("second")) 0239 return 2; 0240 else if (editionStringLower == QLatin1String("third")) 0241 return 3; 0242 else if (editionStringLower == QLatin1String("fourth")) 0243 return 4; 0244 else if (editionStringLower == QLatin1String("fifth") || editionStringLower == QLatin1String("fivth")) 0245 return 5; 0246 else if (editionStringLower == QLatin1String("sixth")) 0247 return 6; 0248 else if (editionStringLower == QLatin1String("seventh")) 0249 return 7; 0250 else if (editionStringLower == QLatin1String("eighth") || editionStringLower == QLatin1String("eigth")) 0251 return 8; 0252 else if (editionStringLower == QLatin1String("nineth") || editionStringLower == QLatin1String("ninth")) 0253 return 9; 0254 else if (editionStringLower == QLatin1String("tenth")) 0255 return 10; 0256 else if (editionStringLower == QLatin1String("eleventh")) 0257 return 11; 0258 else if (editionStringLower == QLatin1String("twelvth") || editionStringLower == QLatin1String("twelfth")) 0259 return 12; 0260 else if (editionStringLower == QLatin1String("thirdteeth")) 0261 return 13; 0262 else if (editionStringLower == QLatin1String("fourteenth")) 0263 return 14; 0264 else if (editionStringLower == QLatin1String("fifteenth")) 0265 return 15; 0266 else if (editionStringLower == QLatin1String("sixteenth")) 0267 return 16; 0268 0269 // No test above succeeded, so communicate that conversion failed 0270 *ok = false; 0271 return 0; 0272 } 0273 0274 int FileExporter::monthStringToNumber(const QString &monthString, bool *ok) 0275 { 0276 if (ok != nullptr) 0277 *ok = false; 0278 0279 if (monthString.isEmpty()) 0280 return -1; 0281 else if (monthString[0].isDigit()) { 0282 int result = -1; 0283 bool _ok = false; 0284 if (monthString.length() >= 2 && monthString[1].isDigit()) 0285 result = monthString.left(2).toInt(&_ok); 0286 else 0287 result = monthString.left(1).toInt(&_ok); 0288 if (_ok && result >= 1 && result <= 12) { 0289 if (ok != nullptr) 0290 *ok = true; 0291 return result; 0292 } 0293 } 0294 0295 if (monthString.length() < 3) 0296 return -1; 0297 0298 const QString needle = monthString.left(3).toLower(); 0299 for (int i = 0; i < 12; i++) 0300 if (needle == KBibTeX::MonthsTriple[i]) { 0301 if (ok != nullptr) 0302 *ok = true; 0303 return i + 1; 0304 } 0305 0306 if (ok != nullptr) 0307 *ok = false; 0308 return -1; 0309 }