diff options
Diffstat (limited to 'src/exporters')
-rw-r--r-- | src/exporters/Makefile.am | 13 | ||||
-rw-r--r-- | src/exporters/baseexporter.cpp | 172 | ||||
-rw-r--r-- | src/exporters/baseexporter.h | 95 | ||||
-rw-r--r-- | src/exporters/cookmlexporter.cpp | 121 | ||||
-rw-r--r-- | src/exporters/cookmlexporter.h | 38 | ||||
-rw-r--r-- | src/exporters/htmlbookexporter.cpp | 160 | ||||
-rw-r--r-- | src/exporters/htmlbookexporter.h | 52 | ||||
-rw-r--r-- | src/exporters/htmlexporter.cpp | 570 | ||||
-rw-r--r-- | src/exporters/htmlexporter.h | 78 | ||||
-rw-r--r-- | src/exporters/kreexporter.cpp | 264 | ||||
-rw-r--r-- | src/exporters/kreexporter.h | 51 | ||||
-rw-r--r-- | src/exporters/mmfexporter.cpp | 220 | ||||
-rw-r--r-- | src/exporters/mmfexporter.h | 51 | ||||
-rw-r--r-- | src/exporters/plaintextexporter.cpp | 176 | ||||
-rw-r--r-- | src/exporters/plaintextexporter.h | 42 | ||||
-rw-r--r-- | src/exporters/recipemlexporter.cpp | 195 | ||||
-rw-r--r-- | src/exporters/recipemlexporter.h | 45 | ||||
-rw-r--r-- | src/exporters/rezkonvexporter.cpp | 322 | ||||
-rw-r--r-- | src/exporters/rezkonvexporter.h | 51 |
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("\"",""") + "</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("\"",""") + "\">\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 |