/* A KIPI plugin to generate HTML image galleries Copyright 2006 Aurelien Gateau 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 #include #include #include // KDE #include #include #include #include #include #include #include // KIPI #include #include #include // libxslt #include #include #include #include // Local #include "abstractthemeparameter.h" #include "galleryinfo.h" #include "theme.h" #include "xmlutils.h" namespace KIPIHTMLExport { typedef TQMap 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::Iterator collectionIt=mInfo->mCollectionList.begin(); TQValueList::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 xslt= xsltParseStylesheetFile( (const xmlChar*) xsltFileName.local8Bit().data() ); if (!xslt) { logError(i18n("Could not load XSL file '%1'").arg(xsltFileName)); return false; } CWrapper 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 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