/***************************************************************************
    copyright            : (C) 2008 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 "xmlstatehandler.h"
#include "tellico_xml.h"
#include "../latin1literal.h"
#include "../collection.h"
#include "../collectionfactory.h"
#include "../collections/bibtexcollection.h"
#include "../image.h"
#include "../imagefactory.h"
#include "../isbnvalidator.h"
#include "../tellico_utils.h"
#include "../tellico_debug.h"

#include <tdelocale.h>
#include <kmdcodec.h>

namespace {

inline
TQString attValue(const TQXmlAttributes& atts, const char* name, const TQString& defaultValue=TQString()) {
  int idx = atts.index(TQString::fromLatin1(name));
  return idx < 0 ? defaultValue : atts.value(idx);
}

inline
TQString attValue(const TQXmlAttributes& atts, const char* name, const char* defaultValue) {
  Q_ASSERT(defaultValue);
  return attValue(atts, name, TQString::fromLatin1(defaultValue));
}

}

using Tellico::Import::SAX::StateHandler;
using Tellico::Import::SAX::NullHandler;
using Tellico::Import::SAX::RootHandler;
using Tellico::Import::SAX::DocumentHandler;
using Tellico::Import::SAX::CollectionHandler;
using Tellico::Import::SAX::FieldsHandler;
using Tellico::Import::SAX::FieldHandler;
using Tellico::Import::SAX::FieldPropertyHandler;
using Tellico::Import::SAX::BibtexPreambleHandler;
using Tellico::Import::SAX::BibtexMacrosHandler;
using Tellico::Import::SAX::BibtexMacroHandler;
using Tellico::Import::SAX::EntryHandler;
using Tellico::Import::SAX::FieldValueContainerHandler;
using Tellico::Import::SAX::FieldValueHandler;
using Tellico::Import::SAX::DateValueHandler;
using Tellico::Import::SAX::TableColumnHandler;
using Tellico::Import::SAX::ImagesHandler;
using Tellico::Import::SAX::ImageHandler;
using Tellico::Import::SAX::FiltersHandler;
using Tellico::Import::SAX::FilterHandler;
using Tellico::Import::SAX::FilterRuleHandler;
using Tellico::Import::SAX::BorrowersHandler;
using Tellico::Import::SAX::BorrowerHandler;
using Tellico::Import::SAX::LoanHandler;

StateHandler* StateHandler::nextHandler(const TQString& ns_, const TQString& localName_, const TQString& qName_) {
  StateHandler* handler = nextHandlerImpl(ns_, localName_, qName_);
  if(!handler) {
    myWarning() << "StateHandler::nextHandler() - no handler for " << localName_ << endl;
  }
  return handler ? handler : new NullHandler(d);
}

StateHandler* RootHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("tellico") || localName_ == Latin1Literal("bookcase")) {
    return new DocumentHandler(d);
  }
  return new RootHandler(d);
}

StateHandler* DocumentHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("collection")) {
    return new CollectionHandler(d);
  } else if(localName_ == Latin1Literal("filters")) {
    return new FiltersHandler(d);
  } else if(localName_ == Latin1Literal("borrowers")) {
    return new BorrowersHandler(d);
  }
  return 0;
}

bool DocumentHandler::start(const TQString&, const TQString& localName_, const TQString&, const TQXmlAttributes& atts_) {
  // the syntax version field name changed from "version" to "syntaxVersion" in version 3
  int idx = atts_.index(TQString::fromLatin1("syntaxVersion"));
  if(idx < 0) {
    idx = atts_.index(TQString::fromLatin1("version"));
  }
  if(idx < 0) {
    myWarning() << "RootHandler::start() - no syntax version" << endl;
    return false;
  }
  d->syntaxVersion = atts_.value(idx).toUInt();
  if(d->syntaxVersion > Tellico::XML::syntaxVersion) {
    d->error = i18n("It is from a future version of Tellico.");
    return false;
  } else if(Tellico::XML::versionConversion(d->syntaxVersion, Tellico::XML::syntaxVersion)) {
    // going from 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;
  }
  if((d->syntaxVersion > 6 && localName_ != Latin1Literal("tellico")) ||
     (d->syntaxVersion < 7 && localName_ != Latin1Literal("bookcase"))) {
    // no error message
    myWarning() << "RootHandler::start() - bad root element name" << endl;
    return false;
  }
  d->ns = d->syntaxVersion > 6 ? Tellico::XML::nsTellico : Tellico::XML::nsBookcase;
  return true;
}

bool DocumentHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

StateHandler* CollectionHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if((d->syntaxVersion > 3 && localName_ == Latin1Literal("fields")) ||
     (d->syntaxVersion < 4 && localName_ == Latin1Literal("attributes"))) {
    return new FieldsHandler(d);
  } else if(localName_ == Latin1Literal("bibtex-preamble")) {
    return new BibtexPreambleHandler(d);
  } else if(localName_ == Latin1Literal("macros")) {
    return new BibtexMacrosHandler(d);
  } else if(localName_ == d->entryName) {
    return new EntryHandler(d);
  } else if(localName_ == Latin1Literal("images")) {
    return new ImagesHandler(d);
  }
  return 0;
}

bool CollectionHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  d->collTitle = attValue(atts_, "title");
  d->collType = attValue(atts_, "type").toInt();
  d->entryName = attValue(atts_, "unit");

  Q_ASSERT(d->collType);
  return true;
}

bool CollectionHandler::end(const TQString&, const TQString&, const TQString&) {
  d->coll->addEntries(d->entries);
  // a little hidden capability was to just have a local path as an image file name
  // and on reading the xml file, Tellico would load the image file, too
  // here, we need to scan all the image values in all the entries and check
  // maybe this is too costly, especially since the capability wasn't advertised?
  Data::FieldVec fields = d->coll->imageFields();
  for(Data::EntryVecIt entry = d->entries.begin(); entry != d->entries.end(); ++entry) {
    for(Data::FieldVecIt field = fields.begin(); field != fields.end(); ++field) {
      TQString value = entry->field(field, false);
      // 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);
        entry->setField(field->name(), value);
      }
    }
  }
  return true;
}

StateHandler* FieldsHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if((d->syntaxVersion > 3 && localName_ == Latin1Literal("field")) ||
     (d->syntaxVersion < 4 && localName_ == Latin1Literal("attribute"))) {
    return new FieldHandler(d);
  }
  return 0;
}

bool FieldsHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  d->defaultFields = false;
  return true;
}

bool FieldsHandler::end(const TQString&, const TQString&, const TQString&) {
  // add default fields if there was a default field name, or no names at all
  const bool addFields = d->defaultFields || d->fields.isEmpty();
  // in syntax 4, the element name was changed to "entry", always, rather than depending on
  // on the entryName of the collection.
  if(d->syntaxVersion > 3) {
    d->entryName = TQString::fromLatin1("entry");
    Data::Collection::Type type = static_cast<Data::Collection::Type>(d->collType);
    d->coll = CollectionFactory::collection(type, addFields);
  } else {
    d->coll = CollectionFactory::collection(d->entryName, addFields);
  }

  if(!d->collTitle.isEmpty()) {
    d->coll->setTitle(d->collTitle);
  }

  d->coll->addFields(d->fields);

//  as a special case, for old book collections with a bibtex-id field, convert to Bibtex
  if(d->syntaxVersion < 4 && d->collType == Data::Collection::Book
     && d->coll->hasField(TQString::fromLatin1("bibtex-id"))) {
    d->coll = Data::BibtexCollection::convertBookCollection(d->coll);
  }

  return true;
}

StateHandler* FieldHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("prop")) {
    return new FieldPropertyHandler(d);
  }
  return 0;
}

bool FieldHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  // special case: if the i18n attribute equals true, then translate the title, description, and category
  const bool isI18n = attValue(atts_, "i18n") == Latin1Literal("true");

  TQString name  = attValue(atts_, "name", "unknown");
  if(name == Latin1Literal("_default")) {
    d->defaultFields = true;
    return true;
  }

  TQString title  = attValue(atts_, "title", i18n("Unknown"));
  if(isI18n) {
    title = i18n(title.utf8());
  }

  TQString typeStr = attValue(atts_, "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(TQRegExp(TQString::fromLatin1("\\s*;\\s*")),
                                             attValue(atts_, "allowed"));
    if(isI18n) {
      for(TQStringList::Iterator word = allowed.begin(); word != allowed.end(); ++word) {
        (*word) = i18n((*word).utf8());
      }
    }
    field = new Data::Field(name, title, allowed);
  } else {
    field = new Data::Field(name, title, type);
  }

  int idx = atts_.index(TQString::fromLatin1("category"));
  if(idx > -1) {
    // at one point, the categories had keyboard accels
    TQString cat = atts_.value(idx);
    if(d->syntaxVersion < 9 && cat.find('&') > -1) {
      cat.remove('&');
    }
    if(isI18n) {
      cat = i18n(cat.utf8());
    }
    field->setCategory(cat);
  }

  idx = atts_.index(TQString::fromLatin1("flags"));
  if(idx > -1) {
    int flags = atts_.value(idx).toInt();
    // I also changed the enum values for syntax 3, but the only custom field
    // would have been bibtex-id
    if(d->syntaxVersion < 3 && 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(d->syntaxVersion < 4 && name == Latin1Literal("title")) {
      flags |= Data::Field::NoDelete;
    }
    field->setFlags(flags);
  }

  TQString formatStr = attValue(atts_, "format", TQString::number(Data::Field::FormatNone));
  Data::Field::FormatFlag format = static_cast<Data::Field::FormatFlag>(formatStr.toInt());
  field->setFormatFlag(format);

  idx = atts_.index(TQString::fromLatin1("description"));
  if(idx > -1) {
    TQString desc = atts_.value(idx);
    if(isI18n) {
      desc = i18n(desc.utf8());
    }
    field->setDescription(desc);
  }

  if(d->syntaxVersion < 5 && atts_.index(TQString::fromLatin1("bibtex-field")) > -1) {
    field->setProperty(TQString::fromLatin1("bibtex"), attValue(atts_, "bibtex-field"));
  }

  // Table2 is deprecated
  if(type == Data::Field::Table2) {
    field->setType(Data::Field::Table);
    field->setProperty(TQString::fromLatin1("columns"), TQChar('2'));
  }

  // for syntax 8, rating fields got their own type
  if(d->syntaxVersion < 8) {
    Data::Field::convertOldRating(field); // does all its own checking
  }
  d->fields.append(field);

  return true;
}

bool FieldHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

bool FieldPropertyHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  // there should be at least one field already so we can add properties to it
  Q_ASSERT(!d->fields.isEmpty());
  Data::FieldPtr field = d->fields.back();

  m_propertyName = attValue(atts_, "name");

  // all track fields in music collections prior to version 9 get converted to three columns
  if(d->syntaxVersion < 9) {
    if(d->collType == Data::Collection::Album && field->name() == Latin1Literal("track")) {
      field->setProperty(TQString::fromLatin1("columns"), TQChar('3'));
      field->setProperty(TQString::fromLatin1("column1"), i18n("Title"));
      field->setProperty(TQString::fromLatin1("column2"), i18n("Artist"));
      field->setProperty(TQString::fromLatin1("column3"), i18n("Length"));
    } else if(d->collType == Data::Collection::Video && field->name() == Latin1Literal("cast")) {
      field->setProperty(TQString::fromLatin1("column1"), i18n("Actor/Actress"));
      field->setProperty(TQString::fromLatin1("column2"), i18n("Role"));
    }
  }

  return true;
}

bool FieldPropertyHandler::end(const TQString&, const TQString&, const TQString&) {
  Q_ASSERT(!m_propertyName.isEmpty());
  // add the previous property
  Data::FieldPtr field = d->fields.back();
  field->setProperty(m_propertyName, d->text);
  return true;
}

bool BibtexPreambleHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool BibtexPreambleHandler::end(const TQString&, const TQString&, const TQString&) {
  Q_ASSERT(d->coll);
  if(d->coll && d->collType == Data::Collection::Bibtex && !d->text.isEmpty()) {
    Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(d->coll.data());
    c->setPreamble(d->text);
  }
  return true;
}

StateHandler* BibtexMacrosHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("macro")) {
    return new BibtexMacroHandler(d);
  }
  return 0;
}

bool BibtexMacrosHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool BibtexMacrosHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

bool BibtexMacroHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  m_macroName = attValue(atts_, "name");
  return true;
}

bool BibtexMacroHandler::end(const TQString&, const TQString&, const TQString&) {
  if(d->coll && d->collType == Data::Collection::Bibtex && !m_macroName.isEmpty() && !d->text.isEmpty()) {
    Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(d->coll.data());
    c->addMacro(m_macroName, d->text);
  }
  return true;
}

StateHandler* EntryHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(d->coll->hasField(localName_)) {
    return new FieldValueHandler(d);
  }
  return new FieldValueContainerHandler(d);
}

bool EntryHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  // the entries must come after the fields
  if(!d->coll || d->coll->fields().isEmpty()) {
    myWarning() << "EntryHandler::start() - entries must come after fields are defined" << endl;
    // TODO: i18n
    d->error = TQString::fromLatin1("File format error: entries must come after fields are defined");
    return false;
  }
  int id = attValue(atts_, "id").toInt();
  Data::EntryPtr entry;
  if(id > 0) {
    entry = new Data::Entry(d->coll, id);
  } else {
    entry = new Data::Entry(d->coll);
  }
  d->entries.append(entry);
  return true;
}

bool EntryHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

StateHandler* FieldValueContainerHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(d->coll->hasField(localName_)) {
    return new FieldValueHandler(d);
  }
  return new FieldValueContainerHandler(d);
}

bool FieldValueContainerHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool FieldValueContainerHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

StateHandler* FieldValueHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("year") ||
     localName_ == Latin1Literal("month") ||
     localName_ == Latin1Literal("day")) {
    return new DateValueHandler(d);
  } else if(localName_ == Latin1Literal("column")) {
    return new TableColumnHandler(d);
  }
  return 0;
}

bool FieldValueHandler::start(const TQString&, const TQString&, const TQString& localName_, const TQXmlAttributes& atts_) {
  d->currentField = d->coll->fieldByName(localName_);
  m_i18n = attValue(atts_, "i18n") == Latin1Literal("true");
  m_validateISBN = attValue(atts_, "validate") != Latin1Literal("no");
  return true;
}

bool FieldValueHandler::end(const TQString&, const TQString& localName_, const TQString&) {
  Data::FieldPtr f = d->coll->fieldByName(localName_);
  if(!f) {
    myWarning() << "FieldValueHandler::end() - no field named " << localName_ << endl;
    return true;
  }
  // if it's a derived value, no field value is added
  if(f->type() == Data::Field::Dependent) {
    return true;
  }

  Data::EntryPtr entry = d->entries.back();
  Q_ASSERT(entry);
  TQString fieldName = localName_;
  TQString fieldValue = d->text;

  if(d->syntaxVersion < 2 && fieldName == Latin1Literal("keywords")) {
    // in version 2, "keywords" changed to "keyword"
    fieldName = TQString::fromLatin1("keyword");
  } else if(d->syntaxVersion < 4 && f->type() == Data::Field::Bool) {
    // in version 3 and prior, checkbox attributes had no text(), set it to "true"
    fieldValue = TQString::fromLatin1("true");
  } else if(d->syntaxVersion < 8 && f->type() == Data::Field::Rating) {
    // in version 8, old rating fields get changed
    bool ok;
    uint i = Tellico::toUInt(fieldValue, &ok);
    if(ok) {
      fieldValue = TQString::number(i);
    }
  } else if(!d->textBuffer.isEmpty()) {
    // for dates and tables, the value is built up from child elements
#ifndef NDEBUG
    if(!d->text.isEmpty()) {
      myWarning() << "FieldValueHandler::end() - ignoring value for field " << localName_ << ": " << d->text << endl;
    }
#endif
    fieldValue = d->textBuffer;
    d->textBuffer = TQString();
  }
  // this is not an else branch, the data may be in the textBuffer
  if(d->syntaxVersion < 9 && d->coll->type() == Data::Collection::Album && fieldName == Latin1Literal("track")) {
    // yes, this assumes the artist has already been set
    fieldValue += TQString::fromLatin1("::");
    fieldValue += entry->field(TQString::fromLatin1("artist"));
  }
  // special case: if the i18n attribute equals true, then translate the title, description, and category
  if(m_i18n) {
    fieldValue = i18n(fieldValue.utf8());
  }
  // special case for isbn fields, go ahead and validate
  if(m_validateISBN && fieldName == Latin1Literal("isbn")) {
    ISBNValidator val(0);
    val.fixup(fieldValue);
  }
  if(fieldValue.isEmpty()) {
    return true;
  }
  // for fields with multiple values, we need to add on the new value
  TQString oldValue = entry->field(fieldName);
  if(!oldValue.isEmpty()) {
    fieldValue = oldValue + TQString::fromLatin1("; ") + fieldValue;
  }
  entry->setField(fieldName, fieldValue);
  return true;
}

bool DateValueHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool DateValueHandler::end(const TQString&, const TQString& localName_, const TQString&) {
  // the data value is y-m-d even if there are no date values
  if(d->textBuffer.isEmpty()) {
    d->textBuffer = TQString::fromLatin1("--");
  }
  TQStringList tokens = TQStringList::split('-', d->textBuffer, true /* allow empty */);
  Q_ASSERT(tokens.size() == 3);
  if(localName_ == Latin1Literal("year")) {
    tokens[0] = d->text;
  } else if(localName_ == Latin1Literal("month")) {
    tokens[1] = d->text;
  } else if(localName_ == Latin1Literal("day")) {
    tokens[2] = d->text;
  }
  d->textBuffer = tokens.join(TQChar('-'));
  return true;
}

