/***************************************************************************
    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 "htmlexporter.h"
#include "xslthandler.h"
#include "tellicoxmlexporter.h"
#include "../document.h"
#include "../collection.h"
#include "../filehandler.h"
#include "../imagefactory.h"
#include "../latin1literal.h"
#include "../tellico_kernel.h"
#include "../tellico_utils.h"
#include "../progressmanager.h"
#include "../core/tellico_config.h"
#include "../tellico_debug.h"

#include <kstandarddirs.h>
#include <kconfig.h>
#include <kglobal.h>
#include <kio/netaccess.h>
#include <kapplication.h>
#include <klocale.h>

#include <tqdom.h>
#include <tqgroupbox.h>
#include <tqlayout.h>
#include <tqcheckbox.h>
#include <tqwhatsthis.h>
#include <tqfile.h>
#include <tqhbox.h>
#include <tqlabel.h>

extern "C" {
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>
}

using Tellico::Export::HTMLExporter;

HTMLExporter::HTMLExporter() : Tellico::Export::Exporter(),
    m_handler(0),
    m_printHeaders(true),
    m_printGrouped(false),
    m_exportEntryFiles(false),
    m_cancelled(false),
    m_parseDOM(true),
    m_checkCreateDir(true),
    m_imageWidth(0),
    m_imageHeight(0),
    m_widget(0),
    m_xsltFile(TQString::fromLatin1("tellico2html.xsl")) {
}

HTMLExporter::HTMLExporter(Data::CollPtr coll_) : Tellico::Export::Exporter(coll_),
    m_handler(0),
    m_printHeaders(true),
    m_printGrouped(false),
    m_exportEntryFiles(false),
    m_cancelled(false),
    m_parseDOM(true),
    m_checkCreateDir(true),
    m_imageWidth(0),
    m_imageHeight(0),
    m_widget(0),
    m_xsltFile(TQString::fromLatin1("tellico2html.xsl")) {
}

HTMLExporter::~HTMLExporter() {
  delete m_handler;
  m_handler = 0;
}

TQString HTMLExporter::formatString() const {
  return i18n("HTML");
}

TQString HTMLExporter::fileFilter() const {
  return i18n("*.html|HTML Files (*.html)") + TQChar('\n') + i18n("*|All Files");
}

void HTMLExporter::reset() {
  // since the ExportUTF8 option may have changed, need to delete handler
  delete m_handler;
  m_handler = 0;
  m_files.clear();
  m_links.clear();
  m_copiedFiles.clear();
}

bool HTMLExporter::exec() {
  if(url().isEmpty() || !url().isValid()) {
    kdWarning() << "HTMLExporter::exec() - trying to export to invalid URL" << endl;
    return false;
  }

  // check file exists first
  // if we're not forcing, ask use
  bool force = (options() & Export::ExportForce) || FileHandler::queryExists(url());
  if(!force) {
    return false;
  }

  if(!m_parseDOM) {
    return FileHandler::writeTextURL(url(), text(), options() & Export::ExportUTF8, force);
  }

  m_cancelled = false;
  // TODO: maybe need label?
  if(options() & ExportProgress) {
    ProgressItem& item = ProgressManager::self()->newProgressItem(this, TQString(), true);
    item.setTotalSteps(100);
    connect(&item, TQT_SIGNAL(signalCancelled(ProgressItem*)), TQT_SLOT(slotCancel()));
  }
  // ok if not ExportProgress, no worries
  ProgressItem::Done done(this);

  htmlDocPtr htmlDoc = htmlParseDoc(reinterpret_cast<xmlChar*>(text().utf8().data()), NULL);
  xmlNodePtr root = xmlDocGetRootElement(htmlDoc);
  if(root == 0) {
    myDebug() << "HTMLExporter::exec() - no root" << endl;
    return false;
  }
  parseDOM(root);

  if(m_cancelled) {
    return true; // intentionally cancelled
  }
  ProgressManager::self()->setProgress(this, 15);

  xmlChar* c;
  int bytes;
  htmlDocDumpMemory(htmlDoc, &c, &bytes);
  TQString allText;
  if(bytes > 0) {
    allText = TQString::fromUtf8(reinterpret_cast<const char*>(c), bytes);
    xmlFree(c);
  }

  if(m_cancelled) {
    return true; // intentionally cancelled
  }
  ProgressManager::self()->setProgress(this, 20);

  bool success = FileHandler::writeTextURL(url(), allText, options() & Export::ExportUTF8, force);
  success &= copyFiles() && (!m_exportEntryFiles || writeEntryFiles());
  return success;
}

bool HTMLExporter::loadXSLTFile() {
  TQString xsltfile = locate("appdata", m_xsltFile);
  if(xsltfile.isNull()) {
    myDebug() << "HTMLExporter::loadXSLTFile() - no xslt file for " << m_xsltFile << endl;
    return false;
  }

  KURL u;
  u.setPath(xsltfile);
  // do NOT do namespace processing, it messes up the XSL declaration since
  // TQDom thinks there are no elements in the Tellico namespace and as a result
  // removes the namespace declaration
  TQDomDocument dom = FileHandler::readXMLFile(u, false);
  if(dom.isNull()) {
    myDebug() << "HTMLExporter::loadXSLTFile() - error loading xslt file: " << xsltfile << endl;
    return false;
  }

  // notes about utf-8 encoding:
  // all params should be passed to XSLTHandler in utf8
  // input string to XSLTHandler should be in utf-8, EVEN IF DOM STRING SAYS OTHERWISE

  // the stylesheet prints utf-8 by default, if using locale encoding, need
  // to change the encoding attribute on the xsl:output element
  if(!(options() & Export::ExportUTF8)) {
    XSLTHandler::setLocaleEncoding(dom);
  }

  delete m_handler;
  m_handler = new XSLTHandler(dom, TQFile::encodeName(xsltfile), true /*translate*/);
  if(!m_handler->isValid()) {
    delete m_handler;
    m_handler = 0;
    return false;
  }

  if(m_exportEntryFiles) {
    // export entries to same place as all the other date files
    m_handler->addStringParam("entrydir", TQFile::encodeName(fileDir().fileName())+ '/');
    // be sure to link all the entries
    m_handler->addParam("link-entries", "true()");
  }

  if(!m_collectionURL.isEmpty()) {
    TQString s = TQString::fromLatin1("../") + m_collectionURL.fileName();
    m_handler->addStringParam("collection-file", s.utf8());
  }

  // look for a file that gets installed to know the installation directory
  // if parseDOM, that means we want the locations to be the actual location
  // otherwise, we assume it'll be relative
  if(m_parseDOM && m_dataDir.isEmpty()) {
    m_dataDir = KGlobal::dirs()->findResourceDir("appdata", TQString::fromLatin1("pics/tellico.png"));
  } else if(!m_parseDOM) {
    m_dataDir.truncate(0);
  }
  if(!m_dataDir.isEmpty()) {
    m_handler->addStringParam("datadir", TQFile::encodeName(m_dataDir));
  }

  setFormattingOptions(collection());

  return m_handler->isValid();
}

