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

0001 /***************************************************************************
0002  *   SPDX-License-Identifier: GPL-2.0-or-later
0003  *                                                                         *
0004  *   SPDX-FileCopyrightText: 2004-2019 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 "bibutils.h"
0021 
0022 #include <QProcess>
0023 #include <QBuffer>
0024 #include <QByteArray>
0025 #include <QStandardPaths>
0026 
0027 #include "logging_io.h"
0028 
0029 class BibUtils::Private
0030 {
0031 public:
0032     BibUtils::Format format;
0033 
0034     Private(BibUtils *parent)
0035             : format(BibUtils::Format::MODS)
0036     {
0037         Q_UNUSED(parent)
0038     }
0039 };
0040 
0041 BibUtils::BibUtils()
0042         : d(new BibUtils::Private(this))
0043 {
0044     /// nothing
0045 }
0046 
0047 BibUtils::~BibUtils()
0048 {
0049     delete d;
0050 }
0051 
0052 void BibUtils::setFormat(const BibUtils::Format format) {
0053     d->format = format;
0054 }
0055 
0056 BibUtils::Format BibUtils::format() const {
0057     return d->format;
0058 }
0059 
0060 bool BibUtils::available() {
0061     enum State {untested, avail, unavail};
0062     static State state = untested;
0063     /// Perform test only once, later rely on statically stored result
0064     if (state == untested) {
0065         /// Test a number of known BibUtils programs
0066         static const QStringList programs {QStringLiteral("bib2xml"), QStringLiteral("isi2xml"), QStringLiteral("ris2xml"), QStringLiteral("end2xml")};
0067         state = avail;
0068         for (const QString &program : programs) {
0069             const QString fullPath = QStandardPaths::findExecutable(program);
0070             if (fullPath.isEmpty()) {
0071                 state = unavail; ///< missing a single program is reason to assume that BibUtils is not correctly installed
0072                 break;
0073             }
0074         }
0075         if (state == avail)
0076             qCDebug(LOG_KBIBTEX_IO) << "BibUtils found, using it to import/export certain types of bibliographies";
0077         else if (state == unavail)
0078             qCWarning(LOG_KBIBTEX_IO) << "No or only an incomplete installation of BibUtils found";
0079     }
0080     return state == avail;
0081 }
0082 
0083 bool BibUtils::convert(QIODevice &source, const BibUtils::Format sourceFormat, QIODevice &destination, const BibUtils::Format destinationFormat) const {
0084     /// To proceed, either the source format or the destination format
0085     /// has to be MODS, otherwise ...
0086     if (sourceFormat != BibUtils::Format::MODS && destinationFormat != BibUtils::Format::MODS) {
0087         /// Add indirection: convert source format to MODS,
0088         /// then convert MODS data to destination format
0089 
0090         /// Intermediate buffer to hold MODS data
0091         QBuffer buffer;
0092         bool result = convert(source, sourceFormat, buffer, BibUtils::Format::MODS);
0093         if (result)
0094             result = convert(buffer, BibUtils::Format::MODS, destination, destinationFormat);
0095         return result;
0096     }
0097 
0098     QString bibUtilsProgram;
0099     QString utf8Argument = QStringLiteral("-un");
0100 
0101     /// Determine part of BibUtils program name that represents source format
0102     switch (sourceFormat) {
0103     case BibUtils::Format::MODS: bibUtilsProgram = QStringLiteral("xml"); utf8Argument = QStringLiteral("-nb"); break;
0104     case BibUtils::Format::BibTeX: bibUtilsProgram = QStringLiteral("bib"); break;
0105     case BibUtils::Format::BibLaTeX: bibUtilsProgram = QStringLiteral("biblatex"); break;
0106     case BibUtils::Format::ISI: bibUtilsProgram = QStringLiteral("isi"); break;
0107     case BibUtils::Format::RIS: bibUtilsProgram = QStringLiteral("ris"); break;
0108     case BibUtils::Format::EndNote: bibUtilsProgram = QStringLiteral("end"); break;
0109     case BibUtils::Format::EndNoteXML: bibUtilsProgram = QStringLiteral("endx"); break;
0110     /// case ADS not supported by BibUtils
0111     case BibUtils::Format::WordBib: bibUtilsProgram = QStringLiteral("wordbib"); break;
0112     case BibUtils::Format::Copac: bibUtilsProgram = QStringLiteral("copac"); break;
0113     case BibUtils::Format::Med: bibUtilsProgram = QStringLiteral("med"); break;
0114     default:
0115         qCWarning(LOG_KBIBTEX_IO) << "Unsupported BibUtils input format:" << sourceFormat;
0116         return false;
0117     }
0118 
0119     bibUtilsProgram.append(QStringLiteral("2"));
0120 
0121     /// Determine part of BibUtils program name that represents destination format
0122     switch (destinationFormat) {
0123     case BibUtils::Format::MODS: bibUtilsProgram.append(QStringLiteral("xml")); break;
0124     case BibUtils::Format::BibTeX: bibUtilsProgram.append(QStringLiteral("bib")); break;
0125     /// case BibLaTeX not supported by BibUtils
0126     case BibUtils::Format::ISI: bibUtilsProgram.append(QStringLiteral("isi")); break;
0127     case BibUtils::Format::RIS: bibUtilsProgram.append(QStringLiteral("ris")); break;
0128     case BibUtils::Format::EndNote: bibUtilsProgram.append(QStringLiteral("end")); break;
0129     /// case EndNoteXML not supported by BibUtils
0130     case BibUtils::Format::ADS: bibUtilsProgram.append(QStringLiteral("ads")); break;
0131     case BibUtils::Format::WordBib: bibUtilsProgram.append(QStringLiteral("wordbib")); break;
0132     /// case Copac not supported by BibUtils
0133     /// case Med not supported by BibUtils
0134     default:
0135         qCWarning(LOG_KBIBTEX_IO) << "Unsupported BibUtils output format:" << destinationFormat;
0136         return false;
0137     }
0138 
0139     /// Test if required BibUtils program is available
0140     bibUtilsProgram = QStandardPaths::findExecutable(bibUtilsProgram);
0141     if (bibUtilsProgram.isEmpty())
0142         return false;
0143 
0144     /// Test if source device is readable
0145     if (!source.isReadable() && !source.open(QIODevice::ReadOnly))
0146         return false;
0147     /// Test if destination device is writable
0148     if (!destination.isWritable() && !destination.open(QIODevice::WriteOnly)) {
0149         source.close();
0150         return false;
0151     }
0152 
0153     QProcess bibUtilsProcess;
0154     const QStringList arguments {QStringLiteral("-i"), QStringLiteral("utf8"), utf8Argument};
0155     /// Start BibUtils program/process
0156     bibUtilsProcess.start(bibUtilsProgram, arguments);
0157 
0158     bool result = bibUtilsProcess.waitForStarted();
0159     if (result) {
0160         /// Write source data to process's stdin
0161         bibUtilsProcess.write(source.readAll());
0162         /// Close process's stdin start transformation
0163         bibUtilsProcess.closeWriteChannel();
0164         result = bibUtilsProcess.waitForFinished();
0165 
0166         /// If process run without problems ...
0167         if (result && bibUtilsProcess.exitStatus() == QProcess::NormalExit) {
0168             /// Read process's output, i.e. the transformed data
0169             const QByteArray stdOut = bibUtilsProcess.readAllStandardOutput();
0170             if (!stdOut.isEmpty()) {
0171                 /// Write transformed data to destination device
0172                 const int amountWritten = static_cast<int>(destination.write(stdOut));
0173                 /// Check that the same amount of bytes is written
0174                 /// as received from the BibUtils program
0175                 result = amountWritten == stdOut.size();
0176             } else
0177                 result = false;
0178         }
0179         else
0180             result = false;
0181     }
0182 
0183     /// In case it did not terminate earlier
0184     bibUtilsProcess.terminate();
0185 
0186     /// Close both source and destination device
0187     source.close();
0188     destination.close();
0189 
0190     return result;
0191 }
0192 
0193 QDebug operator<<(QDebug dbg, const BibUtils::Format &format)
0194 {
0195     static const auto pairs = QHash<int, const char *> {
0196         {static_cast<int>(BibUtils::Format::MODS), "MODS"},
0197         {static_cast<int>(BibUtils::Format::BibTeX), "BibTeX"},
0198         {static_cast<int>(BibUtils::Format::BibLaTeX), "BibLaTeX"},
0199         {static_cast<int>(BibUtils::Format::ISI), "ISI"},
0200         {static_cast<int>(BibUtils::Format::RIS), "RIS"},
0201         {static_cast<int>(BibUtils::Format::EndNote), "EndNote"},
0202         {static_cast<int>(BibUtils::Format::EndNoteXML), "EndNoteXML"},
0203         {static_cast<int>(BibUtils::Format::ADS), "ADS"},
0204         {static_cast<int>(BibUtils::Format::WordBib), "WordBib"},
0205         {static_cast<int>(BibUtils::Format::Copac), "Copac"},
0206         {static_cast<int>(BibUtils::Format::Med), "Med"}
0207     };
0208     dbg.nospace();
0209     const int formatInt = static_cast<int>(format);
0210     dbg << (pairs.contains(formatInt) ? pairs[formatInt] : "???");
0211     return dbg;
0212 }