summaryrefslogtreecommitdiffstats
path: root/src/exporters
diff options
context:
space:
mode:
Diffstat (limited to 'src/exporters')
-rw-r--r--src/exporters/Makefile.am13
-rw-r--r--src/exporters/baseexporter.cpp172
-rw-r--r--src/exporters/baseexporter.h95
-rw-r--r--src/exporters/cookmlexporter.cpp121
-rw-r--r--src/exporters/cookmlexporter.h38
-rw-r--r--src/exporters/htmlbookexporter.cpp160
-rw-r--r--src/exporters/htmlbookexporter.h52
-rw-r--r--src/exporters/htmlexporter.cpp570
-rw-r--r--src/exporters/htmlexporter.h78
-rw-r--r--src/exporters/kreexporter.cpp264
-rw-r--r--src/exporters/kreexporter.h51
-rw-r--r--src/exporters/mmfexporter.cpp220
-rw-r--r--src/exporters/mmfexporter.h51
-rw-r--r--src/exporters/plaintextexporter.cpp176
-rw-r--r--src/exporters/plaintextexporter.h42
-rw-r--r--src/exporters/recipemlexporter.cpp195
-rw-r--r--src/exporters/recipemlexporter.h45
-rw-r--r--src/exporters/rezkonvexporter.cpp322
-rw-r--r--src/exporters/rezkonvexporter.h51
19 files changed, 2716 insertions, 0 deletions
diff --git a/src/exporters/Makefile.am b/src/exporters/Makefile.am
new file mode 100644
index 0000000..fcedc46
--- /dev/null
+++ b/src/exporters/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. $(all_includes)
+
+METASOURCES = AUTO
+noinst_LTLIBRARIES = libkrecipesexporters.la
+
+libkrecipesexporters_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+libkrecipesexporters_la_SOURCES = kreexporter.cpp baseexporter.cpp cookmlexporter.cpp \
+ recipemlexporter.cpp mmfexporter.cpp htmlexporter.cpp plaintextexporter.cpp \
+ rezkonvexporter.cpp htmlbookexporter.cpp
+
+noinst_HEADERS = kreexporter.h baseexporter.h cookmlexporter.h \
+ recipemlexporter.h mmfexporter.h htmlexporter.h plaintextexporter.h
diff --git a/src/exporters/baseexporter.cpp b/src/exporters/baseexporter.cpp
new file mode 100644
index 0000000..60dc5a1
--- /dev/null
+++ b/src/exporters/baseexporter.cpp
@@ -0,0 +1,172 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Cyril Bosselut ([email protected]) *
+* 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 "baseexporter.h"
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+
+#include <tdeaboutdata.h>
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <tdemessagebox.h>
+#include <ktar.h>
+#include <kstandarddirs.h>
+
+#include "backends/recipedb.h"
+
+BaseExporter::BaseExporter( const TQString& _filename, const TQString &format ) :
+ file( 0 ),
+ tar_file( 0 ),
+ filename( _filename ),
+ m_progress_dlg( 0 ),
+ compress(false)
+{
+ //automatically append extension
+ TQString extension = format.right( format.length()-2 );
+ if ( filename.right( extension.length() ) != extension )
+ filename += "." + extension;
+}
+
+BaseExporter::~BaseExporter()
+{
+ delete file;
+ delete tar_file;
+}
+
+int BaseExporter::headerFlags() const
+{
+ return RecipeDB::None;
+}
+
+void BaseExporter::setCompressed( bool b )
+{
+ compress = b;
+}
+
+void BaseExporter::exporter( const TQValueList<int> &ids, RecipeDB *database, KProgressDialog *progress_dlg )
+{
+ m_progress_dlg = progress_dlg;
+
+ if ( createFile() )
+ saveToFile( ids, database );
+ else
+ kdDebug() << "no output file has been selected for export." << endl;
+}
+
+void BaseExporter::exporter( int id, RecipeDB *database, KProgressDialog *progress_dlg )
+{
+ TQValueList<int> single_recipe_list;
+ single_recipe_list << id ;
+ exporter( single_recipe_list, database, progress_dlg );
+}
+
+void BaseExporter::writeStream( TQTextStream &stream, const RecipeList &recipe_list )
+{
+ stream << createHeader(recipe_list);
+ stream << createContent(recipe_list);
+ stream << createFooter();
+}
+
+bool BaseExporter::createFile()
+{
+ if ( file )
+ return true;
+
+ if ( !filename.isEmpty() ) {
+ if ( compress ) {
+ tar_file = new KTar( filename, "application/x-gzip" );
+ TQFileInfo fi( filename );
+ file = new TQFile( locateLocal( "tmp",fi.fileName()+"ml" ) );
+ }
+ else
+ file = new TQFile(filename);
+
+ return (file != 0);
+ }
+ else
+ return false;
+}
+
+TQString BaseExporter::fileName() const
+{
+ return filename;
+}
+
+void BaseExporter::saveToFile( const TQValueList<int> &ids, RecipeDB *database )
+{
+ if ( file->open( IO_WriteOnly ) ) {
+ if ( m_progress_dlg )
+ m_progress_dlg->progressBar()->setTotalSteps( ids.count()/progressInterval() );
+
+ TQValueList<int> ids_copy = ids;
+ TQTextStream stream( file );
+ stream.setEncoding( TQTextStream::UnicodeUTF8 );
+
+ RecipeList recipe_list;
+ if ( headerFlags() != RecipeDB::None ) {
+ database->loadRecipes( &recipe_list, headerFlags(), ids );
+ }
+ stream << createHeader( recipe_list );
+
+ recipe_list.clear();
+ for ( uint i = 0; i < ids.count(); i += progressInterval() ) {
+ TQValueList<int> sub_list;
+ for ( int sub_i = 0; sub_i < progressInterval(); ++sub_i ) {
+ if ( ids_copy.count() == 0 ) break;
+
+ sub_list << *ids_copy.begin();
+ ids_copy.remove( ids_copy.begin() );
+ }
+
+ RecipeList recipe_list;
+ database->loadRecipes( &recipe_list, supportedItems(), sub_list );
+
+ TQString content = createContent( recipe_list );
+ if ( !content.isEmpty() )
+ stream << content;
+
+ if ( m_progress_dlg && m_progress_dlg->wasCancelled() )
+ break;
+
+ if ( m_progress_dlg ) {
+ m_progress_dlg->progressBar()->advance( progressInterval() );
+ kapp->processEvents();
+ }
+ }
+
+ stream << createFooter();
+
+ if ( tar_file && tar_file->open( IO_WriteOnly ) ) {
+ //close, which flushes the buffer, and then open for reading
+ file->close();
+ file->open( IO_ReadOnly );
+
+ TQFileInfo fi( file->name() );
+ TQByteArray data = file->readAll();
+ tar_file->writeFile( fi.fileName(), fi.owner(), fi.group(), data.size(), data );
+ tar_file->close();
+ }
+
+ file->close();
+ }
+}
+
+TQString BaseExporter::krecipes_version() const
+{
+ TDEInstance * this_instance = TDEGlobal::instance();
+ if ( this_instance && this_instance->aboutData() )
+ return this_instance->aboutData() ->version();
+
+ return TQString::null; //Oh, well. We couldn't get the version.
+}
+
diff --git a/src/exporters/baseexporter.h b/src/exporters/baseexporter.h
new file mode 100644
index 0000000..d814fb4
--- /dev/null
+++ b/src/exporters/baseexporter.h
@@ -0,0 +1,95 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut ([email protected]) *
+* 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. *
+***************************************************************************/
+
+#ifndef BASEEXPORTER_H
+#define BASEEXPORTER_H
+
+#include <tqstringlist.h>
+
+#include <tdeapplication.h>
+#include <kprogress.h>
+
+#include "datablocks/recipelist.h"
+
+class TQFile;
+
+class KTar;
+
+class RecipeDB;
+
+class BaseExporter
+{
+public:
+ BaseExporter( const TQString &file, const TQString &ext );
+ virtual ~BaseExporter();
+
+ /** Subclasses must report which items it is able to work with.
+ * These should be or'ed together items from RecipeDB::RecipeItems
+ */
+ virtual int supportedItems() const = 0;
+
+ /** Export the recipes with the given ids to the file specified in the constructor.
+ * Optionally, a progress dialog may be given to specify the progress made.
+ */
+ void exporter( const TQValueList<int> &ids, RecipeDB *database, KProgressDialog * = 0 );
+
+ /** Convenience function for the above, which exports a single recipe. */
+ void exporter( int id, RecipeDB *database, KProgressDialog * = 0 );
+
+ /** Returns the actual filename that will be written to during the export.
+ * Note that this can differ somewhat from the filename passed in the
+ * constructor.
+ */
+ TQString fileName() const;
+
+ /** Write the given recipe list to a text stream.
+ * This can be used to export recipes without use of the database.
+ */
+ void writeStream( TQTextStream &, const RecipeList & );
+
+protected:
+ virtual TQString createContent( const RecipeList & ) = 0;
+ virtual TQString createFooter(){ return TQString(); }
+ virtual TQString createHeader( const RecipeList & ){ return TQString(); }
+
+ /** The number of recipes to load into memory at once. This many recipes will be
+ * loaded from the database, processed, and then another batch of this many will be
+ * processed until all recipes are exported.
+ */
+ virtual int progressInterval() const { return 50; }
+
+ /** Extra RecipeDB::RecipeItems that a subclass requires when creating a file's header.
+ * For example, the Krecipes file format requires writing the category hierarchy in the header,
+ * so it's exporter adds RecipeDB::Categories.
+ */
+ virtual int headerFlags() const;
+
+ /** Make generated file a gzipped tarball */
+ void setCompressed( bool );
+
+ /** Attempt to return the version of the application via
+ * TDEGlobal::instance()->aboutData()->version()
+ * This can be used by exporters to put the version of the app exporting the file.
+ */
+ TQString krecipes_version() const;
+
+private:
+ bool createFile();
+ void saveToFile( const TQValueList<int> &ids, RecipeDB *database );
+
+ TQFile* file;
+ KTar *tar_file;
+ TQString filename;
+ KProgressDialog *m_progress_dlg;
+ bool compress;
+};
+
+#endif //BASEEXPORTER_H
diff --git a/src/exporters/cookmlexporter.cpp b/src/exporters/cookmlexporter.cpp
new file mode 100644
index 0000000..f8f9d40
--- /dev/null
+++ b/src/exporters/cookmlexporter.cpp
@@ -0,0 +1,121 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* 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 "cookmlexporter.h"
+
+#include <tqbuffer.h>
+#include <tqdom.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqfile.h>
+
+#include <tdeconfig.h>
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <tdetempfile.h>
+#include <kmdcodec.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+
+#include "backends/recipedb.h"
+
+CookMLExporter::CookMLExporter( const TQString& filename, const TQString &format ) :
+ BaseExporter( filename, format )
+{}
+
+
+CookMLExporter::~CookMLExporter()
+{}
+
+int CookMLExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Ratings;
+}
+
+TQString CookMLExporter::createHeader( const RecipeList& )
+{
+ TQString xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ xml += "<!DOCTYPE cookml PUBLIC \"-\" \"cookml.dtd\">";
+ xml += "<cookml version=\"1.0.13\" prog=\"Krecipes\" progver=\""+krecipes_version()+"\">";
+ return xml;
+}
+
+TQString CookMLExporter::createFooter()
+{
+ return "</cookml>";
+}
+
+TQString CookMLExporter::createContent( const RecipeList& recipes )
+{
+ TQString xml;
+ TQDomDocument doc;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ TQDomElement recipe_tag = doc.createElement( "recipe" );
+ recipe_tag.setAttribute( "lang", ( TDEGlobal::locale() ) ->language() );
+
+ //cookml_tag.appendChild( recipe_tag );
+
+ TQDomElement head_tag = doc.createElement( "head" );
+ head_tag.setAttribute( "title", ( *recipe_it ).title );
+ head_tag.setAttribute( "servingqty", ( *recipe_it ).yield.amount );
+ head_tag.setAttribute( "servingtype", ( *recipe_it ).yield.type );
+ head_tag.setAttribute( "rid", "" ); //FIXME:what's this...recipe ID??
+ recipe_tag.appendChild( head_tag );
+
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ TQDomElement cat_tag = doc.createElement( "cat" );
+ cat_tag.appendChild( doc.createTextNode( ( *cat_it ).name ) );
+ head_tag.appendChild( cat_tag );
+ }
+
+ for ( ElementList::const_iterator author_it = ( *recipe_it ).authorList.begin(); author_it != ( *recipe_it ).authorList.end(); ++author_it ) {
+ TQDomElement sourceline_tag = doc.createElement( "sourceline" );
+ sourceline_tag.appendChild( doc.createTextNode( ( *author_it ).name ) );
+ head_tag.appendChild( sourceline_tag );
+ }
+
+ TQDomElement picbin_tag = doc.createElement( "picbin" );
+ picbin_tag.setAttribute( "format", "JPG" );
+
+ TQByteArray data;
+ TQBuffer buffer( data );
+ buffer.open( IO_WriteOnly );
+ TQImageIO iio( &buffer, "JPEG" );
+ iio.setImage( ( *recipe_it ).photo.convertToImage() );
+ iio.write();
+
+ picbin_tag.appendChild( doc.createTextNode( KCodecs::base64Encode( data, true ) ) );
+ head_tag.appendChild( picbin_tag );
+
+ TQDomElement part_tag = doc.createElement( "part" );
+ for ( IngredientList::const_iterator ing_it = ( *recipe_it ).ingList.begin(); ing_it != ( *recipe_it ).ingList.end(); ++ing_it ) {
+ TQDomElement ingredient_tag = doc.createElement( "ingredient" );
+ ingredient_tag.setAttribute( "qty", TQString::number( ( *ing_it ).amount ) );
+ ingredient_tag.setAttribute( "unit", ( ( *ing_it ).amount > 1 ) ? ( *ing_it ).units.plural : ( *ing_it ).units.name );
+ ingredient_tag.setAttribute( "item", ( *ing_it ).name );
+ ingredient_tag.setAttribute( "preparation", ( *ing_it ).prepMethodList.join(",") );
+ part_tag.appendChild( ingredient_tag );
+ }
+ recipe_tag.appendChild( part_tag );
+
+ TQDomElement preparation_tag = doc.createElement( "preparation" );
+ recipe_tag.appendChild( preparation_tag );
+
+ TQDomElement text_tag = doc.createElement( "text" );
+ preparation_tag.appendChild( text_tag );
+ text_tag.appendChild( doc.createTextNode( ( *recipe_it ).instructions ) );
+
+ xml += recipe_tag.text();
+ }
+
+ return xml;
+}
diff --git a/src/exporters/cookmlexporter.h b/src/exporters/cookmlexporter.h
new file mode 100644
index 0000000..7e8d99a
--- /dev/null
+++ b/src/exporters/cookmlexporter.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* 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. *
+***************************************************************************/
+
+#ifndef COOKMLEXPORTER_H
+#define COOKMLEXPORTER_H
+
+#include "baseexporter.h"
+
+/**
+ * Export class for the Meal-Master file format
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * ---none?---
+ */
+class CookMLExporter : public BaseExporter
+{
+public:
+ CookMLExporter( const TQString&, const TQString& );
+ virtual ~CookMLExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual TQString createContent( const RecipeList& );
+ virtual TQString createHeader( const RecipeList& );
+ virtual TQString createFooter();
+};
+
+#endif //COOKMLEXPORTER_H
diff --git a/src/exporters/htmlbookexporter.cpp b/src/exporters/htmlbookexporter.cpp
new file mode 100644
index 0000000..93440a5
--- /dev/null
+++ b/src/exporters/htmlbookexporter.cpp
@@ -0,0 +1,160 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* 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 "htmlbookexporter.h"
+
+#include <tqfile.h>
+#include <tqstylesheet.h>
+
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/categorytree.h"
+
+HTMLBookExporter::HTMLBookExporter( CategoryTree *categories, const TQString& basedir, const TQString &format ) :
+ HTMLExporter( basedir+"/index", format ), m_categories(categories), m_basedir(basedir)
+{
+}
+
+HTMLBookExporter::~HTMLBookExporter()
+{
+}
+
+int HTMLBookExporter::headerFlags() const
+{
+ return RecipeDB::Categories | RecipeDB::Title;
+}
+
+TQString HTMLBookExporter::createContent( const RecipeList& recipes )
+{
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ TQMap<TQString,TQTextStream*>::iterator stream_it = fileMap.find( (*cat_it).name );
+ (**stream_it) << "<br /><br />";
+ (**stream_it) << TQString("<a name=\""+(*recipe_it).title+"\" />");
+ (**stream_it) << HTMLExporter::createContent(*recipe_it);
+ (**stream_it) << TQString("[ <a href=\"#top\">Top</a> ]");
+ (**stream_it) << TQString("[ <a href=\"index.html\">Back</a> ]");
+ (**stream_it) << "<br /><br />";
+ }
+ }
+
+ return TQString::null;
+}
+
+TQString HTMLBookExporter::createHeader( const RecipeList &list )
+{
+ TQString output = HTMLExporter::createHeader(list);
+
+ TQString catLinks;
+ TQTextStream catLinksStream(&catLinks,IO_WriteOnly);
+ createCategoryStructure(catLinksStream,list);
+
+ return output+"<h1>Krecipes Recipes</h1><div>"+catLinks+"</li></ul></div>";
+}
+
+TQString HTMLBookExporter::createFooter()
+{
+ TQMap<TQString,TQTextStream*>::const_iterator it;
+ for ( it = fileMap.begin(); it != fileMap.end(); ++it ) {
+ (**it) << HTMLExporter::createFooter();
+
+ (*it)->device()->close();
+
+ //does it matter the order of deletion here?
+ TQIODevice *file = (*it)->device();
+ delete *it;
+ delete file;
+ }
+
+ TQString output = HTMLExporter::createFooter();
+ return output;
+}
+
+void HTMLBookExporter::createCategoryStructure( TQTextStream &xml, const RecipeList &recipes )
+{
+ TQValueList<int> categoriesUsed;
+ for ( RecipeList::const_iterator recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ TQMap<TQString,TQTextStream*>::iterator stream_it = fileMap.find( (*cat_it).name );
+ if ( categoriesUsed.find( ( *cat_it ).id ) == categoriesUsed.end() ) {
+ categoriesUsed << ( *cat_it ).id;
+
+ TQString catPageName = m_basedir+"/"+escape((*cat_it).name)+".html";
+ TQFile *catPage = new TQFile( catPageName );
+ catPage->open( IO_WriteOnly );
+ TQTextStream *stream = new TQTextStream( catPage );
+ stream->setEncoding( TQTextStream::UnicodeUTF8 );
+ (*stream) << HTMLExporter::createHeader(recipes);
+ (*stream) << TQString("<a name=\"top\" />");
+ (*stream) << "<h1>"<<(*cat_it).name<<"</h1>";
+
+ stream_it = fileMap.insert((*cat_it).name,stream);
+ }
+ (**stream_it) << TQString("[ <a href=\"#" + (*recipe_it).title + "\">" + (*recipe_it).title + "</a> ]");
+ }
+ }
+
+ if ( !categoriesUsed.empty() ) {
+ //only keep the relevant category structure
+ removeIfUnused( categoriesUsed, m_categories );
+
+ xml << "<ul>\n";
+ writeCategoryStructure( xml, m_categories );
+ xml << "</ul>\n";
+ }
+}
+
+bool HTMLBookExporter::removeIfUnused( const TQValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show )
+{
+ for ( CategoryTree * it = parent->firstChild(); it; it = it->nextSibling() ) {
+ if ( cat_ids.find( it->category.id ) != cat_ids.end() ) {
+ parent_should_show = true;
+ removeIfUnused( cat_ids, it, true ); //still recurse, but doesn't affect 'parent'
+ }
+ else {
+ bool result = removeIfUnused( cat_ids, it );
+ if ( parent_should_show == false )
+ parent_should_show = result;
+ }
+ }
+
+ if ( !parent_should_show && parent->category.id != -1 ) {
+ //FIXME: CategoryTree is broken when deleting items
+ //delete parent;
+
+ parent->category.id = -2; //temporary workaround
+ }
+
+ return parent_should_show;
+}
+
+void HTMLBookExporter::writeCategoryStructure( TQTextStream &xml, const CategoryTree *categoryTree )
+{
+ if ( categoryTree->category.id != -2 ) {
+ if ( categoryTree->category.id != -1 ) {
+ TQString catPageName = TQStyleSheet::escape(categoryTree->category.name)+".html";
+
+ xml << "\t<li>\n\t\t<a href=\""+catPageName+"\">"+TQStyleSheet::escape( categoryTree->category.name ).replace("\"","&quot;") + "</a>\n";
+ }
+
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ if ( categoryTree->parent() != 0 )
+ xml << "<ul>\n";
+ writeCategoryStructure( xml, child_it );
+ if ( categoryTree->parent() != 0 )
+ xml << "</ul>\n";
+ }
+
+ if ( categoryTree->category.id != -1 )
+ xml << "\t</li>\n";
+ }
+}
diff --git a/src/exporters/htmlbookexporter.h b/src/exporters/htmlbookexporter.h
new file mode 100644
index 0000000..d8b5884
--- /dev/null
+++ b/src/exporters/htmlbookexporter.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* 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. *
+***************************************************************************/
+
+#ifndef HTMLBOOKEXPORTER_H
+#define HTMLBOOKEXPORTER_H
+
+#include <tqmap.h>
+#include <tqvaluelist.h>
+
+#include "baseexporter.h"
+#include "htmlexporter.h"
+
+class RecipeDB;
+class CategoryTree;
+
+/**
+ * Exports a given recipe list as HTML
+ * @author Jason Kivlighn
+ */
+class HTMLBookExporter : public HTMLExporter
+{
+public:
+ HTMLBookExporter( CategoryTree *categories, const TQString&, const TQString& );
+ virtual ~HTMLBookExporter();
+
+protected:
+ virtual TQString createContent( const RecipeList & );
+ virtual TQString createHeader( const RecipeList & );
+ virtual TQString createFooter();
+
+ virtual int headerFlags() const;
+
+private:
+ void createCategoryStructure( TQTextStream &xml, const RecipeList &recipes );
+ bool removeIfUnused( const TQValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show = false );
+ void writeCategoryStructure( TQTextStream &xml, const CategoryTree *categoryTree );
+
+ TQMap<TQString,TQTextStream*> fileMap;
+
+ RecipeDB *database;
+ CategoryTree *m_categories;
+ TQString m_basedir;
+};
+
+#endif //HTMLBOOKEXPORTER_H
diff --git a/src/exporters/htmlexporter.cpp b/src/exporters/htmlexporter.cpp
new file mode 100644
index 0000000..b4c44f4
--- /dev/null
+++ b/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 <tqptrdict.h>
+#include <tqimage.h>
+#include <tqfileinfo.h>
+#include <tqdir.h>
+#include <tqstylesheet.h> //for TQStyleSheet::escape() to escape for HTML
+#include <dom/dom_element.h>
+#include <tqpainter.h>
+#include <tqfileinfo.h>
+
+#include <tdeconfig.h>
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <tdehtml_part.h>
+#include <tdehtmlview.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 TQString& filename, const TQString &format ) :
+ BaseExporter( filename, format )
+{
+ TDEConfig *config = TDEGlobal::config();
+ config->setGroup( "Page Setup" );
+
+ //let's do everything we can to be sure at least some layout is loaded
+ TQString template_filename = config->readEntry( "Template", locate( "appdata", "layouts/Default.template" ) );
+ if ( template_filename.isEmpty() || !TQFile::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() || !TQFile::exists( m_layoutFilename ) )
+ m_layoutFilename = locate( "appdata", "layouts/Default.klo" );
+ kdDebug() << "Using layout file: " << m_layoutFilename << endl;
+}
+
+HTMLExporter::~HTMLExporter()
+{
+}
+
+void HTMLExporter::setTemplate( const TQString &filename )
+{
+ TQFile templateFile( filename );
+ if ( templateFile.open( IO_ReadOnly ) ) {
+ m_templateFilename = filename;
+ m_templateContent = TQString( templateFile.readAll() );
+ }
+ else
+ kdDebug()<<"couldn't find/open template file"<<endl;
+}
+
+void HTMLExporter::setStyle( const TQString &filename )
+{
+ m_layoutFilename = filename;
+}
+
+int HTMLExporter::supportedItems() const
+{
+ int items = RecipeDB::All ^ RecipeDB::Properties;
+
+ TQMap<TQString,bool>::const_iterator it = m_visibilityMap.find("properties");
+ if ( it == m_visibilityMap.end() || it.data() == true )
+ items |= RecipeDB::Properties;
+
+ return RecipeDB::All;
+}
+
+TQString HTMLExporter::createContent( const Recipe& recipe )
+{
+ TQString templateCopy = m_templateContent;
+
+ storePhoto( recipe );
+
+ populateTemplate( recipe, templateCopy );
+ return templateCopy;
+}
+
+TQString HTMLExporter::createContent( const RecipeList& recipes )
+{
+ TQString fileContent;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ fileContent += createContent(*recipe_it);
+ }
+
+ return fileContent;
+}
+
+TQString HTMLExporter::createHeader( const RecipeList & )
+{
+ m_visibilityMap.clear();
+ m_columnsMap.clear();
+
+ TDEConfig *config = TDEGlobal::config();
+ config->setGroup( "Page Setup" );
+
+ m_error = false;
+
+ if ( m_templateContent.isEmpty() ) {
+ TQString 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;
+ }
+
+ TQFile layoutFile( m_layoutFilename );
+ TQString error; int line; int column;
+ TQDomDocument 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
+ TQDir dir;
+ TQFileInfo fi(fileName());
+ dir.mkdir( fi.dirPath(true) + "/" + fi.baseName() + "_photos" );
+
+ RecipeList::const_iterator recipe_it;
+
+ TDELocale*loc = TDEGlobal::locale();
+ TQString encoding = loc->encoding();
+
+ TQString 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 += TQString( "<title>%1</title>" ).arg( i18n( "Krecipes Recipes" ) );
+
+ output += "<style type=\"text/css\">\n";
+
+ TQString cssContent;
+ TQFileInfo info(m_templateFilename);
+ TQFile cssFile(info.dirPath(true) + "/" + info.baseName() + ".css");
+ kdDebug()<<info.dirPath(true) + "/" + info.baseName() + ".css"<<endl;
+ if ( cssFile.open( IO_ReadOnly ) ) {
+ cssContent = TQString( cssFile.readAll() );
+ }
+ output += cssContent;
+
+ output += m_cachedCSS;
+ m_cachedCSS = TQString::null;
+ output += "</style>";
+ output += "</head>";
+ output += "<body class=\"background\">";
+
+ return output;
+}
+
+void HTMLExporter::beginObject( const TQString &object )
+{
+ m_cachedCSS += "."+object+" { \n";
+}
+
+void HTMLExporter::endObject()
+{
+ m_cachedCSS += " } \n";
+}
+
+void HTMLExporter::loadBackgroundColor( const TQString &/*object*/, const TQColor& color )
+{
+ m_cachedCSS += bgColorAsCSS(color);
+}
+
+void HTMLExporter::loadFont( const TQString &/*object*/, const TQFont& font )
+{
+ m_cachedCSS += fontAsCSS(font);
+}
+
+void HTMLExporter::loadTextColor( const TQString &/*object*/, const TQColor& color )
+{
+ m_cachedCSS += textColorAsCSS(color);
+}
+
+void HTMLExporter::loadVisibility( const TQString &object, bool visible )
+{
+ m_cachedCSS += visibilityAsCSS(visible);
+ m_visibilityMap.insert(object,visible);
+}
+
+void HTMLExporter::loadAlignment( const TQString &/*object*/, int alignment )
+{
+ m_cachedCSS += alignmentAsCSS(alignment);
+}
+
+void HTMLExporter::loadBorder( const TQString &/*object*/, const KreBorder& border )
+{
+ m_cachedCSS += borderAsCSS(border);
+}
+
+void HTMLExporter::loadColumns( const TQString & object, int cols )
+{
+ m_columnsMap.insert(object,cols);
+kdDebug()<<object<<" has "<<cols<<" columns"<<endl;
+}
+
+TQString HTMLExporter::createFooter()
+{
+ if ( m_error )
+ return TQString::null;
+
+ return "</body></html>";
+}
+
+void HTMLExporter::storePhoto( const Recipe &recipe )
+{
+ TQImage image;
+ TQString photo_name;
+ if ( recipe.photo.isNull() ) {
+ image = TQImage( defaultPhoto );
+ photo_name = "default_photo";
+ }
+ else {
+ image = recipe.photo.convertToImage();
+ photo_name = TQString::number(recipe.recipeID);
+ }
+
+ TQPixmap pm = image;//image.smoothScale( phwidth, 0, TQImage::ScaleMax );
+
+ TQFileInfo fi(fileName());
+ TQString photo_path = fi.dirPath(true) + "/" + fi.baseName() + "_photos/" + photo_name + ".png";
+ if ( !TQFile::exists( photo_path ) ) {
+ pm.save( photo_path, "PNG" );
+ }
+}
+
+TQString HTMLExporter::HTMLIfVisible( const TQString &name, const TQString &html )
+{
+ TQMap<TQString,bool>::const_iterator it = m_visibilityMap.find(name);
+ if ( it == m_visibilityMap.end() || it.data() == true )
+ return html;
+ else
+ return TQString::null;
+}
+
+void HTMLExporter::populateTemplate( const Recipe &recipe, TQString &content )
+{
+ TDEConfig * config = TDEGlobal::config();
+
+ //=======================TITLE======================//
+ content = content.replace("**TITLE**",HTMLIfVisible("title",recipe.title));
+
+ //=======================INSTRUCTIONS======================//
+ TQString instr_html = TQStyleSheet::escape( recipe.instructions );
+ instr_html.replace( "\n", "<br />" );
+ content = content.replace( "**INSTRUCTIONS**", HTMLIfVisible("instructions",instr_html) );
+
+ //=======================SERVINGS======================//
+ TQString yield_html = TQString( "<b>%1: </b>%2" ).arg( i18n( "Yield" ) ).arg( recipe.yield.toString() );
+ content = content.replace( "**YIELD**", HTMLIfVisible("yield",yield_html) );
+
+ //=======================PREP TIME======================//
+ TQString preptime_html;
+ if ( !recipe.prepTime.isNull() && recipe.prepTime.isValid() )
+ preptime_html = TQString( "<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========================//
+ TQString photo_name;
+ if ( recipe.photo.isNull() )
+ photo_name = "default_photo";
+ else
+ photo_name = TQString::number(recipe.recipeID);
+
+ TQFileInfo fi(fileName());
+ TQString 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======================//
+ TQString 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 += TQStyleSheet::escape( ( *author_it ).name );
+ counter++;
+ }
+ if ( !authors_html.isEmpty() )
+ authors_html.prepend( TQString( "<b>%1: </b>" ).arg( i18n( "Authors" ) ) );
+ content = content.replace( "**AUTHORS**", HTMLIfVisible("authors",authors_html) );
+
+ //=======================CATEGORIES======================//
+ TQString 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 += TQStyleSheet::escape( ( *cat_it ).name );
+ counter++;
+ }
+ if ( !categories_html.isEmpty() )
+ categories_html.prepend( TQString( "<b>%1: </b>" ).arg( i18n( "Categories" ) ) );
+
+ content = content.replace( "**CATEGORIES**", HTMLIfVisible("categories",categories_html) );
+
+ //=======================INGREDIENTS======================//
+ TQString ingredients_html;
+ config->setGroup( "Formatting" );
+
+ bool useAbbreviations = config->readBoolEntry("AbbreviateUnits");
+
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ TQString ingredient_format = config->readEntry( "Ingredient", "%n%p: %a %u" );
+
+ TQMap<TQString,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() ) {
+ TQString 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>";
+ }
+
+ TQString 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 = "";
+
+ TQString unit = ( *ing_it ).units.determineName( ( *ing_it ).amount + ( *ing_it ).amount_offset, useAbbreviations );
+
+ TQString tmp_format( ingredient_format );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%n" ) ), TQStyleSheet::escape( ( *ing_it ).name ) );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%a" ) ), amount_str );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%u" ) ), TQStyleSheet::escape(unit) );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%p" ) ), ( ( *ing_it ).prepMethodList.count() == 0 ) ?
+ TQString::fromLatin1( "" ) : TQString::fromLatin1( "; " ) + TQStyleSheet::escape( ( *ing_it ).prepMethodList.join(",") ) );
+
+ if ( (*ing_it).substitutes.count() > 0 )
+ tmp_format += ", "+i18n("or");
+
+ ingredients_html += TQString( "<li>%1</li>" ).arg( tmp_format );
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ TQString 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 = "";
+
+ TQString unit = ( *sub_it ).units.determineName( ( *sub_it ).amount + ( *sub_it ).amount_offset, config->readBoolEntry("AbbreviateUnits") );
+
+ TQString tmp_format( ingredient_format );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%n" ) ), TQStyleSheet::escape( ( *sub_it ).name ) );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%a" ) ), amount_str );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%u" ) ), TQStyleSheet::escape(unit) );
+ tmp_format.replace( TQRegExp( TQString::fromLatin1( "%p" ) ), ( ( *sub_it ).prepMethodList.count() == 0 ) ?
+ TQString::fromLatin1( "" ) : TQString::fromLatin1( "; " ) + TQStyleSheet::escape( ( *sub_it ).prepMethodList.join(",") ) );
+
+ ++sub_it;
+ if ( sub_it != (*ing_it).substitutes.end() )
+ tmp_format += ", "+i18n("or");
+ ingredients_html += TQString( "<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======================//
+ TQString properties_html;
+
+ TQStringList 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
+
+ TQString amount_str;
+
+ double prop_amount = (*prop_it).amount;
+ if ( prop_amount > 0 ) { //TODO: make the precision configuratble
+ prop_amount = double( tqRound( prop_amount * 10.0 ) ) / 10.0; //not a "chemistry experiment" ;) Let's only have one decimal place
+ amount_str = beautify( TDEGlobal::locale() ->formatNumber( prop_amount, 5 ) );
+ }
+ else
+ amount_str = "0";
+
+ properties_html += TQString( "<li>%1: <nobr>%2 %3</nobr></li>" )
+ .arg( TQStyleSheet::escape( (*prop_it).name ) )
+ .arg( amount_str )
+ .arg( TQStyleSheet::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======================//
+ TQString ratings_html;
+ if ( recipe.ratingList.count() > 0 )
+ ratings_html += TQString("<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 ) {
+ TQString image_url = fi.baseName() + "_photos/" + TQString::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 ( !TQFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
+ TQPixmap 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) );
+
+ TQString overall_html;
+ if ( rating_total > 0 ) {
+ double average = int(2*rating_sum/rating_total)/2;
+ overall_html += TQString("<b>%1:</b>").arg(i18n("Overall Rating"));
+ TQString image_url = fi.baseName() + "_photos/" + TQString::number(average) + "-stars.png";
+ image_url = KURL::encode_string( image_url );
+ overall_html += "<img src=\""+image_url+"\" />";
+ if ( !TQFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
+ TQPixmap 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 TQString &filename, int recipe_id )
+{
+ TQValueList<int> id;
+ id << recipe_id;
+ removeHTMLFiles( filename, id );
+}
+
+void HTMLExporter::removeHTMLFiles( const TQString &filename, const TQValueList<int> &recipe_ids )
+{
+ //remove HTML file
+ TQFile old_file( filename + ".html" );
+ if ( old_file.exists() )
+ old_file.remove();
+
+ //remove photos
+ for ( TQValueList<int>::const_iterator it = recipe_ids.begin(); it != recipe_ids.end(); ++it ) {
+ TQFile photo( filename + "_photos/" + TQString::number(*it) + ".png" );
+ if ( photo.exists() )
+ photo.remove(); //remove photos in directory before removing it
+ }
+
+ //take care of the default photo
+ TQFile photo( filename + "_photos/default_photo.png" );
+ if ( photo.exists() ) photo.remove();
+
+ //remove photo directory
+ TQDir photo_dir;
+ photo_dir.rmdir( filename + "_photos" );
+
+ for ( double d = 0.5; d < 5.5; d += 0.5 ) {
+ if ( TQFile::exists(filename+"_photos/"+TQString::number(d)+"-stars.png") ) photo.remove(filename+"_photos/"+TQString::number(d)+"-stars.png");
+ }
+}
+
+TQString HTMLExporter::escape( const TQString & str )
+{
+ TQString tmp( str );
+ return tmp.replace( '/', "_" );
+}
diff --git a/src/exporters/htmlexporter.h b/src/exporters/htmlexporter.h
new file mode 100644
index 0000000..95cee4c
--- /dev/null
+++ b/src/exporters/htmlexporter.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+* Copyright (C) 2003-2006 by *
+* 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. *
+***************************************************************************/
+
+#ifndef HTMLEXPORTER_H
+#define HTMLEXPORTER_H
+
+#include <tqdom.h>
+#include <tqmap.h>
+
+#include "baseexporter.h"
+#include "klomanager.h"
+
+class RecipeDB;
+class KProgress;
+
+/**
+ * Exports a given recipe list as HTML
+ * @author Jason Kivlighn
+ */
+class HTMLExporter : public BaseExporter, protected KLOManager
+{
+public:
+ HTMLExporter( const TQString&, const TQString& );
+ virtual ~HTMLExporter();
+
+ virtual int supportedItems() const;
+
+ static void removeHTMLFiles( const TQString &filename, int recipe_id );
+ static void removeHTMLFiles( const TQString &filename, const TQValueList<int> &recipe_ids );
+
+ void setTemplate( const TQString &filename );
+ void setStyle( const TQString &filename );
+
+protected:
+ TQString createContent( const Recipe& recipe );
+ virtual TQString createContent( const RecipeList & );
+ virtual TQString createHeader( const RecipeList & );
+ virtual TQString createFooter();
+
+ virtual int progressInterval() const { return 1; }
+
+ virtual void loadBackgroundColor( const TQString &obj, const TQColor& );
+ virtual void loadFont( const TQString &obj, const TQFont& );
+ virtual void loadTextColor( const TQString &obj, const TQColor& );
+ virtual void loadVisibility( const TQString &obj, bool );
+ virtual void loadAlignment( const TQString &obj, int );
+ virtual void loadBorder( const TQString &obj, const KreBorder& );
+ virtual void loadColumns( const TQString & obj, int cols );
+
+ virtual void beginObject( const TQString &obj );
+ virtual void endObject();
+
+ static TQString escape( const TQString & );
+
+ TQString m_templateContent;
+
+private:
+ void storePhoto( const Recipe &recipe );
+ void populateTemplate( const Recipe &recipe, TQString &content );
+ void replaceIfVisible( TQString &content, const TQString &name, const TQString &html );
+ TQString HTMLIfVisible( const TQString &name, const TQString &html );
+
+ TQString m_layoutFilename;
+ TQString m_templateFilename;
+ TQString m_cachedCSS;
+ TQMap<TQString,bool> m_visibilityMap;
+ TQMap<TQString,int> m_columnsMap;
+ bool m_error;
+};
+
+#endif //HTMLEXPORTER_H
diff --git a/src/exporters/kreexporter.cpp b/src/exporters/kreexporter.cpp
new file mode 100644
index 0000000..e0d27c0
--- /dev/null
+++ b/src/exporters/kreexporter.cpp
@@ -0,0 +1,264 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut ([email protected]) *
+* *
+* Copyright (C) 2003-2005 by *
+* 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 "kreexporter.h"
+
+#include <tqfile.h>
+#include <tqstylesheet.h>
+#include <tqbuffer.h>
+#include <tqimage.h>
+
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <kmdcodec.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+
+#include "backends/recipedb.h"
+
+KreExporter::KreExporter( CategoryTree *_categories, const TQString& filename, const TQString &format ) :
+ BaseExporter( filename, format ), categories( _categories )
+{
+ if ( format == "*.kre" ) {
+ setCompressed(true);
+ }
+}
+
+
+KreExporter::~KreExporter()
+{
+ delete categories;
+}
+
+int KreExporter::supportedItems() const
+{
+ return RecipeDB::All;
+}
+
+int KreExporter::headerFlags() const
+{
+ return RecipeDB::Categories;
+}
+
+TQString KreExporter::createHeader( const RecipeList& recipes )
+{
+ TQString xml;
+
+ xml += "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ xml += "<krecipes version=\"" + krecipes_version() + "\" lang=\"" + ( TDEGlobal::locale() )->language() + "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"krecipes.xsd\">\n";
+
+ createCategoryStructure( xml, recipes );
+
+ return xml;
+}
+
+TQString KreExporter::createFooter()
+{
+ return "</krecipes>\n";
+}
+
+TQString KreExporter::generateIngredient( const IngredientData &ing )
+{
+ TQString xml;
+
+ xml += "<name>" + TQStyleSheet::escape( ( ing ).name ) + "</name>\n";
+ xml += "<amount>";
+ if ( ing.amount_offset < 1e-10 ) {
+ xml += TQString::number( ing.amount );
+ }
+ else {
+ xml += "<min>"+TQString::number( ing.amount )+"</min>";
+ xml += "<max>"+TQString::number( ing.amount + ing.amount_offset )+"</max>";
+ }
+ xml += "</amount>\n";
+ TQString unit_str = ( ing.amount+ing.amount_offset > 1 ) ? ing.units.plural : ing.units.name;
+ xml += "<unit>" + TQStyleSheet::escape( unit_str ) + "</unit>\n";
+ if ( ing.prepMethodList.count() > 0 )
+ xml += "<prep>" + TQStyleSheet::escape( ing.prepMethodList.join(",") ) + "</prep>\n";
+
+ return xml;
+}
+
+//TODO: use TQDOM (see recipemlexporter.cpp)?
+TQString KreExporter::createContent( const RecipeList& recipes )
+{
+ TQString xml;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+
+ xml += "<krecipes-recipe>\n";
+ xml += "<krecipes-description>\n";
+ xml += "<title>" + TQStyleSheet::escape( ( *recipe_it ).title ) + "</title>\n";
+
+ for ( ElementList::const_iterator author_it = ( *recipe_it ).authorList.begin(); author_it != ( *recipe_it ).authorList.end(); ++author_it )
+ xml += "<author>" + TQStyleSheet::escape( ( *author_it ).name ) + "</author>\n";
+
+ xml += "<pictures>\n";
+ if ( !( *recipe_it ).photo.isNull() ) {
+ xml += "<pic format=\"JPEG\" id=\"1\"><![CDATA["; //fixed id until we implement multiple photos ability
+ TQByteArray data;
+ TQBuffer buffer( data );
+ buffer.open( IO_WriteOnly );
+ TQImageIO iio( &buffer, "JPEG" );
+ iio.setImage( ( *recipe_it ).photo.convertToImage() );
+ iio.write();
+
+ xml += KCodecs::base64Encode( data, true );
+
+ xml += "]]></pic>\n";
+ }
+ xml += "</pictures>\n";
+ xml += "<category>\n";
+
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it )
+ xml += "<cat>" + TQStyleSheet::escape( ( *cat_it ).name ) + "</cat>\n";
+
+ xml += "</category>\n";
+ xml += "<yield>";
+ xml += "<amount>";
+ if ( ( *recipe_it ).yield.amount_offset < 1e-10 ) {
+ xml += TQString::number( ( *recipe_it ).yield.amount );
+ }
+ else {
+ xml += "<min>"+TQString::number( ( *recipe_it ).yield.amount )+"</min>";
+ xml += "<max>"+TQString::number( ( *recipe_it ).yield.amount + ( *recipe_it ).yield.amount_offset )+"</max>";
+ }
+ xml += "</amount>";
+ xml += "<type>"+( *recipe_it ).yield.type+"</type>";
+ xml += "</yield>\n";
+ xml += "<preparation-time>";
+ xml += ( *recipe_it ).prepTime.toString( "hh:mm" );
+ xml += "</preparation-time>\n";
+ xml += "</krecipes-description>\n";
+ xml += "<krecipes-ingredients>\n";
+
+ IngredientList list_copy = ( *recipe_it ).ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ TQString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+ if ( !group.isEmpty() )
+ xml += "<ingredient-group name=\"" + TQStyleSheet::escape(group) + "\">\n";
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ xml += "<ingredient>\n";
+
+ xml += generateIngredient(*ing_it);
+
+ if ( (*ing_it).substitutes.count() > 0 ) {
+ xml += "<substitutes>\n";
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ xml += "<ingredient>\n";
+ xml += generateIngredient(*sub_it);
+ xml += "</ingredient>\n";
+ }
+ xml += "</substitutes>\n";
+ }
+
+ xml += "</ingredient>\n";
+ }
+
+ if ( !group.isEmpty() )
+ xml += "</ingredient-group>\n";
+ }
+
+ /// @todo add ingredient properties
+
+ xml += "</krecipes-ingredients>\n";
+ xml += "<krecipes-instructions>\n";
+ xml += TQStyleSheet::escape( ( *recipe_it ).instructions );
+ xml += "</krecipes-instructions>\n";
+
+ //ratings
+ xml += "<krecipes-ratings>";
+ for ( RatingList::const_iterator rating_it = (*recipe_it).ratingList.begin(); rating_it != (*recipe_it).ratingList.end(); ++rating_it ) {
+ xml += "<rating>";
+ xml += "<comment>"+TQStyleSheet::escape( ( *rating_it ).comment )+"</comment>";
+ xml += "<rater>"+TQStyleSheet::escape( ( *rating_it ).rater )+"</rater>";
+
+ xml += "<criterion>";
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ xml += "<criteria>";
+ xml += "<name>"+(*rc_it).name+"</name>";
+ xml += "<stars>"+TQString::number((*rc_it).stars)+"</stars>";
+ xml += "</criteria>";
+ }
+ xml += "</criterion>";
+ xml += "</rating>";
+ }
+ xml += "</krecipes-ratings>";
+
+ xml += "</krecipes-recipe>\n";
+ }
+
+ return xml;
+}
+
+void KreExporter::createCategoryStructure( TQString &xml, const RecipeList &recipes )
+{
+ TQValueList<int> categoriesUsed;
+ for ( RecipeList::const_iterator recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ if ( categoriesUsed.find( ( *cat_it ).id ) == categoriesUsed.end() )
+ categoriesUsed << ( *cat_it ).id;
+ }
+ }
+
+ if ( !categoriesUsed.empty() ) {
+ //only keep the relevant category structure
+ removeIfUnused( categoriesUsed, categories );
+
+ xml += "<krecipes-category-structure>\n";
+ writeCategoryStructure( xml, categories );
+ xml += "</krecipes-category-structure>\n";
+ }
+}
+
+bool KreExporter::removeIfUnused( const TQValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show )
+{
+ for ( CategoryTree * it = parent->firstChild(); it; it = it->nextSibling() ) {
+ if ( cat_ids.find( it->category.id ) != cat_ids.end() ) {
+ parent_should_show = true;
+ removeIfUnused( cat_ids, it, true ); //still recurse, but doesn't affect 'parent'
+ }
+ else {
+ bool result = removeIfUnused( cat_ids, it );
+ if ( parent_should_show == false )
+ parent_should_show = result;
+ }
+ }
+
+ if ( !parent_should_show && parent->category.id != -1 ) {
+ //FIXME: CategoryTree is broken when deleting items
+ //delete parent;
+
+ parent->category.id = -2; //temporary workaround
+ }
+
+ return parent_should_show;
+}
+
+void KreExporter::writeCategoryStructure( TQString &xml, const CategoryTree *categoryTree )
+{
+ if ( categoryTree->category.id != -2 ) {
+ if ( categoryTree->category.id != -1 )
+ xml += "<category name=\"" + TQStyleSheet::escape( categoryTree->category.name ).replace("\"","&quot;") + "\">\n";
+
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ writeCategoryStructure( xml, child_it );
+ }
+
+ if ( categoryTree->category.id != -1 )
+ xml += "</category>\n";
+ }
+}
+
diff --git a/src/exporters/kreexporter.h b/src/exporters/kreexporter.h
new file mode 100644
index 0000000..d6b0b17
--- /dev/null
+++ b/src/exporters/kreexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut ([email protected]) *
+* *
+* Copyright (C) 2003-2005 by *
+* 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. *
+***************************************************************************/
+
+#ifndef KREEXPORTER_H
+#define KREEXPORTER_H
+
+#include "baseexporter.h"
+#include "datablocks/categorytree.h"
+
+class IngredientData;
+
+/**
+Export class for Krecipes native file format (.kre, .kreml)
+
+@author Cyril Bosselut and Jason Kivlighn
+*/
+class KreExporter : public BaseExporter
+{
+public:
+ KreExporter( CategoryTree *, const TQString&, const TQString& );
+ virtual ~KreExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual TQString createContent( const RecipeList & );
+ virtual TQString createHeader( const RecipeList & );
+ virtual TQString createFooter();
+
+ virtual int headerFlags() const;
+
+private:
+ bool removeIfUnused( const TQValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show = false );
+ void createCategoryStructure( TQString &xml, const RecipeList &recipes );
+ void writeCategoryStructure( TQString &xml, const CategoryTree *categoryTree );
+ TQString generateIngredient( const IngredientData &ing );
+
+ CategoryTree *categories;
+};
+
+#endif
diff --git a/src/exporters/mmfexporter.cpp b/src/exporters/mmfexporter.cpp
new file mode 100644
index 0000000..33ee1ec
--- /dev/null
+++ b/src/exporters/mmfexporter.cpp
@@ -0,0 +1,220 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* 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 "mmfexporter.h"
+
+#include <tqregexp.h>
+
+#include <tdeconfig.h>
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/mixednumber.h"
+#include "mmdata.h"
+
+MMFExporter::MMFExporter( const TQString& filename, const TQString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+MMFExporter::~MMFExporter()
+{}
+
+int MMFExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo ^ RecipeDB::Ratings ^ RecipeDB::Authors;
+}
+
+TQString MMFExporter::createContent( const RecipeList& recipes )
+{
+ TQString content;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ writeMMFHeader( content, *recipe_it );
+ content += "\n";
+ writeMMFIngredients( content, *recipe_it );
+ content += "\n";
+ writeMMFDirections( content, *recipe_it );
+ content += "\n";
+
+ content += "-----\n"; //end of recipe indicator
+ }
+
+ return content;
+}
+
+/* Header:
+ * Line 1 - contains five hyphens and "Meal-Master" somewhere in the line
+ * Line 2 - "Title:" followed by a blank space; maximum of 60 char
+ * Line 3 - "Categories:" followed by a blank space; Maximum of 5
+ * Line 4 - Numeric quantity representing the # of servings (1-9999)
+ */
+void MMFExporter::writeMMFHeader( TQString &content, const Recipe &recipe )
+{
+ content += TQString( "----- Exported by Krecipes v%1 [Meal-Master Export Format] -----\n\n" ).arg( krecipes_version() );
+
+ TQString title = recipe.title;
+ title.truncate( 60 );
+ content += " Title: " + title + "\n";
+
+ int i = 0;
+ TQStringList categories;
+ for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
+ i++;
+
+ if ( i == 6 )
+ break; //maximum of 5 categories
+ categories << ( *cat_it ).name;
+ }
+ TQString cat_str = " Categories: " + categories.join( ", " );
+ cat_str.truncate( 67 );
+ content += cat_str + "\n";
+
+ content += " Servings: " + TQString::number( TQMIN( 9999, recipe.yield.amount ) ) + "\n"; //some yield info is lost here, but hey, that's the MM format
+}
+
+/* Ingredient lines:
+ * Positions 1-7 contains a numeric quantity
+ * Positions 9-10 Meal-Master unit of measure codes
+ * Positions 12-39 contain text for an ingredient name, or a "-"
+ * in position 12 and text in positions 13-39 (the latter is a
+ * "continuation" line for the previous ingredient name)
+ */
+void MMFExporter::writeMMFIngredients( TQString &content, const Recipe &recipe )
+{
+ //this format requires ingredients without a group to be written first
+ for ( IngredientList::const_iterator ing_it = recipe.ingList.begin(); ing_it != recipe.ingList.end(); ++ing_it ) {
+ if ( ( *ing_it ).groupID == -1 ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ TQValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+
+ IngredientList list_copy = recipe.ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ if ( group_list[ 0 ].groupID == -1 ) //we already handled this group
+ continue;
+
+ TQString group = group_list[ 0 ].group.left( 76 ); //just use the first's name... they're all the same
+ if ( !group.isEmpty() ) {
+ int length = group.length();
+ TQString filler_lt = TQString().fill( '-', ( 76 - length ) / 2 );
+ TQString filler_rt = ( length % 2 ) ? TQString().fill( '-', ( 76 - length ) / 2 + 1 ) : filler_lt;
+ content += filler_lt + group + filler_rt + "\n";
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ TQValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+}
+
+void MMFExporter::writeSingleIngredient( TQString &content, const Ingredient &ing, bool is_sub )
+{
+ TDEConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ //columns 1-7
+ if ( ing.amount > 0 )
+ content += MixedNumber( ing.amount ).toString( number_format, false ).rightJustify( 7, ' ', true ) + " ";
+ else
+ content += " ";
+
+ //columns 9-10
+ bool found_short_form = false;
+ for ( int i = 0; unit_info[ i ].short_form; i++ ) {
+ if ( unit_info[ i ].expanded_form == ing.units.name ||
+ unit_info[ i ].plural_expanded_form == ing.units.plural ||
+ unit_info[ i ].short_form == ing.units.name ) {
+ found_short_form = true;
+ content += TQString( unit_info[ i ].short_form ).leftJustify( 2 ) + " ";
+ break;
+ }
+ }
+ if ( !found_short_form ) {
+ kdDebug() << "Warning: unable to find Meal-Master abbreviation for: " << ing.units.name << endl;
+ kdDebug() << " This ingredient (" << ing.name << ") will be exported without a unit" << endl;
+ content += " ";
+ }
+
+ //columns 12-39
+ TQString ing_name( ing.name );
+ if ( ing.prepMethodList.count() > 0 )
+ ing_name += "; " + ing.prepMethodList.join(", ");
+
+ if ( is_sub )
+ ing_name += ", or";
+
+ if ( !found_short_form )
+ ing_name.prepend( ( ing.amount > 1 ? ing.units.plural : ing.units.name ) + " " );
+
+ //try and split the ingredient on a word boundry
+ int split_index;
+ if ( ing_name.length() > 28 ) {
+ split_index = ing_name.left(28).findRev(" ")+1;
+ if ( split_index == 0 )
+ split_index = 28;
+ }
+ else
+ split_index = 28;
+
+ content += ing_name.left(split_index) + "\n";
+
+ for ( unsigned int i = 0; i < ( ing_name.length() - 1 ) / 28; i++ ) //if longer than 28 chars, continue on next line(s)
+ content += " -" + ing_name.mid( 28 * ( i ) + split_index, 28 ) + "\n";
+}
+
+void MMFExporter::writeMMFDirections( TQString &content, const Recipe &recipe )
+{
+ TQStringList lines = TQStringList::split("\n",recipe.instructions,true);
+ for ( TQStringList::const_iterator it = lines.begin(); it != lines.end(); ++it ) {
+ content += wrapText( *it, 80 ).join( "\n" ) + "\n";
+ }
+}
+
+TQStringList MMFExporter::wrapText( const TQString& str, int at ) const
+{
+ TQStringList ret;
+ TQString copy( str );
+ bool stop = false;
+ while ( !stop ) {
+ TQString line( copy.left( at ) );
+ if ( line.length() >= copy.length() )
+ stop = true;
+ else {
+ TQRegExp rxp( "(\\s\\S*)$", false ); // last word in the new line
+ rxp.setMinimal( true ); // one word, only one word, please
+ line = line.replace( rxp, "" ); // remove last word
+ }
+ copy = copy.remove( 0, line.length() );
+ line = line.stripWhiteSpace();
+ line.prepend( " " ); // indent line by one char
+ ret << line; // output of current line
+ }
+
+ return ret;
+}
diff --git a/src/exporters/mmfexporter.h b/src/exporters/mmfexporter.h
new file mode 100644
index 0000000..6cb1d8a
--- /dev/null
+++ b/src/exporters/mmfexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* 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. *
+***************************************************************************/
+
+#ifndef MMFEXPORTER_H
+#define MMFEXPORTER_H
+
+#include "baseexporter.h"
+
+/**
+ * Export class for the Meal-Master file format
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * -Recipe photo
+ * -Authors
+ * -5 category maximum
+ * -Title is limited to 60 characters
+ * -Servings are limited to the range of 0-9999
+ * -Units are limited: If a given unit does not have a
+ * corresponding MM abbrev., otherwise it will be
+ * exported without a unit.
+ */
+class MMFExporter : public BaseExporter
+{
+public:
+ MMFExporter( const TQString&, const TQString& );
+ virtual ~MMFExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual TQString createContent( const RecipeList & );
+
+private:
+ void writeMMFHeader( TQString &content, const Recipe &recipe );
+ void writeMMFIngredients( TQString &content, const Recipe &recipe );
+ void writeSingleIngredient( TQString &content, const Ingredient &ing, bool is_sub = false );
+ void writeMMFDirections( TQString &content, const Recipe &recipe );
+
+ TQStringList wrapText( const TQString& str, int at ) const;
+};
+
+#endif //MMFEXPORTER_H
diff --git a/src/exporters/plaintextexporter.cpp b/src/exporters/plaintextexporter.cpp
new file mode 100644
index 0000000..db9902e
--- /dev/null
+++ b/src/exporters/plaintextexporter.cpp
@@ -0,0 +1,176 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* 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 "plaintextexporter.h"
+
+#include <tdeconfig.h>
+#include <tdeglobal.h>
+#include <tdelocale.h>
+
+#include "backends/recipedb.h"
+
+PlainTextExporter::PlainTextExporter( const TQString& filename, const TQString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+PlainTextExporter::~PlainTextExporter()
+{}
+
+int PlainTextExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo;
+}
+
+TQString PlainTextExporter::generateIngredient( const IngredientData &ing, MixedNumber::Format number_format )
+{
+ TDEConfig *config = TDEGlobal::config();
+
+ TQString content;
+
+ TQString amount_str = MixedNumber( ing.amount ).toString( number_format );
+
+ if ( ing.amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ing.amount + ing.amount_offset ).toString( number_format );
+ else if ( ing.amount <= 1e-10 )
+ amount_str = "";
+
+ content += amount_str;
+ if ( !amount_str.isEmpty() )
+ content += " ";
+
+ TQString unit_str = ing.units.determineName( ing.amount + ing.amount_offset, config->readBoolEntry("AbbreviateUnits") );
+
+ content += unit_str;
+ if ( !unit_str.isEmpty() )
+ content += " ";
+
+ content += ing.name;
+
+ if ( ing.prepMethodList.count() > 0 )
+ content += "; "+ing.prepMethodList.join(", ");
+
+ return content;
+}
+
+
+TQString PlainTextExporter::createContent( const RecipeList& recipes )
+{
+ TDEConfig *config = TDEGlobal::config();
+ config->setGroup( "Formatting" );
+
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ TQString content;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ content += ( *recipe_it ).title + "\n\n";
+
+ if ( ( *recipe_it ).authorList.count() > 0 ) {
+ content += TQString("%1: ").arg(i18n("Authors"));
+ content += ( *recipe_it ).authorList.join(", ");
+ content += "\n";
+ }
+
+ if ( ( *recipe_it ).categoryList.count() > 0 ) {
+ content += TQString("%1: ").arg(i18n("Categories"));
+ content += ( *recipe_it ).categoryList.join(", ");
+ content += "\n";
+ }
+
+ if ( ( *recipe_it ).yield.amount > 0 ) {
+ content += TQString("%1: ").arg(i18n("Yields"));
+ content += ( *recipe_it ).yield.toString();
+ content += "\n";
+ }
+
+ if ( !( *recipe_it ).prepTime.isNull() ) {
+ content += TQString("%1: ").arg(i18n("Preparation Time"));
+ content += ( *recipe_it ).prepTime.toString( "hh:mm" );
+ content += "\n";
+ }
+
+ content += "\n";
+
+ IngredientList list_copy = ( *recipe_it ).ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ TQString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+ if ( !group.isEmpty() )
+ content += group + ":\n";
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ if ( !group.isEmpty() )
+ content += " ";
+
+ content += generateIngredient(*ing_it,number_format);
+
+ if ( (*ing_it).substitutes.count() > 0 )
+ content += ", "+i18n("or");
+ content += "\n";
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ if ( !group.isEmpty() )
+ content += " ";
+
+ content += generateIngredient(*sub_it,number_format);
+ sub_it++;
+ if ( sub_it != (*ing_it).substitutes.end() )
+ content += ", "+i18n("or");
+ content += "\n";
+ }
+ }
+ }
+
+ content += "\n";
+
+ /// @todo add ingredient properties
+
+ content += ( *recipe_it ).instructions;
+
+ content += "\n\n";
+
+ if ( (*recipe_it).ratingList.count() > 0 )
+ content += "----------"+i18n("Ratings")+"----------\n";
+
+ for ( RatingList::const_iterator rating_it = (*recipe_it).ratingList.begin(); rating_it != (*recipe_it).ratingList.end(); ++rating_it ) {
+ if ( !( *rating_it ).rater.isEmpty() )
+ content += " "+( *rating_it ).rater+"\n";
+
+ if ( (*rating_it).ratingCriteriaList.size() > 0 )
+ content += "\n";
+
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ //FIXME: This is an ugly hack, but I don't know how else to be i18n friendly (if this is even that)
+ // and still be able to display the amount as a fraction
+ TQString starsTrans = i18n("1 star","%n stars",tqRound((*rc_it).stars));
+ starsTrans.replace(TQString::number(tqRound((*rc_it).stars)),MixedNumber((*rc_it).stars).toString());
+
+ content += " "+(*rc_it).name+": "+starsTrans+"\n";
+ }
+
+ if ( (*rating_it).ratingCriteriaList.size() > 0 )
+ content += "\n";
+
+ if ( !( *rating_it ).comment.isEmpty() )
+ content += " "+( *rating_it ).comment+"\n";
+
+ content += "\n";
+ }
+
+ if ( (*recipe_it).ratingList.size() > 0 )
+ content += "\n";
+
+ content += "-----\n\n"; //end of recipe indicator
+ }
+
+ return content;
+}
+
diff --git a/src/exporters/plaintextexporter.h b/src/exporters/plaintextexporter.h
new file mode 100644
index 0000000..1f4a727
--- /dev/null
+++ b/src/exporters/plaintextexporter.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* 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. *
+***************************************************************************/
+
+#ifndef PLAINTEXTEXPORTER_H
+#define PLAINTEXTEXPORTER_H
+
+#include "baseexporter.h"
+
+#include "datablocks/mixednumber.h"
+
+class IngredientData;
+
+/**
+ * Export class to export recipes into text format.
+ * Recipes exported with this class are not meant to be imported back
+ * into Krecipes or any other program.
+ *
+ * @author Jason Kivlighn
+ */
+class PlainTextExporter : public BaseExporter
+{
+public:
+ PlainTextExporter( const TQString&, const TQString& );
+ virtual ~PlainTextExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual TQString createContent( const RecipeList & );
+
+private:
+ TQString generateIngredient( const IngredientData &ing, MixedNumber::Format number_format );
+};
+
+#endif //PLAINTEXTEXPORTER_H
diff --git a/src/exporters/recipemlexporter.cpp b/src/exporters/recipemlexporter.cpp
new file mode 100644
index 0000000..4831004
--- /dev/null
+++ b/src/exporters/recipemlexporter.cpp
@@ -0,0 +1,195 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* 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 "recipemlexporter.h"
+
+#include <kdebug.h>
+#include <tdelocale.h>
+
+#include "backends/recipedb.h"
+
+RecipeMLExporter::RecipeMLExporter( const TQString& filename, const TQString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+RecipeMLExporter::~RecipeMLExporter()
+{}
+
+int RecipeMLExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo ^ RecipeDB::Ratings;
+}
+
+TQString RecipeMLExporter::createHeader( const RecipeList& )
+{
+ TQString xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ xml += "<!DOCTYPE recipeml PUBLIC \"-//FormatData//DTD RecipeML 0.5//EN\" \
+ \"http://www.formatdata.com/recipeml/recipeml.dtd\">";
+ xml += "<recipeml version=\"0.5\" generator=\"Krecipes v"+krecipes_version()+"\">\n";
+ return xml;
+}
+
+TQString RecipeMLExporter::createFooter()
+{
+ return "</recipeml>";
+}
+
+void RecipeMLExporter::createIngredient( TQDomElement &ing_tag, const IngredientData &ing, TQDomDocument &doc )
+{
+ TQDomElement amt_tag = doc.createElement( "amt" );
+ ing_tag.appendChild( amt_tag );
+
+ TQDomElement qty_tag = doc.createElement( "qty" );
+ amt_tag.appendChild( qty_tag );
+ if ( ing.amount_offset < 1e-10 )
+ qty_tag.appendChild( doc.createTextNode( TQString::number( ing.amount ) ) );
+ else {
+ TQDomElement range_tag = doc.createElement( "range" );
+ qty_tag.appendChild(range_tag);
+
+ TQDomElement q1_tag = doc.createElement( "q1" );
+ q1_tag.appendChild( doc.createTextNode( TQString::number( ing.amount ) ) );
+ TQDomElement q2_tag = doc.createElement( "q2" );
+ q2_tag.appendChild( doc.createTextNode( TQString::number( ing.amount + ing.amount_offset ) ) );
+
+ range_tag.appendChild(q1_tag);
+ range_tag.appendChild(q2_tag);
+ }
+
+ TQDomElement unit_tag = doc.createElement( "unit" );
+ amt_tag.appendChild( unit_tag );
+ unit_tag.appendChild( doc.createTextNode( ( ing.amount > 1 ) ? ing.units.plural : ing.units.name ) );
+
+ TQDomElement item_tag = doc.createElement( "item" );
+ item_tag.appendChild( doc.createTextNode( ing.name ) );
+ ing_tag.appendChild( item_tag );
+
+ TQDomElement prep_tag = doc.createElement( "prep" );
+ prep_tag.appendChild( doc.createTextNode( ing.prepMethodList.join(",") ) );
+ ing_tag.appendChild( prep_tag );
+}
+
+TQString RecipeMLExporter::createContent( const RecipeList& recipes )
+{
+ TQDomDocument doc;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ TQDomElement recipe_tag = doc.createElement( "recipe" );
+
+ doc.appendChild(recipe_tag);
+ //recipe_root.appendChild( recipe_tag ); //will append to either <menu> if exists or else <recipeml>
+
+ TQDomElement head_tag = doc.createElement( "head" );
+ recipe_tag.appendChild( head_tag );
+
+ TQDomElement title_tag = doc.createElement( "title" );
+ title_tag.appendChild( doc.createTextNode( ( *recipe_it ).title ) );
+ head_tag.appendChild( title_tag );
+
+ TQDomElement source_tag = doc.createElement( "source" );
+ for ( ElementList::const_iterator author_it = ( *recipe_it ).authorList.begin(); author_it != ( *recipe_it ).authorList.end(); ++author_it ) {
+ TQDomElement srcitem_tag = doc.createElement( "srcitem" );
+ srcitem_tag.appendChild( doc.createTextNode( ( *author_it ).name ) );
+ source_tag.appendChild( srcitem_tag );
+ }
+ head_tag.appendChild( source_tag );
+
+ TQDomElement categories_tag = doc.createElement( "categories" );
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ TQDomElement cat_tag = doc.createElement( "cat" );
+ cat_tag.appendChild( doc.createTextNode( ( *cat_it ).name ) );
+ categories_tag.appendChild( cat_tag );
+ }
+ head_tag.appendChild( categories_tag );
+
+ TQDomElement yield_tag = doc.createElement( "yield" );
+ if ( ( *recipe_it ).yield.amount_offset < 1e-10 )
+ yield_tag.appendChild( doc.createTextNode( TQString::number( ( *recipe_it ).yield.amount ) ) );
+ else {
+ TQDomElement range_tag = doc.createElement( "range" );
+ yield_tag.appendChild(range_tag);
+
+ TQDomElement q1_tag = doc.createElement( "q1" );
+ q1_tag.appendChild( doc.createTextNode( TQString::number(( *recipe_it ).yield.amount ) ) );
+ TQDomElement q2_tag = doc.createElement( "q2" );
+ q2_tag.appendChild( doc.createTextNode( TQString::number( ( *recipe_it ).yield.amount + ( *recipe_it ).yield.amount_offset ) ) );
+
+ range_tag.appendChild(q1_tag);
+ range_tag.appendChild(q2_tag);
+ }
+ if ( !( *recipe_it ).yield.type.isEmpty() ) {
+ TQDomElement yield_unit_tag = doc.createElement( "unit" );
+ yield_unit_tag.appendChild( doc.createTextNode(( *recipe_it ).yield.type) );
+ yield_tag.appendChild( yield_unit_tag );
+ }
+
+ head_tag.appendChild( yield_tag );
+
+ if ( !( *recipe_it ).prepTime.isNull() ) {
+ TQDomElement preptime_tag = doc.createElement( "preptime" );
+ head_tag.appendChild( preptime_tag );
+ preptime_tag.setAttribute( "type", i18n( "Total" ) );
+
+ TQDomElement preptime_time_tag = doc.createElement( "time" );
+ preptime_tag.appendChild( preptime_time_tag );
+
+ TQDomElement preptime_min_qty_tag = doc.createElement( "qty" );
+ preptime_time_tag.appendChild( preptime_min_qty_tag );
+ preptime_min_qty_tag.appendChild( doc.createTextNode( TQString::number( ( *recipe_it ).prepTime.minute() + ( *recipe_it ).prepTime.hour() * 60 ) ) );
+
+ TQDomElement preptime_min_unit_tag = doc.createElement( "timeunit" );
+ preptime_time_tag.appendChild( preptime_min_unit_tag );
+ preptime_min_unit_tag.appendChild( doc.createTextNode( "minutes" ) );
+ }
+
+ TQDomElement ingredients_tag = doc.createElement( "ingredients" );
+ IngredientList list_copy = ( *recipe_it ).ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ TQDomElement ing_root;
+
+ TQString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+ if ( !group.isEmpty() ) {
+ TQDomElement ingdiv_tag = doc.createElement( "ing-div" );
+ TQDomElement title_tag = doc.createElement( "title" );
+ title_tag.appendChild( doc.createTextNode( group ) );
+ ingdiv_tag.appendChild( title_tag );
+ ingredients_tag.appendChild( ingdiv_tag );
+ ing_root = ingdiv_tag;
+ }
+ else
+ ing_root = ingredients_tag;
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ TQDomElement ing_tag = doc.createElement( "ing" );
+ ing_root.appendChild( ing_tag );
+
+ createIngredient( ing_tag, *ing_it, doc );
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ TQDomElement alt_ing_tag = doc.createElement( "alt-ing" );
+ ing_tag.appendChild( alt_ing_tag );
+ createIngredient( alt_ing_tag, *sub_it, doc );
+ }
+ }
+ }
+ recipe_tag.appendChild( ingredients_tag );
+
+ TQDomElement directions_tag = doc.createElement( "directions" );
+ recipe_tag.appendChild( directions_tag );
+
+ TQDomElement step_tag = doc.createElement( "step" ); //we've just got everything in one step
+ directions_tag.appendChild( step_tag );
+ step_tag.appendChild( doc.createTextNode( ( *recipe_it ).instructions ) );
+ }
+
+ return doc.toString();
+}
diff --git a/src/exporters/recipemlexporter.h b/src/exporters/recipemlexporter.h
new file mode 100644
index 0000000..33b45d9
--- /dev/null
+++ b/src/exporters/recipemlexporter.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* 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. *
+***************************************************************************/
+
+#ifndef RECIPEMLEXPORTER_H
+#define RECIPEMLEXPORTER_H
+
+#include "baseexporter.h"
+
+#include <tqdom.h>
+
+class IngredientData;
+
+/**
+ * Export class for the RecipeML file format <http://www.formatdata.com/recipeml>
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * -Recipe photo
+ */
+class RecipeMLExporter : public BaseExporter
+{
+public:
+ RecipeMLExporter( const TQString&, const TQString& );
+ virtual ~RecipeMLExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual TQString createContent( const RecipeList& );
+ virtual TQString createHeader( const RecipeList& );
+ virtual TQString createFooter();
+
+private:
+ void createIngredient( TQDomElement &ing_tag, const IngredientData &, TQDomDocument &doc );
+};
+
+#endif //RECIPEMLEXPORTER_H
diff --git a/src/exporters/rezkonvexporter.cpp b/src/exporters/rezkonvexporter.cpp
new file mode 100644
index 0000000..b286b65
--- /dev/null
+++ b/src/exporters/rezkonvexporter.cpp
@@ -0,0 +1,322 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* 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 "rezkonvexporter.h"
+
+#include <tqregexp.h>
+
+#include <tdeconfig.h>
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/mixednumber.h"
+
+struct translate_unit_info
+{
+ const char *english;
+ const char *english_plural;
+ const char *german;
+ const char *german_plural;
+};
+
+static translate_unit_info translate_units[] = {
+ {"g", "g", "Gramm", "Gramms" },
+ {"ml", "ml", "ml", "ml"},
+ {"l", "l", "Ltr.", "Ltr."},
+ {"kg", "kg", "kg", "kg"},
+ {"mg", "mg", "mg", "mg"},
+ {"teaspoon", "teaspoons", "Teel.", "Teel."},
+ {"t.", "t.", "Teel.", "Teel."},
+ {"t", "t", "Teel.", "Teel."},
+ {"tsp.", "tsp.", "Teel.", "Teel."},
+ {"tsp", "tsp", "Teel.", "Teel."},
+ {"tablespoon", "tablespoons", "Essl.", "Essl."},
+ {"tbsp.", "tbsp.", "Essl.", "Essl."},
+ {"tbsp", "tbsp", "Essl.", "Essl."},
+ {"T", "T", "Essl.", "Essl."},
+ {"T.", "T.", "Essl.", "Essl."},
+ {"cup", "cups", "Tasse", "Tassen"},
+ {"c.", "c.", "Tasse", "Tassen"},
+ {"can", "cans", "Dose", "Dosen"},
+ {"drop", "drops", "Tropfen", "Tropfen"},
+ {"large", "large", "gro�", "gro�"},
+ {"medium", "medium", "mittl.", "mittl."},
+ {"small", "small", "klein.", "klein."},
+ {"pinch", "pinches", "Prise", "Prisen"},
+ {"package", "packages", "Pack.", "Pack."},
+ {"bunch", "bunches", "Bund.", "Bunde"},
+ {"stem", "stems", "Strange", "Strangen"},
+ {"twig", "twigs", "Zweig", "Zweige"},
+ {"tip of a knife", "tips of a knife", "Messersp.", "Messersp."},
+ {"sheet", "sheets", "Blatt", "Bl�tter"},
+ {"handful", "handfuls", "Handvoll", "Handvoll"},
+ {"head", "heads", "Kopf", "K�pfe"},
+ {"slice", "slices", "Scheibe", "Sheiben"},
+ {"some", "some", "Einige", "Einige"},
+ {"a little", "a little", "Etwas", "Etwas"},
+ {"little can", "little cans", "D�schen", "D�schen"},
+ {"glass", "glasses", "Glas", "Gl�ser"},
+ {"piece", "pieces", "St�ck", "St�ck"},
+ {"pot", "pots", "Topf", "Topf"},
+ {"generous", "generous", "Reichlich", "Reichlich"},
+ {"dash", "dashes", "Spritzer", "Spritzer"},
+ {"clove", "cloves", "Zehe", "Zehen"},
+ {"slice", "slices", "Platte", "Platten"},
+ {"shot", "shots", "Schuss", "Schuss"},
+ {"peduncle", "peduncles", "Stiel", "Stiele"},
+ {"heaping teaspoon", "heaping teaspoons", "geh. TL", "geh. TL"},
+ {"heaping tsp.", "heaping tsp.", "geh. TL", "geh. TL"},
+ {"heaping tsp.", "heaping tsp.", "geh. TL", "geh. TL"},
+ {"heaping tablespoon", "heaping tablespoon", "geh. EL", "geh. EL"},
+ {"heaping tbsp.", "heaping tbsp.", "geh. EL", "geh. EL"},
+#if 0
+ {"pound", "pounds", "", ""},
+ {"lb.", "lbs.", "", ""},
+ {"ounce", "ounces", "", ""},
+ {"oz.", "oz.", "", ""},
+#endif
+ {"", "", "", ""},
+ { 0, 0, 0, 0 }
+ };
+
+RezkonvExporter::RezkonvExporter( const TQString& filename, const TQString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+RezkonvExporter::~RezkonvExporter()
+{}
+
+int RezkonvExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo ^ RecipeDB::Ratings;
+}
+
+TQString RezkonvExporter::createContent( const RecipeList& recipes )
+{
+ TQString content;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ writeHeader( content, *recipe_it );
+ content += "\n";
+ writeIngredients( content, *recipe_it );
+ content += "\n";
+ writeDirections( content, *recipe_it );
+ content += "\n";
+
+ content += "=====\n\n"; //end of recipe indicator
+ }
+
+ return content;
+}
+
+/* Header:
+ * Line 1 - contains five hyphens and "Meal-Master" somewhere in the line
+ * Line 2 - "Title:" followed by a blank space; maximum of 60 char
+ * Line 3 - "Categories:" followed by a blank space; Maximum of 5
+ * Line 4 - Numeric quantity representing the # of servings (1-9999)
+ */
+void RezkonvExporter::writeHeader( TQString &content, const Recipe &recipe )
+{
+ content += TQString( "===== Exported by Krecipes v%1 [REZKONV Export Format] =====\n\n" ).arg( krecipes_version() );
+
+ TQString title = recipe.title;
+ title.truncate( 60 );
+ content += TQString("Titel: ").rightJustify( 13, ' ', true) + title + "\n";
+
+ int i = 0;
+ TQStringList categories;
+ for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
+ i++;
+
+ if ( i == 6 )
+ break; //maximum of 5 categories
+ categories << ( *cat_it ).name;
+ }
+ TQString cat_str = TQString("Kategorien: ").rightJustify( 13, ' ', true) + categories.join( ", " );
+ cat_str.truncate( 67 );
+ content += cat_str + "\n";
+
+ content += TQString("Menge: ").rightJustify( 13, ' ', true) + recipe.yield.toString() + "\n";
+}
+
+/* Ingredient lines:
+ * Positions 1-7 contains a numeric quantity
+ * Positions 9-10 Meal-Master unit of measure codes
+ * Positions 12-39 contain text for an ingredient name, or a "-"
+ * in position 12 and text in positions 13-39 (the latter is a
+ * "continuation" line for the previous ingredient name)
+ */
+void RezkonvExporter::writeIngredients( TQString &content, const Recipe &recipe )
+{
+ //this format requires ingredients without a group to be written first
+ for ( IngredientList::const_iterator ing_it = recipe.ingList.begin(); ing_it != recipe.ingList.end(); ++ing_it ) {
+ if ( ( *ing_it ).groupID == -1 ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ TQValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+
+ IngredientList list_copy = recipe.ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ if ( group_list[ 0 ].groupID == -1 ) //we already handled this group
+ continue;
+
+ TQString group = group_list[ 0 ].group.left( 76 ); //just use the first's name... they're all the same
+ if ( !group.isEmpty() ) {
+ int length = group.length();
+ TQString filler_lt = TQString().fill( '=', ( 76 - length ) / 2 );
+ TQString filler_rt = ( length % 2 ) ? TQString().fill( '=', ( 76 - length ) / 2 + 1 ) : filler_lt;
+ content += filler_lt + group + filler_rt + "\n";
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ TQValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+
+ TQString authorLines;
+ if ( recipe.authorList.count() > 0 ) {
+ content += "============================== TQUELLE ==============================\n";
+ authorLines = " "+(*recipe.authorList.begin()).name+"\n";
+ }
+ for ( ElementList::const_iterator author_it = ++recipe.authorList.begin(); author_it != recipe.authorList.end(); ++author_it ) {
+ authorLines += " -- ";
+ authorLines += (*author_it).name + "\n";
+ }
+ if ( !authorLines.isEmpty() )
+ authorLines += "\n";
+ content += authorLines;
+}
+
+void RezkonvExporter::writeSingleIngredient( TQString &content, const IngredientData &ing, bool is_sub )
+{
+ TDEConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ //columns 1-7
+ if ( ing.amount > 1e-8 ) {
+ TQString amount_str = MixedNumber( ing.amount ).toString( number_format, false );
+
+ if ( ing.amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ing.amount + ing.amount_offset ).toString( number_format, false );
+
+ if ( amount_str.length() > 7 ) { //too long, let's try the other formatting
+ MixedNumber::Format other_format = (number_format == MixedNumber::DecimalFormat) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ TQString new_amount_str = MixedNumber( ing.amount ).toString( other_format, false );
+
+ if ( ing.amount_offset > 0 )
+ new_amount_str += "-"+MixedNumber( ing.amount + ing.amount_offset ).toString( other_format, false );
+
+ if (new_amount_str.length() > 7) { //still too long, use original formatting, but truncate it
+ amount_str = amount_str.left(7);
+ kdDebug()<<"Warning: Amount text too long, truncating"<<endl;
+ }
+ }
+ content += amount_str.rightJustify( 7, ' ', true ) + " ";
+ }
+ else
+ content += TQString().fill(' ',7+1);
+
+ //columns 9-19
+ bool found_translation = false;
+ for ( int i = 0; translate_units[ i ].english; i++ ) {
+ if ( translate_units[ i ].english == ing.units.name || translate_units[ i ].english_plural == ing.units.plural || translate_units[ i ].german == ing.units.name || translate_units[ i ].german_plural == ing.units.plural ) {
+ found_translation = true;
+ TQString unit;
+ if ( ing.amount > 1 )
+ unit += translate_units[i].german;
+ else
+ unit += translate_units[i].german_plural;
+ content += unit.leftJustify( 9 ) + " ";
+ break;
+ }
+ }
+ if ( !found_translation ) {
+ kdDebug() << "Warning: unable to find German translation for: " << ing.units.name << endl;
+ kdDebug() << " This ingredient (" << ing.name << ") will be exported without a unit" << endl;
+ content += TQString().fill(' ',9+1);
+ }
+
+ //columns 21-70
+ TQString ing_name( ing.name );
+ if ( ing.prepMethodList.count() > 0 )
+ ing_name += "; " + ing.prepMethodList.join(", ");
+
+ if ( is_sub )
+ ing_name += ", or"; //FIXME: what's 'or' in German?
+
+ if ( !found_translation )
+ ing_name.prepend( ( ing.amount > 1 ? ing.units.plural : ing.units.name ) + " " );
+
+ //try and split the ingredient on a word boundry
+ int split_index;
+ if ( ing_name.length() > 50 ) {
+ split_index = ing_name.left(50).findRev(" ")+1;
+ if ( split_index == 0 )
+ split_index = 50;
+ }
+ else
+ split_index = 50;
+
+ content += ing_name.left(split_index) + "\n";
+
+ for ( unsigned int i = 0; i < ( ing_name.length() - 1 ) / 50; i++ ) //if longer than 50 chars, continue on next line(s)
+ content += TQString().fill(' ',(7+1)+(9+1)) + "-" + ing_name.mid( 50 * ( i ) + split_index, 50 ) + "\n";
+}
+
+void RezkonvExporter::writeDirections( TQString &content, const Recipe &recipe )
+{
+ TQStringList lines = TQStringList::split("\n",recipe.instructions,true);
+ for ( TQStringList::const_iterator it = lines.begin(); it != lines.end(); ++it ) {
+ content += wrapText( *it, 80 ).join( "\n" ) + "\n";
+ }
+}
+
+TQStringList RezkonvExporter::wrapText( const TQString& str, int at ) const
+{
+ TQStringList ret;
+ TQString copy( str );
+ bool stop = false;
+ while ( !stop ) {
+ TQString line( copy.left( at ) );
+ if ( line.length() >= copy.length() )
+ stop = true;
+ else {
+ TQRegExp rxp( "(\\s\\S*)$", false ); // last word in the new line
+ rxp.setMinimal( true ); // one word, only one word, please
+ line = line.replace( rxp, "" ); // remove last word
+ }
+ copy = copy.remove( 0, line.length() );
+ line = line.stripWhiteSpace();
+ line.prepend( " " ); // indent line by one char
+ ret << line; // output of current line
+ }
+
+ return ret;
+}
diff --git a/src/exporters/rezkonvexporter.h b/src/exporters/rezkonvexporter.h
new file mode 100644
index 0000000..32ffbd4
--- /dev/null
+++ b/src/exporters/rezkonvexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* 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. *
+***************************************************************************/
+
+#ifndef REZKONVEXPORTER_H
+#define REZKONVEXPORTER_H
+
+#include "baseexporter.h"
+
+/**
+ * Export class for the Rezkonv file format
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * -Recipe photo
+ * -Authors
+ * -5 category maximum
+ * -Title is limited to 60 characters
+ * -Servings are limited to the range of 0-9999
+ * -Units are limited: If a given unit does not have a
+ * corresponding MM abbrev., otherwise it will be
+ * exported without a unit.
+ */
+class RezkonvExporter : public BaseExporter
+{
+public:
+ RezkonvExporter( const TQString&, const TQString& );
+ virtual ~RezkonvExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual TQString createContent( const RecipeList & );
+
+private:
+ void writeHeader( TQString &content, const Recipe &recipe );
+ void writeIngredients( TQString &content, const Recipe &recipe );
+ void writeSingleIngredient( TQString &content, const IngredientData &ing, bool is_sub = false );
+ void writeDirections( TQString &content, const Recipe &recipe );
+
+ TQStringList wrapText( const TQString& str, int at ) const;
+};
+
+#endif //REZKONVEXPORTER_H