bool TableColumnHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool TableColumnHandler::end(const TQString&, const TQString&, const TQString&) {
  // for old collections, if the second column holds the track length, bump it to next column
  if(d->syntaxVersion < 9 &&
     d->coll->type() == Data::Collection::Album &&
     d->currentField->name() == Latin1Literal("track") &&
     !d->textBuffer.isEmpty() &&
     d->textBuffer.contains(TQString::fromLatin1("::")) == 0) {
    TQRegExp rx(TQString::fromLatin1("\\d+:\\d\\d"));
    if(rx.exactMatch(d->text)) {
      d->text += TQString::fromLatin1("::");
      d->text += d->entries.back()->field(TQString::fromLatin1("artist"));
    }
  }

  if(!d->textBuffer.isEmpty()) {
    d->textBuffer += TQString::fromLatin1("::");
  }
  d->textBuffer += d->text;
  return true;
}

StateHandler* ImagesHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("image")) {
    return new ImageHandler(d);
  }
  return 0;
}

bool ImagesHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  // reset variable that gets updated in the image handler
  d->hasImages = false;
  return true;
}

bool ImagesHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

bool ImageHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  m_format = attValue(atts_, "format");
  m_link = attValue(atts_, "link") == Latin1Literal("true");
  // idClean() already calls shareString()
  m_imageId = m_link ? shareString(attValue(atts_, "id"))
                     : Data::Image::idClean(attValue(atts_, "id"));
  m_width = attValue(atts_, "width").toInt();
  m_height = attValue(atts_, "height").toInt();
  return true;
}

