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 }