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.
kipi-plugins/kipi-plugins/htmlexport/generator.cpp

532 lines
14 KiB

/*
A KIPI plugin to generate HTML image galleries
Copyright 2006 Aurelien Gateau <aurelien dot gateau at free.fr>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// Self
#include "generator.moc"
// TQt
#include <tqdir.h>
#include <tqfile.h>
#include <tqpainter.h>
#include <tqregexp.h>
// KDE
#include <tdeaboutdata.h>
#include <tdeapplication.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdeio/netaccess.h>
#include <kstandarddirs.h>
#include <kurl.h>
// KIPI
#include <libkipi/batchprogressdialog.h>
#include <libkipi/imageinfo.h>
#include <libkipi/interface.h>
// libxslt
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#include <libxslt/xslt.h>
#include <libexslt/exslt.h>
// Local
#include "abstractthemeparameter.h"
#include "galleryinfo.h"
#include "theme.h"
#include "xmlutils.h"
namespace KIPIHTMLExport {
typedef TQMap<TQCString,TQCString> XsltParameterMap;
/**
* Produce a web-friendly file name
*/
TQString webifyFileName(TQString fileName) {
fileName=fileName.lower();
// Remove potentially troublesome chars
fileName=fileName.replace(TQRegExp("[^-0-9a-z]+"), "_");
return fileName;
}
/**
* This helper class is used to make sure we use unique filenames
*/
class UniqueNameHelper {
public:
TQString makeNameUnique(TQString name) {
TQString nameBase = name;
int count=2;
while (mList.findIndex(name)!=-1) {
name = nameBase + TQString::number(count);
++count;
};
mList.append(name);
return name;
}
private:
TQStringList mList;
};
/**
* Prepare an XSLT param, managing quote mess.
* abc => 'abc'
* a"bc => 'a"bc'
* a'bc => "a'bc"
* a"'bc => concat('a"', "'", 'bc')
*/
TQCString makeXsltParam(const TQString& txt) {
TQString param;
static const char apos='\'';
static const char quote='"';
if (txt.find(apos)==-1) {
// First or second case: no apos
param= apos + txt + apos;
} else if (txt.find(quote)==-1) {
// Third case: only apos, no quote
param= quote + txt + quote;
} else {
// Forth case: both apos and quote :-(
TQStringList lst=TQStringList::split(apos, txt, true /*allowEmptyEntries*/);
TQStringList::Iterator it=lst.begin(), end=lst.end();
param= "concat(";
param+= apos + *it + apos;
++it;
for (;it!=end; ++it) {
param+= ", \"'\", ";
param+= apos + *it + apos;
}
param+= ")";
}
//kdDebug() << "param: " << txt << " => " << param << endl;
return param.utf8();
}
/**
* Genearate a square thumbnail from @fullImage of @size x @size pixels
*/
TQImage generateSquareThumbnail(const TQImage& fullImage, int size) {
TQImage image = fullImage.smoothScale(size, size, TQImage::ScaleMax);
if (image.width() == size && image.height() == size) {
return image;
}
TQPixmap croppedPix(size, size);
TQPainter painter(&croppedPix);
int sx=0, sy=0;
if (image.width()>size) {
sx=(image.width() - size)/2;
} else {
sy=(image.height() - size)/2;
}
painter.drawImage(0, 0, image, sx, sy, size, size);
painter.end();
return croppedPix.convertToImage();
}
struct Generator::Private {
KIPI::Interface* mInterface;
GalleryInfo* mInfo;
KIPI::BatchProgressDialog* mProgressDialog;
Theme::Ptr mTheme;
// State info
bool mWarnings;
TQString mXMLFileName;
UniqueNameHelper mUniqueNameHelper;
bool init() {
mTheme=Theme::findByInternalName(mInfo->theme());
if (!mTheme) {
logError( i18n("Could not find theme in '%1'").arg(mInfo->theme()) );
return false;
}
return true;
}
bool copyTheme() {
mProgressDialog->addedAction(i18n("Copying theme"), KIPI::ProgressMessage);
KURL srcURL=KURL(mTheme->directory());
KURL destURL=mInfo->destKURL();
destURL.addPath(srcURL.filename());
if (TQFile::exists(destURL.path())) {
TDEIO::NetAccess::del(destURL, mProgressDialog);
}
bool ok=TDEIO::NetAccess::dircopy(srcURL, destURL, mProgressDialog);
if (!ok) {
logError(i18n("Could not copy theme"));
return false;
}
return true;
}
bool writeDataToFile(const TQByteArray& data, const TQString& destPath) {
TQFile destFile(destPath);
if (!destFile.open(IO_WriteOnly)) {
logWarning(i18n("Could not open file '%1' for writing").arg(destPath));
return false;
}
if (destFile.writeBlock(data) != (TQ_LONG)data.size()) {
logWarning(i18n("Could not save image to file '%1'").arg(destPath));
return false;
}
return true;
}
/**
* Helper class for generateImageAndXMLForURL
*/
void appendImageElementToXML(XMLWriter& xmlWriter, const TQString& elementName, const TQString& fileName, const TQImage& image) {
XMLAttributeList attrList;
attrList.append("fileName", fileName);
attrList.append("width", image.width());
attrList.append("height", image.height());
XMLElement elem(xmlWriter, elementName, &attrList);
}
/**
* Generate images (full and thumbnail) for imageURL
* Fills xmlWriter with info about this image
*/
void generateImageAndXMLForURL(XMLWriter& xmlWriter, const TQString& destDir, const KURL& imageURL) {
KIPI::ImageInfo info=mInterface->info(imageURL);
// Load image
TQString path=imageURL.path();
TQFile imageFile(path);
if (!imageFile.open(IO_ReadOnly)) {
logWarning(i18n("Could not read image '%1'").arg(path));
return;
}
TQString imageFormat = TQImageIO::imageFormat(&imageFile);
if (imageFormat.isEmpty()) {
logWarning(i18n("Format of image '%1' is unknown").arg(path));
return;
}
imageFile.close();
imageFile.open(IO_ReadOnly);
TQByteArray imageData = imageFile.readAll();
TQImage originalImage;
if (!originalImage.loadFromData(imageData) ) {
logWarning(i18n("Error loading image '%1'").arg(path));
return;
}
// Process images
TQImage fullImage = originalImage;
if (!mInfo->useOriginalImageAsFullImage()) {
if (mInfo->fullResize()) {
int size = mInfo->fullSize();
fullImage = fullImage.smoothScale(size, size, TQImage::ScaleMin);
}
if (info.angle() != 0) {
TQWMatrix matrix;
matrix.rotate(info.angle());
fullImage = fullImage.xForm(matrix);
}
}
TQImage thumbnail = generateSquareThumbnail(fullImage, mInfo->thumbnailSize());
// Save images
TQString baseFileName = webifyFileName(info.title());
baseFileName = mUniqueNameHelper.makeNameUnique(baseFileName);
// Save full
TQString fullFileName;
if (mInfo->useOriginalImageAsFullImage()) {
fullFileName = baseFileName + "." + imageFormat.lower();
if (!writeDataToFile(imageData, destDir + "/" + fullFileName)) {
return;
}
} else {
fullFileName = baseFileName + "." + mInfo->fullFormatString().lower();
TQString destPath = destDir + "/" + fullFileName;
if (!fullImage.save(destPath, mInfo->fullFormatString().ascii(), mInfo->fullQuality())) {
logWarning(i18n("Could not save image '%1' to '%2'").arg(path).arg(destPath));
return;
}
}
// Save original
TQString originalFileName;
if (mInfo->copyOriginalImage()) {
originalFileName = "original_" + fullFileName;
if (!writeDataToFile(imageData, destDir + "/" + originalFileName)) {
return;
}
}
// Save thumbnail
TQString thumbnailFileName = "thumb_" + baseFileName + "." + mInfo->thumbnailFormatString().lower();
TQString destPath = destDir + "/" + thumbnailFileName;
if (!thumbnail.save(destPath, mInfo->thumbnailFormatString().ascii(), mInfo->thumbnailQuality())) {
logWarning(i18n("Could not save thumbnail for image '%1' to '%2'").arg(path).arg(destPath));
return;
}
// Write XML
XMLElement imageX(xmlWriter, "image");
xmlWriter.writeElement("title", info.title());
xmlWriter.writeElement("description", info.description());
appendImageElementToXML(xmlWriter, "full", fullFileName, fullImage);
appendImageElementToXML(xmlWriter, "thumbnail", thumbnailFileName, thumbnail);
if (mInfo->copyOriginalImage()) {
appendImageElementToXML(xmlWriter, "original", originalFileName, originalImage);
}
}
bool generateImagesAndXML() {
TQString baseDestDir=mInfo->destKURL().path();
if (!createDir(baseDestDir)) return false;
mXMLFileName=baseDestDir + "/gallery.xml";
XMLWriter xmlWriter;
if (!xmlWriter.open(mXMLFileName)) {
logError(i18n("Could not create gallery.xml"));
return false;
}
XMLElement collectionsX(xmlWriter, "collections");
// Loop on collections
TQValueList<KIPI::ImageCollection>::Iterator collectionIt=mInfo->mCollectionList.begin();
TQValueList<KIPI::ImageCollection>::Iterator collectionEnd=mInfo->mCollectionList.end();
for (; collectionIt!=collectionEnd; ++collectionIt) {
KIPI::ImageCollection collection=*collectionIt;
logInfo( i18n("Generating files for \"%1\"").arg(collection.name()) );
TQString collectionFileName = webifyFileName(collection.name());
TQString destDir = baseDestDir + "/" + collectionFileName;
if (!createDir(destDir)) return false;
XMLElement collectionX(xmlWriter, "collection");
xmlWriter.writeElement("name", collection.name());
xmlWriter.writeElement("fileName", collectionFileName);
// Loop on image in collection
KURL::List imageList = collection.images();
KURL::List::Iterator it = imageList.begin();
KURL::List::Iterator end = imageList.end();
int pos = 1;
int count = imageList.count();
for (; it!=end; ++it, ++pos) {
mProgressDialog->setProgress(pos, count);
tqApp->processEvents();
generateImageAndXMLForURL(xmlWriter, destDir, *it);
}
}
return true;
}
/**
* Add to map all the i18n parameters.
*/
void addI18nParameters(XsltParameterMap& map) {
map["i18nPrevious"] = makeXsltParam(i18n("Previous"));
map["i18nNext"] = makeXsltParam(i18n("Next"));
map["i18nCollectionList"] = makeXsltParam(i18n("Collection List"));
map["i18nOriginalImage"] = makeXsltParam(i18n("Original Image"));
map["i18nUp"] = makeXsltParam(i18n("Go Up"));
}
/**
* Add to map all the theme parameters, as specified by the user.
*/
void addThemeParameters(XsltParameterMap& map) {
Theme::ParameterList parameterList = mTheme->parameterList();
TQString themeInternalName = mTheme->internalName();
Theme::ParameterList::ConstIterator
it = parameterList.begin(),
end = parameterList.end();
for (; it!=end; ++it) {
AbstractThemeParameter* themeParameter = *it;
TQCString internalName = themeParameter->internalName();
TQString value = mInfo->getThemeParameterValue(
themeInternalName,
internalName,
themeParameter->defaultValue());
map[internalName] = makeXsltParam(value);
}
}
bool generateHTML() {
logInfo(i18n("Generating HTML files"));
TQString xsltFileName=mTheme->directory() + "/template.xsl";
CWrapper<xsltStylesheetPtr, xsltFreeStylesheet> xslt= xsltParseStylesheetFile( (const xmlChar*) xsltFileName.local8Bit().data() );
if (!xslt) {
logError(i18n("Could not load XSL file '%1'").arg(xsltFileName));
return false;
}
CWrapper<xmlDocPtr, xmlFreeDoc> xmlGallery=xmlParseFile( mXMLFileName.local8Bit().data() );
if (!xmlGallery) {
logError(i18n("Could not load XML file '%1'").arg(mXMLFileName));
return false;
}
// Prepare parameters
XsltParameterMap map;
addI18nParameters(map);
addThemeParameters(map);
const char** params=new const char*[map.size()*2+1];
XsltParameterMap::Iterator it=map.begin(), end=map.end();
const char** ptr=params;
for (;it!=end; ++it) {
*ptr=it.key().data();
++ptr;
*ptr=it.data().data();
++ptr;
}
*ptr=0;
// Move to the destination dir, so that external documents get correctly
// produced
TQString oldCD=TQDir::currentDirPath();
TQDir::setCurrent(mInfo->destKURL().path());
CWrapper<xmlDocPtr, xmlFreeDoc> xmlOutput= xsltApplyStylesheet(xslt, xmlGallery, params);
TQDir::setCurrent(oldCD);
//delete []params;
if (!xmlOutput) {
logError(i18n("Error processing XML file"));
return false;
}
TQString destFileName=mInfo->destKURL().path() + "/index.html";
FILE* file=fopen(destFileName.local8Bit().data(), "w");
if (!file) {
logError(i18n("Could not open '%1' for writing").arg(destFileName));
return false;
}
xsltSaveResultToFile(file, xmlOutput, xslt);
fclose(file);
return true;
}
bool createDir(const TQString& dirName) {
TQStringList parts = TQStringList::split('/', dirName);
TQStringList::ConstIterator it = parts.begin(), end = parts.end();
TQDir dir = TQDir::root();
for( ;it!=end; ++it) {
TQString part = *it;
if (!dir.exists(part)) {
if (!dir.mkdir(part)) {
logError(i18n("Could not create folder '%1' in '%2'").arg(part).arg(dir.absPath()));
return false;
}
}
dir.cd(part);
}
return true;
}
void logInfo(const TQString& msg) {
mProgressDialog->addedAction(msg, KIPI::ProgressMessage);
}
void logError(const TQString& msg) {
mProgressDialog->addedAction(msg, KIPI::ErrorMessage);
}
void logWarning(const TQString& msg) {
mProgressDialog->addedAction(msg, KIPI::WarningMessage);
mWarnings=true;
}
};
Generator::Generator(KIPI::Interface* interface, GalleryInfo* info, KIPI::BatchProgressDialog* progressDialog)
: TQObject() {
d=new Private;
d->mInterface=interface;
d->mInfo=info;
d->mProgressDialog=progressDialog;
d->mWarnings=false;
}
Generator::~Generator() {
delete d;
}
bool Generator::run() {
if (!d->init()) return false;
TQString destDir=d->mInfo->destKURL().path();
if (!d->createDir(destDir)) return false;
if (!d->copyTheme()) return false;
if (!d->generateImagesAndXML()) return false;
exsltRegisterAll();
bool result=d->generateHTML();
xsltCleanupGlobals();
xmlCleanupParser();
return result;
}
bool Generator::warnings() const {
return d->mWarnings;
}
} // namespace