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 }