TQString HTMLExporter::text() {
  if((!m_handler || !m_handler->isValid()) && !loadXSLTFile()) {
    kdWarning() << "HTMLExporter::text() - error loading xslt file: " << m_xsltFile << endl;
    return TQString();
  }

  Data::CollPtr coll = collection();
  if(!coll) {
    myDebug() << "HTMLExporter::text() - no collection pointer!" << endl;
    return TQString();
  }

  if(m_groupBy.isEmpty()) {
    m_printGrouped = false; // can't group if no groups exist
  }

  GUI::CursorSaver cs;
  writeImages(coll);

  // now grab the XML
  TellicoXMLExporter exporter(coll);
  exporter.setURL(url());
  exporter.setEntries(entries());
  exporter.setIncludeGroups(m_printGrouped);
// yes, this should be in utf8, always
  exporter.setOptions(options() | Export::ExportUTF8 | Export::ExportImages);
  TQDomDocument output = exporter.exportXML();
#if 0
  TQFile f(TQString::fromLatin1("/tmp/test.xml"));
  if(f.open(IO_WriteOnly)) {
    TQTextStream t(&f);
    t << output.toString();
  }
  f.close();
#endif

  TQString text = m_handler->applyStylesheet(output.toString());
#if 0
  TQFile f2(TQString::fromLatin1("/tmp/test.html"));
  if(f2.open(IO_WriteOnly)) {
    TQTextStream t(&f2);
    t << text;
//    t << "\n\n-------------------------------------------------------\n\n";
//    t << Tellico::i18nReplace(text);
  }
  f2.close();
#endif
  // the XSLT file gets translated instead
//  return Tellico::i18nReplace(text);
  return text;
}

