You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1022 lines
36 KiB
1022 lines
36 KiB
/***************************************************************************
|
|
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 <tqdom.h>
|
|
#include <tqbuffer.h>
|
|
#include <tqfile.h>
|
|
#include <tqtimer.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 TQString& 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;
|
|
}
|
|
|
|
TQCString s; // read first 5 characters
|
|
if(source() == URL) {
|
|
if(!fileRef().open()) {
|
|
return 0;
|
|
}
|
|
TQIODevice* 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 = TQCString(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 ? TQByteArray(fileRef().file()->readAll()) : TQByteArray(data()), true);
|
|
} else {
|
|
m_format = Zip;
|
|
loadZipData();
|
|
}
|
|
return m_coll;
|
|
}
|
|
|
|
void TellicoImporter::loadXMLData(const TQByteArray& data_, bool loadImages_) {
|
|
ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
|
|
item.setTotalSteps(100);
|
|
connect(&item, TQT_SIGNAL(signalCancelled(ProgressItem*)), TQT_SLOT(slotCancel()));
|
|
ProgressItem::Done done(this);
|
|
|
|
TQDomDocument dom;
|
|
TQString errorMsg;
|
|
int errorLine, errorColumn;
|
|
if(!dom.setContent(data_, true, &errorMsg, &errorLine, &errorColumn)) {
|
|
TQString str = i18n(errorLoad).tqarg(url().fileName()) + TQChar('\n');
|
|
str += i18n("There is an XML parsing error in line %1, column %2.").tqarg(errorLine).tqarg(errorColumn);
|
|
str += TQString::tqfromLatin1("\n");
|
|
str += i18n("The error message from TQt is:");
|
|
str += TQString::tqfromLatin1("\n\t") + errorMsg;
|
|
myDebug() << str << endl;
|
|
setStatusMessage(str);
|
|
m_format = Error;
|
|
return;
|
|
}
|
|
|
|
TQDomElement root = dom.documentElement();
|
|
|
|
// the syntax version field name changed from "version" to "syntaxVersion" in version 3
|
|
uint syntaxVersion;
|
|
if(root.hasAttribute(TQString::tqfromLatin1("syntaxVersion"))) {
|
|
syntaxVersion = root.attribute(TQString::tqfromLatin1("syntaxVersion")).toInt();
|
|
} else if (root.hasAttribute(TQString::tqfromLatin1("version"))) {
|
|
syntaxVersion = root.attribute(TQString::tqfromLatin1("version")).toInt();
|
|
} else {
|
|
if(!url().isEmpty()) {
|
|
setStatusMessage(i18n(errorLoad).tqarg(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).tqarg(url().fileName()));
|
|
}
|
|
m_format = Error;
|
|
return;
|
|
}
|
|
|
|
if(syntaxVersion > XML::syntaxVersion) {
|
|
if(!url().isEmpty()) {
|
|
TQString str = i18n(errorLoad).tqarg(url().fileName()) + TQChar('\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
|
|
TQString 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
|
|
TQDomElement collelem;
|
|
for(TQDomNode 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;
|
|
}
|
|
|
|
TQString title = collelem.attribute(TQString::tqfromLatin1("title"));
|
|
|
|
// be careful not to have element name collision
|
|
// for fields, each true field element is a child of a fields element
|
|
TQDomNodeList fieldelems;
|
|
for(TQDomNode 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"))) {
|
|
TQDomElement e = n.toElement();
|
|
fieldelems = e.elementsByTagNameNS(m_namespace, (syntaxVersion > 3) ? TQString::tqfromLatin1("field")
|
|
: TQString::tqfromLatin1("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) {
|
|
TQString name = fieldelems.item(0).toElement().attribute(TQString::tqfromLatin1("name"));
|
|
addFields = (name == Latin1Literal("_default"));
|
|
// removeChild only works for immediate tqchildren
|
|
// remove _default field
|
|
if(addFields) {
|
|
fieldelems.item(0).parentNode().removeChild(fieldelems.item(0));
|
|
}
|
|
}
|
|
|
|
TQString 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 = TQString::tqfromLatin1("entry");
|
|
TQString typeStr = collelem.attribute(TQString::tqfromLatin1("type"));
|
|
Data::Collection::Type type = static_cast<Data::Collection::Type>(typeStr.toInt());
|
|
m_coll = CollectionFactory::collection(type, addFields);
|
|
} else {
|
|
entryName = collelem.attribute(TQString::tqfromLatin1("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());
|
|
TQDomNodeList macroelems;
|
|
for(TQDomNode 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, TQString::tqfromLatin1("macro"));
|
|
break;
|
|
}
|
|
}
|
|
// myDebug() << "TellicoImporter::loadXMLData() - found " << macroelems.count() << " macros" << endl;
|
|
for(uint j = 0; c && j < macroelems.count(); ++j) {
|
|
TQDomElement elem = macroelems.item(j).toElement();
|
|
c->addMacro(elem.attribute(TQString::tqfromLatin1("name")), elem.text());
|
|
}
|
|
|
|
for(TQDomNode 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(TQString::tqfromLatin1("bibtex-id"))) {
|
|
m_coll = Data::BibtexCollection::convertBookCollection(m_coll);
|
|
}
|
|
|
|
const uint count = collelem.childNodes().count();
|
|
const uint stepSize = TQMAX(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
|
|
TQDomNodeList imgelems;
|
|
for(TQDomNode 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, TQString::tqfromLatin1("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(TQDomNode 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(TQDomNode n = root.firstChild(); !n.isNull() && !m_cancelled; n = n.nextSibling()) {
|
|
if(n.namespaceURI() != m_namespace) {
|
|
continue;
|
|
}
|
|
if(n.localName() == Latin1Literal("borrowers")) {
|
|
TQDomNodeList borrowerElems = n.toElement().elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("borrower"));
|
|
for(uint j = 0; j < borrowerElems.count(); ++j) {
|
|
readBorrower(borrowerElems.item(j).toElement());
|
|
}
|
|
} else if(n.localName() == Latin1Literal("filters")) {
|
|
TQDomNodeList filterElems = n.toElement().elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("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 TQDomElement& elem_) {
|
|
// special case: if the i18n attribute equals true, then translate the title, description, and category
|
|
bool isI18n = elem_.attribute(TQString::tqfromLatin1("i18n")) == Latin1Literal("true");
|
|
|
|
TQString name = elem_.attribute(TQString::tqfromLatin1("name"), TQString::tqfromLatin1("unknown"));
|
|
TQString title = elem_.attribute(TQString::tqfromLatin1("title"), i18n("Unknown"));
|
|
if(isI18n) {
|
|
title = i18n(title.utf8());
|
|
}
|
|
|
|
TQString typeStr = elem_.attribute(TQString::tqfromLatin1("type"), TQString::number(Data::Field::Line));
|
|
Data::Field::Type type = static_cast<Data::Field::Type>(typeStr.toInt());
|
|
|
|
Data::FieldPtr field;
|
|
if(type == Data::Field::Choice) {
|
|
TQStringList allowed = TQStringList::split(TQString::tqfromLatin1(";"),
|
|
elem_.attribute(TQString::tqfromLatin1("allowed")));
|
|
if(isI18n) {
|
|
for(TQStringList::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(TQString::tqfromLatin1("category"))) {
|
|
// at one point, the categories had keyboard accels
|
|
TQString cat = elem_.attribute(TQString::tqfromLatin1("category"));
|
|
if(syntaxVersion_ < 9 && cat.find('&') > -1) {
|
|
cat.remove('&');
|
|
}
|
|
if(isI18n) {
|
|
cat = i18n(cat.utf8());
|
|
}
|
|
field->setCategory(cat);
|
|
}
|
|
|
|
if(elem_.hasAttribute(TQString::tqfromLatin1("flags"))) {
|
|
int flags = elem_.attribute(TQString::tqfromLatin1("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);
|
|
}
|
|
|
|
TQString formatStr = elem_.attribute(TQString::tqfromLatin1("format"), TQString::number(Data::Field::FormatNone));
|
|
Data::Field::FormatFlag format = static_cast<Data::Field::FormatFlag>(formatStr.toInt());
|
|
field->setFormatFlag(format);
|
|
|
|
if(elem_.hasAttribute(TQString::tqfromLatin1("description"))) {
|
|
TQString desc = elem_.attribute(TQString::tqfromLatin1("description"));
|
|
if(isI18n) {
|
|
desc = i18n(desc.utf8());
|
|
}
|
|
field->setDescription(desc);
|
|
}
|
|
|
|
if(syntaxVersion_ >= 5) {
|
|
TQDomNodeList props = elem_.elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("prop"));
|
|
for(uint i = 0; i < props.count(); ++i) {
|
|
TQDomElement e = props.item(i).toElement();
|
|
field->setProperty(e.attribute(TQString::tqfromLatin1("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(TQString::tqfromLatin1("columns"), TQChar('3'));
|
|
field->setProperty(TQString::tqfromLatin1("column1"), i18n("Title"));
|
|
field->setProperty(TQString::tqfromLatin1("column2"), i18n("Artist"));
|
|
field->setProperty(TQString::tqfromLatin1("column3"), i18n("Length"));
|
|
} else if(m_coll->type() == Data::Collection::Video && field->name() == Latin1Literal("cast")) {
|
|
field->setProperty(TQString::tqfromLatin1("column1"), i18n("Actor/Actress"));
|
|
field->setProperty(TQString::tqfromLatin1("column2"), i18n("Role"));
|
|
}
|
|
}
|
|
} else if(elem_.hasAttribute(TQString::tqfromLatin1("bibtex-field"))) {
|
|
field->setProperty(TQString::tqfromLatin1("bibtex"), elem_.attribute(TQString::tqfromLatin1("bibtex-field")));
|
|
}
|
|
|
|
// Table2 is deprecated
|
|
if(field->type() == Data::Field::Table2) {
|
|
field->setType(Data::Field::Table);
|
|
field->setProperty(TQString::tqfromLatin1("columns"), TQChar('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() << TQString(" Added field: %1, %2").tqarg(field->name()).tqarg(field->title()) << endl;
|
|
}
|
|
|
|
void TellicoImporter::readEntry(uint syntaxVersion_, const TQDomElement& entryElem_) {
|
|
const int id = entryElem_.attribute(TQString::tqfromLatin1("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 tqchildren
|
|
for(TQDomNode node = entryElem_.firstChild(); !node.isNull(); node = node.nextSibling()) {
|
|
TQDomElement elem = node.toElement();
|
|
if(elem.isNull()) {
|
|
continue;
|
|
}
|
|
|
|
bool isI18n = elem.attribute(TQString::tqfromLatin1("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(), TQString::tqfromLatin1("true"));
|
|
continue;
|
|
}
|
|
|
|
TQString 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 tqchildren
|
|
// 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() == TQDomNode::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()) {
|
|
TQString value;
|
|
TQDomNode yNode = elem.elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("year")).item(0);
|
|
if(!yNode.isNull()) {
|
|
value += yNode.toElement().text();
|
|
}
|
|
value += '-';
|
|
TQDomNode mNode = elem.elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("month")).item(0);
|
|
if(!mNode.isNull()) {
|
|
value += mNode.toElement().text();
|
|
}
|
|
value += '-';
|
|
TQDomNode dNode = elem.elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("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?
|
|
TQString 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()) {
|
|
TQString 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 = TQString::number(i);
|
|
}
|
|
} else if(syntaxVersion_ < 2 && name == Latin1Literal("keywords")) {
|
|
// in version 2, "keywords" changed to "keyword"
|
|
name = TQString::tqfromLatin1("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(TQString::tqfromLatin1("validate")) != Latin1Literal("no")) {
|
|
val.fixup(value);
|
|
}
|
|
}
|
|
entry->setField(name, value);
|
|
}
|
|
} else { // if no field by the tag name, then it has tqchildren, 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"));
|
|
|
|
TQStringList values;
|
|
// concatenate values
|
|
for(TQDomNode childNode = node.firstChild(); !childNode.isNull(); childNode = childNode.nextSibling()) {
|
|
TQString value;
|
|
// don't worry about i18n here, Tables are never translated
|
|
TQDomNodeList cols = childNode.toElement().elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("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
|
|
TQRegExp rx(TQString::tqfromLatin1("\\d+:\\d\\d"));
|
|
if(rx.exactMatch(cols.item(i).toElement().text())) {
|
|
value += entry->field(TQString::tqfromLatin1("artist"));
|
|
value += TQString::tqfromLatin1("::");
|
|
}
|
|
}
|
|
value += cols.item(i).toElement().text().stripWhiteSpace();
|
|
if(i < cols.count()-1) {
|
|
value += TQString::tqfromLatin1("::");
|
|
} else if(oldTracks && cols.count() == 1) {
|
|
value += TQString::tqfromLatin1("::");
|
|
value += entry->field(TQString::tqfromLatin1("artist"));
|
|
}
|
|
}
|
|
values += value;
|
|
} else {
|
|
// really loose here, we don't even check that the element name
|
|
// is what we think it is
|
|
TQString s = childNode.toElement().text().stripWhiteSpace();
|
|
if(isI18n && !s.isEmpty()) {
|
|
value += i18n(s.utf8());
|
|
} else {
|
|
value += s;
|
|
}
|
|
if(oldTracks) {
|
|
value += TQString::tqfromLatin1("::");
|
|
value += entry->field(TQString::tqfromLatin1("artist"));
|
|
}
|
|
if(values.findIndex(value) == -1) {
|
|
values += value;
|
|
}
|
|
}
|
|
}
|
|
entry->setField(name, values.join(TQString::tqfromLatin1("; ")));
|
|
}
|
|
} // end field value loop
|
|
|
|
m_coll->addEntries(entry);
|
|
}
|
|
|
|
void TellicoImporter::readImage(const TQDomElement& elem_, bool loadImage_) {
|
|
TQString format = elem_.attribute(TQString::tqfromLatin1("format"));
|
|
const bool link = elem_.attribute(TQString::tqfromLatin1("link")) == Latin1Literal("true");
|
|
TQString id = shareString(link ? elem_.attribute(TQString::tqfromLatin1("id"))
|
|
: Data::Image::idClean(elem_.attribute(TQString::tqfromLatin1("id"))));
|
|
|
|
bool readInfo = true;
|
|
if(loadImage_) {
|
|
TQByteArray ba;
|
|
KCodecs::base64Decode(TQCString(elem_.text().latin1()), ba);
|
|
if(!ba.isEmpty()) {
|
|
TQString 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(TQString::tqfromLatin1("width")).toInt();
|
|
int height = elem_.attribute(TQString::tqfromLatin1("height")).toInt();
|
|
Data::ImageInfo info(id, format.latin1(), width, height, link);
|
|
ImageFactory::cacheImageInfo(info);
|
|
}
|
|
}
|
|
|
|
void TellicoImporter::readFilter(const TQDomElement& elem_) {
|
|
FilterPtr f = new Filter(Filter::MatchAny);
|
|
f->setName(elem_.attribute(TQString::tqfromLatin1("name")));
|
|
|
|
TQString match = elem_.attribute(TQString::tqfromLatin1("match"));
|
|
if(match == Latin1Literal("all")) {
|
|
f->setMatch(Filter::MatchAll);
|
|
}
|
|
|
|
TQDomNodeList rules = elem_.elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("rule"));
|
|
for(uint i = 0; i < rules.count(); ++i) {
|
|
TQDomElement e = rules.item(i).toElement();
|
|
if(e.isNull()) {
|
|
continue;
|
|
}
|
|
|
|
TQString field = e.attribute(TQString::tqfromLatin1("field"));
|
|
// empty field means match any of them
|
|
TQString pattern = e.attribute(TQString::tqfromLatin1("pattern"));
|
|
// empty pattern is bad
|
|
if(pattern.isEmpty()) {
|
|
kdWarning() << "TellicoImporter::readFilter() - empty rule!" << endl;
|
|
continue;
|
|
}
|
|
TQString function = e.attribute(TQString::tqfromLatin1("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 TQDomElement& elem_) {
|
|
TQString name = elem_.attribute(TQString::tqfromLatin1("name"));
|
|
TQString uid = elem_.attribute(TQString::tqfromLatin1("uid"));
|
|
Data::BorrowerPtr b = new Data::Borrower(name, uid);
|
|
|
|
TQDomNodeList loans = elem_.elementsByTagNameNS(m_namespace, TQString::tqfromLatin1("loan"));
|
|
for(uint i = 0; i < loans.count(); ++i) {
|
|
TQDomElement e = loans.item(i).toElement();
|
|
if(e.isNull()) {
|
|
continue;
|
|
}
|
|
long id = e.attribute(TQString::tqfromLatin1("entryRef")).toLong();
|
|
Data::EntryPtr entry = m_coll->entryById(id);
|
|
if(!entry) {
|
|
myDebug() << "TellicoImporter::readBorrower() - no entry with id = " << id << endl;
|
|
continue;
|
|
}
|
|
TQString uid = e.attribute(TQString::tqfromLatin1("uid"));
|
|
TQDate loanDate, dueDate;
|
|
TQString s = e.attribute(TQString::tqfromLatin1("loanDate"));
|
|
if(!s.isEmpty()) {
|
|
loanDate = TQDate::fromString(s, Qt::ISODate);
|
|
}
|
|
s = e.attribute(TQString::tqfromLatin1("dueDate"));
|
|
if(!s.isEmpty()) {
|
|
dueDate = TQDate::fromString(s, Qt::ISODate);
|
|
}
|
|
Data::LoanPtr loan = new Data::Loan(entry, loanDate, dueDate, e.text());
|
|
loan->setUID(uid);
|
|
b->addLoan(loan);
|
|
s = e.attribute(TQString::tqfromLatin1("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 TQBuffer(data());
|
|
m_zip = new KZip(TQT_TQIODEVICE(m_buffer));
|
|
}
|
|
if(!m_zip->open(IO_ReadOnly)) {
|
|
setStatusMessage(i18n(errorLoad).tqarg(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) {
|
|
TQString str = i18n(errorLoad).tqarg(url().fileName()) + TQChar('\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(TQString::tqfromLatin1("tellico.xml"));
|
|
if(!entry) {
|
|
entry = dir->entry(TQString::tqfromLatin1("bookcase.xml"));
|
|
}
|
|
if(!entry || !entry->isFile()) {
|
|
TQString str = i18n(errorLoad).tqarg(url().fileName()) + TQChar('\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 TQByteArray 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(TQString::tqfromLatin1("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 TQStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries();
|
|
const uint stepSize = TQMAX(s_stepSize, images.count()/100);
|
|
|
|
uint j = 0;
|
|
for(TQStringList::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
|
|
TQTimer::singleShot(3000, this, TQT_SLOT(deleteLater()));
|
|
}
|
|
}
|
|
|
|
bool TellicoImporter::loadImage(const TQString& 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;
|
|
}
|
|
TQString 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
|
|
TQTimer::singleShot(3000, this, TQT_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).tqarg(url_.fileName()));
|
|
}
|
|
u = url_;
|
|
return false;
|
|
}
|
|
|
|
const KArchiveDirectory* dir = zip.directory();
|
|
if(!dir) {
|
|
if(u != url_) {
|
|
Kernel::self()->sorry(i18n(errorImageLoad).tqarg(url_.fileName()));
|
|
}
|
|
u = url_;
|
|
zip.close();
|
|
return false;
|
|
}
|
|
|
|
const KArchiveEntry* imgDirEntry = dir->entry(TQString::tqfromLatin1("images"));
|
|
if(!imgDirEntry || !imgDirEntry->isDirectory()) {
|
|
zip.close();
|
|
return false;
|
|
}
|
|
const TQStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries();
|
|
for(TQStringList::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(TQString::tqfromLatin1("read"))) {
|
|
FilterPtr f = new Filter(Filter::MatchAny);
|
|
f->setName(i18n("Unread Books"));
|
|
f->append(new FilterRule(TQString::tqfromLatin1("read"), TQString::tqfromLatin1("true"), FilterRule::FuncNotContains));
|
|
m_coll->addFilter(f);
|
|
m_modified = true;
|
|
}
|
|
break;
|
|
|
|
case Data::Collection::Video:
|
|
if(m_coll->hasField(TQString::tqfromLatin1("year"))) {
|
|
FilterPtr f = new Filter(Filter::MatchAny);
|
|
f->setName(i18n("Old Movies"));
|
|
// old movies from before 1960
|
|
f->append(new FilterRule(TQString::tqfromLatin1("year"), TQString::tqfromLatin1("19[012345]\\d"), FilterRule::FuncRegExp));
|
|
m_coll->addFilter(f);
|
|
m_modified = true;
|
|
}
|
|
if(m_coll->hasField(TQString::tqfromLatin1("widescreen"))) {
|
|
FilterPtr f = new Filter(Filter::MatchAny);
|
|
f->setName(i18n("Widescreen"));
|
|
f->append(new FilterRule(TQString::tqfromLatin1("widescreen"), TQString::tqfromLatin1("true"), FilterRule::FuncContains));
|
|
m_coll->addFilter(f);
|
|
m_modified = true;
|
|
}
|
|
break;
|
|
|
|
case Data::Collection::Album:
|
|
if(m_coll->hasField(TQString::tqfromLatin1("year"))) {
|
|
FilterPtr f = new Filter(Filter::MatchAny);
|
|
f->setName(i18n("80's Music"));
|
|
f->append(new FilterRule(TQString::tqfromLatin1("year"), TQString::tqfromLatin1("198\\d"),FilterRule::FuncRegExp));
|
|
m_coll->addFilter(f);
|
|
m_modified = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if(m_coll->hasField(TQString::tqfromLatin1("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(TQString::tqfromLatin1("rating"));
|
|
bool ok;
|
|
uint min = Tellico::toUInt(field->property(TQString::tqfromLatin1("minimum")), &ok);
|
|
if(!ok) {
|
|
min = 1;
|
|
}
|
|
uint max = Tellico::toUInt(field->property(TQString::tqfromLatin1("maximum")), &ok);
|
|
if(!ok) {
|
|
min = 5;
|
|
}
|
|
for(uint i = TQMAX(min, static_cast<uint>(0.8*(max-min+1))); i <= max; ++i) {
|
|
filter->append(new FilterRule(TQString::tqfromLatin1("rating"), TQString::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"
|