File indexing completed on 2024-05-12 05:09:45

0001 /***************************************************************************
0002     Copyright (C) 2005-2020 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include <config.h>
0026 
0027 #include "z3950connection.h"
0028 #include "z3950fetcher.h"
0029 #include "messagehandler.h"
0030 #include "../utils/iso5426converter.h"
0031 #include "../utils/iso6937converter.h"
0032 #include "../tellico_debug.h"
0033 
0034 #include <KLocalizedString>
0035 #include <KConfigGroup>
0036 
0037 #include <QFile>
0038 #include <QApplication>
0039 
0040 #ifdef HAVE_YAZ
0041 extern "C" {
0042 #include <yaz/zoom.h>
0043 #include <yaz/marcdisp.h>
0044 #include <yaz/yaz-version.h>
0045 }
0046 #endif
0047 
0048 namespace {
0049   static const size_t Z3950_DEFAULT_MAX_RECORDS = 20;
0050 
0051 #ifdef HAVE_YAZ
0052   class QueryDestroyer {
0053   public:
0054     QueryDestroyer(ZOOM_query query_) : query(query_) {}
0055     ~QueryDestroyer() { if(query) ZOOM_query_destroy(query); }
0056   private:
0057     ZOOM_query query;
0058   };
0059 
0060   class ResultDestroyer {
0061   public:
0062     ResultDestroyer(ZOOM_resultset result_) : result(result_) {}
0063     ~ResultDestroyer() { if(result) ZOOM_resultset_destroy(result); }
0064   private:
0065     ZOOM_resultset result;
0066   };
0067 
0068   class YazCloser {
0069   public:
0070     YazCloser(yaz_iconv_t iconv_) : iconv(iconv_), marc(nullptr) {}
0071     YazCloser(yaz_iconv_t iconv_, yaz_marc_t marc_) : iconv(iconv_), marc(marc_) {}
0072     ~YazCloser() {
0073       if(iconv) yaz_iconv_close(iconv);
0074       if(marc) yaz_marc_destroy(marc);
0075     }
0076   private:
0077     yaz_iconv_t iconv;
0078     yaz_marc_t marc;
0079   };
0080 #endif
0081 }
0082 
0083 using namespace Tellico;
0084 using Tellico::Fetch::Z3950ResultFound;
0085 using Tellico::Fetch::Z3950Connection;
0086 
0087 Z3950ResultFound::Z3950ResultFound(const QString& s) : QEvent(uid())
0088     , m_result(s) {
0089   ++Z3950Connection::resultsLeft;
0090 }
0091 
0092 Z3950ResultFound::~Z3950ResultFound() {
0093   --Z3950Connection::resultsLeft;
0094 }
0095 
0096 class Z3950Connection::Private {
0097 public:
0098 #ifdef HAVE_YAZ
0099   Private() : conn_opt(nullptr), conn(nullptr) {}
0100   ~Private() {
0101     ZOOM_options_destroy(conn_opt);
0102     ZOOM_connection_destroy(conn);
0103   };
0104 
0105   ZOOM_options conn_opt;
0106   ZOOM_connection conn;
0107 #else
0108   Private() {}
0109 #endif
0110 };
0111 
0112 int Z3950Connection::resultsLeft = 0;
0113 
0114 Z3950Connection::Z3950Connection(Fetch::Fetcher* fetcher,
0115                                  const QString& host,
0116                                  uint port,
0117                                  const QString& dbname,
0118                                  const QString& syntax,
0119                                  const QString& esn)
0120     : QThread()
0121     , d(new Private())
0122     , m_connected(false)
0123     , m_aborted(false)
0124     , m_fetcher(fetcher)
0125     , m_host(host)
0126     , m_port(port)
0127     , m_dbname(dbname)
0128     , m_syntax(syntax)
0129     , m_esn(esn)
0130     , m_start(0)
0131     , m_limit(Z3950_DEFAULT_MAX_RECORDS)
0132     , m_hasMore(false) {
0133   Q_ASSERT(fetcher);
0134   Q_ASSERT(fetcher->type() == Fetch::Z3950);
0135 }
0136 
0137 Z3950Connection::~Z3950Connection() {
0138   m_connected = false;
0139   delete d;
0140   d = nullptr;
0141 }
0142 
0143 void Z3950Connection::reset() {
0144   m_start = 0;
0145   m_limit = Z3950_DEFAULT_MAX_RECORDS;
0146 }
0147 
0148 void Z3950Connection::setQuery(const QString& query_) {
0149   m_pqn = query_;
0150 }
0151 
0152 void Z3950Connection::setUserPassword(const QString& user_, const QString& pword_) {
0153   m_user = user_;
0154   m_password = pword_;
0155 }
0156 
0157 // since the character set goes into a yaz api call
0158 // I'm paranoid about user insertions, so just grab 64
0159 // characters at most
0160 void Z3950Connection::setCharacterSet(const QString& queryCharSet_, const QString& responseCharSet_) {
0161   m_queryCharSet = queryCharSet_.left(64);
0162   m_responseCharSet = responseCharSet_.isEmpty() ? m_queryCharSet : responseCharSet_.left(64);
0163 }
0164 
0165 void Z3950Connection::run() {
0166   m_aborted = false;
0167   m_hasMore = false;
0168   resultsLeft = 0;
0169 #ifdef HAVE_YAZ
0170 
0171   if(!makeConnection()) {
0172     done();
0173     return;
0174   }
0175 
0176   ZOOM_query query = ZOOM_query_create();
0177   QueryDestroyer qd(query);
0178 
0179   const QByteArray ba = queryToByteArray(m_pqn);
0180   int errcode = ZOOM_query_prefix(query, ba.constData());
0181   if(errcode != 0) {
0182     myDebug() << "query error: " << m_pqn;
0183     QString s = i18n("Query error!");
0184     s += QLatin1Char(' ') + m_pqn;
0185     done(s, MessageHandler::Error);
0186     return;
0187   }
0188 
0189   ZOOM_resultset resultSet = ZOOM_connection_search(d->conn, query);
0190   ResultDestroyer rd(resultSet);
0191 
0192   // check abort status
0193   if(m_aborted) {
0194     done();
0195     return;
0196   }
0197 
0198   // I know the LOC wants the syntax = "xml" and esn = "mods"
0199   // to get MODS data, that seems a bit odd...
0200   // esn only makes sense for marc and grs-1
0201   // if syntax is mods, set esn to mods too
0202   QByteArray type = "raw";
0203   if(m_syntax == QLatin1String("mods")) {
0204     m_syntax = QStringLiteral("xml");
0205     ZOOM_resultset_option_set(resultSet, "elementSetName", "mods");
0206     type = "xml";
0207   } else {
0208     ZOOM_resultset_option_set(resultSet, "elementSetName", m_esn.toLatin1().constData());
0209   }
0210   ZOOM_resultset_option_set(resultSet, "start", QByteArray::number(static_cast<int>(m_start)).constData());
0211   ZOOM_resultset_option_set(resultSet, "count", QByteArray::number(static_cast<int>(m_limit-m_start)).constData());
0212   // search in default syntax, unless syntax is already set
0213   if(!m_syntax.isEmpty()) {
0214     ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", m_syntax.toLatin1().constData());
0215   }
0216 
0217   const char* errmsg;
0218   const char* addinfo;
0219   errcode = ZOOM_connection_error(d->conn, &errmsg, &addinfo);
0220   if(errcode != 0) {
0221     m_connected = false;
0222 
0223     QString s = i18n("Connection search error %1: %2", errcode, responseToString(errmsg));
0224     if(!QByteArray(addinfo).isEmpty()) {
0225       s += QLatin1String(" (") + responseToString(addinfo) + QLatin1Char(')');
0226     }
0227     myDebug() << QStringLiteral("[%1/%2]").arg(m_host, m_dbname) << s;
0228     done(s, MessageHandler::Error);
0229     return;
0230   }
0231 
0232   const size_t numResults = ZOOM_resultset_size(resultSet);
0233 
0234   QString newSyntax = m_syntax;
0235   if(numResults > 0) {
0236 //    myLog() << "current syntax is " << m_syntax << " (" << numResults << " results)";
0237     // so now we know that results exist, might have to check syntax
0238 
0239     if(m_syntax == QLatin1String("ads")) {
0240       // ads syntax is really 1.2.840.10003.5.1000.147.1
0241       // see http://adsabs.harvard.edu/abs_doc/ads_server.html
0242       ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", "1.2.840.10003.5.1000.147.1");
0243     }
0244 
0245     int len;
0246     ZOOM_record rec = ZOOM_resultset_record(resultSet, 0);
0247     // want raw unless it's mods
0248     ZOOM_record_get(rec, type.constData(), &len);
0249     if(len > 0 && m_syntax.isEmpty()) {
0250       newSyntax = QString::fromLatin1(ZOOM_record_get(rec, "syntax", &len)).toLower();
0251       myLog() << "syntax guess is " << newSyntax;
0252       if(newSyntax == QLatin1String("mods") || newSyntax == QLatin1String("xml")) {
0253         m_syntax = QStringLiteral("xml");
0254         ZOOM_resultset_option_set(resultSet, "elementSetName", "mods");
0255       } else if(newSyntax == QLatin1String("grs-1")) {
0256         // if it's defaulting to grs-1, go ahead and change it to try to get a marc
0257         // record since grs-1 is a last resort for us
0258         newSyntax.clear();
0259       }
0260     }
0261     if(newSyntax != QLatin1String("xml") &&
0262        newSyntax != QLatin1String("usmarc") &&
0263        newSyntax != QLatin1String("marc21") &&
0264        newSyntax != QLatin1String("unimarc") &&
0265        newSyntax != QLatin1String("grs-1") &&
0266        newSyntax != QLatin1String("ads")) {
0267       myLog() << "changing z39.50 syntax to MODS";
0268       newSyntax = QStringLiteral("xml");
0269       ZOOM_resultset_option_set(resultSet, "elementSetName", "mods");
0270       ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.toLatin1().constData());
0271       rec = ZOOM_resultset_record(resultSet, 0);
0272       ZOOM_record_get(rec, "xml", &len);
0273       if(len == 0) {
0274         // change set name back
0275         ZOOM_resultset_option_set(resultSet, "elementSetName", m_esn.toLatin1().constData());
0276         newSyntax = QStringLiteral("usmarc"); // try usmarc
0277         myLog() << "changing z39.50 syntax to USMARC";
0278         ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.toLatin1().constData());
0279         rec = ZOOM_resultset_record(resultSet, 0);
0280         ZOOM_record_get(rec, "raw", &len);
0281       }
0282       if(len == 0) {
0283         newSyntax = QStringLiteral("marc21"); // try marc21
0284         myLog() << "changing z39.50 syntax to MARC21";
0285         ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.toLatin1().constData());
0286         rec = ZOOM_resultset_record(resultSet, 0);
0287         ZOOM_record_get(rec, "raw", &len);
0288       }
0289       if(len == 0) {
0290         newSyntax = QStringLiteral("unimarc"); // try unimarc
0291         myLog() << "changing z39.50 syntax to UNIMARC";
0292         ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.toLatin1().constData());
0293         rec = ZOOM_resultset_record(resultSet, 0);
0294         ZOOM_record_get(rec, "raw", &len);
0295       }
0296       if(len == 0) {
0297         newSyntax = QStringLiteral("grs-1"); // try grs-1
0298         myLog() << "changing z39.50 syntax to GRS-1";
0299         ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.toLatin1().constData());
0300         rec = ZOOM_resultset_record(resultSet, 0);
0301         ZOOM_record_get(rec, "raw", &len);
0302       }
0303       if(len == 0) {
0304         myLog() << "giving up";
0305         done(i18n("Record syntax error"), MessageHandler::Error);
0306         return;
0307       }
0308       myLog() << "final syntax is " << newSyntax;
0309     }
0310   }
0311 
0312   // go back to fooling ourselves and calling it mods
0313   if(m_syntax == QLatin1String("xml")) {
0314     m_syntax = QStringLiteral("mods");
0315   }
0316   if(newSyntax == QLatin1String("xml")) {
0317     newSyntax = QStringLiteral("mods");
0318   }
0319   // save syntax change for next time
0320   if(m_syntax != newSyntax) {
0321     if(m_fetcher) {
0322       qApp->postEvent(m_fetcher.data(), new Z3950SyntaxChange(newSyntax));
0323     }
0324     m_syntax = newSyntax;
0325   }
0326 
0327   if(m_queryCharSet.isEmpty()) {
0328     m_queryCharSet = QStringLiteral("marc-8");
0329   }
0330   if(m_responseCharSet.isEmpty()) {
0331     m_responseCharSet = m_queryCharSet;
0332   }
0333 
0334   const size_t realLimit = qMin(numResults, m_limit);
0335 
0336   bool showError = true;
0337   for(size_t i = m_start; i < realLimit && !m_aborted; ++i) {
0338 //    myLog() << "grabbing index" << i;
0339     ZOOM_record rec = ZOOM_resultset_record(resultSet, i);
0340     if(!rec) {
0341       myDebug() << "no record returned for index" << i;
0342       errcode = ZOOM_connection_error(d->conn, &errmsg, &addinfo);
0343       if(errcode != 0) {
0344         QString s = i18n("Connection search error %1: %2", errcode, responseToString(errmsg));
0345         if(!QByteArray(addinfo).isEmpty()) {
0346           s += QLatin1String(" (") + responseToString(addinfo) + QLatin1Char(')');
0347         }
0348         myDebug() << QStringLiteral("[%1/%2]").arg(m_host, m_dbname) << s;
0349         if(showError) {
0350           showError = false;
0351           m_hasMore = true;
0352           done(s, MessageHandler::Error);
0353         }
0354       }
0355       continue;
0356     }
0357     int len;
0358     QString data;
0359     if(m_syntax == QLatin1String("mods")) {
0360       data = responseToString(ZOOM_record_get(rec, "xml", &len));
0361     } else if(m_syntax == QLatin1String("grs-1")) {
0362       // grs-1 means we have to try to parse the rendered data, very ugly...
0363       data = responseToString(ZOOM_record_get(rec, "render", &len));
0364     } else if(m_syntax == QLatin1String("ads")) {
0365       data = responseToString(ZOOM_record_get(rec, "raw", &len));
0366       // haven't been able to figure out how to include line endings
0367       // just mangle the result by replacing % with \n%
0368       data.replace(QLatin1Char('%'), QLatin1String("\n%"));
0369     } else {
0370 #if 0
0371       myWarning() << "Remove debug from z3950connection.cpp";
0372       {
0373         QFile f1(QLatin1String("/tmp/z3950.raw"));
0374         if(f1.open(QIODevice::WriteOnly)) {
0375           QDataStream t(&f1);
0376           t << ZOOM_record_get(rec, "raw", &len);
0377         }
0378         f1.close();
0379       }
0380 #endif
0381       data = toXML(ZOOM_record_get(rec, "raw", &len), m_responseCharSet);
0382     }
0383     if(m_fetcher) {
0384       auto ev = new Z3950ResultFound(data);
0385       QApplication::postEvent(m_fetcher.data(), ev);
0386     }
0387   }
0388 
0389   m_hasMore = m_limit < numResults;
0390   if(m_hasMore) {
0391     m_start = m_limit;
0392     m_limit += Z3950_DEFAULT_MAX_RECORDS;
0393   }
0394 #endif
0395   done();
0396 }
0397 
0398 bool Z3950Connection::makeConnection() {
0399   if(m_connected) {
0400     return true;
0401   }
0402 // I don't know what to do except assume database, user, and password are in locale encoding
0403 #ifdef HAVE_YAZ
0404   d->conn_opt = ZOOM_options_create();
0405   ZOOM_options_set(d->conn_opt, "implementationName", "Tellico");
0406   QByteArray ba = queryToByteArray(m_dbname);
0407   ZOOM_options_set(d->conn_opt, "databaseName",       ba.constData());
0408   ba = queryToByteArray(m_user);
0409   ZOOM_options_set(d->conn_opt, "user",               ba.constData());
0410   ba = queryToByteArray(m_password);
0411   ZOOM_options_set(d->conn_opt, "password",           ba.constData());
0412 
0413   d->conn = ZOOM_connection_create(d->conn_opt);
0414   ZOOM_connection_connect(d->conn, m_host.toLatin1().constData(), m_port);
0415 
0416   int errcode;
0417   const char* errmsg; // unused: carries same info as 'errcode'
0418   const char* addinfo;
0419   errcode = ZOOM_connection_error(d->conn, &errmsg, &addinfo);
0420   if(errcode != 0) {
0421     ZOOM_options_destroy(d->conn_opt);
0422     ZOOM_connection_destroy(d->conn);
0423     m_connected = false;
0424 
0425     QString s = i18n("Connection error %1: %2", errcode, responseToString(errmsg));
0426     if(!QByteArray(addinfo).isEmpty()) {
0427       s += QLatin1String(" (") + responseToString(addinfo) + QLatin1Char(')');
0428     }
0429     myDebug() << QStringLiteral("[%1/%2]").arg(m_host, m_dbname) << s;
0430     done(s, MessageHandler::Error);
0431     return false;
0432   }
0433 #else
0434   Q_UNUSED(m_port);
0435 #endif
0436   m_connected = true;
0437   return true;
0438 }
0439 
0440 void Z3950Connection::done() {
0441   checkPendingEvents();
0442   if(m_fetcher) {
0443     qApp->postEvent(m_fetcher.data(), new Z3950ConnectionDone(m_hasMore));
0444   }
0445 }
0446 
0447 void Z3950Connection::done(const QString& msg_, int type_) {
0448   checkPendingEvents();
0449   if(!m_fetcher) {
0450     return;
0451   }
0452   if(m_aborted) {
0453     qApp->postEvent(m_fetcher.data(), new Z3950ConnectionDone(m_hasMore));
0454   } else {
0455     qApp->postEvent(m_fetcher.data(), new Z3950ConnectionDone(m_hasMore, msg_, type_));
0456   }
0457 }
0458 
0459 void Z3950Connection::checkPendingEvents() {
0460   // if there's still some pending result events, go ahead and just wait 1 second
0461   if(resultsLeft > 0) {
0462     sleep(1);
0463   }
0464 }
0465 
0466 inline
0467 QByteArray Z3950Connection::queryToByteArray(const QString& text_) {
0468   return iconvRun(text_.toUtf8(), QStringLiteral("utf-8"), m_queryCharSet);
0469 }
0470 
0471 inline
0472 QString Z3950Connection::responseToString(const QByteArray& text_) {
0473   return QString::fromUtf8(iconvRun(text_, m_responseCharSet, QStringLiteral("utf-8")));
0474 }
0475 
0476 // static
0477 QByteArray Z3950Connection::iconvRun(const QByteArray& text_, const QString& fromCharSet_, const QString& toCharSet_) {
0478 #ifdef HAVE_YAZ
0479   if(text_.isEmpty() || toCharSet_.isEmpty()) {
0480     return text_;
0481   }
0482 
0483   if(fromCharSet_ == toCharSet_) {
0484     return text_;
0485   }
0486 
0487   yaz_iconv_t cd = yaz_iconv_open(toCharSet_.toLatin1().constData(), fromCharSet_.toLatin1().constData());
0488   if(!cd) {
0489     // maybe it's iso 5426, which we sorta support
0490     QString charSetLower = fromCharSet_.toLower();
0491     charSetLower.remove(QLatin1Char('-')).remove(QLatin1Char(' '));
0492     if(charSetLower == QLatin1String("iso5426")) {
0493       return iconvRun(Iso5426Converter::toUtf8(text_).toUtf8().constData(), QStringLiteral("utf-8"), toCharSet_);
0494     } else if(charSetLower == QLatin1String("iso6937")) {
0495       return iconvRun(Iso6937Converter::toUtf8(text_).toUtf8().constData(), QStringLiteral("utf-8"), toCharSet_);
0496     }
0497     myWarning() << "conversion from" << fromCharSet_
0498                 << "to" << toCharSet_ << "is unsupported";
0499     return text_;
0500   }
0501 
0502   YazCloser closer(cd);
0503 
0504   const char* input = text_.constData();
0505   size_t inlen = text_.length();
0506 
0507   size_t outlen = 2 * inlen;  // this is enough, right?
0508   QVector<char> result0(outlen);
0509   char* result = result0.data();
0510 
0511   int r = yaz_iconv(cd, const_cast<char**>(&input), &inlen, &result, &outlen);
0512   if(r <= 0) {
0513     myDebug() << "can't convert buffer from" << fromCharSet_ << "to" << toCharSet_;
0514     return text_;
0515   }
0516   // bug in yaz, need to flush buffer to catch last character
0517   yaz_iconv(cd, nullptr, nullptr, &result, &outlen);
0518 
0519   // length is pointer difference
0520   ptrdiff_t len = result - result0.data();
0521 
0522   QByteArray output(result0.data(), len+1);
0523 //  myDebug() << "-------------------------------------------";
0524 //  myDebug() << output;
0525 //  myDebug() << "-------------------------------------------";
0526   return output;
0527 #endif
0528   Q_UNUSED(fromCharSet_);
0529   Q_UNUSED(toCharSet_);
0530   return text_;
0531 }
0532 
0533 QString Z3950Connection::toXML(const QByteArray& marc_, const QString& charSet_) {
0534 #ifdef HAVE_YAZ
0535   if(marc_.isEmpty()) {
0536     myDebug() << "empty string";
0537     return QString();
0538   }
0539 
0540   yaz_iconv_t cd = yaz_iconv_open("utf-8", charSet_.toLatin1().constData());
0541   if(!cd) {
0542     // maybe it's iso 5426, which we sorta support
0543     QString charSetLower = charSet_.toLower();
0544     charSetLower.remove(QLatin1Char('-')).remove(QLatin1Char(' '));
0545     if(charSetLower == QLatin1String("iso5426")) {
0546       return toXML(Iso5426Converter::toUtf8(marc_).toUtf8(), QStringLiteral("utf-8"));
0547     } else if(charSetLower == QLatin1String("iso6937")) {
0548       return toXML(Iso6937Converter::toUtf8(marc_).toUtf8(), QStringLiteral("utf-8"));
0549     }
0550     myWarning() << "conversion from" << charSet_ << "is unsupported";
0551     return QString();
0552   }
0553 
0554   yaz_marc_t mt = yaz_marc_create();
0555   yaz_marc_iconv(mt, cd);
0556   yaz_marc_xml(mt, YAZ_MARC_MARCXML);
0557 
0558   YazCloser closer(cd, mt);
0559 
0560   // first 5 bytes are length
0561   bool ok;
0562 #if YAZ_VERSIONL < 0x030000
0563   int len = marc_.left(5).toInt(&ok);
0564 #else
0565   size_t len = marc_.left(5).toInt(&ok);
0566 #endif
0567   if(ok && (len < 25 || len > 100000)) {
0568     myDebug() << "bad length:" << len;
0569     return QString();
0570   }
0571 
0572 #if YAZ_VERSIONL < 0x030000
0573   char* result;
0574 #else
0575   const char* result;
0576 #endif
0577   int r = yaz_marc_decode_buf(mt, marc_.constData(), -1, &result, &len);
0578   if(r <= 0) {
0579     myDebug() << "can't decode buffer";
0580     return QString();
0581   }
0582 
0583   QString output = QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
0584   output += QString::fromUtf8(result, len+1);
0585 //  myDebug() << QCString(result);
0586 //  myDebug() << "-------------------------------------------";
0587 //  myDebug() << output;
0588 
0589   return output;
0590 #else // no yaz
0591   Q_UNUSED(marc_);
0592   Q_UNUSED(charSet_);
0593   return QString();
0594 #endif
0595 }