File indexing completed on 2024-05-12 16:46:34

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 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 "tellicoxmlexporter.h"
0026 #include "tellico_xml.h"
0027 #include "../utils/bibtexhandler.h" // needed for cleaning text
0028 #include "../entrygroup.h"
0029 #include "../collections/bibtexcollection.h"
0030 #include "../images/imagefactory.h"
0031 #include "../images/image.h"
0032 #include "../images/imageinfo.h"
0033 #include "../core/filehandler.h"
0034 #include "../utils/string_utils.h"
0035 #include "../document.h"
0036 #include "../fieldformat.h"
0037 #include "../models/entrysortmodel.h"
0038 #include "../models/modelmanager.h"
0039 #include "../models/modeliterator.h"
0040 #include "../models/models.h"
0041 #include "../tellico_debug.h"
0042 
0043 #include <KLocalizedString>
0044 #include <KConfigGroup>
0045 
0046 #include <QDir>
0047 #include <QGroupBox>
0048 #include <QCheckBox>
0049 #include <QDomDocument>
0050 #include <QTextCodec>
0051 #include <QVBoxLayout>
0052 
0053 #include <algorithm>
0054 
0055 using namespace Tellico;
0056 using Tellico::Export::TellicoXMLExporter;
0057 
0058 TellicoXMLExporter::TellicoXMLExporter(Tellico::Data::CollPtr coll) : Exporter(coll),
0059       m_includeImages(false), m_includeGroups(false), m_widget(nullptr), m_checkIncludeImages(nullptr) {
0060   setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
0061 }
0062 
0063 TellicoXMLExporter::~TellicoXMLExporter() {
0064 }
0065 
0066 QString TellicoXMLExporter::formatString() const {
0067   return i18n("XML");
0068 }
0069 
0070 QString TellicoXMLExporter::fileFilter() const {
0071   return i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;") + i18n("All Files") + QLatin1String(" (*)");
0072 }
0073 
0074 bool TellicoXMLExporter::exec() {
0075   QDomDocument doc = exportXML();
0076   if(doc.isNull()) {
0077     return false;
0078   }
0079   return FileHandler::writeTextURL(url(), doc.toString(),
0080                                    options() & ExportUTF8,
0081                                    options() & Export::ExportForce);
0082 }
0083 
0084 QString TellicoXMLExporter::text() const {
0085   return exportXML().toString();
0086 }
0087 
0088 QDomDocument TellicoXMLExporter::exportXML() const {
0089   int exportVersion = XML::syntaxVersion;
0090 
0091   if(exportVersion == 12 && !version12Needed()) {
0092     exportVersion = 11;
0093   }
0094 
0095   QDomImplementation impl;
0096   // Bug 443845 - but do not just silent drop the invalid characters
0097   // since that drops emojis and unicode points with surrogate encoding
0098   // instead Tellico::removeControlCodes is used everywhere that
0099   // QDomDocument::createTextNode() is called
0100 //  impl.setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
0101   QDomDocumentType doctype = impl.createDocumentType(QStringLiteral("tellico"),
0102                                                      XML::pubTellico(exportVersion),
0103                                                      XML::dtdTellico(exportVersion));
0104   //default namespace
0105   const QString& ns = XML::nsTellico;
0106 
0107   QDomDocument dom = impl.createDocument(ns, QStringLiteral("tellico"), doctype);
0108 
0109   // root tellico element
0110   QDomElement root = dom.documentElement();
0111 
0112   QString encodeStr = QStringLiteral("version=\"1.0\" encoding=\"");
0113   if(options() & Export::ExportUTF8) {
0114     encodeStr += QLatin1String("UTF-8");
0115   } else {
0116     encodeStr += QLatin1String(QTextCodec::codecForLocale()->name());
0117   }
0118   encodeStr += QLatin1Char('"');
0119 
0120   // createDocument creates a root node, insert the processing instruction before it
0121   dom.insertBefore(dom.createProcessingInstruction(QStringLiteral("xml"), encodeStr), root);
0122 
0123   root.setAttribute(QStringLiteral("syntaxVersion"), exportVersion);
0124 
0125   FieldFormat::Request format = (options() & Export::ExportFormatted ?
0126                                                 FieldFormat::ForceFormat :
0127                                                 FieldFormat::AsIsFormat);
0128 
0129   exportCollectionXML(dom, root, format);
0130 
0131   // clear image list
0132   m_images.clear();
0133 
0134   return dom;
0135 }
0136 
0137 void TellicoXMLExporter::exportCollectionXML(QDomDocument& dom_, QDomElement& parent_, int format_) const {
0138   Data::CollPtr coll = collection();
0139   if(!coll) {
0140     myWarning() << "no collection pointer!";
0141     return;
0142   }
0143 
0144   QDomElement collElem = dom_.createElement(QStringLiteral("collection"));
0145   collElem.setAttribute(QStringLiteral("type"), coll->type());
0146   collElem.setAttribute(QStringLiteral("title"), coll->title());
0147 
0148   QDomElement fieldsElem = dom_.createElement(QStringLiteral("fields"));
0149   collElem.appendChild(fieldsElem);
0150 
0151   foreach(Data::FieldPtr field, fields()) {
0152     exportFieldXML(dom_, fieldsElem, field);
0153   }
0154 
0155   if(coll->type() == Data::Collection::Bibtex) {
0156     const Data::BibtexCollection* c = static_cast<const Data::BibtexCollection*>(coll.data());
0157     if(!c->preamble().isEmpty()) {
0158       QDomElement preElem = dom_.createElement(QStringLiteral("bibtex-preamble"));
0159       preElem.appendChild(dom_.createTextNode(removeControlCodes(c->preamble())));
0160       collElem.appendChild(preElem);
0161     }
0162 
0163     QDomElement macrosElem = dom_.createElement(QStringLiteral("macros"));
0164     for(StringMap::ConstIterator macroIt = c->macroList().constBegin(); macroIt != c->macroList().constEnd(); ++macroIt) {
0165       if(!macroIt.value().isEmpty()) {
0166         QDomElement macroElem = dom_.createElement(QStringLiteral("macro"));
0167         macroElem.setAttribute(QStringLiteral("name"), macroIt.key());
0168         macroElem.appendChild(dom_.createTextNode(removeControlCodes(macroIt.value())));
0169         macrosElem.appendChild(macroElem);
0170       }
0171     }
0172     if(macrosElem.childNodes().count() > 0) {
0173       collElem.appendChild(macrosElem);
0174     }
0175   }
0176 
0177   foreach(Data::EntryPtr entry, entries()) {
0178     exportEntryXML(dom_, collElem, entry, format_);
0179   }
0180 
0181   if(!m_images.isEmpty() && (options() & Export::ExportImages)) {
0182     QDomElement imgsElem = dom_.createElement(QStringLiteral("images"));
0183     const QStringList imageIds = m_images.values();
0184     foreach(const QString& id, m_images) {
0185       exportImageXML(dom_, imgsElem, id);
0186     }
0187     if(imgsElem.hasChildNodes()) {
0188       collElem.appendChild(imgsElem);
0189     }
0190   }
0191 
0192   if(m_includeGroups) {
0193     exportGroupXML(dom_, collElem);
0194   }
0195 
0196   parent_.appendChild(collElem);
0197 
0198   // the borrowers and filters are in the tellico object, not the collection
0199   if(options() & Export::ExportComplete) {
0200     QDomElement bElem = dom_.createElement(QStringLiteral("borrowers"));
0201     foreach(Data::BorrowerPtr borrower, coll->borrowers()) {
0202       exportBorrowerXML(dom_, bElem, borrower);
0203     }
0204     if(bElem.hasChildNodes()) {
0205       parent_.appendChild(bElem);
0206     }
0207 
0208     QDomElement fElem = dom_.createElement(QStringLiteral("filters"));
0209     foreach(FilterPtr filter, coll->filters()) {
0210       exportFilterXML(dom_, fElem, filter);
0211     }
0212     if(fElem.hasChildNodes()) {
0213       parent_.appendChild(fElem);
0214     }
0215   }
0216 }
0217 
0218 void TellicoXMLExporter::exportFieldXML(QDomDocument& dom_, QDomElement& parent_, Tellico::Data::FieldPtr field_) const {
0219   QDomElement elem = dom_.createElement(QStringLiteral("field"));
0220 
0221   elem.setAttribute(QStringLiteral("name"),     field_->name());
0222   elem.setAttribute(QStringLiteral("title"),    field_->title());
0223   elem.setAttribute(QStringLiteral("category"), field_->category());
0224   elem.setAttribute(QStringLiteral("type"),     field_->type());
0225   elem.setAttribute(QStringLiteral("flags"),    field_->flags());
0226   elem.setAttribute(QStringLiteral("format"),   field_->formatType());
0227 
0228   if(field_->type() == Data::Field::Choice) {
0229     elem.setAttribute(QStringLiteral("allowed"), field_->allowed().join(QLatin1String(";")));
0230   }
0231 
0232   // only save description if it's not equal to title, which is the default
0233   // title is never empty, so this indirectly checks for empty descriptions
0234   if(field_->description() != field_->title()) {
0235     elem.setAttribute(QStringLiteral("description"), field_->description());
0236   }
0237 
0238   for(StringMap::ConstIterator it = field_->propertyList().begin(); it != field_->propertyList().end(); ++it) {
0239     if(it.value().isEmpty()) {
0240       continue;
0241     }
0242     QDomElement e = dom_.createElement(QStringLiteral("prop"));
0243     e.setAttribute(QStringLiteral("name"), it.key());
0244     e.appendChild(dom_.createTextNode(removeControlCodes(it.value())));
0245     elem.appendChild(e);
0246   }
0247 
0248   parent_.appendChild(elem);
0249 }
0250 
0251 void TellicoXMLExporter::exportEntryXML(QDomDocument& dom_, QDomElement& parent_, Tellico::Data::EntryPtr entry_, int format_) const {
0252   QDomElement entryElem = dom_.createElement(QStringLiteral("entry"));
0253   entryElem.setAttribute(QStringLiteral("id"), QString::number(entry_->id()));
0254 
0255   // iterate through every field for the entry
0256   foreach(Data::FieldPtr fIt, fields()) {
0257     QString fieldName = fIt->name();
0258 
0259     // Date fields are special, don't format in export
0260     QString fieldValue = (format_ == FieldFormat::ForceFormat && fIt->type() != Data::Field::Date) ?
0261                                                            entry_->formattedField(fieldName, FieldFormat::ForceFormat) :
0262                                                            entry_->field(fieldName);
0263     if(options() & ExportClean) {
0264       BibtexHandler::cleanText(fieldValue);
0265     }
0266 
0267     // if empty, then no field element is added and just continue
0268     if(fieldValue.isEmpty()) {
0269       continue;
0270     }
0271 
0272     // optionally, verify images exist
0273     if(fIt->type() == Data::Field::Image && (options() & Export::ExportVerifyImages)) {
0274       if(!ImageFactory::validImage(fieldValue)) {
0275         myDebug() << "entry: " << entry_->title();
0276         myDebug() << "skipping image: " << fieldValue;
0277         continue;
0278       }
0279     }
0280 
0281     if(fIt->type() == Data::Field::Table) {
0282       // who cares about grammar, just add an 's' to the name
0283       QDomElement parElem = dom_.createElement(fieldName + QLatin1Char('s'));
0284       entryElem.appendChild(parElem);
0285 
0286       bool ok;
0287       int ncols = Tellico::toUInt(fIt->property(QStringLiteral("columns")), &ok);
0288       if(!ok || ncols < 1) {
0289         ncols = 1;
0290       }
0291       foreach(const QString& rowValue, FieldFormat::splitTable(fieldValue)) {
0292         QDomElement fieldElem = dom_.createElement(fieldName);
0293         parElem.appendChild(fieldElem);
0294 
0295         QStringList columnValues = FieldFormat::splitRow(rowValue);
0296         if(ncols < columnValues.count()) {
0297           // need to combine all the last values, from ncols-1 to end
0298           QString lastValue = QStringList(columnValues.mid(ncols-1)).join(FieldFormat::columnDelimiterString());
0299           columnValues = columnValues.mid(0, ncols);
0300           columnValues.replace(ncols-1, lastValue);
0301         }
0302         for(int col = 0; col < columnValues.count(); ++col) {
0303           QDomElement elem = dom_.createElement(QStringLiteral("column"));
0304           elem.appendChild(dom_.createTextNode(removeControlCodes(columnValues.at(col))));
0305           fieldElem.appendChild(elem);
0306         }
0307       }
0308       continue;
0309     }
0310 
0311     if(fIt->hasFlag(Data::Field::AllowMultiple)) {
0312       // if multiple versions are allowed, split them into separate elements
0313       // parent element if field contains multiple values, child of entryElem
0314       // who cares about grammar, just add an QLatin1Char('s') to the name
0315       QDomElement parElem = dom_.createElement(fieldName + QLatin1Char('s'));
0316       entryElem.appendChild(parElem);
0317 
0318       // the space after the semi-colon is enforced when the field is set for the entry
0319       QStringList fields = FieldFormat::splitValue(fieldValue);
0320       for(QStringList::ConstIterator it = fields.constBegin(); it != fields.constEnd(); ++it) {
0321         // element for field value, child of either entryElem or ParentElem
0322         QDomElement fieldElem = dom_.createElement(fieldName);
0323         fieldElem.appendChild(dom_.createTextNode(removeControlCodes(*it)));
0324         parElem.appendChild(fieldElem);
0325       }
0326     } else {
0327       QDomElement fieldElem = dom_.createElement(fieldName);
0328       entryElem.appendChild(fieldElem);
0329       // Date fields get special treatment
0330       if(fIt->type() == Data::Field::Date) {
0331         // as of Tellico in KF5 (3.0), just forget about the calendar attribute for the moment, always use gregorian
0332         fieldElem.setAttribute(QStringLiteral("calendar"), QStringLiteral("gregorian"));
0333 #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
0334         QStringList s = fieldValue.split(QLatin1Char('-'), QString::KeepEmptyParts);
0335 #else
0336         QStringList s = fieldValue.split(QLatin1Char('-'), Qt::KeepEmptyParts);
0337 #endif
0338         if(s.count() > 0 && !s[0].isEmpty()) {
0339           QDomElement e = dom_.createElement(QStringLiteral("year"));
0340           fieldElem.appendChild(e);
0341           e.appendChild(dom_.createTextNode(s[0]));
0342         }
0343         if(s.count() > 1 && !s[1].isEmpty()) {
0344           QDomElement e = dom_.createElement(QStringLiteral("month"));
0345           fieldElem.appendChild(e);
0346           e.appendChild(dom_.createTextNode(s[1]));
0347         }
0348         if(s.count() > 2 && !s[2].isEmpty()) {
0349           QDomElement e = dom_.createElement(QStringLiteral("day"));
0350           fieldElem.appendChild(e);
0351           e.appendChild(dom_.createTextNode(s[2]));
0352         }
0353       } else if(fIt->type() == Data::Field::URL &&
0354                 fIt->property(QStringLiteral("relative")) == QLatin1String("true")) {
0355         // if a relative URL and url() is not empty, change the value!
0356         QUrl old_url = Data::Document::self()->URL().resolved(QUrl(fieldValue));
0357         if(options() & Export::ExportAbsoluteLinks) {
0358           fieldElem.appendChild(dom_.createTextNode(old_url.url()));
0359         } else if(!url().isEmpty()) {
0360           QUrl new_url(url());
0361           if(new_url.scheme() == old_url.scheme() &&
0362              new_url.host() == old_url.host()) {
0363             // try to calculate a new relative url
0364             QString relPath = QDir(new_url.path()).relativeFilePath(old_url.path());
0365             fieldElem.appendChild(dom_.createTextNode(relPath));
0366           } else {
0367             // use the absolute url here
0368             fieldElem.appendChild(dom_.createTextNode(old_url.url()));
0369           }
0370         } else {
0371           fieldElem.appendChild(dom_.createTextNode(removeControlCodes(fieldValue)));
0372         }
0373       } else {
0374         fieldElem.appendChild(dom_.createTextNode(removeControlCodes(fieldValue)));
0375       }
0376     }
0377 
0378     if(fIt->type() == Data::Field::Image) {
0379       // possible to have more than one entry with the same image
0380       // only want to include it in the output xml once
0381       m_images.add(fieldValue);
0382     }
0383   } // end field loop
0384 
0385   parent_.appendChild(entryElem);
0386 }
0387 
0388 void TellicoXMLExporter::exportImageXML(QDomDocument& dom_, QDomElement& parent_, const QString& id_) const {
0389   if(id_.isEmpty()) {
0390     myDebug() << "empty image!";
0391     return;
0392   }
0393 //  myLog() << "id = " << id_;
0394 
0395   QDomElement imgElem = dom_.createElement(QStringLiteral("image"));
0396   if(m_includeImages) {
0397     const Data::Image& img = ImageFactory::imageById(id_);
0398     if(img.isNull()) {
0399       return;
0400     }
0401     imgElem.setAttribute(QStringLiteral("format"), QLatin1String(img.format()));
0402     imgElem.setAttribute(QStringLiteral("id"),     QString(img.id()));
0403     imgElem.setAttribute(QStringLiteral("width"),  img.width());
0404     imgElem.setAttribute(QStringLiteral("height"), img.height());
0405     if(img.linkOnly()) {
0406       imgElem.setAttribute(QStringLiteral("link"), QStringLiteral("true"));
0407     }
0408     QByteArray imgText = img.byteArray().toBase64();
0409     imgElem.appendChild(dom_.createTextNode(QLatin1String(imgText)));
0410   } else {
0411     const Data::ImageInfo& info = ImageFactory::imageInfo(id_);
0412     if(info.isNull()) {
0413       return;
0414     }
0415     imgElem.setAttribute(QStringLiteral("format"), QLatin1String(info.format));
0416     imgElem.setAttribute(QStringLiteral("id"),     QString(info.id));
0417     // only load the images to read the size if necessary
0418     const bool loadImageIfNecessary = options() & Export::ExportImageSize;
0419     imgElem.setAttribute(QStringLiteral("width"),  info.width(loadImageIfNecessary));
0420     imgElem.setAttribute(QStringLiteral("height"), info.height(loadImageIfNecessary));
0421     if(info.linkOnly) {
0422       imgElem.setAttribute(QStringLiteral("link"), QStringLiteral("true"));
0423     }
0424   }
0425   parent_.appendChild(imgElem);
0426 }
0427 
0428 void TellicoXMLExporter::exportGroupXML(QDomDocument& dom_, QDomElement& parent_) const {
0429   Data::EntryList vec = entries();
0430   bool exportAll = collection()->entries().count() == vec.count();
0431   // iterate over each group, which are the first children
0432   for(ModelIterator gIt(ModelManager::self()->groupModel()); gIt.group(); ++gIt) {
0433     if(gIt.group()->isEmpty()) {
0434       continue;
0435     }
0436     QDomElement groupElem = dom_.createElement(QStringLiteral("group"));
0437     groupElem.setAttribute(QStringLiteral("title"), gIt.group()->groupName());
0438     // now iterate over all entry items in the group
0439     Data::EntryList sorted = sortEntries(*gIt.group());
0440     foreach(Data::EntryPtr eIt, sorted) {
0441       if(!exportAll && vec.indexOf(eIt) == -1) {
0442         continue;
0443       }
0444       QDomElement entryRefElem = dom_.createElement(QStringLiteral("entryRef"));
0445       entryRefElem.setAttribute(QStringLiteral("id"), QString::number(eIt->id()));
0446       groupElem.appendChild(entryRefElem);
0447     }
0448     if(groupElem.hasChildNodes()) {
0449       parent_.appendChild(groupElem);
0450     }
0451   }
0452 }
0453 
0454 void TellicoXMLExporter::exportFilterXML(QDomDocument& dom_, QDomElement& parent_, Tellico::FilterPtr filter_) const {
0455   QDomElement filterElem = dom_.createElement(QStringLiteral("filter"));
0456   filterElem.setAttribute(QStringLiteral("name"), filter_->name());
0457 
0458   QString match = (filter_->op() == Filter::MatchAll) ? QStringLiteral("all") : QStringLiteral("any");
0459   filterElem.setAttribute(QStringLiteral("match"), match);
0460 
0461   foreach(FilterRule* rule, *filter_) {
0462     QDomElement ruleElem = dom_.createElement(QStringLiteral("rule"));
0463     ruleElem.setAttribute(QStringLiteral("field"), rule->fieldName());
0464     ruleElem.setAttribute(QStringLiteral("pattern"), rule->pattern());
0465     switch(rule->function()) {
0466       case FilterRule::FuncContains:
0467         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("contains"));
0468         break;
0469       case FilterRule::FuncNotContains:
0470         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("notcontains"));
0471         break;
0472       case FilterRule::FuncEquals:
0473         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("equals"));
0474         break;
0475       case FilterRule::FuncNotEquals:
0476         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("notequals"));
0477         break;
0478       case FilterRule::FuncRegExp:
0479         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("regexp"));
0480         break;
0481       case FilterRule::FuncNotRegExp:
0482         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("notregexp"));
0483         break;
0484       case FilterRule::FuncBefore:
0485         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("before"));
0486         break;
0487       case FilterRule::FuncAfter:
0488         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("after"));
0489         break;
0490       case FilterRule::FuncGreater:
0491         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("greaterthan"));
0492         break;
0493       case FilterRule::FuncLess:
0494         ruleElem.setAttribute(QStringLiteral("function"), QStringLiteral("lessthan"));
0495         break;
0496       /* If anything is updated here, be sure to update xmlstatehandler */
0497     }
0498     filterElem.appendChild(ruleElem);
0499   }
0500 
0501   parent_.appendChild(filterElem);
0502 }
0503 
0504 void TellicoXMLExporter::exportBorrowerXML(QDomDocument& dom_, QDomElement& parent_,
0505                                            Tellico::Data::BorrowerPtr borrower_) const {
0506   if(borrower_->isEmpty()) {
0507     return;
0508   }
0509 
0510   QDomElement bElem = dom_.createElement(QStringLiteral("borrower"));
0511   parent_.appendChild(bElem);
0512 
0513   bElem.setAttribute(QStringLiteral("name"), borrower_->name());
0514   bElem.setAttribute(QStringLiteral("uid"), borrower_->uid());
0515 
0516   foreach(Data::LoanPtr it, borrower_->loans()) {
0517     QDomElement lElem = dom_.createElement(QStringLiteral("loan"));
0518     bElem.appendChild(lElem);
0519 
0520     lElem.setAttribute(QStringLiteral("uid"), it->uid());
0521     lElem.setAttribute(QStringLiteral("entryRef"), QString::number(it->entry()->id()));
0522     lElem.setAttribute(QStringLiteral("loanDate"), it->loanDate().toString(Qt::ISODate));
0523     lElem.setAttribute(QStringLiteral("dueDate"), it->dueDate().toString(Qt::ISODate));
0524     if(it->inCalendar()) {
0525       lElem.setAttribute(QStringLiteral("calendar"), QStringLiteral("true"));
0526     }
0527 
0528     lElem.appendChild(dom_.createTextNode(it->note()));
0529   }
0530 }
0531 
0532 QWidget* TellicoXMLExporter::widget(QWidget* parent_) {
0533   if(m_widget) {
0534     return m_widget;
0535   }
0536 
0537   m_widget = new QWidget(parent_);
0538   QVBoxLayout* l = new QVBoxLayout(m_widget);
0539 
0540   QGroupBox* gbox = new QGroupBox(i18n("Tellico XML Options"), m_widget);
0541   QVBoxLayout* vlay = new QVBoxLayout(gbox);
0542 
0543   m_checkIncludeImages = new QCheckBox(i18n("Include images in XML document"), gbox);
0544   m_checkIncludeImages->setChecked(m_includeImages);
0545   m_checkIncludeImages->setWhatsThis(i18n("If checked, the images in the document will be included "
0546                                           "in the XML stream as base64 encoded elements."));
0547 
0548   vlay->addWidget(m_checkIncludeImages);
0549 
0550   l->addWidget(gbox);
0551   l->addStretch(1);
0552   return m_widget;
0553 }
0554 
0555 void TellicoXMLExporter::readOptions(KSharedConfigPtr config_) {
0556   KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString()));
0557   m_includeImages = group.readEntry("Include Images", m_includeImages);
0558 }
0559 
0560 void TellicoXMLExporter::saveOptions(KSharedConfigPtr config_) {
0561   m_includeImages = m_checkIncludeImages->isChecked();
0562 
0563   KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString()));
0564   group.writeEntry("Include Images", m_includeImages);
0565 }
0566 
0567 Tellico::Data::EntryList TellicoXMLExporter::sortEntries(const Data::EntryList& entries_) const {
0568   Data::EntryList sorted = entries_;
0569 
0570   EntrySortModel* model = static_cast<EntrySortModel*>(ModelManager::self()->entryModel());
0571   // have to go in reverse for sorting
0572   Data::FieldList fields;
0573   Data::FieldPtr field;
0574   if(model->tertiarySortColumn() > -1) {
0575     field = model->headerData(model->tertiarySortColumn(), Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0576     if(field) {
0577       fields << field;
0578     } else {
0579       myDebug() << "no field for tertiary sort column" << model->tertiarySortColumn();
0580     }
0581   }
0582   if(model->secondarySortColumn() > -1) {
0583     field = model->headerData(model->secondarySortColumn(), Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0584     if(field) {
0585       fields << field;
0586     } else {
0587       myDebug() << "no field for secondary sort column" << model->secondarySortColumn();
0588     }
0589   }
0590   if(model->sortColumn() > -1) {
0591     field = model->headerData(model->sortColumn(), Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0592     if(field) {
0593       fields << field;
0594     } else {
0595       myDebug() << "no field for primary sort column" << model->sortColumn();
0596     }
0597   }
0598 
0599   // now sort
0600   foreach(Data::FieldPtr field, fields) {
0601     std::sort(sorted.begin(), sorted.end(), Data::EntryCmp(field->name()));
0602   }
0603 
0604   return sorted;
0605 }
0606 
0607 bool TellicoXMLExporter::version12Needed() const {
0608   // version 12 is only necessary if the new filter rules are used
0609   foreach(FilterPtr filter, collection()->filters()) {
0610     foreach(FilterRule* rule, *filter) {
0611       if(rule->function() == FilterRule::FuncBefore ||
0612          rule->function() == FilterRule::FuncAfter ||
0613          rule->function() == FilterRule::FuncGreater ||
0614          rule->function() == FilterRule::FuncLess) {
0615         return true;
0616       }
0617     }
0618   }
0619   return false;
0620 }