void HTMLExporter::setFormattingOptions(Data::CollPtr coll) {
  TQString file = Kernel::self()->URL().fileName();
  if(file != i18n("Untitled")) {
    m_handler->addStringParam("filename", TQFile::encodeName(file));
  }
  m_handler->addStringParam("cdate", KGlobal::locale()->formatDate(TQDate::currentDate()).utf8());
  m_handler->addParam("show-headers", m_printHeaders ? "true()" : "false()");
  m_handler->addParam("group-entries", m_printGrouped ? "true()" : "false()");

  TQStringList sortTitles;
  if(!m_sort1.isEmpty()) {
    sortTitles << m_sort1;
  }
  if(!m_sort2.isEmpty()) {
    sortTitles << m_sort2;
  }

  // the third sort column may be same as first
  if(!m_sort3.isEmpty() && sortTitles.findIndex(m_sort3) == -1) {
    sortTitles << m_sort3;
  }

  if(sortTitles.count() > 0) {
    m_handler->addStringParam("sort-name1", coll->fieldNameByTitle(sortTitles[0]).utf8());
    if(sortTitles.count() > 1) {
      m_handler->addStringParam("sort-name2", coll->fieldNameByTitle(sortTitles[1]).utf8());
      if(sortTitles.count() > 2) {
        m_handler->addStringParam("sort-name3", coll->fieldNameByTitle(sortTitles[2]).utf8());
      }
    }
  }

  // no longer showing "sorted by..." since the column headers are clickable
  // but still use "grouped by"
  TQString sortString;
  if(m_printGrouped) {
    TQString s;
    // if more than one, then it's the People pseudo-group
    if(m_groupBy.count() > 1) {
      s = i18n("People");
    } else {
      s = coll->fieldTitleByName(m_groupBy[0]);
    }
    sortString = i18n("(grouped by %1)").arg(s);

    TQString groupFields;
    for(TQStringList::ConstIterator it = m_groupBy.begin(); it != m_groupBy.end(); ++it) {
      Data::FieldPtr f = coll->fieldByName(*it);
      if(!f) {
        continue;
      }
      if(f->flags() & Data::Field::AllowMultiple) {
        groupFields += TQString::fromLatin1("tc:") + *it + TQString::fromLatin1("s/tc:") + *it;
      } else {
        groupFields += TQString::fromLatin1("tc:") + *it;
      }
      int ncols = 0;
      if(f->type() == Data::Field::Table) {
        bool ok;
        ncols = Tellico::toUInt(f->property(TQString::fromLatin1("columns")), &ok);
        if(!ok) {
          ncols = 1;
        }
      }
      if(ncols > 1) {
        groupFields += TQString::fromLatin1("/tc:column[1]");
      }
      if(*it != m_groupBy.last()) {
        groupFields += '|';
      }
    }
//    myDebug() << groupFields << endl;
    m_handler->addStringParam("group-fields", groupFields.utf8());
    m_handler->addStringParam("sort-title", sortString.utf8());
  }

  TQString pageTitle = coll->title();
  pageTitle += TQChar(' ') + sortString;
  m_handler->addStringParam("page-title", pageTitle.utf8());

  TQStringList showFields;
  for(TQStringList::ConstIterator it = m_columns.begin(); it != m_columns.end(); ++it) {
    showFields << coll->fieldNameByTitle(*it);
  }
  m_handler->addStringParam("column-names", showFields.join(TQChar(' ')).utf8());

  if(m_imageWidth > 0 && m_imageHeight > 0) {
    m_handler->addParam("image-width", TQCString().setNum(m_imageWidth));
    m_handler->addParam("image-height", TQCString().setNum(m_imageHeight));
  }

  // add system colors to stylesheet
  const int type = coll->type();
  m_handler->addStringParam("font",     TQString(Config::templateFont(type).family()).latin1());
  m_handler->addStringParam("fontsize", TQCString().setNum(Config::templateFont(type).pointSize()));
  m_handler->addStringParam("bgcolor",  TQString(Config::templateBaseColor(type).name()).latin1());
  m_handler->addStringParam("fgcolor",  TQString(Config::templateTextColor(type).name()).latin1());
  m_handler->addStringParam("color1",   TQString(Config::templateHighlightedTextColor(type).name()).latin1());
  m_handler->addStringParam("color2",   TQString(Config::templateHighlightedBaseColor(type).name()).latin1());

  // add locale code to stylesheet (for sorting)
  m_handler->addStringParam("lang",  KGlobal::locale()->languagesTwoAlpha().first().utf8());
}

