1 /***************************************************************************
2  *   Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
16  ***************************************************************************/
17 
18 #include "bibutils.h"
19 
20 #include <QProcess>
21 #include <QBuffer>
22 #include <QByteArray>
23 #include <QStandardPaths>
24 
25 #include "logging_io.h"
26 
27 class BibUtils::Private
28 {
29 public:
30     BibUtils::Format format;
31 
Private(BibUtils * parent)32     Private(BibUtils *parent)
33             : format(BibUtils::MODS)
34     {
35         Q_UNUSED(parent)
36     }
37 };
38 
BibUtils()39 BibUtils::BibUtils()
40         : d(new BibUtils::Private(this))
41 {
42     /// nothing
43 }
44 
~BibUtils()45 BibUtils::~BibUtils()
46 {
47     delete d;
48 }
49 
setFormat(const BibUtils::Format format)50 void BibUtils::setFormat(const BibUtils::Format format) {
51     d->format = format;
52 }
53 
format() const54 BibUtils::Format BibUtils::format() const {
55     return d->format;
56 }
57 
available()58 bool BibUtils::available() {
59     enum State {untested, avail, unavail};
60     static State state = untested;
61     /// Perform test only once, later rely on statically stored result
62     if (state == untested) {
63         /// Test a number of known BibUtils programs
64         static const QStringList programs {QStringLiteral("bib2xml"), QStringLiteral("isi2xml"), QStringLiteral("ris2xml"), QStringLiteral("end2xml")};
65         state = avail;
66         for (const QString &program : programs) {
67             const QString fullPath = QStandardPaths::findExecutable(program);
68             if (fullPath.isEmpty()) {
69                 state = unavail; ///< missing a single program is reason to assume that BibUtils is not correctly installed
70                 break;
71             }
72         }
73         if (state == avail)
74             qCDebug(LOG_KBIBTEX_IO) << "BibUtils found, using it to import/export certain types of bibliographies";
75         else if (state == unavail)
76             qCWarning(LOG_KBIBTEX_IO) << "No or only an incomplete installation of BibUtils found";
77     }
78     return state == avail;
79 }
80 
convert(QIODevice & source,const BibUtils::Format sourceFormat,QIODevice & destination,const BibUtils::Format destinationFormat) const81 bool BibUtils::convert(QIODevice &source, const BibUtils::Format sourceFormat, QIODevice &destination, const BibUtils::Format destinationFormat) const {
82     /// To proceed, either the source format or the destination format
83     /// has to be MODS, otherwise ...
84     if (sourceFormat != MODS && destinationFormat != MODS) {
85         /// Add indirection: convert source format to MODS,
86         /// then convert MODS data to destination format
87 
88         /// Intermediate buffer to hold MODS data
89         QBuffer buffer;
90         bool result = convert(source, sourceFormat, buffer, BibUtils::MODS);
91         if (result)
92             result = convert(buffer, BibUtils::MODS, destination, destinationFormat);
93         return result;
94     }
95 
96     QString bibUtilsProgram;
97     QString utf8Argument = QStringLiteral("-un");
98 
99     /// Determine part of BibUtils program name that represents source format
100     switch (sourceFormat) {
101     case MODS: bibUtilsProgram = QStringLiteral("xml"); utf8Argument = QStringLiteral("-nb"); break;
102     case BibTeX: bibUtilsProgram = QStringLiteral("bib"); break;
103     case BibLaTeX: bibUtilsProgram = QStringLiteral("biblatex"); break;
104     case ISI: bibUtilsProgram = QStringLiteral("isi"); break;
105     case RIS: bibUtilsProgram = QStringLiteral("ris"); break;
106     case EndNote: bibUtilsProgram = QStringLiteral("end"); break;
107     case EndNoteXML: bibUtilsProgram = QStringLiteral("endx"); break;
108     /// case ADS not supported by BibUtils
109     case WordBib: bibUtilsProgram = QStringLiteral("wordbib"); break;
110     case Copac: bibUtilsProgram = QStringLiteral("copac"); break;
111     case Med: bibUtilsProgram = QStringLiteral("med"); break;
112     default:
113         qCWarning(LOG_KBIBTEX_IO) << "Unsupported BibUtils input format:" << sourceFormat;
114         return false;
115     }
116 
117     bibUtilsProgram.append(QStringLiteral("2"));
118 
119     /// Determine part of BibUtils program name that represents destination format
120     switch (destinationFormat) {
121     case MODS: bibUtilsProgram.append(QStringLiteral("xml")); break;
122     case BibTeX: bibUtilsProgram.append(QStringLiteral("bib")); break;
123     /// case BibLaTeX not supported by BibUtils
124     case ISI: bibUtilsProgram.append(QStringLiteral("isi")); break;
125     case RIS: bibUtilsProgram.append(QStringLiteral("ris")); break;
126     case EndNote: bibUtilsProgram.append(QStringLiteral("end")); break;
127     /// case EndNoteXML not supported by BibUtils
128     case ADS: bibUtilsProgram.append(QStringLiteral("ads")); break;
129     case WordBib: bibUtilsProgram.append(QStringLiteral("wordbib")); break;
130     /// case Copac not supported by BibUtils
131     /// case Med not supported by BibUtils
132     default:
133         qCWarning(LOG_KBIBTEX_IO) << "Unsupported BibUtils output format:" << destinationFormat;
134         return false;
135     }
136 
137     /// Test if required BibUtils program is available
138     bibUtilsProgram = QStandardPaths::findExecutable(bibUtilsProgram);
139     if (bibUtilsProgram.isEmpty())
140         return false;
141 
142     /// Test if source device is readable
143     if (!source.isReadable() && !source.open(QIODevice::ReadOnly))
144         return false;
145     /// Test if destination device is writable
146     if (!destination.isWritable() && !destination.open(QIODevice::WriteOnly)) {
147         source.close();
148         return false;
149     }
150 
151     QProcess bibUtilsProcess;
152     const QStringList arguments {QStringLiteral("-i"), QStringLiteral("utf8"), utf8Argument};
153     /// Start BibUtils program/process
154     bibUtilsProcess.start(bibUtilsProgram, arguments);
155 
156     bool result = bibUtilsProcess.waitForStarted();
157     if (result) {
158         /// Write source data to process's stdin
159         bibUtilsProcess.write(source.readAll());
160         /// Close process's stdin start transformation
161         bibUtilsProcess.closeWriteChannel();
162         result = bibUtilsProcess.waitForFinished();
163 
164         /// If process run without problems ...
165         if (result && bibUtilsProcess.exitStatus() == QProcess::NormalExit) {
166             /// Read process's output, i.e. the transformed data
167             const QByteArray stdOut = bibUtilsProcess.readAllStandardOutput();
168             if (!stdOut.isEmpty()) {
169                 /// Write transformed data to destination device
170                 const int amountWritten = destination.write(stdOut);
171                 /// Check that the same amount of bytes is written
172                 /// as received from the BibUtils program
173                 result = amountWritten == stdOut.size();
174             } else
175                 result = false;
176         }
177         else
178             result = false;
179     }
180 
181     /// In case it did not terminate earlier
182     bibUtilsProcess.terminate();
183 
184     /// Close both source and destination device
185     source.close();
186     destination.close();
187 
188     return result;
189 }
190