bool ImageHandler::end(const TQString&, const TQString&, const TQString&) {
  bool readInfo = true;
  if(d->loadImages) {
    TQByteArray ba;
    KCodecs::base64Decode(TQCString(d->text.latin1()), ba);
    if(!ba.isEmpty()) {
      TQString result = ImageFactory::addImage(ba, m_format, m_imageId);
      if(result.isEmpty()) {
        myDebug() << "TellicoImporter::readImage(XML) - null image for " << m_imageId << endl;
      }
      d->hasImages = true;
      readInfo = false;
    }
  }
  if(readInfo) {
    // a width or height of 0 is ok here
    Data::ImageInfo info(m_imageId, m_format.latin1(), m_width, m_height, m_link);
    ImageFactory::cacheImageInfo(info);
  }
  return true;
}

StateHandler* FiltersHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("filter")) {
    return new FilterHandler(d);
  }
  return 0;
}

bool FiltersHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool FiltersHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

StateHandler* FilterHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("rule")) {
    return new FilterRuleHandler(d);
  }
  return 0;
}

bool FilterHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  d->filter = new Filter(Filter::MatchAny);
  d->filter->setName(attValue(atts_, "name"));

  if(attValue(atts_, "match") == Latin1Literal("all")) {
    d->filter->setMatch(Filter::MatchAll);
  }
  return true;
}