void HTMLExporter::writeImages(Data::CollPtr coll_) {
  // keep track of which image fields to write, this is for field names
  StringSet imageFields;
  for(TQStringList::ConstIterator it = m_columns.begin(); it != m_columns.end(); ++it) {
    if(coll_->fieldByTitle(*it)->type() == Data::Field::Image) {
      imageFields.add(*it);
    }
  }

  // all the images potentially used in the HTML export need to be written to disk
  // if we're exporting entry files, then we'll certainly want all the image fields written
  // if we're not exporting to a file, then we might be exporting an entry template file
  // and so we need to write all of them too.
  if(m_exportEntryFiles || url().isEmpty()) {
    // add all image fields to string list
    Data::FieldVec fields = coll_->imageFields();
    for(Data::FieldVec::Iterator fieldIt = fields.begin(); fieldIt != fields.end(); ++fieldIt) {
      imageFields.add(fieldIt->name());
    }
  }

  // all of them are going to get written to tmp file
  bool useTemp = url().isEmpty();
  KURL imgDir;
  TQString imgDirRelative;
  // really some convoluted logic here
  // basically, four cases. 1) we're writing to a tmp file, for printing probably
  // so then write all the images to the tmp directory, 2) we're exporting to HTML, and
  // this is the main collection file, in which case m_parseDOM is always true;
  // 3) we're exporting HTML, and this is the first entry file, for which parseDOM is true
  // and exportEntryFiles is false. Then the image file will get copied in copyFiles() and is
  // probably an image in the entry template. 4) we're exporting HTML, and this is not the
  // first entry file, in which case, we want to refer directly to the target dir
  if(useTemp) { // everything goes in the tmp dir
    imgDir.setPath(ImageFactory::tempDir());
    imgDirRelative = imgDir.path();
  } else if(m_parseDOM) {
    imgDir = fileDir(); // copy to fileDir
    imgDirRelative = Data::Document::self()->allImagesOnDisk() ? ImageFactory::dataDir() : ImageFactory::tempDir();
    createDir();
  } else {
    imgDir = fileDir();
    imgDirRelative = KURL::relativeURL(url(), imgDir);
    createDir();
  }
  m_handler->addStringParam("imgdir", TQFile::encodeName(imgDirRelative));

  int count = 0;
  const int processCount = 100; // process after every 100 events

  TQStringList fieldsList = imageFields.toList();
  StringSet imageSet; // track which images are written
  for(TQStringList::ConstIterator fieldName = fieldsList.begin(); fieldName != fieldsList.end(); ++fieldName) {
    for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
      TQString id = entryIt->field(*fieldName);
      // if no id or is already writen, continue
      if(id.isEmpty() || imageSet.has(id)) {
        continue;
      }
      imageSet.add(id);
      // try writing
      bool success = useTemp ? ImageFactory::writeCachedImage(id, ImageFactory::TempDir)
                             : ImageFactory::writeImage(id, imgDir, true);
      if(!success) {
        kdWarning() << "HTMLExporter::writeImages() - unable to write image file: "
                    << imgDir.path() << id << endl;
      }

      if(++count == processCount) {
        kapp->processEvents();
        count = 0;
      }
    }
  }
}

TQWidget* HTMLExporter::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("HTML Options"), m_widget);
  l->addWidget(box);

  m_checkPrintHeaders = new TQCheckBox(i18n("Print field headers"), box);
  TQWhatsThis::add(m_checkPrintHeaders, i18n("If checked, the field names will be "
                                            "printed as table headers."));
  m_checkPrintHeaders->setChecked(m_printHeaders);

  m_checkPrintGrouped = new TQCheckBox(i18n("Group the entries"), box);
  TQWhatsThis::add(m_checkPrintGrouped, i18n("If checked, the entries will be grouped by "
                                            "the selected field."));
  m_checkPrintGrouped->setChecked(m_printGrouped);

  m_checkExportEntryFiles = new TQCheckBox(i18n("Export individual entry files"), box);
  TQWhatsThis::add(m_checkExportEntryFiles, i18n("If checked, individual files will be created for each entry."));
  m_checkExportEntryFiles->setChecked(m_exportEntryFiles);

  l->addStretch(1);
  return m_widget;
}

