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.
tellico/src/translators/tellicoxmlexporter.cpp

506 lines
21 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 "tellicoxmlexporter.h"
#include "../collections/bibtexcollection.h"
#include "../imagefactory.h"
#include "../image.h"
#include "../controller.h" // needed for getting groupView pointer
#include "../entryitem.h"
#include "../latin1literal.h"
#include "../filehandler.h"
#include "../groupiterator.h"
#include "../tellico_utils.h"
#include "../tellico_kernel.h"
#include "../tellico_debug.h"
#include "tellico_xml.h"
#include "../document.h" // needed for sorting groups
#include "../translators/bibtexhandler.h" // needed for cleaning text
#include <klocale.h>
#include <kconfig.h>
#include <kmdcodec.h>
#include <kglobal.h>
#include <kcalendarsystem.h>
#include <tqlayout.h>
#include <tqgroupbox.h>
#include <tqcheckbox.h>
#include <tqwhatsthis.h>
#include <tqdom.h>
#include <tqtextcodec.h>
using Tellico::Export::TellicoXMLExporter;
TellicoXMLExporter::TellicoXMLExporter() : Exporter(),
m_includeImages(false), m_includeGroups(false), m_widget(0) {
setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
}
TellicoXMLExporter::TellicoXMLExporter(Data::CollPtr coll) : Exporter(coll),
m_includeImages(false), m_includeGroups(false), m_widget(0) {
setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
}
TQString TellicoXMLExporter::formatString() const {
return i18n("XML");
}
TQString TellicoXMLExporter::fileFilter() const {
return i18n("*.xml|XML Files (*.xml)") + TQChar('\n') + i18n("*|All Files");
}
bool TellicoXMLExporter::exec() {
TQDomDocument doc = exportXML();
if(doc.isNull()) {
return false;
}
return FileHandler::writeTextURL(url(), doc.toString(),
options() & ExportUTF8,
options() & Export::ExportForce);
}
TQDomDocument TellicoXMLExporter::exportXML() const {
// don't be hard on people with older versions. The only difference with DTD 10 was adding
// a board game collection, so use 9 still unless it's a board game
int exportVersion = (XML::syntaxVersion == 10 && collection()->type() != Data::Collection::BoardGame)
? 9
: XML::syntaxVersion;
TQDomImplementation impl;
TQDomDocumentType doctype = impl.createDocumentType(TQString::tqfromLatin1("tellico"),
XML::pubTellico(exportVersion),
XML::dtdTellico(exportVersion));
//default namespace
const TQString& ns = XML::nsTellico;
TQDomDocument dom = impl.createDocument(ns, TQString::tqfromLatin1("tellico"), doctype);
// root tellico element
TQDomElement root = dom.documentElement();
TQString encodeStr = TQString::tqfromLatin1("version=\"1.0\" encoding=\"");
if(options() & Export::ExportUTF8) {
encodeStr += TQString::tqfromLatin1("UTF-8");
} else {
encodeStr += TQString::tqfromLatin1(TQTextCodec::codecForLocale()->mimeName());
}
encodeStr += TQChar('"');
// createDocument creates a root node, insert the processing instruction before it
dom.insertBefore(dom.createProcessingInstruction(TQString::tqfromLatin1("xml"), encodeStr), root);
root.setAttribute(TQString::tqfromLatin1("syntaxVersion"), exportVersion);
exportCollectionXML(dom, root, options() & Export::ExportFormatted);
// clear image list
m_images.clear();
return dom;
}
TQString TellicoXMLExporter::exportXMLString() const {
return exportXML().toString();
}
void TellicoXMLExporter::exportCollectionXML(TQDomDocument& dom_, TQDomElement& parent_, bool format_) const {
Data::CollPtr coll = collection();
if(!coll) {
kdWarning() << "TellicoXMLExporter::exportCollectionXML() - no collection pointer!" << endl;
return;
}
TQDomElement collElem = dom_.createElement(TQString::tqfromLatin1("collection"));
collElem.setAttribute(TQString::tqfromLatin1("type"), coll->type());
collElem.setAttribute(TQString::tqfromLatin1("title"), coll->title());
TQDomElement fieldsElem = dom_.createElement(TQString::tqfromLatin1("fields"));
collElem.appendChild(fieldsElem);
Data::FieldVec fields = coll->fields();
for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
exportFieldXML(dom_, fieldsElem, fIt);
}
if(coll->type() == Data::Collection::Bibtex) {
const Data::BibtexCollection* c = static_cast<const Data::BibtexCollection*>(coll.data());
if(!c->preamble().isEmpty()) {
TQDomElement preElem = dom_.createElement(TQString::tqfromLatin1("bibtex-preamble"));
preElem.appendChild(dom_.createTextNode(c->preamble()));
collElem.appendChild(preElem);
}
TQDomElement macrosElem = dom_.createElement(TQString::tqfromLatin1("macros"));
for(StringMap::ConstIterator macroIt = c->macroList().constBegin(); macroIt != c->macroList().constEnd(); ++macroIt) {
if(!macroIt.data().isEmpty()) {
TQDomElement macroElem = dom_.createElement(TQString::tqfromLatin1("macro"));
macroElem.setAttribute(TQString::tqfromLatin1("name"), macroIt.key());
macroElem.appendChild(dom_.createTextNode(macroIt.data()));
macrosElem.appendChild(macroElem);
}
}
if(macrosElem.childNodes().count() > 0) {
collElem.appendChild(macrosElem);
}
}
Data::EntryVec evec = entries();
for(Data::EntryVec::Iterator entry = evec.begin(); entry != evec.end(); ++entry) {
exportEntryXML(dom_, collElem, entry, format_);
}
if(!m_images.isEmpty() && (options() & Export::ExportImages)) {
TQDomElement imgsElem = dom_.createElement(TQString::tqfromLatin1("images"));
collElem.appendChild(imgsElem);
const TQStringList imageIds = m_images.toList();
for(TQStringList::ConstIterator it = imageIds.begin(); it != imageIds.end(); ++it) {
exportImageXML(dom_, imgsElem, *it);
}
}
if(m_includeGroups) {
exportGroupXML(dom_, collElem);
}
parent_.appendChild(collElem);
// the borrowers and filters are in the tellico object, not the collection
if(options() & Export::ExportComplete) {
TQDomElement bElem = dom_.createElement(TQString::tqfromLatin1("borrowers"));
Data::BorrowerVec borrowers = coll->borrowers();
for(Data::BorrowerVec::Iterator bIt = borrowers.begin(); bIt != borrowers.end(); ++bIt) {
exportBorrowerXML(dom_, bElem, bIt);
}
if(bElem.hasChildNodes()) {
parent_.appendChild(bElem);
}
TQDomElement fElem = dom_.createElement(TQString::tqfromLatin1("filters"));
FilterVec filters = coll->filters();
for(FilterVec::Iterator fIt = filters.begin(); fIt != filters.end(); ++fIt) {
exportFilterXML(dom_, fElem, fIt);
}
if(fElem.hasChildNodes()) {
parent_.appendChild(fElem);
}
}
}
void TellicoXMLExporter::exportFieldXML(TQDomDocument& dom_, TQDomElement& parent_, Data::FieldPtr field_) const {
TQDomElement elem = dom_.createElement(TQString::tqfromLatin1("field"));
elem.setAttribute(TQString::tqfromLatin1("name"), field_->name());
elem.setAttribute(TQString::tqfromLatin1("title"), field_->title());
elem.setAttribute(TQString::tqfromLatin1("category"), field_->category());
elem.setAttribute(TQString::tqfromLatin1("type"), field_->type());
elem.setAttribute(TQString::tqfromLatin1("flags"), field_->flags());
elem.setAttribute(TQString::tqfromLatin1("format"), field_->formatFlag());
if(field_->type() == Data::Field::Choice) {
elem.setAttribute(TQString::tqfromLatin1("allowed"), field_->allowed().join(TQString::tqfromLatin1(";")));
}
// only save description if it's not equal to title, which is the default
// title is never empty, so this indirectly checks for empty descriptions
if(field_->description() != field_->title()) {
elem.setAttribute(TQString::tqfromLatin1("description"), field_->description());
}
for(StringMap::ConstIterator it = field_->propertyList().begin(); it != field_->propertyList().end(); ++it) {
if(it.data().isEmpty()) {
continue;
}
TQDomElement e = dom_.createElement(TQString::tqfromLatin1("prop"));
e.setAttribute(TQString::tqfromLatin1("name"), it.key());
e.appendChild(dom_.createTextNode(it.data()));
elem.appendChild(e);
}
parent_.appendChild(elem);
}
void TellicoXMLExporter::exportEntryXML(TQDomDocument& dom_, TQDomElement& parent_, Data::EntryPtr entry_, bool format_) const {
TQDomElement entryElem = dom_.createElement(TQString::tqfromLatin1("entry"));
entryElem.setAttribute(TQString::tqfromLatin1("id"), entry_->id());
// iterate through every field for the entry
Data::FieldVec fields = entry_->collection()->fields();
for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
TQString fieldName = fIt->name();
// Date fields are special, don't format in export
TQString fieldValue = (format_ && fIt->type() != Data::Field::Date) ? entry_->formattedField(fieldName)
: entry_->field(fieldName);
if(options() & ExportClean) {
BibtexHandler::cleanText(fieldValue);
}
// if empty, then no field element is added and just continue
if(fieldValue.isEmpty()) {
continue;
}
// optionally, verify images exist
if(fIt->type() == Data::Field::Image && (options() & Export::ExportVerifyImages)) {
if(!ImageFactory::validImage(fieldValue)) {
myDebug() << "TellicoXMLExporter::exportEntryXML() - entry: " << entry_->title() << endl;
myDebug() << "TellicoXMLExporter::exportEntryXML() - skipping image: " << fieldValue << endl;
continue;
}
}
// if multiple versions are allowed, split them into separate elements
if(fIt->flags() & Data::Field::AllowMultiple) {
// parent element if field contains multiple values, child of entryElem
// who cares about grammar, just add an 's' to the name
TQDomElement parElem = dom_.createElement(fieldName + 's');
entryElem.appendChild(parElem);
// the space after the semi-colon is enforced when the field is set for the entry
TQStringList fields = TQStringList::split(TQString::tqfromLatin1("; "), fieldValue, true);
for(TQStringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
// element for field value, child of either entryElem or ParentElem
TQDomElement fieldElem = dom_.createElement(fieldName);
// special case for multi-column tables
int ncols = 0;
if(fIt->type() == Data::Field::Table) {
bool ok;
ncols = Tellico::toUInt(fIt->property(TQString::tqfromLatin1("columns")), &ok);
if(!ok) {
ncols = 1;
}
}
if(ncols > 1) {
for(int col = 0; col < ncols; ++col) {
TQDomElement elem;
elem = dom_.createElement(TQString::tqfromLatin1("column"));
elem.appendChild(dom_.createTextNode((*it).section(TQString::tqfromLatin1("::"), col, col)));
fieldElem.appendChild(elem);
}
} else {
fieldElem.appendChild(dom_.createTextNode(*it));
}
parElem.appendChild(fieldElem);
}
} else {
TQDomElement fieldElem = dom_.createElement(fieldName);
entryElem.appendChild(fieldElem);
// Date fields get special treatment
if(fIt->type() == Data::Field::Date) {
fieldElem.setAttribute(TQString::tqfromLatin1("calendar"), KGlobal::locale()->calendar()->calendarName());
TQStringList s = TQStringList::split('-', fieldValue, true);
if(s.count() > 0 && !s[0].isEmpty()) {
TQDomElement e = dom_.createElement(TQString::tqfromLatin1("year"));
fieldElem.appendChild(e);
e.appendChild(dom_.createTextNode(s[0]));
}
if(s.count() > 1 && !s[1].isEmpty()) {
TQDomElement e = dom_.createElement(TQString::tqfromLatin1("month"));
fieldElem.appendChild(e);
e.appendChild(dom_.createTextNode(s[1]));
}
if(s.count() > 2 && !s[2].isEmpty()) {
TQDomElement e = dom_.createElement(TQString::tqfromLatin1("day"));
fieldElem.appendChild(e);
e.appendChild(dom_.createTextNode(s[2]));
}
} else if(fIt->type() == Data::Field::URL &&
fIt->property(TQString::tqfromLatin1("relative")) == Latin1Literal("true") &&
!url().isEmpty()) {
// if a relative URL and url() is not empty, change the value!
KURL old_url(Kernel::self()->URL(), fieldValue);
fieldElem.appendChild(dom_.createTextNode(KURL::relativeURL(url(), old_url)));
} else {
fieldElem.appendChild(dom_.createTextNode(fieldValue));
}
}
if(fIt->type() == Data::Field::Image) {
// possible to have more than one entry with the same image
// only want to include it in the output xml once
m_images.add(fieldValue);
}
} // end field loop
parent_.appendChild(entryElem);
}
void TellicoXMLExporter::exportImageXML(TQDomDocument& dom_, TQDomElement& parent_, const TQString& id_) const {
if(id_.isEmpty()) {
myDebug() << "TellicoXMLExporter::exportImageXML() - empty image!" << endl;
return;
}
// myLog() << "TellicoXMLExporter::exportImageXML() - id = " << id_ << endl;
TQDomElement imgElem = dom_.createElement(TQString::tqfromLatin1("image"));
if(m_includeImages) {
const Data::Image& img = ImageFactory::imageById(id_);
if(img.isNull()) {
myDebug() << "TellicoXMLExporter::exportImageXML() - null image - " << id_ << endl;
return;
}
imgElem.setAttribute(TQString::tqfromLatin1("format"), img.format().data());
imgElem.setAttribute(TQString::tqfromLatin1("id"), img.id());
imgElem.setAttribute(TQString::tqfromLatin1("width"), img.width());
imgElem.setAttribute(TQString::tqfromLatin1("height"), img.height());
if(img.linkOnly()) {
imgElem.setAttribute(TQString::tqfromLatin1("link"), TQString::tqfromLatin1("true"));
}
TQCString imgText = KCodecs::base64Encode(img.byteArray());
imgElem.appendChild(dom_.createTextNode(TQString::tqfromLatin1(imgText)));
} else {
const Data::ImageInfo& info = ImageFactory::imageInfo(id_);
if(info.isNull()) {
return;
}
imgElem.setAttribute(TQString::tqfromLatin1("format"), info.format.data());
imgElem.setAttribute(TQString::tqfromLatin1("id"), info.id);
// only load the images to read the size if necessary
const bool loadImageIfNecessary = options() & Export::ExportImageSize;
imgElem.setAttribute(TQString::tqfromLatin1("width"), info.width(loadImageIfNecessary));
imgElem.setAttribute(TQString::tqfromLatin1("height"), info.height(loadImageIfNecessary));
if(info.linkOnly) {
imgElem.setAttribute(TQString::tqfromLatin1("link"), TQString::tqfromLatin1("true"));
}
}
parent_.appendChild(imgElem);
}
void TellicoXMLExporter::exportGroupXML(TQDomDocument& dom_, TQDomElement& parent_) const {
Data::EntryVec vec = entries(); // need a copy for ::contains();
bool exportAll = collection()->entries().count() == vec.count();
// iterate over each group, which are the first tqchildren
for(GroupIterator gIt = Controller::self()->groupIterator(); gIt.group(); ++gIt) {
if(gIt.group()->isEmpty()) {
continue;
}
TQDomElement groupElem = dom_.createElement(TQString::tqfromLatin1("group"));
groupElem.setAttribute(TQString::tqfromLatin1("title"), gIt.group()->groupName());
// now iterate over all entry items in the group
Data::EntryVec sorted = Data::Document::self()->sortEntries(*gIt.group());
for(Data::EntryVec::Iterator eIt = sorted.begin(); eIt != sorted.end(); ++eIt) {
if(!exportAll && !vec.contains(eIt)) {
continue;
}
TQDomElement entryRefElem = dom_.createElement(TQString::tqfromLatin1("entryRef"));
entryRefElem.setAttribute(TQString::tqfromLatin1("id"), eIt->id());
groupElem.appendChild(entryRefElem);
}
if(groupElem.hasChildNodes()) {
parent_.appendChild(groupElem);
}
}
}
void TellicoXMLExporter::exportFilterXML(TQDomDocument& dom_, TQDomElement& parent_, FilterPtr filter_) const {
TQDomElement filterElem = dom_.createElement(TQString::tqfromLatin1("filter"));
filterElem.setAttribute(TQString::tqfromLatin1("name"), filter_->name());
TQString match = (filter_->op() == Filter::MatchAll) ? TQString::tqfromLatin1("all") : TQString::tqfromLatin1("any");
filterElem.setAttribute(TQString::tqfromLatin1("match"), match);
for(TQPtrListIterator<FilterRule> it(*filter_); it.current(); ++it) {
TQDomElement ruleElem = dom_.createElement(TQString::tqfromLatin1("rule"));
ruleElem.setAttribute(TQString::tqfromLatin1("field"), it.current()->fieldName());
ruleElem.setAttribute(TQString::tqfromLatin1("pattern"), it.current()->pattern());
switch(it.current()->function()) {
case FilterRule::FuncContains:
ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("contains"));
break;
case FilterRule::FuncNotContains:
ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("notcontains"));
break;
case FilterRule::FuncEquals:
ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("equals"));
break;
case FilterRule::FuncNotEquals:
ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("notequals"));
break;
case FilterRule::FuncRegExp:
ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("regexp"));
break;
case FilterRule::FuncNotRegExp:
ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("notregexp"));
break;
default:
kdWarning() << "TellicoXMLExporter::exportFilterXML() - no matching rule function!" << endl;
}
filterElem.appendChild(ruleElem);
}
parent_.appendChild(filterElem);
}
void TellicoXMLExporter::exportBorrowerXML(TQDomDocument& dom_, TQDomElement& parent_,
Data::BorrowerPtr borrower_) const {
if(borrower_->isEmpty()) {
return;
}
TQDomElement bElem = dom_.createElement(TQString::tqfromLatin1("borrower"));
parent_.appendChild(bElem);
bElem.setAttribute(TQString::tqfromLatin1("name"), borrower_->name());
bElem.setAttribute(TQString::tqfromLatin1("uid"), borrower_->uid());
const Data::LoanVec& loans = borrower_->loans();
for(Data::LoanVec::ConstIterator it = loans.constBegin(); it != loans.constEnd(); ++it) {
TQDomElement lElem = dom_.createElement(TQString::tqfromLatin1("loan"));
bElem.appendChild(lElem);
lElem.setAttribute(TQString::tqfromLatin1("uid"), it->uid());
lElem.setAttribute(TQString::tqfromLatin1("entryRef"), it->entry()->id());
lElem.setAttribute(TQString::tqfromLatin1("loanDate"), it->loanDate().toString(Qt::ISODate));
lElem.setAttribute(TQString::tqfromLatin1("dueDate"), it->dueDate().toString(Qt::ISODate));
if(it->inCalendar()) {
lElem.setAttribute(TQString::tqfromLatin1("calendar"), TQString::tqfromLatin1("true"));
}
lElem.appendChild(dom_.createTextNode(it->note()));
}
}
TQWidget* TellicoXMLExporter::widget(TQWidget* parent_, const char* name_/*=0*/) {
if(m_widget && TQT_BASE_OBJECT(m_widget->parent()) == TQT_BASE_OBJECT(parent_)) {
return m_widget;
}
m_widget = new TQWidget(parent_, name_);
TQVBoxLayout* l = new TQVBoxLayout(m_widget);
TQGroupBox* box = new TQGroupBox(1, Qt::Horizontal, i18n("Tellico XML Options"), m_widget);
l->addWidget(box);
m_checkIncludeImages = new TQCheckBox(i18n("Include images in XML document"), box);
m_checkIncludeImages->setChecked(m_includeImages);
TQWhatsThis::add(m_checkIncludeImages, i18n("If checked, the images in the document will be included "
"in the XML stream as base64 encoded elements."));
return m_widget;
}
void TellicoXMLExporter::readOptions(KConfig* config_) {
KConfigGroup group(config_, TQString::tqfromLatin1("ExportOptions - %1").tqarg(formatString()));
m_includeImages = group.readBoolEntry("Include Images", m_includeImages);
}
void TellicoXMLExporter::saveOptions(KConfig* config_) {
m_includeImages = m_checkIncludeImages->isChecked();
KConfigGroup group(config_, TQString::tqfromLatin1("ExportOptions - %1").tqarg(formatString()));
group.writeEntry("Include Images", m_includeImages);
}
#include "tellicoxmlexporter.moc"