bool FilterHandler::end(const TQString&, const TQString&, const TQString&) {
  if(d->coll && !d->filter->isEmpty()) {
    d->coll->addFilter(d->filter);
  }
  d->filter = FilterPtr();
  return true;
}

bool FilterRuleHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  TQString field = attValue(atts_, "field");
  // empty field means match any of them
  TQString pattern = attValue(atts_, "pattern");
  // empty pattern is bad
  if(pattern.isEmpty()) {
    myWarning() << "FilterRuleHandler::start() - empty rule!" << endl;
    return true;
  }
  TQString function = attValue(atts_, "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 {
    myWarning() << "FilterRuleHandler::start() - invalid rule function: " << function << endl;
    return true;
  }
  d->filter->append(new FilterRule(field, pattern, func));
  return true;
}

bool FilterRuleHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

StateHandler* BorrowersHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("borrower")) {
    return new BorrowerHandler(d);
  }
  return 0;
}

bool BorrowersHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes&) {
  return true;
}

bool BorrowersHandler::end(const TQString&, const TQString&, const TQString&) {
  return true;
}

StateHandler* BorrowerHandler::nextHandlerImpl(const TQString&, const TQString& localName_, const TQString&) {
  if(localName_ == Latin1Literal("loan")) {
    return new LoanHandler(d);
  }
  return 0;
}