void HTMLExporter::readOptions(KConfig* config_) {
  KConfigGroup exportConfig(config_, TQString::fromLatin1("ExportOptions - %1").arg(formatString()));
  m_printHeaders = exportConfig.readBoolEntry("Print Field Headers", m_printHeaders);
  m_printGrouped = exportConfig.readBoolEntry("Print Grouped", m_printGrouped);
  m_exportEntryFiles = exportConfig.readBoolEntry("Export Entry Files", m_exportEntryFiles);

  // read current entry export template
  m_entryXSLTFile = Config::templateName(collection()->type());
  m_entryXSLTFile = locate("appdata", TQString::fromLatin1("entry-templates/")
                                      + m_entryXSLTFile + TQString::fromLatin1(".xsl"));
}

void HTMLExporter::saveOptions(KConfig* config_) {
  KConfigGroup cfg(config_, TQString::fromLatin1("ExportOptions - %1").arg(formatString()));
  m_printHeaders = m_checkPrintHeaders->isChecked();
  cfg.writeEntry("Print Field Headers", m_printHeaders);
  m_printGrouped = m_checkPrintGrouped->isChecked();
  cfg.writeEntry("Print Grouped", m_printGrouped);
  m_exportEntryFiles = m_checkExportEntryFiles->isChecked();
  cfg.writeEntry("Export Entry Files", m_exportEntryFiles);
}

void HTMLExporter::setXSLTFile(const TQString& filename_) {
  if(m_xsltFile == filename_) {
    return;
  }

  m_xsltFile = filename_;
  m_xsltFilePath = TQString();
  reset();
}

KURL HTMLExporter::fileDir() const {
  if(url().isEmpty()) {
    return KURL();
  }
  KURL fileDir = url();
  // cd to directory of target URL
  fileDir.cd(TQString::fromLatin1(".."));
  fileDir.addPath(fileDirName());
  return fileDir;
}

TQString HTMLExporter::fileDirName() const {
  if(!m_collectionURL.isEmpty()) {
    return TQString::fromLatin1("/");
  }
  return url().fileName().section('.', 0, 0) + TQString::fromLatin1("_files/");
}

// how ugly is this?
const xmlChar* HTMLExporter::handleLink(const xmlChar* link_) {
  return reinterpret_cast<xmlChar*>(qstrdup(handleLink(TQString::fromUtf8(reinterpret_cast<const char*>(link_))).utf8()));
}

TQString HTMLExporter::handleLink(const TQString& link_) {
  if(m_links.contains(link_)) {
    return m_links[link_];
  }
  // assume that if the link_ is not relative, then we don't need to copy it
  if(!KURL::isRelativeURL(link_)) {
    return link_;
  }

  if(m_xsltFilePath.isEmpty()) {
    m_xsltFilePath = locate("appdata", m_xsltFile);
    if(m_xsltFilePath.isNull()) {
      kdWarning() << "HTMLExporter::handleLink() - no xslt file for " << m_xsltFile << endl;
    }
  }

  KURL u;
  u.setPath(m_xsltFilePath);
  u = KURL(u, link_);

  // one of the "quirks" of the html export is that img src urls are set to point to
  // the tmpDir() when exporting entry files from a collection, but those images
  // don't actually exist, and they get copied in writeImages() instead.
  // so we only need to keep track of the url if it exists
  const bool exists = KIO::NetAccess::exists(u, false, 0);
  if(exists) {
    m_files.append(u);
  }

  // if we're exporting entry files, we want pics/ to
  // go in pics/
  const bool isPic = link_.startsWith(m_dataDir + TQString::fromLatin1("pics/"));
  TQString midDir;
  if(m_exportEntryFiles && isPic) {
    midDir = TQString::fromLatin1("pics/");
  }
  // pictures are special since they might not exist when the HTML is exported, since they might get copied later
  // on the other hand, don't change the file location if it doesn't exist
  if(isPic || exists) {
    m_links.insert(link_, fileDirName() + midDir + u.fileName());
  } else {
    m_links.insert(link_, link_);
  }
  return m_links[link_];
}

