File indexing completed on 2024-05-19 16:18:56
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 Z3950ResultFound* ev = new Z3950ResultFound(data); 0384 if(m_fetcher) { 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:" << (ok ? len : -1); 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 }