bool BorrowerHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  TQString name = attValue(atts_, "name");
  TQString uid = attValue(atts_, "uid");
  d->borrower = new Data::Borrower(name, uid);

  return true;
}

bool BorrowerHandler::end(const TQString&, const TQString&, const TQString&) {
  if(d->coll && !d->borrower->isEmpty()) {
    d->coll->addBorrower(d->borrower);
  }
  d->borrower = Data::BorrowerPtr();
  return true;
}

bool LoanHandler::start(const TQString&, const TQString&, const TQString&, const TQXmlAttributes& atts_) {
  m_id = attValue(atts_, "entryRef").toInt();
  m_uid = attValue(atts_, "uid");
  m_loanDate = attValue(atts_, "loanDate");
  m_dueDate = attValue(atts_, "dueDate");
  m_inCalendar = attValue(atts_, "calendar") == Latin1Literal("true");
  return true;
}

bool LoanHandler::end(const TQString&, const TQString&, const TQString&) {
  Data::EntryPtr entry = d->coll->entryById(m_id);
  if(!entry) {
    myWarning() << "LoanHandler::end() - no entry with id = " << m_id << endl;
    return true;
  }
  TQDate loanDate, dueDate;
  if(!m_loanDate.isEmpty()) {
    loanDate = TQDate::fromString(m_loanDate, TQt::ISODate);
  }
  if(!m_dueDate.isEmpty()) {
    dueDate = TQDate::fromString(m_dueDate, TQt::ISODate);
  }

  Data::LoanPtr loan = new Data::Loan(entry, loanDate, dueDate, d->text);
  loan->setUID(m_uid);
  loan->setInCalendar(m_inCalendar);
  d->borrower->addLoan(loan);
  return true;
}