const xmlChar* HTMLExporter::analyzeInternalCSS(const xmlChar* str_) {
  return reinterpret_cast<xmlChar*>(qstrdup(analyzeInternalCSS(TQString::fromUtf8(reinterpret_cast<const char*>(str_))).utf8()));
}

TQString HTMLExporter::analyzeInternalCSS(const TQString& str_) {
  TQString str = str_;
  int start = 0;
  int end = 0;
  const TQString url = TQString::fromLatin1("url(");
  for(int pos = str.find(url); pos >= 0; pos = str.find(url, pos+1)) {
    pos += 4; // url(
    if(str[pos] ==  '"' || str[pos] == '\'') {
      ++pos;
    }

    start = pos;
    pos = str.find(')', start);
    end = pos;
    if(str[pos-1] == '"' || str[pos-1] == '\'') {
      --end;
    }

    str.replace(start, end-start, handleLink(str.mid(start, end-start)));
  }
  return str;
}

void HTMLExporter::createDir() {
  if(!m_checkCreateDir) {
    return;
  }
  KURL dir = fileDir();
  if(dir.isEmpty()) {
    myDebug() << "HTMLExporter::createDir() - called on empty URL!" << endl;
    return;
  }
  if(KIO::NetAccess::exists(dir, false, 0)) {
    m_checkCreateDir = false;
  } else {
    m_checkCreateDir = !KIO::NetAccess::mkdir(dir, m_widget);
  }
}

bool HTMLExporter::copyFiles() {
  if(m_files.isEmpty()) {
    return true;
  }
  const uint start = 20;
  const uint maxProgress = m_exportEntryFiles ? 40 : 80;
  const uint stepSize = TQMAX(1, m_files.count()/maxProgress);
  uint j = 0;

  createDir();
  KURL target;
  for(KURL::List::ConstIterator it = m_files.begin(); it != m_files.end() && !m_cancelled; ++it, ++j) {
    if(m_copiedFiles.has((*it).url())) {
      continue;
    }

    if(target.isEmpty()) {
      target = fileDir();
    }
    target.setFileName((*it).fileName());
    bool success = KIO::NetAccess::file_copy(*it, target, -1, true /* overwrite */, false /* resume */, m_widget);
    if(success) {
      m_copiedFiles.add((*it).url());
    } else {
      kdWarning() << "HTMLExporter::copyFiles() - can't copy " << target << endl;
      kdWarning() << KIO::NetAccess::lastErrorString() << endl;
    }
    if(j%stepSize == 0) {
      if(options() & ExportProgress) {
        ProgressManager::self()->setProgress(this, TQMIN(start+j/stepSize, 99));
      }
      kapp->processEvents();
    }
  }
  return true;
}

