diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
commit | e38d2351b83fa65c66ccde443777647ef5cb6cff (patch) | |
tree | 1897fc20e9f73a81c520a5b9f76f8ed042124883 /src/translators/tellicoimporter.cpp | |
download | tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.tar.gz tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.zip |
Added KDE3 version of Tellico
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/tellico@1097620 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/translators/tellicoimporter.cpp')
-rw-r--r-- | src/translators/tellicoimporter.cpp | 1021 |
1 files changed, 1021 insertions, 0 deletions
diff --git a/src/translators/tellicoimporter.cpp b/src/translators/tellicoimporter.cpp new file mode 100644 index 0000000..cb3c7a3 --- /dev/null +++ b/src/translators/tellicoimporter.cpp @@ -0,0 +1,1021 @@ +/*************************************************************************** + copyright : (C) 2003-2006 by Robby Stephenson + email : robby@periapsis.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "tellicoimporter.h" +#include "tellico_xml.h" +#include "../collectionfactory.h" +#include "../collections/bibtexcollection.h" +#include "../entry.h" +#include "../field.h" +#include "../imagefactory.h" +#include "../image.h" +#include "../isbnvalidator.h" +#include "../latin1literal.h" +#include "../tellico_strings.h" +#include "../tellico_kernel.h" +#include "../tellico_utils.h" +#include "../tellico_debug.h" +#include "../progressmanager.h" + +#include <klocale.h> +#include <kmdcodec.h> +#include <kzip.h> +#include <kapplication.h> + +#include <qdom.h> +#include <qbuffer.h> +#include <qfile.h> +#include <qtimer.h> + +using Tellico::Import::TellicoImporter; + +bool TellicoImporter::versionConversion(uint from, uint to) { + // version 10 only added board games to version 9 + return from < to && (from != 9 || to != 10); +} + +TellicoImporter::TellicoImporter(const KURL& url_, bool loadAllImages_) : DataImporter(url_), + m_coll(0), m_loadAllImages(loadAllImages_), m_format(Unknown), m_modified(false), + m_cancelled(false), m_hasImages(false), m_buffer(0), m_zip(0), m_imgDir(0) { +} + +TellicoImporter::TellicoImporter(const QString& text_) : DataImporter(text_), + m_coll(0), m_loadAllImages(true), m_format(Unknown), m_modified(false), + m_cancelled(false), m_hasImages(false), m_buffer(0), m_zip(0), m_imgDir(0) { +} + +TellicoImporter::~TellicoImporter() { + if(m_zip) { + m_zip->close(); + } + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; +} + +Tellico::Data::CollPtr TellicoImporter::collection() { + if(m_coll) { + return m_coll; + } + + QCString s; // read first 5 characters + if(source() == URL) { + if(!fileRef().open()) { + return 0; + } + QIODevice* f = fileRef().file(); + for(uint i = 0; i < 5; ++i) { + s += static_cast<char>(f->getch()); + } + f->reset(); + } else { + if(data().size() < 5) { + m_format = Error; + return 0; + } + s = QCString(data(), 6); + } + + // need to decide if the data is xml text, or a zip file + // if the first 5 characters are <?xml then treat it like text + if(s[0] == '<' && s[1] == '?' && s[2] == 'x' && s[3] == 'm' && s[4] == 'l') { + m_format = XML; + loadXMLData(source() == URL ? fileRef().file()->readAll() : data(), true); + } else { + m_format = Zip; + loadZipData(); + } + return m_coll; +} + +void TellicoImporter::loadXMLData(const QByteArray& data_, bool loadImages_) { + ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true); + item.setTotalSteps(100); + connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel())); + ProgressItem::Done done(this); + + QDomDocument dom; + QString errorMsg; + int errorLine, errorColumn; + if(!dom.setContent(data_, true, &errorMsg, &errorLine, &errorColumn)) { + QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n'); + str += i18n("There is an XML parsing error in line %1, column %2.").arg(errorLine).arg(errorColumn); + str += QString::fromLatin1("\n"); + str += i18n("The error message from Qt is:"); + str += QString::fromLatin1("\n\t") + errorMsg; + myDebug() << str << endl; + setStatusMessage(str); + m_format = Error; + return; + } + + QDomElement root = dom.documentElement(); + + // the syntax version field name changed from "version" to "syntaxVersion" in version 3 + uint syntaxVersion; + if(root.hasAttribute(QString::fromLatin1("syntaxVersion"))) { + syntaxVersion = root.attribute(QString::fromLatin1("syntaxVersion")).toInt(); + } else if (root.hasAttribute(QString::fromLatin1("version"))) { + syntaxVersion = root.attribute(QString::fromLatin1("version")).toInt(); + } else { + if(!url().isEmpty()) { + setStatusMessage(i18n(errorLoad).arg(url().fileName())); + } + m_format = Error; + return; + } +// myDebug() << "TellicoImporter::loadXMLData() - syntaxVersion = " << syntaxVersion << endl; + + if((syntaxVersion > 6 && root.tagName() != Latin1Literal("tellico")) + || (syntaxVersion < 7 && root.tagName() != Latin1Literal("bookcase"))) { + if(!url().isEmpty()) { + setStatusMessage(i18n(errorLoad).arg(url().fileName())); + } + m_format = Error; + return; + } + + if(syntaxVersion > XML::syntaxVersion) { + if(!url().isEmpty()) { + QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n'); + str += i18n("It is from a future version of Tellico."); + myDebug() << str << endl; + setStatusMessage(str); + } else { + myDebug() << "Unable to load collection, from a future version (" << syntaxVersion << ")" << endl; + } + m_format = Error; + return; + } else if(versionConversion(syntaxVersion, XML::syntaxVersion)) { + // going form version 9 to 10, there's no conversion needed + QString str = i18n("Tellico is converting the file to a more recent document format. " + "Information loss may occur if an older version of Tellico is used " + "to read this file in the future."); + myDebug() << str << endl; +// setStatusMessage(str); + m_modified = true; // mark as modified + } + + m_namespace = syntaxVersion > 6 ? XML::nsTellico : XML::nsBookcase; + + // the collection item should be the first dom element child of the root + QDomElement collelem; + for(QDomNode n = root.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.namespaceURI() != m_namespace) { + continue; + } + if(n.isElement() && n.localName() == Latin1Literal("collection")) { + collelem = n.toElement(); + break; + } + } + if(collelem.isNull()) { + kdWarning() << "TellicoImporter::loadDomDocument() - No collection item found." << endl; + return; + } + + QString title = collelem.attribute(QString::fromLatin1("title")); + + // be careful not to have element name collision + // for fields, each true field element is a child of a fields element + QDomNodeList fieldelems; + for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.namespaceURI() != m_namespace) { + continue; + } + // Latin1Literal is a macro, so can't say Latin1Literal(syntaxVersion > 3 ? "fields" : "attributes") + if((syntaxVersion > 3 && n.localName() == Latin1Literal("fields")) + || (syntaxVersion < 4 && n.localName() == Latin1Literal("attributes"))) { + QDomElement e = n.toElement(); + fieldelems = e.elementsByTagNameNS(m_namespace, (syntaxVersion > 3) ? QString::fromLatin1("field") + : QString::fromLatin1("attribute")); + break; + } + } +// myDebug() << "TellicoImporter::loadXMLData() - " << fieldelems.count() << " field(s)" << endl; + + // the dilemma is when to force the new collection to have all the default attributes + // if there are no attributes or if the first one has the special name of _default + bool addFields = (fieldelems.count() == 0); + if(!addFields) { + QString name = fieldelems.item(0).toElement().attribute(QString::fromLatin1("name")); + addFields = (name == Latin1Literal("_default")); + // removeChild only works for immediate children + // remove _default field + if(addFields) { + fieldelems.item(0).parentNode().removeChild(fieldelems.item(0)); + } + } + + QString entryName; + // in syntax 4, the element name was changed to "entry", always, rather than depending on + // on the entryName of the collection. A type field was added to the collection element + // to specify what type of collection it is. + if(syntaxVersion > 3) { + entryName = QString::fromLatin1("entry"); + QString typeStr = collelem.attribute(QString::fromLatin1("type")); + Data::Collection::Type type = static_cast<Data::Collection::Type>(typeStr.toInt()); + m_coll = CollectionFactory::collection(type, addFields); + } else { + entryName = collelem.attribute(QString::fromLatin1("unit")); + m_coll = CollectionFactory::collection(entryName, addFields); + } + + if(!title.isEmpty()) { + m_coll->setTitle(title); + } + + for(uint j = 0; j < fieldelems.count(); ++j) { + readField(syntaxVersion, fieldelems.item(j).toElement()); + } + + if(m_coll->type() == Data::Collection::Bibtex) { + Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(m_coll.data()); + QDomNodeList macroelems; + for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.namespaceURI() != m_namespace) { + continue; + } + if(n.localName() == Latin1Literal("macros")) { + macroelems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("macro")); + break; + } + } +// myDebug() << "TellicoImporter::loadXMLData() - found " << macroelems.count() << " macros" << endl; + for(uint j = 0; c && j < macroelems.count(); ++j) { + QDomElement elem = macroelems.item(j).toElement(); + c->addMacro(elem.attribute(QString::fromLatin1("name")), elem.text()); + } + + for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.namespaceURI() != m_namespace) { + continue; + } + if(n.localName() == Latin1Literal("bibtex-preamble")) { + c->setPreamble(n.toElement().text()); + break; + } + } + } + + if(m_cancelled) { + m_coll = 0; + return; + } + +// as a special case, for old book collections with a bibtex-id field, convert to Bibtex + if(syntaxVersion < 4 && m_coll->type() == Data::Collection::Book + && m_coll->hasField(QString::fromLatin1("bibtex-id"))) { + m_coll = Data::BibtexCollection::convertBookCollection(m_coll); + } + + const uint count = collelem.childNodes().count(); + const uint stepSize = QMAX(s_stepSize, count/100); + const bool showProgress = options() & ImportProgress; + + item.setTotalSteps(count); + + // have to read images before entries so we can figure out if + // linkOnly() is true + // m_loadAllImages only pertains to zip files + QDomNodeList imgelems; + for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.namespaceURI() != m_namespace) { + continue; + } + if(n.localName() == Latin1Literal("images")) { + imgelems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("image")); + break; + } + } + for(uint j = 0; j < imgelems.count(); ++j) { + readImage(imgelems.item(j).toElement(), loadImages_); + } + + if(m_cancelled) { + m_coll = 0; + return; + } + + uint j = 0; + for(QDomNode n = collelem.firstChild(); !n.isNull() && !m_cancelled; n = n.nextSibling(), ++j) { + if(n.namespaceURI() != m_namespace) { + continue; + } + if(n.localName() == entryName) { + readEntry(syntaxVersion, n.toElement()); + + // not exactly right, but close enough + if(showProgress && j%stepSize == 0) { + ProgressManager::self()->setProgress(this, j); + kapp->processEvents(); + } + } else { +// myDebug() << "...skipping " << n.localName() << " (" << n.namespaceURI() << ")" << endl; + } + } // end entry loop + + if(m_cancelled) { + m_coll = 0; + return; + } + + // filters and borrowers are at document root level, not collection + for(QDomNode n = root.firstChild(); !n.isNull() && !m_cancelled; n = n.nextSibling()) { + if(n.namespaceURI() != m_namespace) { + continue; + } + if(n.localName() == Latin1Literal("borrowers")) { + QDomNodeList borrowerElems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("borrower")); + for(uint j = 0; j < borrowerElems.count(); ++j) { + readBorrower(borrowerElems.item(j).toElement()); + } + } else if(n.localName() == Latin1Literal("filters")) { + QDomNodeList filterElems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("filter")); + for(uint j = 0; j < filterElems.count(); ++j) { + readFilter(filterElems.item(j).toElement()); + } + } + } + + // special for user, if using an older document format, add some nice new filters + if(syntaxVersion < 8) { + addDefaultFilters(); + } + + if(m_cancelled) { + m_coll = 0; + } +} + +void TellicoImporter::readField(uint syntaxVersion_, const QDomElement& elem_) { + // special case: if the i18n attribute equals true, then translate the title, description, and category + bool isI18n = elem_.attribute(QString::fromLatin1("i18n")) == Latin1Literal("true"); + + QString name = elem_.attribute(QString::fromLatin1("name"), QString::fromLatin1("unknown")); + QString title = elem_.attribute(QString::fromLatin1("title"), i18n("Unknown")); + if(isI18n) { + title = i18n(title.utf8()); + } + + QString typeStr = elem_.attribute(QString::fromLatin1("type"), QString::number(Data::Field::Line)); + Data::Field::Type type = static_cast<Data::Field::Type>(typeStr.toInt()); + + Data::FieldPtr field; + if(type == Data::Field::Choice) { + QStringList allowed = QStringList::split(QString::fromLatin1(";"), + elem_.attribute(QString::fromLatin1("allowed"))); + if(isI18n) { + for(QStringList::Iterator it = allowed.begin(); it != allowed.end(); ++it) { + (*it) = i18n((*it).utf8()); + } + } + field = new Data::Field(name, title, allowed); + } else { + field = new Data::Field(name, title, type); + } + + if(elem_.hasAttribute(QString::fromLatin1("category"))) { + // at one point, the categories had keyboard accels + QString cat = elem_.attribute(QString::fromLatin1("category")); + if(syntaxVersion_ < 9 && cat.find('&') > -1) { + cat.remove('&'); + } + if(isI18n) { + cat = i18n(cat.utf8()); + } + field->setCategory(cat); + } + + if(elem_.hasAttribute(QString::fromLatin1("flags"))) { + int flags = elem_.attribute(QString::fromLatin1("flags")).toInt(); + // I also changed the enum values for syntax 3, but the only custom field + // would have been bibtex-id + if(syntaxVersion_ < 3 && field->name() == Latin1Literal("bibtex-id")) { + flags = 0; + } + + // in syntax version 4, added a flag to disallow deleting attributes + // if it's a version before that and is the title, then add the flag + if(syntaxVersion_ < 4 && field->name() == Latin1Literal("title")) { + flags |= Data::Field::NoDelete; + } + field->setFlags(flags); + } + + QString formatStr = elem_.attribute(QString::fromLatin1("format"), QString::number(Data::Field::FormatNone)); + Data::Field::FormatFlag format = static_cast<Data::Field::FormatFlag>(formatStr.toInt()); + field->setFormatFlag(format); + + if(elem_.hasAttribute(QString::fromLatin1("description"))) { + QString desc = elem_.attribute(QString::fromLatin1("description")); + if(isI18n) { + desc = i18n(desc.utf8()); + } + field->setDescription(desc); + } + + if(syntaxVersion_ >= 5) { + QDomNodeList props = elem_.elementsByTagNameNS(m_namespace, QString::fromLatin1("prop")); + for(uint i = 0; i < props.count(); ++i) { + QDomElement e = props.item(i).toElement(); + field->setProperty(e.attribute(QString::fromLatin1("name")), e.text()); + } + // all track fields in music collections prior to version 9 get converted to three columns + if(syntaxVersion_ < 9) { + if(m_coll->type() == Data::Collection::Album && field->name() == Latin1Literal("track")) { + field->setProperty(QString::fromLatin1("columns"), QChar('3')); + field->setProperty(QString::fromLatin1("column1"), i18n("Title")); + field->setProperty(QString::fromLatin1("column2"), i18n("Artist")); + field->setProperty(QString::fromLatin1("column3"), i18n("Length")); + } else if(m_coll->type() == Data::Collection::Video && field->name() == Latin1Literal("cast")) { + field->setProperty(QString::fromLatin1("column1"), i18n("Actor/Actress")); + field->setProperty(QString::fromLatin1("column2"), i18n("Role")); + } + } + } else if(elem_.hasAttribute(QString::fromLatin1("bibtex-field"))) { + field->setProperty(QString::fromLatin1("bibtex"), elem_.attribute(QString::fromLatin1("bibtex-field"))); + } + + // Table2 is deprecated + if(field->type() == Data::Field::Table2) { + field->setType(Data::Field::Table); + field->setProperty(QString::fromLatin1("columns"), QChar('2')); + } + // for syntax 8, rating fields got their own type + if(syntaxVersion_ < 8) { + Data::Field::convertOldRating(field); // does all its own checking + } + m_coll->addField(field); +// myDebug() << QString(" Added field: %1, %2").arg(field->name()).arg(field->title()) << endl; +} + +void TellicoImporter::readEntry(uint syntaxVersion_, const QDomElement& entryElem_) { + const int id = entryElem_.attribute(QString::fromLatin1("id")).toInt(); + Data::EntryPtr entry; + if(id > 0) { + entry = new Data::Entry(m_coll, id); + } else { + entry = new Data::Entry(m_coll); + } + + bool oldMusic = (syntaxVersion_ < 9 && m_coll->type() == Data::Collection::Album); + + // iterate over all field value children + for(QDomNode node = entryElem_.firstChild(); !node.isNull(); node = node.nextSibling()) { + QDomElement elem = node.toElement(); + if(elem.isNull()) { + continue; + } + + bool isI18n = elem.attribute(QString::fromLatin1("i18n")) == Latin1Literal("true"); + + // Entry::setField checks to see if an field of 'name' is allowed + // in version 3 and prior, checkbox attributes had no text(), set it to "true" now + if(syntaxVersion_ < 4 && elem.text().isEmpty()) { + // "true" means checked + entry->setField(elem.localName(), QString::fromLatin1("true")); + continue; + } + + QString name = elem.localName(); + Data::FieldPtr f = m_coll->fieldByName(name); + + // if the first child of the node is a text node, just set the attribute text + // otherwise, recurse over the node's children + // this is the case for <authors><author>..</author></authors> + // but if there's nothing but white space, then it's a BaseNode for some reason +// if(node.firstChild().nodeType() == QDomNode::TextNode) { + if(f) { + // if it's a derived value, no field value is added + if(f->type() == Data::Field::Dependent) { + continue; + } + + // special case for Date fields + if(f->type() == Data::Field::Date) { + if(elem.hasChildNodes()) { + QString value; + QDomNode yNode = elem.elementsByTagNameNS(m_namespace, QString::fromLatin1("year")).item(0); + if(!yNode.isNull()) { + value += yNode.toElement().text(); + } + value += '-'; + QDomNode mNode = elem.elementsByTagNameNS(m_namespace, QString::fromLatin1("month")).item(0); + if(!mNode.isNull()) { + value += mNode.toElement().text(); + } + value += '-'; + QDomNode dNode = elem.elementsByTagNameNS(m_namespace, QString::fromLatin1("day")).item(0); + if(!dNode.isNull()) { + value += dNode.toElement().text(); + } + entry->setField(name, value); + } else { + // if no child nodes, the code will later assume the value to be the year + entry->setField(name, elem.text()); + } + // go to next value in loop + continue; + } + + // this may be a performance hit to be stripping white space all the time + // unfortunately, text() will include a carriage-return in cases like + // <value> + // text + // </value + // so we arbitrarily decide that only paragraphs get to have CRs? + QString value = elem.text(); + if(f->type() != Data::Field::Para) { + value = value.stripWhiteSpace(); + } + + if(value.isEmpty()) { + continue; + } + + if(f->type() == Data::Field::Image) { + // image info should have already been loaded + const Data::ImageInfo& info = ImageFactory::imageInfo(value); + // possible that value needs to be cleaned first in which case info is null + if(info.isNull() || !info.linkOnly) { + // for local files only, allow paths here + KURL u = KURL::fromPathOrURL(value); + if(u.isValid() && u.isLocalFile()) { + QString result = ImageFactory::addImage(u, false /* quiet */); + if(!result.isEmpty()) { + value = result; + } + } + value = Data::Image::idClean(value); + } + } + + // in version 8, old rating fields get changed + if(syntaxVersion_ < 8 && f->type() == Data::Field::Rating) { + bool ok; + uint i = Tellico::toUInt(value, &ok); + if(ok) { + value = QString::number(i); + } + } else if(syntaxVersion_ < 2 && name == Latin1Literal("keywords")) { + // in version 2, "keywords" changed to "keyword" + name = QString::fromLatin1("keyword"); + } + // special case: if the i18n attribute equals true, then translate the title, description, and category + if(isI18n) { + entry->setField(name, i18n(value.utf8())); + } else { + // special case for isbn fields, go ahead and validate + if(name == Latin1Literal("isbn")) { + const ISBNValidator val(0); + if(elem.attribute(QString::fromLatin1("validate")) != Latin1Literal("no")) { + val.fixup(value); + } + } + entry->setField(name, value); + } + } else { // if no field by the tag name, then it has children, iterate through them + // the field name has the final 's', so remove it + name.truncate(name.length() - 1); + f = m_coll->fieldByName(name); + + // if it's a derived value, no field value is added + if(!f || f->type() == Data::Field::Dependent) { + continue; + } + + const bool oldTracks = (oldMusic && name == Latin1Literal("track")); + + QStringList values; + // concatenate values + for(QDomNode childNode = node.firstChild(); !childNode.isNull(); childNode = childNode.nextSibling()) { + QString value; + // don't worry about i18n here, Tables are never translated + QDomNodeList cols = childNode.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("column")); + if(cols.count() > 0) { + for(uint i = 0; i < cols.count(); ++i) { + // special case for old tracks + if(oldTracks && i == 1) { + // if the second column holds the track length, bump it to next column + QRegExp rx(QString::fromLatin1("\\d+:\\d\\d")); + if(rx.exactMatch(cols.item(i).toElement().text())) { + value += entry->field(QString::fromLatin1("artist")); + value += QString::fromLatin1("::"); + } + } + value += cols.item(i).toElement().text().stripWhiteSpace(); + if(i < cols.count()-1) { + value += QString::fromLatin1("::"); + } else if(oldTracks && cols.count() == 1) { + value += QString::fromLatin1("::"); + value += entry->field(QString::fromLatin1("artist")); + } + } + values += value; + } else { + // really loose here, we don't even check that the element name + // is what we think it is + QString s = childNode.toElement().text().stripWhiteSpace(); + if(isI18n && !s.isEmpty()) { + value += i18n(s.utf8()); + } else { + value += s; + } + if(oldTracks) { + value += QString::fromLatin1("::"); + value += entry->field(QString::fromLatin1("artist")); + } + if(values.findIndex(value) == -1) { + values += value; + } + } + } + entry->setField(name, values.join(QString::fromLatin1("; "))); + } + } // end field value loop + + m_coll->addEntries(entry); +} + +void TellicoImporter::readImage(const QDomElement& elem_, bool loadImage_) { + QString format = elem_.attribute(QString::fromLatin1("format")); + const bool link = elem_.attribute(QString::fromLatin1("link")) == Latin1Literal("true"); + QString id = shareString(link ? elem_.attribute(QString::fromLatin1("id")) + : Data::Image::idClean(elem_.attribute(QString::fromLatin1("id")))); + + bool readInfo = true; + if(loadImage_) { + QByteArray ba; + KCodecs::base64Decode(QCString(elem_.text().latin1()), ba); + if(!ba.isEmpty()) { + QString result = ImageFactory::addImage(ba, format, id); + if(result.isEmpty()) { + myDebug() << "TellicoImporter::readImage(XML) - null image for " << id << endl; + } + m_hasImages = true; + readInfo = false; + } + } + if(readInfo) { + // a width or height of 0 is ok here + int width = elem_.attribute(QString::fromLatin1("width")).toInt(); + int height = elem_.attribute(QString::fromLatin1("height")).toInt(); + Data::ImageInfo info(id, format.latin1(), width, height, link); + ImageFactory::cacheImageInfo(info); + } +} + +void TellicoImporter::readFilter(const QDomElement& elem_) { + FilterPtr f = new Filter(Filter::MatchAny); + f->setName(elem_.attribute(QString::fromLatin1("name"))); + + QString match = elem_.attribute(QString::fromLatin1("match")); + if(match == Latin1Literal("all")) { + f->setMatch(Filter::MatchAll); + } + + QDomNodeList rules = elem_.elementsByTagNameNS(m_namespace, QString::fromLatin1("rule")); + for(uint i = 0; i < rules.count(); ++i) { + QDomElement e = rules.item(i).toElement(); + if(e.isNull()) { + continue; + } + + QString field = e.attribute(QString::fromLatin1("field")); + // empty field means match any of them + QString pattern = e.attribute(QString::fromLatin1("pattern")); + // empty pattern is bad + if(pattern.isEmpty()) { + kdWarning() << "TellicoImporter::readFilter() - empty rule!" << endl; + continue; + } + QString function = e.attribute(QString::fromLatin1("function")).lower(); + FilterRule::Function func; + if(function == Latin1Literal("contains")) { + func = FilterRule::FuncContains; + } else if(function == Latin1Literal("notcontains")) { + func = FilterRule::FuncNotContains; + } else if(function == Latin1Literal("equals")) { + func = FilterRule::FuncEquals; + } else if(function == Latin1Literal("notequals")) { + func = FilterRule::FuncNotEquals; + } else if(function == Latin1Literal("regexp")) { + func = FilterRule::FuncRegExp; + } else if(function == Latin1Literal("notregexp")) { + func = FilterRule::FuncNotRegExp; + } else { + kdWarning() << "TellicoImporter::readFilter() - invalid rule function: " << function << endl; + continue; + } + f->append(new FilterRule(field, pattern, func)); + } + + if(!f->isEmpty()) { + m_coll->addFilter(f); + } +} + +void TellicoImporter::readBorrower(const QDomElement& elem_) { + QString name = elem_.attribute(QString::fromLatin1("name")); + QString uid = elem_.attribute(QString::fromLatin1("uid")); + Data::BorrowerPtr b = new Data::Borrower(name, uid); + + QDomNodeList loans = elem_.elementsByTagNameNS(m_namespace, QString::fromLatin1("loan")); + for(uint i = 0; i < loans.count(); ++i) { + QDomElement e = loans.item(i).toElement(); + if(e.isNull()) { + continue; + } + long id = e.attribute(QString::fromLatin1("entryRef")).toLong(); + Data::EntryPtr entry = m_coll->entryById(id); + if(!entry) { + myDebug() << "TellicoImporter::readBorrower() - no entry with id = " << id << endl; + continue; + } + QString uid = e.attribute(QString::fromLatin1("uid")); + QDate loanDate, dueDate; + QString s = e.attribute(QString::fromLatin1("loanDate")); + if(!s.isEmpty()) { + loanDate = QDate::fromString(s, Qt::ISODate); + } + s = e.attribute(QString::fromLatin1("dueDate")); + if(!s.isEmpty()) { + dueDate = QDate::fromString(s, Qt::ISODate); + } + Data::LoanPtr loan = new Data::Loan(entry, loanDate, dueDate, e.text()); + loan->setUID(uid); + b->addLoan(loan); + s = e.attribute(QString::fromLatin1("calendar")); + loan->setInCalendar(s == Latin1Literal("true")); + } + if(!b->isEmpty()) { + m_coll->addBorrower(b); + } +} + +void TellicoImporter::loadZipData() { + delete m_buffer; + delete m_zip; + if(source() == URL) { + m_buffer = 0; + m_zip = new KZip(fileRef().fileName()); + } else { + m_buffer = new QBuffer(data()); + m_zip = new KZip(m_buffer); + } + if(!m_zip->open(IO_ReadOnly)) { + setStatusMessage(i18n(errorLoad).arg(url().fileName())); + m_format = Error; + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; + return; + } + + const KArchiveDirectory* dir = m_zip->directory(); + if(!dir) { + QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n'); + str += i18n("The file is empty."); + setStatusMessage(str); + m_format = Error; + m_zip->close(); + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; + return; + } + + // main file was changed from bookcase.xml to tellico.xml as of version 0.13 + const KArchiveEntry* entry = dir->entry(QString::fromLatin1("tellico.xml")); + if(!entry) { + entry = dir->entry(QString::fromLatin1("bookcase.xml")); + } + if(!entry || !entry->isFile()) { + QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n'); + str += i18n("The file contains no collection data."); + setStatusMessage(str); + m_format = Error; + m_zip->close(); + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; + return; + } + + const QByteArray xmlData = static_cast<const KArchiveFile*>(entry)->data(); + loadXMLData(xmlData, false); + if(!m_coll) { + m_format = Error; + m_zip->close(); + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; + return; + } + + if(m_cancelled) { + m_zip->close(); + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; + return; + } + + const KArchiveEntry* imgDirEntry = dir->entry(QString::fromLatin1("images")); + if(!imgDirEntry || !imgDirEntry->isDirectory()) { + m_zip->close(); + delete m_zip; + m_zip = 0; + delete m_buffer; + m_buffer = 0; + return; + } + m_imgDir = static_cast<const KArchiveDirectory*>(imgDirEntry); + m_images.clear(); + m_images.add(m_imgDir->entries()); + m_hasImages = !m_images.isEmpty(); + + // if all the images are not to be loaded, then we're done + if(!m_loadAllImages) { +// myLog() << "TellicoImporter::loadZipData() - delayed loading for " << m_images.count() << " images" << endl; + return; + } + + const QStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries(); + const uint stepSize = QMAX(s_stepSize, images.count()/100); + + uint j = 0; + for(QStringList::ConstIterator it = images.begin(); !m_cancelled && it != images.end(); ++it, ++j) { + const KArchiveEntry* file = m_imgDir->entry(*it); + if(file && file->isFile()) { + ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(), + (*it).section('.', -1).upper(), (*it)); + m_images.remove(*it); + } + if(j%stepSize == 0) { + kapp->processEvents(); + } + } + + if(m_images.isEmpty()) { + // give it some time + QTimer::singleShot(3000, this, SLOT(deleteLater())); + } +} + +bool TellicoImporter::loadImage(const QString& id_) { +// myLog() << "TellicoImporter::loadImage() - id = " << id_ << endl; + if(m_format != Zip || !m_imgDir) { + return false; + } + const KArchiveEntry* file = m_imgDir->entry(id_); + if(!file || !file->isFile()) { + return false; + } + QString newID = ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(), + id_.section('.', -1).upper(), id_); + m_images.remove(id_); + if(m_images.isEmpty()) { + // give it some time + QTimer::singleShot(3000, this, SLOT(deleteLater())); + } + return !newID.isEmpty(); +} + +// static +bool TellicoImporter::loadAllImages(const KURL& url_) { + // only local files are allowed + if(url_.isEmpty() || !url_.isValid() || !url_.isLocalFile()) { +// myDebug() << "TellicoImporter::loadAllImages() - returning" << endl; + return false; + } + + // keep track of url for error reporting + static KURL u; + + KZip zip(url_.path()); + if(!zip.open(IO_ReadOnly)) { + if(u != url_) { + Kernel::self()->sorry(i18n(errorImageLoad).arg(url_.fileName())); + } + u = url_; + return false; + } + + const KArchiveDirectory* dir = zip.directory(); + if(!dir) { + if(u != url_) { + Kernel::self()->sorry(i18n(errorImageLoad).arg(url_.fileName())); + } + u = url_; + zip.close(); + return false; + } + + const KArchiveEntry* imgDirEntry = dir->entry(QString::fromLatin1("images")); + if(!imgDirEntry || !imgDirEntry->isDirectory()) { + zip.close(); + return false; + } + const QStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries(); + for(QStringList::ConstIterator it = images.begin(); it != images.end(); ++it) { + const KArchiveEntry* file = static_cast<const KArchiveDirectory*>(imgDirEntry)->entry(*it); + if(file && file->isFile()) { + ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(), + (*it).section('.', -1).upper(), (*it)); + } + } + zip.close(); + return true; +} + +void TellicoImporter::addDefaultFilters() { + switch(m_coll->type()) { + case Data::Collection::Book: + if(m_coll->hasField(QString::fromLatin1("read"))) { + FilterPtr f = new Filter(Filter::MatchAny); + f->setName(i18n("Unread Books")); + f->append(new FilterRule(QString::fromLatin1("read"), QString::fromLatin1("true"), FilterRule::FuncNotContains)); + m_coll->addFilter(f); + m_modified = true; + } + break; + + case Data::Collection::Video: + if(m_coll->hasField(QString::fromLatin1("year"))) { + FilterPtr f = new Filter(Filter::MatchAny); + f->setName(i18n("Old Movies")); + // old movies from before 1960 + f->append(new FilterRule(QString::fromLatin1("year"), QString::fromLatin1("19[012345]\\d"), FilterRule::FuncRegExp)); + m_coll->addFilter(f); + m_modified = true; + } + if(m_coll->hasField(QString::fromLatin1("widescreen"))) { + FilterPtr f = new Filter(Filter::MatchAny); + f->setName(i18n("Widescreen")); + f->append(new FilterRule(QString::fromLatin1("widescreen"), QString::fromLatin1("true"), FilterRule::FuncContains)); + m_coll->addFilter(f); + m_modified = true; + } + break; + + case Data::Collection::Album: + if(m_coll->hasField(QString::fromLatin1("year"))) { + FilterPtr f = new Filter(Filter::MatchAny); + f->setName(i18n("80's Music")); + f->append(new FilterRule(QString::fromLatin1("year"), QString::fromLatin1("198\\d"),FilterRule::FuncRegExp)); + m_coll->addFilter(f); + m_modified = true; + } + break; + + default: + break; + } + if(m_coll->hasField(QString::fromLatin1("rating"))) { + FilterPtr filter = new Filter(Filter::MatchAny); + filter->setName(i18n("Favorites")); + // check all the numbers, and use top 20% or so + Data::FieldPtr field = m_coll->fieldByName(QString::fromLatin1("rating")); + bool ok; + uint min = Tellico::toUInt(field->property(QString::fromLatin1("minimum")), &ok); + if(!ok) { + min = 1; + } + uint max = Tellico::toUInt(field->property(QString::fromLatin1("maximum")), &ok); + if(!ok) { + min = 5; + } + for(uint i = QMAX(min, static_cast<uint>(0.8*(max-min+1))); i <= max; ++i) { + filter->append(new FilterRule(QString::fromLatin1("rating"), QString::number(i), FilterRule::FuncContains)); + } + if(!filter->isEmpty()) { + m_coll->addFilter(filter); + m_modified = true; + } + } +} + +void TellicoImporter::slotCancel() { + m_cancelled = true; + m_format = Cancel; +} + +#include "tellicoimporter.moc" |