summaryrefslogtreecommitdiffstats
path: root/krecipes/src/exporters/htmlexporter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'krecipes/src/exporters/htmlexporter.cpp')
-rw-r--r--krecipes/src/exporters/htmlexporter.cpp570
1 files changed, 570 insertions, 0 deletions
diff --git a/krecipes/src/exporters/htmlexporter.cpp b/krecipes/src/exporters/htmlexporter.cpp
new file mode 100644
index 0000000..0d56c98
--- /dev/null
+++ b/krecipes/src/exporters/htmlexporter.cpp
@@ -0,0 +1,570 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn ([email protected]) *
+* Unai Garro ([email protected]) *
+* Cyril Bosselut ([email protected]) *
+* *
+* Copyright (C) 2006 Jason Kivlighn ([email protected]) *
+* *
+* 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. *
+***************************************************************************/
+
+#include "htmlexporter.h"
+
+#include <qptrdict.h>
+#include <qimage.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qstylesheet.h> //for QStyleSheet::escape() to escape for HTML
+#include <dom/dom_element.h>
+#include <qpainter.h>
+#include <qfileinfo.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <khtml_part.h>
+#include <khtmlview.h>
+#include <kprogress.h>
+#include <kstandarddirs.h>
+#include <kurl.h>
+#include <kiconloader.h>
+
+#include "datablocks/mixednumber.h"
+#include "backends/recipedb.h"
+#include "dialogs/setupdisplay.h"
+#include "image.h"
+#include "krepagelayout.h"
+
+#include <cmath> //for ceil()
+
+HTMLExporter::HTMLExporter( const QString& filename, const QString &format ) :
+ BaseExporter( filename, format )
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Page Setup" );
+
+ //let's do everything we can to be sure at least some layout is loaded
+ QString template_filename = config->readEntry( "Template", locate( "appdata", "layouts/Default.template" ) );
+ if ( template_filename.isEmpty() || !QFile::exists( template_filename ) )
+ template_filename = locate( "appdata", "layouts/Default.template" );
+ kdDebug() << "Using template file: " << template_filename << endl;
+
+ setTemplate( template_filename );
+
+ //let's do everything we can to be sure at least some layout is loaded
+ m_layoutFilename = config->readEntry( "Layout", locate( "appdata", "layouts/Default.klo" ) );
+ if ( m_layoutFilename.isEmpty() || !QFile::exists( m_layoutFilename ) )
+ m_layoutFilename = locate( "appdata", "layouts/Default.klo" );
+ kdDebug() << "Using layout file: " << m_layoutFilename << endl;
+}
+
+HTMLExporter::~HTMLExporter()
+{
+}
+
+void HTMLExporter::setTemplate( const QString &filename )
+{
+ QFile templateFile( filename );
+ if ( templateFile.open( IO_ReadOnly ) ) {
+ m_templateFilename = filename;
+ m_templateContent = QString( templateFile.readAll() );
+ }
+ else
+ kdDebug()<<"couldn't find/open template file"<<endl;
+}
+
+void HTMLExporter::setStyle( const QString &filename )
+{
+ m_layoutFilename = filename;
+}
+
+int HTMLExporter::supportedItems() const
+{
+ int items = RecipeDB::All ^ RecipeDB::Properties;
+
+ QMap<QString,bool>::const_iterator it = m_visibilityMap.find("properties");
+ if ( it == m_visibilityMap.end() || it.data() == true )
+ items |= RecipeDB::Properties;
+
+ return RecipeDB::All;
+}
+
+QString HTMLExporter::createContent( const Recipe& recipe )
+{
+ QString templateCopy = m_templateContent;
+
+ storePhoto( recipe );
+
+ populateTemplate( recipe, templateCopy );
+ return templateCopy;
+}
+
+QString HTMLExporter::createContent( const RecipeList& recipes )
+{
+ QString fileContent;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ fileContent += createContent(*recipe_it);
+ }
+
+ return fileContent;
+}
+
+QString HTMLExporter::createHeader( const RecipeList & )
+{
+ m_visibilityMap.clear();
+ m_columnsMap.clear();
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Page Setup" );
+
+ m_error = false;
+
+ if ( m_templateContent.isEmpty() ) {
+ QString errorStr = i18n("<html><body>\n"
+ "<p><b>Error: </b>Unable to find a layout file, which is"
+ " needed to view the recipe.</p>"
+ "<p>Krecipes was probably not properly installed.</p>"
+ "</body></html>");
+ m_error = true;
+ return errorStr;
+ }
+
+ QFile layoutFile( m_layoutFilename );
+ QString error; int line; int column;
+ QDomDocument doc;
+ if ( !doc.setContent( &layoutFile, &error, &line, &column ) ) {
+ kdDebug()<<"Unable to load style information. Will create HTML without it..."<<endl;
+ }
+ else
+ processDocument(doc);
+
+ //put all the recipe photos into this directory
+ QDir dir;
+ QFileInfo fi(fileName());
+ dir.mkdir( fi.dirPath(true) + "/" + fi.baseName() + "_photos" );
+
+ RecipeList::const_iterator recipe_it;
+
+ KLocale*loc = KGlobal::locale();
+ QString encoding = loc->encoding();
+
+ QString output = "<html>";
+ output += "<head>";
+ output += "<meta name=\"lang\" content=\"" + loc->language() + "\">\n";
+ output += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
+ output += QString( "<title>%1</title>" ).arg( i18n( "Krecipes Recipes" ) );
+
+ output += "<style type=\"text/css\">\n";
+
+ QString cssContent;
+ QFileInfo info(m_templateFilename);
+ QFile cssFile(info.dirPath(true) + "/" + info.baseName() + ".css");
+ kdDebug()<<info.dirPath(true) + "/" + info.baseName() + ".css"<<endl;
+ if ( cssFile.open( IO_ReadOnly ) ) {
+ cssContent = QString( cssFile.readAll() );
+ }
+ output += cssContent;
+
+ output += m_cachedCSS;
+ m_cachedCSS = QString::null;
+ output += "</style>";
+ output += "</head>";
+ output += "<body class=\"background\">";
+
+ return output;
+}
+
+void HTMLExporter::beginObject( const QString &object )
+{
+ m_cachedCSS += "."+object+" { \n";
+}
+
+void HTMLExporter::endObject()
+{
+ m_cachedCSS += " } \n";
+}
+
+void HTMLExporter::loadBackgroundColor( const QString &/*object*/, const QColor& color )
+{
+ m_cachedCSS += bgColorAsCSS(color);
+}
+
+void HTMLExporter::loadFont( const QString &/*object*/, const QFont& font )
+{
+ m_cachedCSS += fontAsCSS(font);
+}
+
+void HTMLExporter::loadTextColor( const QString &/*object*/, const QColor& color )
+{
+ m_cachedCSS += textColorAsCSS(color);
+}
+
+void HTMLExporter::loadVisibility( const QString &object, bool visible )
+{
+ m_cachedCSS += visibilityAsCSS(visible);
+ m_visibilityMap.insert(object,visible);
+}
+
+void HTMLExporter::loadAlignment( const QString &/*object*/, int alignment )
+{
+ m_cachedCSS += alignmentAsCSS(alignment);
+}
+
+void HTMLExporter::loadBorder( const QString &/*object*/, const KreBorder& border )
+{
+ m_cachedCSS += borderAsCSS(border);
+}
+
+void HTMLExporter::loadColumns( const QString & object, int cols )
+{
+ m_columnsMap.insert(object,cols);
+kdDebug()<<object<<" has "<<cols<<" columns"<<endl;
+}
+
+QString HTMLExporter::createFooter()
+{
+ if ( m_error )
+ return QString::null;
+
+ return "</body></html>";
+}
+
+void HTMLExporter::storePhoto( const Recipe &recipe )
+{
+ QImage image;
+ QString photo_name;
+ if ( recipe.photo.isNull() ) {
+ image = QImage( defaultPhoto );
+ photo_name = "default_photo";
+ }
+ else {
+ image = recipe.photo.convertToImage();
+ photo_name = QString::number(recipe.recipeID);
+ }
+
+ QPixmap pm = image;//image.smoothScale( phwidth, 0, QImage::ScaleMax );
+
+ QFileInfo fi(fileName());
+ QString photo_path = fi.dirPath(true) + "/" + fi.baseName() + "_photos/" + photo_name + ".png";
+ if ( !QFile::exists( photo_path ) ) {
+ pm.save( photo_path, "PNG" );
+ }
+}
+
+QString HTMLExporter::HTMLIfVisible( const QString &name, const QString &html )
+{
+ QMap<QString,bool>::const_iterator it = m_visibilityMap.find(name);
+ if ( it == m_visibilityMap.end() || it.data() == true )
+ return html;
+ else
+ return QString::null;
+}
+
+void HTMLExporter::populateTemplate( const Recipe &recipe, QString &content )
+{
+ KConfig * config = KGlobal::config();
+
+ //=======================TITLE======================//
+ content = content.replace("**TITLE**",HTMLIfVisible("title",recipe.title));
+
+ //=======================INSTRUCTIONS======================//
+ QString instr_html = QStyleSheet::escape( recipe.instructions );
+ instr_html.replace( "\n", "<br />" );
+ content = content.replace( "**INSTRUCTIONS**", HTMLIfVisible("instructions",instr_html) );
+
+ //=======================SERVINGS======================//
+ QString yield_html = QString( "<b>%1: </b>%2" ).arg( i18n( "Yield" ) ).arg( recipe.yield.toString() );
+ content = content.replace( "**YIELD**", HTMLIfVisible("yield",yield_html) );
+
+ //=======================PREP TIME======================//
+ QString preptime_html;
+ if ( !recipe.prepTime.isNull() && recipe.prepTime.isValid() )
+ preptime_html = QString( "<b>%1: </b>%2" ).arg( i18n( "Preparation Time" ) ).arg( recipe.prepTime.toString( "h:mm" ) );
+ content = content.replace( "**PREP_TIME**", HTMLIfVisible("prep_time",preptime_html) );
+
+ //========================PHOTO========================//
+ QString photo_name;
+ if ( recipe.photo.isNull() )
+ photo_name = "default_photo";
+ else
+ photo_name = QString::number(recipe.recipeID);
+
+ QFileInfo fi(fileName());
+ QString image_url = fi.baseName() + "_photos/" + escape( photo_name ) + ".png";
+ image_url = KURL::encode_string( image_url );
+ content = content.replace( "**PHOTO**", HTMLIfVisible("photo",image_url) );
+
+ //=======================AUTHORS======================//
+ QString authors_html;
+
+ int counter = 0;
+ for ( ElementList::const_iterator author_it = recipe.authorList.begin(); author_it != recipe.authorList.end(); ++author_it ) {
+ if ( counter )
+ authors_html += ", ";
+ authors_html += QStyleSheet::escape( ( *author_it ).name );
+ counter++;
+ }
+ if ( !authors_html.isEmpty() )
+ authors_html.prepend( QString( "<b>%1: </b>" ).arg( i18n( "Authors" ) ) );
+ content = content.replace( "**AUTHORS**", HTMLIfVisible("authors",authors_html) );
+
+ //=======================CATEGORIES======================//
+ QString categories_html;
+
+ counter = 0;
+ for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
+ if ( counter )
+ categories_html += ", ";
+ categories_html += QStyleSheet::escape( ( *cat_it ).name );
+ counter++;
+ }
+ if ( !categories_html.isEmpty() )
+ categories_html.prepend( QString( "<b>%1: </b>" ).arg( i18n( "Categories" ) ) );
+
+ content = content.replace( "**CATEGORIES**", HTMLIfVisible("categories",categories_html) );
+
+ //=======================INGREDIENTS======================//
+ QString ingredients_html;
+ config->setGroup( "Formatting" );
+
+ bool useAbbreviations = config->readBoolEntry("AbbreviateUnits");
+
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ QString ingredient_format = config->readEntry( "Ingredient", "%n%p: %a %u" );
+
+ QMap<QString,int>::const_iterator cols_it = m_columnsMap.find("ingredients");
+ int cols = 1;
+ if ( cols_it != m_columnsMap.end() )
+ cols = cols_it.data();
+ int per_col = recipe.ingList.count() / cols;
+ if ( recipe.ingList.count() % cols != 0 ) //round up if division is not exact
+ per_col++;
+
+ int count = 0;
+ IngredientList list_copy = recipe.ingList; //simple workaround until I fix iterating over the list dealing with groups
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ QString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+
+ bool loneHeader = false;
+ if ( count != 0 && count % per_col == 0 ) {
+ loneHeader = true;
+ if ( !group.isEmpty() )
+ ingredients_html += "</ul>";
+ ingredients_html.append("</ul></td><td valign=\"top\"><ul>");
+ if ( !group.isEmpty() )
+ ingredients_html += "<li style=\"page-break-after: avoid\">" + group + ":</li><ul>";
+ }
+ else {
+ if ( !group.isEmpty() )
+ ingredients_html += "<li style=\"page-break-after: avoid\">" + group + ":</li><ul>";
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it, ++count ) {
+ if ( count != 0 && count % per_col == 0 && !loneHeader ) {
+ if ( !group.isEmpty() )
+ ingredients_html += "</ul>";
+ ingredients_html.append("</ul></td><td valign=\"top\"><ul>");
+ if ( !group.isEmpty() )
+ ingredients_html += "<ul>";
+ }
+
+ QString amount_str = MixedNumber( ( *ing_it ).amount ).toString( number_format );
+
+ if ( (*ing_it).amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ( *ing_it ).amount + ( *ing_it ).amount_offset ).toString( number_format );
+ else if ( ( *ing_it ).amount <= 1e-10 )
+ amount_str = "";
+
+ QString unit = ( *ing_it ).units.determineName( ( *ing_it ).amount + ( *ing_it ).amount_offset, useAbbreviations );
+
+ QString tmp_format( ingredient_format );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%n" ) ), QStyleSheet::escape( ( *ing_it ).name ) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%a" ) ), amount_str );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%u" ) ), QStyleSheet::escape(unit) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%p" ) ), ( ( *ing_it ).prepMethodList.count() == 0 ) ?
+ QString::fromLatin1( "" ) : QString::fromLatin1( "; " ) + QStyleSheet::escape( ( *ing_it ).prepMethodList.join(",") ) );
+
+ if ( (*ing_it).substitutes.count() > 0 )
+ tmp_format += ", "+i18n("or");
+
+ ingredients_html += QString( "<li>%1</li>" ).arg( tmp_format );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ QString amount_str = MixedNumber( ( *sub_it ).amount ).toString( number_format );
+
+ if ( (*ing_it).amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ( *sub_it ).amount + ( *sub_it ).amount_offset ).toString( number_format );
+ else if ( ( *sub_it ).amount <= 1e-10 )
+ amount_str = "";
+
+ QString unit = ( *sub_it ).units.determineName( ( *sub_it ).amount + ( *sub_it ).amount_offset, config->readBoolEntry("AbbreviateUnits") );
+
+ QString tmp_format( ingredient_format );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%n" ) ), QStyleSheet::escape( ( *sub_it ).name ) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%a" ) ), amount_str );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%u" ) ), QStyleSheet::escape(unit) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%p" ) ), ( ( *sub_it ).prepMethodList.count() == 0 ) ?
+ QString::fromLatin1( "" ) : QString::fromLatin1( "; " ) + QStyleSheet::escape( ( *sub_it ).prepMethodList.join(",") ) );
+
+ ++sub_it;
+ if ( sub_it != (*ing_it).substitutes.end() )
+ tmp_format += ", "+i18n("or");
+ ingredients_html += QString( "<li>%1</li>" ).arg( tmp_format );
+ }
+ }
+
+ if ( !group.isEmpty() )
+ ingredients_html += "</ul>";
+ }
+ if ( !ingredients_html.isEmpty() ) {
+ ingredients_html.prepend( "<table><tr><td valign=\"top\"><ul>" );
+ ingredients_html.append( "</ul></td></tr></table>" );
+ }
+ content = content.replace( "**INGREDIENTS**", HTMLIfVisible("ingredients",ingredients_html) );
+
+ //=======================PROPERTIES======================//
+ QString properties_html;
+
+ QStringList hiddenList = config->readListEntry("HiddenProperties");
+ IngredientPropertyList visibleProperties;
+ for ( IngredientPropertyList::const_iterator prop_it = recipe.properties.begin(); prop_it != recipe.properties.end(); ++prop_it ) {
+ if ( hiddenList.find((*prop_it).name) == hiddenList.end() )
+ visibleProperties.append( *prop_it );
+ }
+
+ cols_it = m_columnsMap.find("properties");
+ cols = 1;
+ if ( cols_it != m_columnsMap.end() )
+ cols = cols_it.data();
+ per_col = visibleProperties.count() / cols;
+ if ( visibleProperties.count() % cols != 0 ) //round up if division is not exact
+ per_col++;
+
+ count = 0;
+ for ( IngredientPropertyList::const_iterator prop_it = visibleProperties.begin(); prop_it != visibleProperties.end(); ++prop_it ) {
+ if ( count != 0 && count % per_col == 0 )
+ properties_html.append("</ul></td><td valign=\"top\"><ul>");
+
+ // if the amount given is <0, it means the property calculator found that the property was undefined for some ingredients, so the amount will be actually bigger
+
+ QString amount_str;
+
+ double prop_amount = (*prop_it).amount;
+ if ( prop_amount > 0 ) { //TODO: make the precision configuratble
+ prop_amount = double( qRound( prop_amount * 10.0 ) ) / 10.0; //not a "chemistry experiment" ;) Let's only have one decimal place
+ amount_str = beautify( KGlobal::locale() ->formatNumber( prop_amount, 5 ) );
+ }
+ else
+ amount_str = "0";
+
+ properties_html += QString( "<li>%1: <nobr>%2 %3</nobr></li>" )
+ .arg( QStyleSheet::escape( (*prop_it).name ) )
+ .arg( amount_str )
+ .arg( QStyleSheet::escape( (*prop_it).units ) );
+
+ ++count;
+ }
+
+ if ( !properties_html.isEmpty() ) {
+ properties_html.prepend( "<table><tr><td valign=\"top\"><ul>" );
+ properties_html.append( "</ul></td></tr></table>" );
+ }
+ content = content.replace( "**PROPERTIES**", HTMLIfVisible("properties",properties_html) );
+
+ //=======================RATINGS======================//
+ QString ratings_html;
+ if ( recipe.ratingList.count() > 0 )
+ ratings_html += QString("<b>%1:</b>").arg(i18n("Ratings"));
+
+ int rating_total = 0;
+ double rating_sum = 0;
+ for ( RatingList::const_iterator rating_it = recipe.ratingList.begin(); rating_it != recipe.ratingList.end(); ++rating_it ) {
+ ratings_html += "<hr />";
+
+ if ( !( *rating_it ).rater.isEmpty() )
+ ratings_html += "<p><b>"+( *rating_it ).rater+"</b></p>";
+
+ if ( (*rating_it).ratingCriteriaList.count() > 0 )
+ ratings_html += "<table>";
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ QString image_url = fi.baseName() + "_photos/" + QString::number((*rc_it).stars) + "-stars.png";
+ image_url = KURL::encode_string( image_url );
+ ratings_html += "<tr><td>"+(*rc_it).name+":</td><td><img src=\""+image_url+"\" /></td></tr>";
+ if ( !QFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
+ QPixmap starPixmap = Rating::starsPixmap((*rc_it).stars,true);
+ starPixmap.save( fi.dirPath(true) + "/" + image_url, "PNG" );
+ }
+
+ rating_total++;
+ rating_sum += (*rc_it).stars;
+ }
+ if ( (*rating_it).ratingCriteriaList.count() > 0 )
+ ratings_html += "</table>";
+
+ if ( !( *rating_it ).comment.isEmpty() )
+ ratings_html += "<p><i>"+( *rating_it ).comment+"</i></p>";
+ }
+ content = content.replace( "**RATINGS**", HTMLIfVisible("ratings",ratings_html) );
+
+ QString overall_html;
+ if ( rating_total > 0 ) {
+ double average = int(2*rating_sum/rating_total)/2;
+ overall_html += QString("<b>%1:</b>").arg(i18n("Overall Rating"));
+ QString image_url = fi.baseName() + "_photos/" + QString::number(average) + "-stars.png";
+ image_url = KURL::encode_string( image_url );
+ overall_html += "<img src=\""+image_url+"\" />";
+ if ( !QFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
+ QPixmap starPixmap = Rating::starsPixmap(average,true);
+ starPixmap.save( fi.dirPath(true) + "/" + image_url, "PNG" );
+ }
+ }
+ content = content.replace( "**OVERALL_RATING**", HTMLIfVisible("overall_rating",overall_html) );
+}
+
+void HTMLExporter::removeHTMLFiles( const QString &filename, int recipe_id )
+{
+ QValueList<int> id;
+ id << recipe_id;
+ removeHTMLFiles( filename, id );
+}
+
+void HTMLExporter::removeHTMLFiles( const QString &filename, const QValueList<int> &recipe_ids )
+{
+ //remove HTML file
+ QFile old_file( filename + ".html" );
+ if ( old_file.exists() )
+ old_file.remove();
+
+ //remove photos
+ for ( QValueList<int>::const_iterator it = recipe_ids.begin(); it != recipe_ids.end(); ++it ) {
+ QFile photo( filename + "_photos/" + QString::number(*it) + ".png" );
+ if ( photo.exists() )
+ photo.remove(); //remove photos in directory before removing it
+ }
+
+ //take care of the default photo
+ QFile photo( filename + "_photos/default_photo.png" );
+ if ( photo.exists() ) photo.remove();
+
+ //remove photo directory
+ QDir photo_dir;
+ photo_dir.rmdir( filename + "_photos" );
+
+ for ( double d = 0.5; d < 5.5; d += 0.5 ) {
+ if ( QFile::exists(filename+"_photos/"+QString::number(d)+"-stars.png") ) photo.remove(filename+"_photos/"+QString::number(d)+"-stars.png");
+ }
+}
+
+QString HTMLExporter::escape( const QString & str )
+{
+ QString tmp( str );
+ return tmp.replace( '/', "_" );
+}