bool HTMLExporter::writeEntryFiles() {
  if(m_entryXSLTFile.isEmpty()) {
    kdWarning() << "HTMLExporter::writeEntryFiles() - no entry XSLT file" << endl;
    return false;
  }

  const uint start = 60;
  const uint stepSize = TQMAX(1, entries().count()/40);
  uint j = 0;

  // now worry about actually exporting entry files
  // I can't reliable encode a string as a URI, so I'm punting, and I'll just replace everything but
  // a-zA-Z0-9 with an underscore. This MUST match the filename template in tellico2html.xsl
  // the id is used so uniqueness is guaranteed
  const TQRegExp badChars(TQString::fromLatin1("[^-a-zA-Z0-9]"));
  bool formatted = options() & Export::ExportFormatted;

  KURL outputFile = fileDir();

  GUI::CursorSaver cs(TQt::waitCursor);

  HTMLExporter exporter(collection());
  long opt = options() | Export::ExportForce;
  opt &= ~ExportProgress;
  exporter.setOptions(opt);
  exporter.setXSLTFile(m_entryXSLTFile);
  exporter.setCollectionURL(url());
  bool parseDOM = true;

  const TQString title = TQString::fromLatin1("title");
  const TQString html = TQString::fromLatin1(".html");
  bool multipleTitles = collection()->fieldByName(title)->flags() & Data::Field::AllowMultiple;
  Data::EntryVec entries = this->entries(); // not const since the pointer has to be copied
  for(Data::EntryVecIt entryIt = entries.begin(); entryIt != entries.end() && !m_cancelled; ++entryIt, ++j) {
    TQString file = entryIt->field(title, formatted);

    // but only use the first title if it has multiple
    if(multipleTitles) {
      file = file.section(';', 0, 0);
    }
    file.replace(badChars, TQChar('_'));
    file += TQChar('-') + TQString::number(entryIt->id()) + html;
    outputFile.setFileName(file);

    exporter.setEntries(Data::EntryVec(entryIt));
    exporter.setURL(outputFile);
    exporter.exec();

    // no longer need to parse DOM
    if(parseDOM) {
      parseDOM = false;
      exporter.setParseDOM(false);
      // this is rather stupid, but I'm too lazy to figure out the better way
      // since we parsed the DOM for the first entry file to grab any
      // images used in the template, need to resave it so the image links
      // get written correctly
      exporter.exec();
    }

    if(j%stepSize == 0) {
      if(options() & ExportProgress) {
        ProgressManager::self()->setProgress(this, TQMIN(start+j/stepSize, 99));
      }
      kapp->processEvents();
    }
  }
  // the images in "pics/" are special data images, copy them always
  // since the entry files may refer to them, but we don't know that
  TQStringList dataImages;
  dataImages << TQString::fromLatin1("checkmark.png");
  for(uint i = 1; i <= 10; ++i) {
    dataImages << TQString::fromLatin1("stars%1.png").arg(i);
  }
  KURL dataDir;
  dataDir.setPath(KGlobal::dirs()->findResourceDir("appdata", TQString::fromLatin1("pics/tellico.png")) + "pics/");
  KURL target = fileDir();
  target.addPath(TQString::fromLatin1("pics/"));
  KIO::NetAccess::mkdir(target, m_widget);
  for(TQStringList::ConstIterator it = dataImages.begin(); it != dataImages.end(); ++it) {
    dataDir.setFileName(*it);
    target.setFileName(*it);
    KIO::NetAccess::copy(dataDir, target, m_widget);
  }

  return true;
}

void HTMLExporter::slotCancel() {
  m_cancelled = true;
}

void HTMLExporter::parseDOM(xmlNode* node_) {
  if(node_ == 0) {
    myDebug() << "HTMLExporter::parseDOM() - no node" << endl;
    return;
  }

  bool parseChildren = true;

  if(node_->type == XML_ELEMENT_NODE) {
    const TQCString nodeName = TQCString(reinterpret_cast<const char*>(node_->name)).upper();
    xmlElement* elem = reinterpret_cast<xmlElement*>(node_);
    // to speed up things, check now for nodename
    if(nodeName == "IMG" || nodeName == "SCRIPT" || nodeName == "LINK") {
      for(xmlAttribute* attr = elem->attributes; attr; attr = reinterpret_cast<xmlAttribute*>(attr->next)) {
        TQCString attrName = TQCString(reinterpret_cast<const char*>(attr->name)).upper();

        if( (attrName == "SRC" && (nodeName == "IMG" || nodeName == "SCRIPT")) ||
            (attrName == "HREF" && nodeName == "LINK")) {
/*          (attrName == "BACKGROUND" && (nodeName == "BODY" ||
                                                       nodeName == "TABLE" ||
                                                       nodeName == "TH" ||
                                                       nodeName == "TD"))) */
          xmlChar* value = xmlGetProp(node_, attr->name);
          if(value) {
            xmlSetProp(node_, attr->name, handleLink(value));
            xmlFree(value);
          }
          // each node only has one significant attribute, so break now
          break;
        }
      }
    } else if(nodeName == "STYLE") {
      // if the first child is a CDATA, use it, otherwise replace complete node
      xmlNode* nodeToReplace = node_;
      xmlNode* child = node_->children;
      if(child && child->type == XML_CDATA_SECTION_NODE) {
        nodeToReplace = child;
      }
      xmlChar* value = xmlNodeGetContent(nodeToReplace);
      if(value) {
        xmlNodeSetContent(nodeToReplace, analyzeInternalCSS(value));
        xmlFree(value);
      }
      // no longer need to parse child text nodes
      parseChildren = false;
    }
  }

  if(parseChildren) {
    xmlNode* child = node_->children;
    while(child) {
      parseDOM(child);
      child = child->next;
    }
  }
}

#include "htmlexporter.moc"
