summaryrefslogtreecommitdiffstats
path: root/src/importers
diff options
context:
space:
mode:
Diffstat (limited to 'src/importers')
-rw-r--r--src/importers/Makefile.am15
-rw-r--r--src/importers/baseimporter.cpp399
-rw-r--r--src/importers/baseimporter.h126
-rw-r--r--src/importers/kredbimporter.cpp67
-rw-r--r--src/importers/kredbimporter.h38
-rw-r--r--src/importers/kreimporter.cpp309
-rw-r--r--src/importers/kreimporter.h54
-rw-r--r--src/importers/mmfimporter.cpp336
-rw-r--r--src/importers/mmfimporter.h65
-rw-r--r--src/importers/mx2importer.cpp186
-rw-r--r--src/importers/mx2importer.h46
-rw-r--r--src/importers/mxpimporter.cpp382
-rw-r--r--src/importers/mxpimporter.h45
-rw-r--r--src/importers/nycgenericimporter.cpp196
-rw-r--r--src/importers/nycgenericimporter.h44
-rw-r--r--src/importers/recipemlimporter.cpp376
-rw-r--r--src/importers/recipemlimporter.h53
-rw-r--r--src/importers/rezkonvimporter.cpp291
-rw-r--r--src/importers/rezkonvimporter.h42
19 files changed, 3070 insertions, 0 deletions
diff --git a/src/importers/Makefile.am b/src/importers/Makefile.am
new file mode 100644
index 0000000..28ab637
--- /dev/null
+++ b/src/importers/Makefile.am
@@ -0,0 +1,15 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, tqt and TDE
+INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../backends $(all_includes)
+
+noinst_LTLIBRARIES=libkrecipesimporters.la
+libkrecipesimporters_la_SOURCES = mx2importer.cpp mmfimporter.cpp mxpimporter.cpp nycgenericimporter.cpp recipemlimporter.cpp baseimporter.cpp kreimporter.cpp rezkonvimporter.cpp kredbimporter.cpp
+libkrecipesimporters_la_METASOURCES=AUTO
+
+#the library search path.
+libkrecipesimporters_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+noinst_HEADERS = kreimporter.h
diff --git a/src/importers/baseimporter.cpp b/src/importers/baseimporter.cpp
new file mode 100644
index 0000000..b0ecd61
--- /dev/null
+++ b/src/importers/baseimporter.cpp
@@ -0,0 +1,399 @@
+/***************************************************************************
+* 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 "baseimporter.h"
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kdebug.h>
+#include <tdelocale.h>
+#include <kprogress.h>
+#include <tdemessagebox.h>
+
+#include <tqvaluevector.h>
+
+#include "datablocks/recipe.h"
+#include "backends/recipedb.h"
+#include "datablocks/categorytree.h"
+#include "datablocks/unit.h"
+
+BaseImporter::BaseImporter() :
+ m_recipe_list( new RecipeList ),
+ m_cat_structure( 0 ),
+ file_recipe_count( 0 )
+{
+ TDEConfig * config = kapp->config();
+ config->setGroup( "Import" );
+
+ direct = config->readBoolEntry( "DirectImport", false );
+}
+
+BaseImporter::~BaseImporter()
+{
+ delete m_recipe_list;
+ delete m_cat_structure;
+}
+
+void BaseImporter::add( const RecipeList &recipe_list )
+{
+ file_recipe_count += recipe_list.count();
+
+ for ( RecipeList::const_iterator it = recipe_list.begin(); it != recipe_list.end(); ++it ) {
+ Recipe copy = *it;
+ copy.recipeID = -1; //make sure an importer didn't give this a value
+ for ( RatingList::iterator rating_it = copy.ratingList.begin(); rating_it != copy.ratingList.end(); ++rating_it ) {
+ (*rating_it).id = -1;
+ }
+ m_recipe_list->append( copy );
+ }
+
+ if ( direct ) {
+ if ( !m_progress_dialog->wasCancelled() )
+ importRecipes( *m_recipe_list, m_database, m_progress_dialog );
+ }
+}
+
+void BaseImporter::add( const Recipe &recipe )
+{
+ file_recipe_count++;
+ Recipe copy = recipe;
+ copy.recipeID = -1; //make sure an importer didn't give this a value
+
+ if ( direct ) {
+ if ( !m_progress_dialog->wasCancelled() ) {
+ RecipeList list;
+ list.append( recipe );
+ importRecipes( list, m_database, m_progress_dialog );
+ }
+ }
+ else
+ m_recipe_list->append( copy );
+}
+
+void BaseImporter::parseFiles( const TQStringList &filenames )
+{
+ if ( direct )
+ m_filenames = filenames;
+ else {
+ for ( TQStringList::const_iterator file_it = filenames.begin(); file_it != filenames.end(); ++file_it ) {
+ file_recipe_count = 0;
+ parseFile( *file_it );
+ processMessages( *file_it );
+ }
+ }
+}
+
+void BaseImporter::import( RecipeDB *db, bool automatic )
+{
+ if ( direct ) {
+ m_database = db;
+
+ m_progress_dialog = new KProgressDialog( kapp->mainWidget(), 0,
+ i18n( "Importing selected recipes" ), TQString::null, true );
+ KProgress *progress = m_progress_dialog->progressBar();
+ progress->setPercentageVisible(false);
+ progress->setTotalSteps( 0 );
+
+ for ( TQStringList::const_iterator file_it = m_filenames.begin(); file_it != m_filenames.end(); ++file_it ) {
+ file_recipe_count = 0;
+ parseFile( *file_it );
+ processMessages( *file_it );
+
+ if ( m_progress_dialog->wasCancelled() )
+ break;
+ }
+
+ importUnitRatios( db );
+ delete m_progress_dialog;
+ }
+ else {
+ if ( m_recipe_list->count() == 0 )
+ return;
+
+ m_recipe_list->empty();
+ //db->blockSignals(true);
+
+ m_progress_dialog = new KProgressDialog( kapp->mainWidget(), 0,
+ i18n( "Importing selected recipes" ), TQString::null, true );
+ KProgress *progress = m_progress_dialog->progressBar();
+ progress->setTotalSteps( m_recipe_list->count() );
+ progress->setFormat( i18n( "%v/%m Recipes" ) );
+
+ if ( m_cat_structure ) {
+ importCategoryStructure( db, m_cat_structure );
+ delete m_cat_structure;
+ m_cat_structure = 0;
+ }
+ importRecipes( *m_recipe_list, db, m_progress_dialog );
+ importUnitRatios( db );
+
+ //db->blockSignals(false);
+ delete m_progress_dialog; m_progress_dialog = 0;
+ }
+}
+
+void BaseImporter::importIngredient( IngredientData &ing, RecipeDB *db, KProgressDialog *progress_dialog )
+{
+ //cache some data we'll need
+ int max_units_length = db->maxUnitNameLength();
+ int max_group_length = db->maxIngGroupNameLength();
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ //create ingredient groups
+ TQString real_group_name = ing.group.left( max_group_length );
+ int new_group_id = db->findExistingIngredientGroupByName(real_group_name);
+ if ( new_group_id == -1 ) {
+ db->createNewIngGroup( real_group_name );
+ new_group_id = db->lastInsertID();
+ }
+ ing.groupID = new_group_id;
+
+ int new_ing_id = db->findExistingIngredientByName(ing.name);
+ if ( new_ing_id == -1 && !ing.name.isEmpty() )
+ {
+ db->createNewIngredient( ing.name );
+ new_ing_id = db->lastInsertID();
+ }
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ Unit real_unit( ing.units.name.left( max_units_length ), ing.units.plural.left( max_units_length ) );
+ if ( real_unit.name.isEmpty() )
+ real_unit.name = real_unit.plural;
+ else if ( real_unit.plural.isEmpty() )
+ real_unit.plural = real_unit.name;
+
+ int new_unit_id = db->findExistingUnitByName(real_unit.name);
+ if ( new_unit_id == -1 ) {
+ db->createNewUnit( Unit(real_unit.name, real_unit.plural) );
+ new_unit_id = db->lastInsertID();
+ }
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ if ( ing.prepMethodList.count() > 0 ) {
+ for ( ElementList::iterator prep_it = ing.prepMethodList.begin(); prep_it != ing.prepMethodList.end(); ++prep_it ) {
+ int new_prep_id = db->findExistingPrepByName((*prep_it).name);
+ if ( new_prep_id == -1 ) {
+ db->createNewPrepMethod((*prep_it).name);
+ new_prep_id = db->lastInsertID();
+ }
+ (*prep_it).id = new_prep_id;
+ }
+ }
+
+ ing.units.id = new_unit_id;
+ ing.ingredientID = new_ing_id;
+
+ if ( !db->ingredientContainsUnit( new_ing_id, new_unit_id ) )
+ db->addUnitToIngredient( new_ing_id, new_unit_id );
+}
+
+void BaseImporter::importRecipes( RecipeList &selected_recipes, RecipeDB *db, KProgressDialog *progress_dialog )
+{
+ // Load Current Settings
+ TDEConfig *config = kapp->config();
+ config->setGroup( "Import" );
+ bool overwrite = config->readBoolEntry( "OverwriteExisting", false );
+
+ RecipeList::iterator recipe_it; RecipeList::iterator recipe_list_end( selected_recipes.end() );
+ RecipeList::iterator recipe_it_old = selected_recipes.end();
+ for ( recipe_it = selected_recipes.begin(); recipe_it != recipe_list_end; ++recipe_it ) {
+ if ( !direct ) {
+ if ( progress_dialog->wasCancelled() ) {
+ KMessageBox::information( kapp->mainWidget(), i18n( "All recipes up unto this point have been successfully imported." ) );
+ //db->blockSignals(false);
+ return ;
+ }
+ }
+
+ if ( recipe_it_old != selected_recipes.end() )
+ selected_recipes.remove( recipe_it_old );
+
+ progress_dialog->setLabel( TQString( i18n( "Importing recipe: %1" ) ).arg( ( *recipe_it ).title ) );
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+
+ //add all recipe items (authors, ingredients, etc. to the database if they aren't already
+ IngredientList::iterator ing_list_end( ( *recipe_it ).ingList.end() );
+ for ( IngredientList::iterator ing_it = ( *recipe_it ).ingList.begin(); ing_it != ing_list_end; ++ing_it ) {
+ importIngredient( *ing_it, db, progress_dialog );
+
+ for ( TQValueList<IngredientData>::iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ importIngredient( *sub_it, db, progress_dialog );
+ }
+ }
+
+ ElementList::iterator author_list_end( ( *recipe_it ).authorList.end() );
+ for ( ElementList::iterator author_it = ( *recipe_it ).authorList.begin(); author_it != author_list_end; ++author_it ) {
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ int new_author_id = db->findExistingAuthorByName(( *author_it ).name);
+ if ( new_author_id == -1 && !( *author_it ).name.isEmpty() ) {
+ db->createNewAuthor( ( *author_it ).name );
+ new_author_id = db->lastInsertID();
+ }
+
+ ( *author_it ).id = new_author_id;
+ }
+
+ ElementList::iterator cat_list_end( ( *recipe_it ).categoryList.end() );
+ for ( ElementList::iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != cat_list_end; ++cat_it ) {
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ int new_cat_id = db->findExistingCategoryByName(( *cat_it ).name);
+ if ( new_cat_id == -1 && !( *cat_it ).name.isEmpty() ) {
+ db->createNewCategory( ( *cat_it ).name );
+ new_cat_id = db->lastInsertID();
+ }
+
+ ( *cat_it ).id = new_cat_id;
+ }
+
+ if ( !(*recipe_it).yield.type.isEmpty() ) {
+ int new_id = db->findExistingYieldTypeByName((*recipe_it).yield.type);
+ if ( new_id == -1 ) {
+ db->createNewYieldType( (*recipe_it).yield.type );
+ new_id = db->lastInsertID();
+ }
+ (*recipe_it).yield.type_id = new_id;
+ }
+
+ RatingList::iterator rating_list_end( ( *recipe_it ).ratingList.end() );
+ for ( RatingList::iterator rating_it = ( *recipe_it ).ratingList.begin(); rating_it != rating_list_end; ++rating_it ) {
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ for ( RatingCriteriaList::iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ int new_criteria_id = db->findExistingRatingByName(( *rc_it ).name);
+ if ( new_criteria_id == -1 && !( *rc_it ).name.isEmpty() ) {
+ db->createNewRating( ( *rc_it ).name );
+ new_criteria_id = db->lastInsertID();
+ }
+
+ ( *rc_it ).id = new_criteria_id;
+ }
+ }
+
+ if ( overwrite ) //overwrite existing
+ ( *recipe_it ).recipeID = db->findExistingRecipeByName( ( *recipe_it ).title );
+ else //rename
+ ( *recipe_it ).title = db->getUniqueRecipeTitle( ( *recipe_it ).title );
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ //save into the database
+ db->saveRecipe( &( *recipe_it ) );
+
+ recipe_it_old = recipe_it; //store to delete once we've got the next recipe
+ }
+}
+
+void BaseImporter::setCategoryStructure( CategoryTree *cat_structure )
+{
+ if ( direct ) {
+ importCategoryStructure( m_database, cat_structure );
+ }
+ else {
+ delete m_cat_structure;
+ m_cat_structure = cat_structure;
+ }
+}
+
+void BaseImporter::importCategoryStructure( RecipeDB *db, const CategoryTree *categoryTree )
+{
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ int new_cat_id = db->findExistingCategoryByName( child_it->category.name );
+ if ( new_cat_id == -1 ) {
+ db->createNewCategory( child_it->category.name, categoryTree->category.id );
+ new_cat_id = db->lastInsertID();
+ }
+
+ child_it->category.id = new_cat_id;
+
+ importCategoryStructure( db, child_it );
+ }
+}
+
+void BaseImporter::setUnitRatioInfo( UnitRatioList &ratioList, UnitList &unitList )
+{
+ m_ratioList = ratioList;
+ m_unitList = unitList;
+}
+
+void BaseImporter::importUnitRatios( RecipeDB *db )
+{
+ for ( UnitRatioList::const_iterator it = m_ratioList.begin(); it != m_ratioList.end(); ++it ) {
+ TQString unitName1, unitName2;
+ for ( UnitList::const_iterator unit_it = m_unitList.begin(); unit_it != m_unitList.end(); ++unit_it ) {
+ if ( ( *it ).uID1 == ( *unit_it ).id ) {
+ unitName1 = ( *unit_it ).name;
+ if ( !unitName2.isNull() )
+ break;
+ }
+ else if ( ( *it ).uID2 == ( *unit_it ).id ) {
+ unitName2 = ( *unit_it ).name;
+ if ( !unitName1.isNull() )
+ break;
+ }
+ }
+
+ int unitId1 = db->findExistingUnitByName( unitName1 );
+ int unitId2 = db->findExistingUnitByName( unitName2 );
+
+ //the unit needed for the ratio may not have been added, because the
+ //recipes chosen did not include the unit
+ if ( unitId1 != -1 && unitId2 != -1 ) {
+ UnitRatio ratio;
+ ratio.uID1 = unitId1;
+ ratio.uID2 = unitId2;
+ ratio.ratio = ( *it ).ratio;
+ db->saveUnitRatio( &ratio );
+ }
+ }
+}
+
+void BaseImporter::processMessages( const TQString &file )
+{
+ if ( m_error_msgs.count() > 0 ) {
+ //<!doc> ensures it is detected as RichText
+ m_master_error += TQString( i18n( "<!doc>Import of recipes from the file <b>\"%1\"</b> <b>failed</b> due to the following error(s):" ) ).arg( file );
+ m_master_error += "<ul><li>" + m_error_msgs.join( "</li><li>" ) + "</li></ul>";
+
+ m_error_msgs.clear();
+ }
+ else if ( m_warning_msgs.count() > 0 ) {
+ m_master_warning += TQString( i18n( "The file <b>%1</b> generated the following warning(s):" ) ).arg( file );
+ m_master_warning += "<ul><li>" + m_warning_msgs.join( "</li><li>" ) + "</li></ul>";
+
+ m_warning_msgs.clear();
+ }
+}
diff --git a/src/importers/baseimporter.h b/src/importers/baseimporter.h
new file mode 100644
index 0000000..944f50e
--- /dev/null
+++ b/src/importers/baseimporter.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro <[email protected]> *
+* 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 BASEIMPORTER_H
+#define BASEIMPORTER_H
+
+#include <tdelocale.h>
+
+#include <tqstring.h>
+#include <tqstringlist.h>
+
+#include "datablocks/recipelist.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/unitratiolist.h"
+
+class Recipe;
+class RecipeDB;
+class CategoryTree;
+class IngredientData;
+
+class KProgressDialog;
+
+/** @brief Subclass this class to create an importer for a specific file type.
+ *
+ * Subclasses should take the file name of the file to import in their constructor
+ * and then parse the file. For every recipe found in the file, a Recipe object should
+ * be created and added to the importer using the @ref add() function.
+ *
+ * @author Jason Kivlighn
+ */
+class BaseImporter
+{
+public:
+ BaseImporter();
+ virtual ~BaseImporter();
+
+ TQString getMessages() const
+ {
+ return m_master_error + m_master_warning;
+ }
+ TQString getErrorMsg() const
+ {
+ return m_master_error;
+ }
+ TQString getWarningMsg() const
+ {
+ return m_master_warning;
+ }
+
+ void parseFiles( const TQStringList &filenames );
+
+ /** Import all the recipes into the given database. These recipes are the
+ * recipes added to this class by a subclass using the @ref add() method.
+ */
+ void import( RecipeDB *db, bool automatic = false );
+
+ RecipeList recipeList() const { return *m_recipe_list; }
+ void setRecipeList( const RecipeList &list ) { *m_recipe_list = list; }
+
+ const CategoryTree *categoryStructure() const { return m_cat_structure; }
+
+protected:
+ virtual void parseFile( const TQString &filename ) = 0;
+
+ void importRecipes( RecipeList &selected_recipes, RecipeDB *db, KProgressDialog *progess_dialog );
+
+ /** Add a recipe to be imported into the database */
+ void add( const Recipe &recipe );
+ void add( const RecipeList &recipe_list );
+
+ void setCategoryStructure( CategoryTree *cat_structure );
+ void setUnitRatioInfo( UnitRatioList &ratioList, UnitList &unitList );
+
+ int totalCount() const
+ {
+ return m_recipe_list->count();
+ }
+ int fileRecipeCount() const
+ {
+ return file_recipe_count;
+ }
+
+ void setErrorMsg( const TQString & s )
+ {
+ m_error_msgs.append( s );
+ }
+ void addWarningMsg( const TQString & s )
+ {
+ m_warning_msgs.append( s );
+ }
+
+private:
+ void importCategoryStructure( RecipeDB *, const CategoryTree * );
+ void importUnitRatios( RecipeDB * );
+ void importIngredient( IngredientData &ing, RecipeDB *db, KProgressDialog *progress_dialog );
+
+ void processMessages( const TQString &file );
+
+ RecipeList *m_recipe_list;
+ CategoryTree *m_cat_structure;
+ UnitRatioList m_ratioList;
+ UnitList m_unitList;
+
+ TQStringList m_warning_msgs;
+ TQStringList m_error_msgs;
+ TQString m_master_warning;
+ TQString m_master_error;
+
+ int file_recipe_count;
+ bool direct;
+
+ RecipeDB *m_database;
+ KProgressDialog *m_progress_dialog;
+ TQStringList m_filenames;
+};
+
+#endif //BASEIMPORTER_H
diff --git a/src/importers/kredbimporter.cpp b/src/importers/kredbimporter.cpp
new file mode 100644
index 0000000..ebb4d3c
--- /dev/null
+++ b/src/importers/kredbimporter.cpp
@@ -0,0 +1,67 @@
+/***************************************************************************
+* Copyright (C) 2004 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 "kredbimporter.h"
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kdebug.h>
+
+#include "datablocks/recipelist.h"
+#include "datablocks/categorytree.h"
+#include "backends/recipedb.h"
+
+KreDBImporter::KreDBImporter( const TQString &_dbType, const TQString &_host, const TQString &_user, const TQString &_pass, int _port ) : BaseImporter(),
+ dbType( _dbType ),
+ host( _host ),
+ user( _user ),
+ pass( _pass ),
+ port( _port )
+{}
+
+KreDBImporter::~KreDBImporter()
+{}
+
+void KreDBImporter::parseFile( const TQString &file ) //this is either a database file or a database table
+{
+ RecipeDB * database = RecipeDB::createDatabase( dbType, host, user, pass, file, port, file ); //uses 'file' as either table or file name, depending on the database
+
+ if ( database ) {
+ database->connect( false ); //don't create the database if it fails to connect
+
+ if ( database->ok() ) {
+ //set the category structure
+ CategoryTree * tree = new CategoryTree;
+ database->loadCategories( tree );
+ setCategoryStructure( tree );
+
+ #if 0
+ //set unit ratios
+ UnitRatioList ratioList;
+ UnitList unitList;
+ database->loadUnitRatios( &ratioList );
+ database->loadUnits( &unitList );
+
+ setUnitRatioInfo( ratioList, unitList );
+ #endif
+
+ //now load recipes
+ RecipeList recipes;
+ database->loadRecipes( &recipes, RecipeDB::All ^ RecipeDB::Properties );
+
+ //now add these recipes to the importer
+ add( recipes );
+ }
+ else
+ setErrorMsg( database->err() );
+ }
+
+ delete database;
+}
diff --git a/src/importers/kredbimporter.h b/src/importers/kredbimporter.h
new file mode 100644
index 0000000..1afc8e8
--- /dev/null
+++ b/src/importers/kredbimporter.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+* Copyright (C) 2004 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 KREDBIMPORTER_H
+#define KREDBIMPORTER_H
+
+#include <tqstring.h>
+
+#include "baseimporter.h"
+
+/** Class to import recipes from any other Krecipes database backend.
+ * Note: Though independant of database type, the two databases must have the same structure (i.e. be the same version)
+ * @author Jason Kivlighn
+ */
+class KreDBImporter : public BaseImporter
+{
+public:
+ KreDBImporter( const TQString &dbType, const TQString &host = TQString::null, const TQString &user = TQString::null, const TQString &pass = TQString::null, int port = 0 );
+ virtual ~KreDBImporter();
+
+private:
+ virtual void parseFile( const TQString &file_or_table );
+
+ TQString dbType;
+ TQString host;
+ TQString user;
+ TQString pass;
+ int port;
+};
+
+#endif //KREDBIMPORTER_H
diff --git a/src/importers/kreimporter.cpp b/src/importers/kreimporter.cpp
new file mode 100644
index 0000000..2e55589
--- /dev/null
+++ b/src/importers/kreimporter.cpp
@@ -0,0 +1,309 @@
+/***************************************************************************
+* 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 "kreimporter.h"
+
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include <tqfile.h>
+#include <tqstringlist.h>
+#include <kstandarddirs.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/categorytree.h"
+
+KreImporter::KreImporter() : BaseImporter()
+{}
+
+void KreImporter::parseFile( const TQString &filename )
+{
+ TQFile * file = 0;
+ bool unlink = false;
+ kdDebug() << "loading file: %s" << filename << endl;
+
+ if ( filename.right( 4 ) == ".kre" ) {
+ file = new TQFile( filename );
+ kdDebug() << "file is an archive" << endl;
+ KTar* kre = new KTar( filename, "application/x-gzip" );
+ kre->open( IO_ReadOnly );
+ const KArchiveDirectory* dir = kre->directory();
+ TQString name;
+ TQStringList fileList = dir->entries();
+ for ( TQStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it ) {
+ if ( ( *it ).right( 6 ) == ".kreml" ) {
+ name = *it;
+ }
+ }
+ if ( name.isEmpty() ) {
+ kdDebug() << "error: Archive doesn't contain a valid Krecipes file" << endl;
+ setErrorMsg( i18n( "Archive does not contain a valid Krecipes file" ) );
+ return ;
+ }
+ TQString tmp_dir = locateLocal( "tmp", "" );
+ dir->copyTo( tmp_dir );
+ file = new TQFile( tmp_dir + name );
+ kre->close();
+ unlink = true; //remove file after import
+ }
+ else {
+ file = new TQFile( filename );
+ }
+
+ if ( file->open( IO_ReadOnly ) ) {
+ kdDebug() << "file opened" << endl;
+ TQDomDocument doc;
+ TQString error;
+ int line;
+ int column;
+ if ( !doc.setContent( file, &error, &line, &column ) ) {
+ kdDebug() << TQString( "error: \"%1\" at line %2, column %3" ).arg( error ).arg( line ).arg( column ) << endl;
+ setErrorMsg( TQString( i18n( "\"%1\" at line %2, column %3" ) ).arg( error ).arg( line ).arg( column ) );
+ return ;
+ }
+
+ TQDomElement kreml = doc.documentElement();
+
+ if ( kreml.tagName() != "krecipes" ) {
+ setErrorMsg( i18n( "This file does not appear to be a *.kreml file" ) );
+ return ;
+ }
+
+ // TODO Check if there are changes between versions
+ TQString kreVersion = kreml.attribute( "version" );
+ kdDebug() << TQString( i18n( "KreML version %1" ) ).arg( kreVersion ) << endl;
+
+ TQDomNodeList r = kreml.childNodes();
+ TQDomElement krecipe;
+
+ for ( unsigned z = 0; z < r.count(); z++ ) {
+ krecipe = r.item( z ).toElement();
+ TQDomNodeList l = krecipe.childNodes();
+ if ( krecipe.tagName() == "krecipes-recipe" ) {
+ Recipe recipe;
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ if ( el.tagName() == "krecipes-description" ) {
+ readDescription( el.childNodes(), &recipe );
+ }
+ if ( el.tagName() == "krecipes-ingredients" ) {
+ readIngredients( el.childNodes(), &recipe );
+ }
+ if ( el.tagName() == "krecipes-instructions" ) {
+ recipe.instructions = el.text().stripWhiteSpace();
+ }
+ if ( el.tagName() == "krecipes-ratings" ) {
+ readRatings( el.childNodes(), &recipe );
+ }
+ }
+ add
+ ( recipe );
+ }
+ else if ( krecipe.tagName() == "krecipes-category-structure" ) {
+ CategoryTree * tree = new CategoryTree;
+ readCategoryStructure( l, tree );
+ setCategoryStructure( tree );
+ }
+ }
+ }
+ if ( unlink ) {
+ file->remove
+ ();
+ }
+}
+
+KreImporter::~KreImporter()
+{
+}
+
+void KreImporter::readCategoryStructure( const TQDomNodeList& l, CategoryTree *tree )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+
+ TQString category = el.attribute( "name" );
+ CategoryTree *child_node = tree->add
+ ( Element( category ) );
+ readCategoryStructure( el.childNodes(), child_node );
+ }
+}
+
+void KreImporter::readDescription( const TQDomNodeList& l, Recipe *recipe )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ if ( el.tagName() == "title" ) {
+ recipe->title = el.text();
+ kdDebug() << "Found title: " << recipe->title << endl;
+ }
+ else if ( el.tagName() == "author" ) {
+ kdDebug() << "Found author: " << el.text() << endl;
+ recipe->authorList.append( Element( el.text() ) );
+ }
+ else if ( el.tagName() == "serving" ) { //### Keep for < 0.9 compatibility
+ recipe->yield.amount = el.text().toInt();
+ }
+ else if ( el.tagName() == "yield" ) {
+ TQDomNodeList yield_children = el.childNodes();
+ for ( unsigned j = 0; j < yield_children.count(); j++ ) {
+ TQDomElement y = yield_children.item( j ).toElement();
+ if ( y.tagName() == "amount" )
+ readAmount(y,recipe->yield.amount,recipe->yield.amount_offset);
+ else if ( y.tagName() == "type" )
+ recipe->yield.type = y.text();
+ }
+ }
+ else if ( el.tagName() == "preparation-time" ) {
+ recipe->prepTime = TQTime::fromString( el.text() );
+ }
+ else if ( el.tagName() == "category" ) {
+ TQDomNodeList categories = el.childNodes();
+ for ( unsigned j = 0; j < categories.count(); j++ ) {
+ TQDomElement c = categories.item( j ).toElement();
+ if ( c.tagName() == "cat" ) {
+ kdDebug() << "Found category: " << TQString( c.text() ).stripWhiteSpace() << endl;
+ recipe->categoryList.append( Element( TQString( c.text() ).stripWhiteSpace() ) );
+ }
+ }
+ }
+ else if ( el.tagName() == "pictures" ) {
+ if ( el.hasChildNodes() ) {
+ TQDomNodeList pictures = el.childNodes();
+ for ( unsigned j = 0; j < pictures.count(); j++ ) {
+ TQDomElement pic = pictures.item( j ).toElement();
+ TQCString decodedPic;
+ if ( pic.tagName() == "pic" )
+ kdDebug() << "Found photo" << endl;
+ TQPixmap pix;
+ KCodecs::base64Decode( TQCString( pic.text().latin1() ), decodedPic );
+ int len = decodedPic.size();
+ TQByteArray picData( len );
+ memcpy( picData.data(), decodedPic.data(), len );
+ bool ok = pix.loadFromData( picData, "JPEG" );
+ if ( ok ) {
+ recipe->photo = pix;
+ }
+ }
+ }
+ }
+ }
+}
+
+void KreImporter::readIngredients( const TQDomNodeList& l, Recipe *recipe, const TQString &header, Ingredient *ing )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ if ( el.tagName() == "ingredient" ) {
+ TQDomNodeList ingredient = el.childNodes();
+ Ingredient new_ing;
+ for ( unsigned j = 0; j < ingredient.count(); j++ ) {
+ TQDomElement ing = ingredient.item( j ).toElement();
+ if ( ing.tagName() == "name" ) {
+ new_ing.name = TQString( ing.text() ).stripWhiteSpace();
+ kdDebug() << "Found ingredient: " << new_ing.name << endl;
+ }
+ else if ( ing.tagName() == "amount" ) {
+ readAmount(ing,new_ing.amount,new_ing.amount_offset);
+ }
+ else if ( ing.tagName() == "unit" ) {
+ new_ing.units = Unit( ing.text().stripWhiteSpace(), new_ing.amount+new_ing.amount_offset );
+ }
+ else if ( ing.tagName() == "prep" ) {
+ new_ing.prepMethodList = ElementList::split(",",TQString( ing.text() ).stripWhiteSpace());
+ }
+ else if ( ing.tagName() == "substitutes" ) {
+ readIngredients(ing.childNodes(), recipe, header, &new_ing);
+ }
+ }
+ new_ing.group = header;
+
+ if ( !ing )
+ recipe->ingList.append( new_ing );
+ else
+ ing->substitutes.append( new_ing );
+ }
+ else if ( el.tagName() == "ingredient-group" ) {
+ readIngredients( el.childNodes(), recipe, el.attribute( "name" ) );
+ }
+ }
+}
+
+void KreImporter::readAmount( const TQDomElement& amountEl, double &amount, double &amount_offset )
+{
+ TQDomNodeList children = amountEl.childNodes();
+
+ double min = 0,max = 0;
+ for ( unsigned i = 0; i < children.count(); i++ ) {
+ TQDomElement child = children.item( i ).toElement();
+ if ( child.tagName() == "min" ) {
+ min = ( TQString( child.text() ).stripWhiteSpace() ).toDouble();
+ }
+ else if ( child.tagName() == "max" )
+ max = ( TQString( child.text() ).stripWhiteSpace() ).toDouble();
+ else if ( child.tagName().isEmpty() )
+ min = ( TQString( amountEl.text() ).stripWhiteSpace() ).toDouble();
+ }
+
+ amount = min;
+ if ( max > 0 )
+ amount_offset = max-min;
+}
+
+void KreImporter::readRatings( const TQDomNodeList& l, Recipe *recipe )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement child = l.item( i ).toElement();
+ if ( child.tagName() == "rating" ) {
+ Rating r;
+
+ TQDomNodeList ratingChildren = child.childNodes();
+ for ( unsigned j = 0; j < ratingChildren.count(); j++ ) {
+ TQDomElement ratingChild = ratingChildren.item( j ).toElement();
+ if ( ratingChild.tagName() == "comment" ) {
+ r.comment = ratingChild.text();
+ }
+ else if ( ratingChild.tagName() == "rater" ) {
+ r.rater = ratingChild.text();
+ }
+ else if ( ratingChild.tagName() == "criterion" ) {
+ readCriterion(ratingChild.childNodes(),r.ratingCriteriaList);
+ }
+ }
+ recipe->ratingList.append(r);
+ }
+ }
+}
+
+void KreImporter::readCriterion( const TQDomNodeList& l, RatingCriteriaList &rc_list )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement child = l.item( i ).toElement();
+
+ if ( child.tagName() == "criteria" ) {
+ RatingCriteria rc;
+
+ TQDomNodeList criteriaChildren = child.childNodes();
+ for ( unsigned j = 0; j < criteriaChildren.count(); j++ ) {
+ TQDomElement criteriaChild = criteriaChildren.item( j ).toElement();
+
+ if ( criteriaChild.tagName() == "name" ) {
+ rc.name = criteriaChild.text();
+ }
+ else if ( criteriaChild.tagName() == "stars" ) {
+ rc.stars = criteriaChild.text().toDouble();
+ }
+ }
+ rc_list.append(rc);
+ }
+ }
+}
diff --git a/src/importers/kreimporter.h b/src/importers/kreimporter.h
new file mode 100644
index 0000000..af774d2
--- /dev/null
+++ b/src/importers/kreimporter.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+* 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 KREIMPORTER_H
+#define KREIMPORTER_H
+
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+#include <kmdcodec.h>
+#include <ktar.h>
+#include <tdetempfile.h>
+
+#include <tqfile.h>
+#include <tqstringlist.h>
+#include <tqdom.h>
+
+#include "baseimporter.h"
+
+#include "datablocks/recipe.h"
+
+class Recipe;
+class CategoryTree;
+
+/**
+Import for Krecipes native file format (.kre, .kreml)
+
+@author Cyril Bosselut, Jason Kivlighn
+*/
+class KreImporter: public BaseImporter
+{
+public:
+ KreImporter();
+ virtual ~KreImporter();
+
+private:
+ void parseFile( const TQString& filename );
+
+private:
+ void readCategoryStructure( const TQDomNodeList& l, CategoryTree *tree );
+ void readDescription( const TQDomNodeList& l, Recipe* );
+ void readIngredients( const TQDomNodeList& l, Recipe*, const TQString &header = TQString::null, Ingredient *ing = 0 );
+ void readAmount( const TQDomElement& amount1, double &amount2, double &amount_offset );
+ void readRatings( const TQDomNodeList&, Recipe * );
+ void readCriterion( const TQDomNodeList&, RatingCriteriaList &r );
+};
+
+#endif
diff --git a/src/importers/mmfimporter.cpp b/src/importers/mmfimporter.cpp
new file mode 100644
index 0000000..5487d2b
--- /dev/null
+++ b/src/importers/mmfimporter.cpp
@@ -0,0 +1,336 @@
+/***************************************************************************
+* 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 "mmfimporter.h"
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include <tqfile.h>
+#include <tqregexp.h>
+#include <tqtextstream.h>
+#include <tqstringlist.h>
+
+#include "datablocks/mixednumber.h"
+#include "datablocks/recipe.h"
+#include "mmdata.h"
+
+//TODO: pre-parse file and try to correct alignment errors in ingredients?
+
+MMFImporter::MMFImporter() : BaseImporter()
+{}
+
+MMFImporter::~MMFImporter()
+{}
+
+void MMFImporter::parseFile( const TQString &file )
+{
+ resetVars();
+
+ TQFile input( file );
+
+ if ( input.open( IO_ReadOnly ) ) {
+ TQTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ TQString line;
+ while ( !stream.atEnd() ) {
+ line = stream.readLine();
+
+ if ( line.startsWith( "MMMMM" ) ) {
+ version = VersionMMMMM;
+ importMMF( stream );
+ }
+ else if ( line.contains( "Recipe Extracted from Meal-Master (tm) Database" ) ) {
+ version = FromDatabase;
+ importMMF( stream );
+ }
+ else if ( line.startsWith( "-----" ) ) {
+ version = VersionNormal;
+ importMMF( stream );
+ }
+ else if ( line.startsWith( "MM" ) ) {
+ version = VersionBB;
+ ( void ) stream.readLine();
+ importMMF( stream );
+ }
+
+ stream.skipWhiteSpace();
+ }
+
+ if ( fileRecipeCount() == 0 )
+ addWarningMsg( i18n( "No recipes found in this file." ) );
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+void MMFImporter::importMMF( TQTextStream &stream )
+{
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ TQString current;
+
+ //===============FIXED FORMAT================//
+ //line 1: title
+ //line 2: categories (comma or space separated)
+ //line 3: yield (number followed by label)
+
+ //title
+ stream.skipWhiteSpace();
+ current = stream.readLine();
+ m_title = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ kdDebug() << "Found title: " << m_title << endl;
+
+ //categories
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ const char separator = ( version == FromDatabase ) ? ' ' : ',';
+ TQStringList categories = TQStringList::split( separator, current.mid( current.find( ":" ) + 1, current.length() ) );
+ for ( TQStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat;
+ new_cat.name = TQString( *it ).stripWhiteSpace();
+ kdDebug() << "Found category: " << new_cat.name << endl;
+ m_categories.append( new_cat );
+ }
+
+ //servings
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ if ( current.startsWith( "Yield:" ) ) {
+ //get the number between the ":" and the next space after it
+ m_servings = current.mid( current.find( ":" ) + 1,
+ current.find( " ", current.find( ":" ) + 2 ) - current.find( ":" ) ).toInt();
+ kdDebug() << "Found yield: " << m_servings << endl;
+ }
+ else if ( current.startsWith( "Servings:" ) ) //from database version
+ {
+ m_servings = current.mid( current.find( ":" ) + 1, current.length() ).toInt();
+ kdDebug() << "Found servings: " << m_servings << endl;
+ }
+
+ //=======================VARIABLE FORMAT===================//
+ //read lines until ending is found
+ //each line is either an ingredient, ingredient header, or instruction
+ bool instruction_found = false;
+ bool is_sub = false;
+
+ ( void ) stream.readLine();
+ current = stream.readLine();
+ while ( current.stripWhiteSpace() != "MMMMM" &&
+ current.stripWhiteSpace() != "-----" &&
+ current.stripWhiteSpace() != "-----------------------------------------------------------------------------" &&
+ !stream.atEnd() ) {
+ bool col_one_used = loadIngredientLine( current.left( 41 ), m_left_col_ing, is_sub );
+ if ( col_one_used ) //only check for second column if there is an ingredient in the first column
+ loadIngredientLine( current.mid( 41, current.length() ), m_right_col_ing, is_sub );
+
+ if ( instruction_found && col_one_used ) {
+ addWarningMsg( TQString( i18n( "While loading recipe <b>%1</b> "
+ "an ingredient line was found after the directions. "
+ "While this is valid, it most commonly indicates an incorrectly "
+ "formatted recipe." ) ).arg( m_title ) );
+ }
+
+ if ( !col_one_used &&
+ !loadIngredientHeader( current.stripWhiteSpace() ) ) {
+ if ( !current.stripWhiteSpace().isEmpty() )
+ instruction_found = true;
+ m_instructions += current.stripWhiteSpace() + "\n";
+ //kdDebug()<<"Found instruction line: "<<current.stripWhiteSpace()<<endl;
+ }
+
+ current = stream.readLine();
+ }
+ m_instructions = m_instructions.stripWhiteSpace();
+ //kdDebug()<<"Found instructions: "<<m_instructions<<endl;
+
+ putDataInRecipe();
+}
+
+bool MMFImporter::loadIngredientLine( const TQString &string, IngredientList &list, bool &is_sub )
+{
+ //just ignore an empty line
+ if ( string.stripWhiteSpace().isEmpty() )
+ return false;
+
+ Ingredient new_ingredient;
+ new_ingredient.amount = 0; //amount not required, so give default of 0
+
+ if ( string.at( 11 ) == "-" && string.mid( 0, 11 ).stripWhiteSpace().isEmpty() && !list.isEmpty() ) //continuation of previous ingredient
+ {
+ //kdDebug()<<"Appending to last ingredient in column: "<<string.stripWhiteSpace().mid(1,string.length())<<endl;
+ ( *list.at( list.count() - 1 ) ).name += " " + string.stripWhiteSpace().mid( 1, string.length() );
+ TQString name = ( *list.at( list.count() - 1 ) ).name;
+
+ if ( name.endsWith(", or") ) {
+ ( *list.at( list.count() - 1 ) ).name = name.left(name.length()-4);
+ is_sub = true;
+ }
+ else
+ is_sub = false;
+
+ return true;
+ }
+
+ //amount
+ if ( !string.mid( 0, 7 ).stripWhiteSpace().isEmpty() ) {
+ bool ok;
+ MixedNumber amount = MixedNumber::fromString( string.mid( 0, 7 ).stripWhiteSpace(), &ok, false );
+ if ( !ok )
+ return false;
+ else
+ new_ingredient.amount = amount.toDouble();
+ }
+
+ //amount/unit separator
+ if ( string[ 7 ] != ' ' )
+ return false;
+
+ //unit
+ if ( !string.mid( 8, 2 ).stripWhiteSpace().isEmpty() ) {
+ bool is_unit = false;
+ TQString unit( string.mid( 8, 2 ).stripWhiteSpace() );
+ for ( int i = 0; unit_info[ i ].short_form; i++ ) {
+ if ( unit_info[ i ].short_form == unit ) {
+ is_unit = true;
+ if ( new_ingredient.amount <= 1 )
+ unit = unit_info[ i ].expanded_form;
+ else
+ unit = unit_info[ i ].plural_expanded_form;
+
+ break;
+ }
+ }
+ if ( !is_unit ) { /*This gives too many false warnings...
+ addWarningMsg( TQString(i18n("Unit \"%1\" not recognized. "
+ "Used in the context of \"%2\". If this shouldn't be an ingredient line (i.e. is part of the instructions), "
+ "then you can safely ignore this warning, and the recipe will be correctly imported.")).arg(unit).arg(string.stripWhiteSpace()) );*/
+ return false;
+ }
+
+ if ( int(new_ingredient.amount) > 1 )
+ new_ingredient.units.plural = unit;
+ else
+ new_ingredient.units.name = unit;
+ }
+
+ //unit/name separator
+ if ( string[ 10 ] != ' ' || string[ 11 ] == ' ' )
+ return false;
+
+ //name and preparation method
+ new_ingredient.name = string.mid( 11, 41 ).stripWhiteSpace();
+
+ //put in the header... it there is no header, current_header will be TQString::null
+ new_ingredient.group = current_header;
+
+ bool last_is_sub = is_sub;
+ if ( new_ingredient.name.endsWith(", or") ) {
+ new_ingredient.name = new_ingredient.name.left(new_ingredient.name.length()-4);
+ is_sub = true;
+ }
+ else
+ is_sub = false;
+
+ if ( last_is_sub )
+ ( *list.at( list.count() - 1 ) ).substitutes.append(new_ingredient);
+ else
+ list.append( new_ingredient );
+
+ //if we made it this far it is an ingredient line
+ return true;
+}
+
+bool MMFImporter::loadIngredientHeader( const TQString &string )
+{
+ if ( ( string.startsWith( "-----" ) || string.startsWith( "MMMMM" ) ) &&
+ string.length() >= 40 &&
+ ( ( string.at( string.length() / 2 ) != "-" ) ||
+ ( string.at( string.length() / 2 + 1 ) != "-" ) ||
+ ( string.at( string.length() / 2 - 1 ) != "-" ) ) ) {
+ TQString header( string.stripWhiteSpace() );
+
+ //get only the header name
+ header.remove( TQRegExp( "^MMMMM" ) );
+ header.remove( TQRegExp( "^-*" ) ).remove( TQRegExp( "-*$" ) );
+
+ kdDebug() << "found ingredient header: " << header << endl;
+
+ //merge all columns before appending to full ingredient list to maintain the ingredient order
+ for ( IngredientList::iterator ing_it = m_left_col_ing.begin(); ing_it != m_left_col_ing.end(); ++ing_it ) {
+ m_all_ing.append( *ing_it );
+ }
+ m_left_col_ing.empty();
+
+ for ( IngredientList::iterator ing_it = m_right_col_ing.begin(); ing_it != m_right_col_ing.end(); ++ing_it ) {
+ m_all_ing.append( *ing_it );
+ }
+ m_right_col_ing.empty();
+
+ current_header = header;
+ return true;
+ }
+ else
+ return false;
+}
+
+void MMFImporter::putDataInRecipe()
+{
+ for ( IngredientList::const_iterator ing_it = m_left_col_ing.begin(); ing_it != m_left_col_ing.end(); ++ing_it )
+ m_all_ing.append( *ing_it );
+ for ( IngredientList::const_iterator ing_it = m_right_col_ing.begin(); ing_it != m_right_col_ing.end(); ++ing_it )
+ m_all_ing.append( *ing_it );
+
+ for ( IngredientList::iterator ing_it = m_all_ing.begin(); ing_it != m_all_ing.end(); ++ing_it ) {
+ TQString name_and_prep = ( *ing_it ).name;
+ int separator_index = name_and_prep.find( TQRegExp( "(;|,)" ) );
+ if ( separator_index != -1 ) {
+ ( *ing_it ).name = name_and_prep.mid( 0, separator_index ).stripWhiteSpace();
+ ( *ing_it ).prepMethodList = ElementList::split(",",name_and_prep.mid( separator_index + 1, name_and_prep.length() ).stripWhiteSpace() );
+ }
+ }
+
+ //create the recipe
+ Recipe new_recipe;
+ new_recipe.yield.amount = m_servings;
+ new_recipe.yield.type = i18n("servings");
+ new_recipe.title = m_title;
+ new_recipe.instructions = m_instructions;
+ new_recipe.ingList = m_all_ing;
+ new_recipe.categoryList = m_categories;
+ new_recipe.authorList = m_authors;
+ new_recipe.recipeID = -1;
+
+ //put it in the recipe list
+ add
+ ( new_recipe );
+
+ //reset for the next recipe to use these variables
+ resetVars();
+}
+
+void MMFImporter::resetVars()
+{
+ m_left_col_ing.empty();
+ m_right_col_ing.empty();
+ m_all_ing.empty();
+ m_authors.clear();
+ m_categories.clear();
+
+ m_servings = 0;
+
+ m_title = TQString::null;
+ m_instructions = TQString::null;
+
+ current_header = TQString::null;
+}
+
diff --git a/src/importers/mmfimporter.h b/src/importers/mmfimporter.h
new file mode 100644
index 0000000..8e7d0d2
--- /dev/null
+++ b/src/importers/mmfimporter.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+* 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 MMFIMPORTER_H
+#define MMFIMPORTER_H
+
+#include <tqstring.h>
+
+#include "baseimporter.h"
+#include "datablocks/ingredientlist.h"
+#include "datablocks/elementlist.h"
+
+/** Class to import Meal-Master's MMF (Meal-Master Format) file format.
+ * @author Jason Kivlighn
+ */
+class MMFImporter : public BaseImporter
+{
+public:
+ MMFImporter();
+ virtual ~MMFImporter();
+
+private:
+ enum FormatVersion { FromDatabase, VersionMMMMM, VersionBB, VersionNormal };
+
+ virtual void parseFile( const TQString &filename );
+
+ void importMMF( TQTextStream &stream );
+
+ /** Parses the line and save it if the line is a valid ingredient and return true.
+ * Returns false if not an ingredient.
+ */
+ bool loadIngredientLine( const TQString &, IngredientList &, bool &is_sub );
+
+ /** Parses the line and save it if the line is a valid ingredient header and return true.
+ * Returns false if not an ingredient header.
+ */
+ bool loadIngredientHeader( const TQString & );
+
+ void resetVars();
+ void putDataInRecipe();
+
+ int m_servings;
+
+ TQString m_instructions;
+ TQString m_title;
+
+ ElementList m_authors;
+ ElementList m_categories;
+ IngredientList m_left_col_ing;
+ IngredientList m_right_col_ing;
+ IngredientList m_all_ing;
+
+ FormatVersion version;
+
+ TQString current_header;
+};
+
+#endif //MMFIMPORTER_H
diff --git a/src/importers/mx2importer.cpp b/src/importers/mx2importer.cpp
new file mode 100644
index 0000000..75f75a7
--- /dev/null
+++ b/src/importers/mx2importer.cpp
@@ -0,0 +1,186 @@
+/*
+Copyright (C) 2003 Richard L�rk�ng
+Copyright (C) 2003 Jason Kivlighn
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA
+*/
+
+#include "mx2importer.h"
+
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include <tqfile.h>
+#include <tqstringlist.h>
+#include <tqtextstream.h>
+#include <tqdatetime.h>
+
+#include "datablocks/recipe.h"
+
+
+MX2Importer::MX2Importer()
+{}
+
+void MX2Importer::parseFile( const TQString& filename )
+{
+ TQFile file( filename );
+ kdDebug() << "loading file: " << filename << endl;
+ if ( file.open( IO_ReadOnly ) ) {
+ kdDebug() << "file opened" << endl;
+ TQDomDocument doc;
+
+ //hopefully a temporary hack, since MasterCook creates invalid xml declarations
+ TQTextStream stream( &file );
+ TQString all_data = stream.read();
+ if ( all_data.startsWith( "<?xml" ) )
+ all_data.remove( 0, all_data.find( "?>" ) + 2 );
+
+ TQString error;
+ int line;
+ int column;
+ if ( !doc.setContent( all_data, &error, &line, &column ) ) {
+ kdDebug() << TQString( "error: \"%1\" at line %2, column %3" ).arg( error ).arg( line ).arg( column ) << endl;
+ setErrorMsg( TQString( i18n( "\"%1\" at line %2, column %3. This may not be a *.mx2 file." ) ).arg( error ).arg( line ).arg( column ) );
+ return ;
+ }
+
+ TQDomElement mx2 = doc.documentElement();
+
+ // TODO Check if there are changes between versions
+ if ( mx2.tagName() != "mx2" /*|| mx2.attribute("source") != "MasterCook 5.0"*/ ) {
+ setErrorMsg( i18n( "This file does not appear to be a *.mx2 file" ) );
+ return ;
+ }
+
+ TQDomNodeList l = mx2.childNodes();
+
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+
+ if ( el.tagName() == "RcpE" ) {
+ Recipe recipe;
+ recipe.title = el.attribute( "name" );
+
+ Element author( el.attribute( "author" ) );
+ recipe.authorList.append( author );
+
+ readRecipe( el.childNodes(), &recipe );
+ add
+ ( recipe );
+ }
+ }
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+MX2Importer::~MX2Importer()
+{
+}
+
+void MX2Importer::readRecipe( const TQDomNodeList& l, Recipe *recipe )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+
+ TQString tagName = el.tagName();
+ if ( tagName == "Serv" ) {
+ recipe->yield.amount = el.attribute( "qty" ).toInt();
+ recipe->yield.type = i18n("servings");
+ }
+ else if ( tagName == "PrpT" )
+ recipe->prepTime = TQTime::fromString( el.attribute( "elapsed" ) );
+ else if ( tagName == "CatS" ) {
+ TQDomNodeList categories = el.childNodes();
+ for ( unsigned j = 0; j < categories.count(); j++ ) {
+ TQDomElement c = categories.item( j ).toElement();
+ if ( c.tagName() == "CatT" ) {
+ if ( c.text().length() > 0 ) {
+ Element cat( c.text().stripWhiteSpace() );
+ recipe->categoryList.append( cat );
+ }
+ }
+ }
+ }
+ else if ( tagName == "IngR" ) {
+ Ingredient new_ing( el.attribute( "name" ),
+ el.attribute( "qty" ).toDouble(),
+ Unit( el.attribute( "unit" ), el.attribute( "qty" ).toDouble() ) );
+ if ( el.hasChildNodes() ) {
+ TQDomNodeList iChilds = el.childNodes();
+ for ( unsigned j = 0; j < iChilds.count(); j++ ) {
+ TQDomElement iChild = iChilds.item( j ).toElement();
+ if ( iChild.tagName() == "IPrp" )
+ new_ing.prepMethodList.append( Element(iChild.text().stripWhiteSpace()) );
+ else if ( iChild.tagName() == "INtI" )
+ ; // TODO: What does it mean?... ingredient nutrient info?
+ }
+ }
+ recipe->ingList.append( new_ing );
+ }
+ else if ( tagName == "DirS" ) {
+ TQStringList directions;
+ TQDomNodeList dirs = el.childNodes();
+ for ( unsigned j = 0; j < dirs.count(); j++ ) {
+ TQDomElement dir = dirs.item( j ).toElement();
+ if ( dir.tagName() == "DirT" )
+ directions.append( dir.text().stripWhiteSpace() );
+ }
+ TQString directionsText;
+
+ // TODO This is copied from RecipeML, maybe a TQStringList
+ // for directions in Recipe instead?
+ if ( directions.count() > 1 ) {
+ for ( unsigned i = 1; i <= directions.count(); i++ ) {
+ if ( i != 1 ) {
+ directionsText += "\n\n";
+ }
+
+ TQString sWith = TQString( "%1. " ).arg( i );
+ TQString text = directions[ i - 1 ];
+ if ( !text.stripWhiteSpace().startsWith( sWith ) )
+ directionsText += sWith;
+ directionsText += text;
+ }
+ }
+ else
+ directionsText = directions[ 0 ];
+
+ recipe->instructions = directionsText;
+ }
+ else if ( tagName == "SrvI" ) {
+ // Don't know what to do with it, for now add it to directions
+ // btw lets hope this is read after the directions
+ recipe->instructions += "\n\n" + el.text().stripWhiteSpace();
+ }
+ else if ( tagName == "Note" ) {
+ // Don't know what to do with it, for now add it to directions
+ // btw lets hope this is read after the directions
+ recipe->instructions += "\n\n" + el.text().stripWhiteSpace();
+ }
+ else if ( tagName == "Nutr" ) {
+ //example: <Nutr>Per Serving (excluding unknown items): 51 Calories; 6g Fat (99.5% calories from fat); trace Protein; trace Carbohydrate; 0g Dietary Fiber; 16mg Cholesterol; 137mg Sodium. Exchanges: 1 Fat.</Nutr>
+ // Don't know what to do with it, for now add it to directions
+ // btw lets hope this is read after the directions
+ recipe->instructions += "\n\n" + el.text().stripWhiteSpace();
+ }
+ /* tags to check for (example follows:
+ <Srce>SARA&apos;S SECRETS with Sara Moulton - (Show # SS-1B43) - from the TV FOOD NETWORK</Srce>
+ <AltS label="Formatted for MC7" source="07-11-2003 by Joe Comiskey - Mad&apos;s Recipe Emporium"/>
+ */
+ // TODO Have i missed some tag?
+ }
+}
+
diff --git a/src/importers/mx2importer.h b/src/importers/mx2importer.h
new file mode 100644
index 0000000..df298dd
--- /dev/null
+++ b/src/importers/mx2importer.h
@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2003 Richard L�rk�ng
+Copyright (C) 2003 Jason Kivlighn
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA
+*/
+
+#ifndef MX2IMPORTER_H
+#define MX2IMPORTER_H
+
+#include "baseimporter.h"
+
+#include <tqdom.h>
+
+class Recipe;
+
+/** Class to import Mastercook's MX2 file format. This is an XML-based file
+ * format used since version 5 of Mastercook.
+ * @author Jason Kivlighn
+ */
+class MX2Importer : public BaseImporter
+{
+public:
+ MX2Importer();
+ virtual ~MX2Importer();
+
+protected:
+ void parseFile( const TQString& filename );
+
+private:
+ void readRecipe( const TQDomNodeList& l, Recipe* );
+} ;
+
+#endif //MX2IMPORTER_H
diff --git a/src/importers/mxpimporter.cpp b/src/importers/mxpimporter.cpp
new file mode 100644
index 0000000..20fbf0f
--- /dev/null
+++ b/src/importers/mxpimporter.cpp
@@ -0,0 +1,382 @@
+/***************************************************************************
+* 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 "mxpimporter.h"
+
+#include <tqfile.h>
+#include <tqtextstream.h>
+#include <tqstringlist.h>
+#include <tqdatetime.h>
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include "datablocks/mixednumber.h"
+#include "datablocks/recipe.h"
+
+MXPImporter::MXPImporter() : BaseImporter()
+{}
+
+void MXPImporter::parseFile( const TQString &file )
+{
+ TQFile input( file );
+
+ if ( input.open( IO_ReadOnly ) ) {
+ TQTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ TQString line;
+ while ( !stream.atEnd() ) {
+ line = stream.readLine().stripWhiteSpace();
+
+ if ( line.simplifyWhiteSpace().contains( "Exported from MasterCook" ) ) {
+ importMXP( stream );
+ }
+ else if ( line == "{ Exported from MasterCook Mac }" ) {
+ importMac( stream );
+ }
+ else if ( line == "@@@@@" ) {
+ importGeneric( stream );
+ }
+
+ stream.skipWhiteSpace();
+ }
+
+ if ( fileRecipeCount() == 0 )
+ addWarningMsg( i18n( "No recipes found in this file." ) );
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+MXPImporter::~MXPImporter()
+{}
+
+void MXPImporter::importMXP( TQTextStream &stream )
+{
+ Recipe recipe;
+
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ //kdDebug()<<"Found recipe MXP format: * Exported from MasterCook *"<<endl;
+ TQString current;
+
+ // title
+ stream.skipWhiteSpace();
+ recipe.title = stream.readLine().stripWhiteSpace();
+ //kdDebug()<<"Found title: "<<m_title<<endl;
+
+ //author
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "recipe by" ) {
+ Element new_author( current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace() );
+ recipe.authorList.append( new_author );
+ //kdDebug()<<"Found author: "<<new_author.name<<endl;
+ }
+ else {
+ addWarningMsg( TQString( i18n( "While loading recipe \"%1\" "
+ "the field \"Recipe By:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+ }
+
+ //servings
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "serving size" ) {
+ //allows serving size to be loaded even if preparation time is missing
+ int end_index;
+ if ( current.contains( "preparation time", FALSE ) )
+ end_index = current.find( "preparation time", 0, FALSE ) - 15;
+ else
+ end_index = current.length();
+
+ recipe.yield.amount = current.mid( current.find( ":" ) + 1, end_index ).stripWhiteSpace().toInt();
+ recipe.yield.type = i18n("servings");
+ //kdDebug()<<"Found serving size: "<<recipe.yield.amount<<endl;
+ }
+ else {
+ addWarningMsg( TQString( i18n( "While loading recipe \"%1\" "
+ "the field \"Serving Size:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+ }
+
+ if ( current.contains( "preparation time", FALSE ) ) {
+ TQString prep_time = current.mid( current.find( ":", current.find( "preparation time", 0, FALSE ) ) + 1,
+ current.length() ).stripWhiteSpace();
+ recipe.prepTime = TQTime( prep_time.section( ':', 0, 0 ).toInt(), prep_time.section( ':', 1, 1 ).toInt() );
+ kdDebug() << "Found preparation time: " << prep_time << endl;
+ }
+ else {
+ addWarningMsg( TQString( i18n( "While loading recipe \"%1\" "
+ "the field \"Preparation Time:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+ }
+
+ loadCategories( stream, recipe );
+ loadIngredients( stream, recipe );
+ loadInstructions( stream, recipe );
+ loadOptionalFields( stream, recipe );
+
+ add
+ ( recipe );
+
+ if ( !stream.atEnd() ) {
+ importMXP( stream );
+ return ;
+ }
+}
+
+void MXPImporter::loadCategories( TQTextStream &stream, Recipe &recipe )
+{
+ //====================categories====================//
+ stream.skipWhiteSpace();
+ TQString current = stream.readLine().stripWhiteSpace();
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "categories" ) {
+ TQString tmp_str = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+
+ while ( current.stripWhiteSpace() != "Amount Measure Ingredient -- Preparation Method" && !stream.atEnd() ) {
+ if ( !tmp_str.isEmpty() ) {
+ TQStringList categories = TQStringList::split( " ", tmp_str );
+ for ( TQStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat( ( *it ).stripWhiteSpace() );
+ recipe.categoryList.append( new_cat );
+
+ //kdDebug()<<"Found category: "<<new_cat.name<<endl;
+ }
+ }
+
+ current = stream.readLine();
+ tmp_str = current;
+ }
+ //else
+ // kdDebug()<<"No categories found."<<endl;
+ }
+ else {
+ addWarningMsg( TQString( i18n( "While loading recipe \"%1\" "
+ "the field \"Categories:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+
+ //the ingredient loaded will expect the last thing to have been read to be this header line
+ while ( current.stripWhiteSpace() != "Amount Measure Ingredient -- Preparation Method" && !stream.atEnd() )
+ current = stream.readLine();
+ }
+}
+
+void MXPImporter::loadIngredients( TQTextStream &stream, Recipe &recipe )
+{
+ //============ingredients=================//
+ stream.skipWhiteSpace();
+ ( void ) stream.readLine();
+ TQString current = stream.readLine();
+ if ( !current.contains( "NONE" ) && !current.isEmpty() ) {
+ while ( !current.isEmpty() && !stream.atEnd() ) {
+ Ingredient new_ingredient;
+
+ //amount
+ TQString amount_str = current.mid( 0, 9 ).simplifyWhiteSpace();
+ if ( !amount_str.isEmpty() ) // case of amount_str.isEmpty() correctly handled by class default
+ {
+ bool ok;
+ MixedNumber amount( MixedNumber::fromString( amount_str, &ok ) );
+ if ( !ok )
+ {
+ addWarningMsg( TQString( i18n( "While loading recipe \"%1\" Invalid amount \"%2\" in the line \"%3\"" ) ).arg( recipe.title ).arg( amount_str ).arg( current.stripWhiteSpace() ) );
+ current = stream.readLine();
+ continue;
+ }
+ new_ingredient.amount = amount.toDouble();
+ }
+
+ //units
+ TQString units( current.mid( 9, 13 ) );
+ new_ingredient.units = Unit( units.simplifyWhiteSpace(), new_ingredient.amount );
+
+ //name
+ int dash_index = current.find( "--" );
+
+ int length;
+ if ( dash_index == -1 || dash_index == 24 ) //ignore a dash in the first position (index 24)
+ length = current.length();
+ else
+ length = dash_index - 22;
+
+ TQString ingredient_name( current.mid( 22, length ) );
+ new_ingredient.name = ingredient_name.stripWhiteSpace();
+
+ //prep method
+ if ( dash_index != -1 && dash_index != 24 ) //ignore a dash in the first position (index 24)
+ new_ingredient.prepMethodList.append( Element(current.mid( dash_index + 2, current.length() ).stripWhiteSpace()) );
+
+ recipe.ingList.append( new_ingredient );
+ //kdDebug()<<"Found ingredient: amount="<<new_ingredient.amount
+ // <<", unit:"<<new_ingredient.units
+ // <<", name:"<<new_ingredient.name
+ // <<", prep_method:"<<prep_method<<endl;
+
+ current = stream.readLine();
+ }
+ }
+ //else
+ // kdDebug()<<"No ingredients found."<<endl;
+}
+
+void MXPImporter::loadInstructions( TQTextStream &stream, Recipe &recipe )
+{
+ //==========================instructions ( along with other optional fields... mxp format doesn't define end of ingredients and start of other fields )==============//
+ stream.skipWhiteSpace();
+ TQString current = stream.readLine().stripWhiteSpace();
+ while ( !current.contains( "- - - -" ) && !stream.atEnd() ) {
+ if ( current.stripWhiteSpace() == "Source:" ) {
+ Element new_author( getNextQuotedString( stream ) );
+ recipe.authorList.append( new_author );
+ //kdDebug()<<"Found source: "<<new_author.name<<" (adding as author)"<<endl;
+ }
+ else if ( current.stripWhiteSpace() == "Description:" ) {
+ TQString description = getNextQuotedString( stream );
+ //kdDebug()<<"Found description: "<<m_description<<" (adding to end of instructions)"<<endl;
+ recipe.instructions += "\n\nDescription: " + description;
+ }
+ else if ( current.stripWhiteSpace() == "S(Internet Address):" ) {
+ TQString internet = getNextQuotedString( stream );
+ //kdDebug()<<"Found internet address: "<<m_internet<<" (adding to end of instructions)"<<endl;
+ recipe.instructions += "\n\nInternet address: " + internet;
+ }
+ else if ( current.stripWhiteSpace() == "Yield:" ) {
+ recipe.yield.amount = getNextQuotedString( stream ).stripWhiteSpace().toInt();
+ recipe.yield.type = i18n("servings");
+ //kdDebug()<<"Found yield: "<<recipe.yield.amount<<" (adding as servings)"<<endl;
+ }
+ else if ( current.stripWhiteSpace() == "T(Cook Time):" ) {
+ ( void ) getNextQuotedString( stream ); //this would be prep time, but we don't use prep time at the moment
+ //kdDebug()<<"Found cook time: "<<m_prep_time<<" (adding as prep time)"<<endl;
+ }
+ else if ( current.stripWhiteSpace() == "Cuisine:" ) {
+ Element new_cat( getNextQuotedString( stream ) );
+ recipe.categoryList.append( new_cat );
+ //kdDebug()<<"Found cuisine (adding as category): "<<new_cat.name<<endl;
+ }
+ else
+ recipe.instructions += current + "\n";
+
+ current = stream.readLine().stripWhiteSpace();
+ }
+ recipe.instructions = recipe.instructions.stripWhiteSpace();
+ //kdDebug()<<"Found instructions: "<<m_instructions<<endl;
+}
+
+void MXPImporter::loadOptionalFields( TQTextStream &stream, Recipe &recipe )
+{
+ //=================after here, fields are optional=========================//
+ stream.skipWhiteSpace();
+ TQString current = stream.readLine().stripWhiteSpace();
+
+ TQString notes;
+
+ //Note: we simplifyWhiteSpace() because some versions of MasterCook have "Exported from MasterCook" and others have "Exported from MasterCook".
+ // This also could work around a typo or such.
+ while ( !current.simplifyWhiteSpace().contains( "Exported from MasterCook" ) && !stream.atEnd() ) {
+ //suggested wine
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "suggested wine" ) {
+ TQString wine = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found suggested wine: "<<m_wine<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nSuggested wine: " + wine;
+ }
+ //Nutr. Assoc.
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "nutr. assoc." ) {
+ TQString nutr_assoc = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found nutrient association: "<<nutr_assoc<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nNutrient Association: " + nutr_assoc;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "per serving (excluding unknown items)" ) { //per serving... maybe we can do something with this info later
+ TQString per_serving_info = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found per serving (excluding unknown items): "<<per_serving_info<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nPer Serving (excluding unknown items): " + per_serving_info;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "per serving" ) { //per serving... maybe we can do something with this info later
+ TQString per_serving_info = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found per serving: "<<per_serving_info<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nPer Serving: " + per_serving_info;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "food exchanges" ) { //food exchanges... maybe we can do something with this info later
+ TQString food_exchange_info = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found food exchanges: "<<food_exchange_info<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nFood Exchanges: " + food_exchange_info;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "serving ideas" ) { //serving ideas
+ TQString serving_ideas = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found serving ideas: "<<m_serving_ideas<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nServing ideas: " + serving_ideas;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "notes" ) //notes
+ notes = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ else if ( !current.isEmpty() && current != "_____" ) //if it doesn't belong to any other field, assume it a part of a multi-line notes field
+ notes += "\n" + current;
+
+ current = stream.readLine().stripWhiteSpace();
+ }
+
+ /*possible fields to implement later:
+
+ Nutr. Assoc. : 0 0 0 0 0
+
+ Ratings : Cholesterol Rating 5 Complete Meal 3
+ Cost 3 Depth 3
+ Difficulty 2 Fanciness 7
+ Fat Content 5 Good For Crowds 10
+ Intensity 5 Intricacy 2
+ Kid Appeal 3 Looks 5
+ Portability 3 Richness 7
+ Serving Temperature 8 Spicy Hotness 2
+ Tartness 7
+
+ */
+ if ( !notes.isNull() ) {
+ //kdDebug()<<TQString("Found notes: %s (adding to end of instructions)").arg(m_notes)<<endl;
+ recipe.instructions += "\n\nNotes: " + notes.stripWhiteSpace();
+ }
+}
+
+void MXPImporter::importGeneric( TQTextStream & /*stream*/ )
+{
+ setErrorMsg( i18n( "MasterCook's Generic Export format is currently not supported. Please write to [email protected] to request support for this format." ) );
+ //not even sure it this is worth writing... its rather obsolete
+}
+
+void MXPImporter::importMac( TQTextStream & /*stream*/ )
+{
+ setErrorMsg( i18n( "MasterCook Mac's Export format is currently not supported. Please write to [email protected] to request support for this format." ) );
+ //not even sure it this is worth writing... its rather obsolete
+}
+
+TQString MXPImporter::getNextQuotedString( TQTextStream &stream )
+{
+ stream.skipWhiteSpace();
+ TQString current = stream.readLine().stripWhiteSpace();
+ TQString return_str;
+
+ if ( current.left( 1 ) == "\"" )
+ return_str = current.mid( 1, current.length() - 1 );
+ else
+ return current;
+
+ while ( current.right( 1 ) != "\"" && !stream.atEnd() ) {
+ current = stream.readLine().stripWhiteSpace();
+ return_str += "\n" + current;
+ }
+
+ //take off quote at end
+ return_str = return_str.mid( 0, return_str.length() - 1 );
+
+ return return_str.stripWhiteSpace();
+}
diff --git a/src/importers/mxpimporter.h b/src/importers/mxpimporter.h
new file mode 100644
index 0000000..6bdee22
--- /dev/null
+++ b/src/importers/mxpimporter.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* 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 MXPIMPORTER_H
+#define MXPIMPORTER_H
+
+#include <tqstring.h>
+
+#include "baseimporter.h"
+
+/** Class to import MasterCook's MXP (MasterCook Export) file format.
+ * This is a human-readable format used in Mastercook up until version 4.
+ * @author Jason Kivlighn
+ */
+class MXPImporter : public BaseImporter
+{
+public:
+ MXPImporter();
+ virtual ~MXPImporter();
+
+protected:
+ void parseFile( const TQString& filename );
+
+private:
+ void importMXP( TQTextStream &stream );
+
+ void loadCategories( TQTextStream &stream, Recipe &recipe );
+ void loadIngredients( TQTextStream &stream, Recipe &recipe );
+ void loadInstructions( TQTextStream &stream, Recipe &recipe );
+ void loadOptionalFields( TQTextStream &stream, Recipe &recipe );
+
+ void importMac( TQTextStream &stream );
+ void importGeneric( TQTextStream &stream );
+
+ TQString getNextQuotedString( TQTextStream &stream );
+};
+
+#endif //MXPIMPORTER_H
diff --git a/src/importers/nycgenericimporter.cpp b/src/importers/nycgenericimporter.cpp
new file mode 100644
index 0000000..30e5bdf
--- /dev/null
+++ b/src/importers/nycgenericimporter.cpp
@@ -0,0 +1,196 @@
+/***************************************************************************
+* 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 "nycgenericimporter.h"
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include <tqfile.h>
+#include <tqtextstream.h>
+#include <tqstringlist.h>
+#include <tqregexp.h>
+
+#include "datablocks/mixednumber.h"
+#include "datablocks/recipe.h"
+
+NYCGenericImporter::NYCGenericImporter() : BaseImporter()
+{}
+
+void NYCGenericImporter::parseFile( const TQString &file )
+{
+ first = true;
+
+ m_recipe.empty();
+
+ TQFile input( file );
+ if ( input.open( IO_ReadOnly ) ) {
+ TQTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ if ( !stream.atEnd() && stream.readLine().startsWith( "@@@@@" ) )
+ importNYCGeneric( stream );
+ else {
+ setErrorMsg( i18n( "File does not appear to be a valid NYC export." ) );
+ return ;
+ }
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+NYCGenericImporter::~NYCGenericImporter()
+{}
+
+void NYCGenericImporter::importNYCGeneric( TQTextStream &stream )
+{
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ TQString current;
+
+ stream.skipWhiteSpace();
+
+ //title
+ while ( !( current = stream.readLine() ).isEmpty() && !stream.atEnd() )
+ m_recipe.title = current;
+
+ //categories
+ while ( !( current = stream.readLine() ).isEmpty() && !stream.atEnd() ) {
+ if ( current[ 0 ].isNumber() ) {
+ loadIngredientLine( current );
+ break;
+ } //oops, this is really an ingredient line (there was no category line)
+
+ TQStringList categories = TQStringList::split( ',', current );
+
+ if ( categories.count() > 0 && categories[ 0 ].upper() == "none" ) //there are no categories
+ break;
+
+ for ( TQStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat( TQString( *it ).stripWhiteSpace() );
+ kdDebug() << "Found category: " << new_cat.name << endl;
+ m_recipe.categoryList.append( new_cat );
+ }
+ }
+
+ //ingredients
+ while ( !( current = stream.readLine() ).isEmpty() && !stream.atEnd() )
+ loadIngredientLine( current );
+
+ //everything else is the instructions with optional "contributor", "prep time" and "yield"
+ bool found_next;
+ while ( !( found_next = ( current = stream.readLine() ).startsWith( "@@@@@" ) ) && !stream.atEnd() ) {
+ if ( current.startsWith( "Contributor:" ) ) {
+ Element new_author( current.mid( current.find( ':' ) + 1, current.length() ).stripWhiteSpace() );
+ kdDebug() << "Found author: " << new_author.name << endl;
+ m_recipe.authorList.append( new_author );
+ }
+ else if ( current.startsWith( "Preparation Time:" ) ) {
+ m_recipe.prepTime = TQTime::fromString( current.mid( current.find( ':' ), current.length() ) );
+ }
+ else if ( current.startsWith( "Yield:" ) ) {
+ int colon_index = current.find( ':' );
+ int amount_type_sep_index = current.find(" ",colon_index+1);
+
+ m_recipe.yield.amount = current.mid( colon_index+2, amount_type_sep_index-colon_index ).toDouble();
+ m_recipe.yield.type = current.mid( amount_type_sep_index+3, current.length() );
+ }
+ else if ( current.startsWith( "NYC Nutrition Analysis (per serving or yield unit):" ) ) {
+ //m_recipe.instructions += current + "\n";
+ }
+ else if ( current.startsWith( "NYC Nutrilink:" ) ) {
+ //m_recipe.instructions += current + "\n";
+ }
+ else if ( !current.stripWhiteSpace().isEmpty() && !current.startsWith("** Exported from Now You're Cooking!") ) {
+ m_recipe.instructions += current + "\n";
+ }
+ }
+
+ m_recipe.instructions = m_recipe.instructions.stripWhiteSpace();
+ putDataInRecipe();
+
+ if ( found_next )
+ importNYCGeneric( stream );
+}
+
+void NYCGenericImporter::putDataInRecipe()
+{
+ //put it in the recipe list
+ add( m_recipe );
+
+ //reset for the next recipe
+ m_recipe.empty();
+}
+
+void NYCGenericImporter::loadIngredientLine( const TQString &line )
+{
+ TQString current = line;
+
+ if ( current.contains( "-----" ) ) {
+ current_header = current.stripWhiteSpace();
+ kdDebug() << "Found ingredient header: " << current_header << endl;
+ return ;
+ }
+
+ MixedNumber amount( 0, 0, 1 );
+ TQString unit;
+ TQString name;
+ TQString prep;
+
+ TQStringList ingredient_line = TQStringList::split( ' ', current );
+
+ bool found_amount = false;
+
+ if ( !ingredient_line.empty() ) //probably an unnecessary check... but to be safe
+ {
+ bool ok;
+ MixedNumber test_amount = MixedNumber::fromString( ingredient_line[ 0 ], &ok );
+ if ( ok )
+ {
+ amount = amount + test_amount;
+ ingredient_line.pop_front();
+ found_amount = true;
+ }
+ }
+ if ( !ingredient_line.empty() ) //probably an unnecessary check... but to be safe
+ {
+ bool ok;
+ MixedNumber test_amount = MixedNumber::fromString( ingredient_line[ 0 ], &ok );
+ if ( ok )
+ {
+ amount = amount + test_amount;
+ ingredient_line.pop_front();
+ found_amount = true;
+ }
+ }
+
+ if ( found_amount ) {
+ unit = ingredient_line[ 0 ];
+ ingredient_line.pop_front();
+ }
+
+ //now join each separate part of ingredient (name, unit, amount)
+ name = ingredient_line.join( " " );
+
+ int prep_sep_index = name.find( TQRegExp( "(--|,;;)" ) );
+ if ( prep_sep_index == -1 )
+ prep_sep_index = name.length();
+
+ name = name.left( prep_sep_index ).stripWhiteSpace();
+ prep = name.mid( prep_sep_index+1, name.length() ).stripWhiteSpace();
+
+ Ingredient new_ingredient( name, amount.toDouble(), Unit( unit, amount.toDouble() ) );
+ new_ingredient.group = current_header;
+ new_ingredient.prepMethodList = ElementList::split(",",prep);
+ m_recipe.ingList.append( new_ingredient );
+
+}
+
diff --git a/src/importers/nycgenericimporter.h b/src/importers/nycgenericimporter.h
new file mode 100644
index 0000000..6b3bc2f
--- /dev/null
+++ b/src/importers/nycgenericimporter.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+* 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 NYCGENERICIMPORTER_H
+#define NYCGENERICIMPORTER_H
+
+#include <tqstring.h>
+#include <tqdatetime.h>
+
+#include "baseimporter.h"
+#include "datablocks/ingredientlist.h"
+#include "datablocks/elementlist.h"
+
+/** Class to import The NYC (Now You're Cooking) Generic Export file format.
+ * @author Jason Kivlighn
+ */
+class NYCGenericImporter : public BaseImporter
+{
+public:
+ NYCGenericImporter();
+ ~NYCGenericImporter();
+
+protected:
+ void parseFile( const TQString& filename );
+
+private:
+ void importNYCGeneric( TQTextStream &stream );
+ void putDataInRecipe();
+ void loadIngredientLine( const TQString & );
+
+ Recipe m_recipe;
+ TQString current_header;
+
+ bool first;
+};
+
+#endif //NYCGENERICIMPORTER_H
diff --git a/src/importers/recipemlimporter.cpp b/src/importers/recipemlimporter.cpp
new file mode 100644
index 0000000..8866648
--- /dev/null
+++ b/src/importers/recipemlimporter.cpp
@@ -0,0 +1,376 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Richard L�rk�ng *
+* *
+* 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 "recipemlimporter.h"
+
+#include <tqfile.h>
+#include <tqdatetime.h>
+
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/mixednumber.h"
+
+RecipeMLImporter::RecipeMLImporter() : BaseImporter()
+{}
+
+void RecipeMLImporter::parseFile( const TQString& file )
+{
+ TQFile input( file );
+ if ( input.open( IO_ReadOnly ) ) {
+ TQDomDocument doc;
+ TQString error;
+ int line;
+ int column;
+ if ( !doc.setContent( &input, &error, &line, &column ) ) {
+ setErrorMsg( TQString( i18n( "\"%1\" at line %2, column %3. This may not be a RecipeML file." ) ).arg( error ).arg( line ).arg( column ) );
+ return ;
+ }
+
+ TQDomElement recipeml = doc.documentElement();
+
+ if ( recipeml.tagName() != "recipeml" ) {
+ setErrorMsg( i18n( "This file does not appear to be a valid RecipeML archive." ) );
+ return ;
+ }
+
+ TQDomNodeList l = recipeml.childNodes();
+
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ TQString tagName = el.tagName();
+
+ if ( tagName == "meta" )
+ continue;
+ else if ( tagName == "recipe" )
+ readRecipemlRecipe( el );
+ else if ( tagName == "menu" )
+ readRecipemlMenu( el );
+ else
+ kdDebug() << "Unknown tag within <recipeml>: " << tagName << endl;
+ }
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+RecipeMLImporter::~RecipeMLImporter()
+{}
+
+void RecipeMLImporter::readRecipemlRecipe( const TQDomElement& recipe_element )
+{
+ recipe.empty();
+
+ TQDomNodeList l = recipe_element.childNodes();
+
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ TQString tagName = el.tagName();
+
+ if ( tagName == "head" )
+ readRecipemlHead( el );
+ else if ( tagName == "ingredients" )
+ readRecipemlIngs( el );
+ else if ( tagName == "directions" )
+ readRecipemlDirections( el );
+ else if ( tagName == "description" ) {} //TODO: what do we do with this?
+ else if ( tagName == "equipment" ) {} //TODO: what do we do with this?
+ else if ( tagName == "nutrition" ) {} //TODO: what do we do with this?
+ else if ( tagName == "diet-exchanges" ) {} //TODO: what do we do with this?
+ else
+ kdDebug() << "Unknown tag within <recipe>: " << el.tagName() << endl;
+ }
+
+ add
+ ( recipe );
+}
+
+void RecipeMLImporter::readRecipemlHead( const TQDomElement& head )
+{
+ TQDomNodeList l = head.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ TQString tagName = el.tagName();
+
+ if ( tagName == "title" )
+ recipe.title = el.text().stripWhiteSpace();
+ else if ( tagName == "subtitle" )
+ recipe.title += ": " + el.text().stripWhiteSpace();
+ else if ( tagName == "version" ) {} //TODO: what do we do with this?
+ else if ( tagName == "source" )
+ readRecipemlSrcItems( el );
+ else if ( tagName == "categories" ) {
+ TQDomNodeList categories = el.childNodes();
+ for ( unsigned j = 0; j < categories.count(); j++ ) {
+ TQDomElement c = categories.item( j ).toElement();
+ if ( c.tagName() == "cat" ) {
+ recipe.categoryList.append( Element( c.text() ) );
+ }
+ }
+ }
+ else if ( tagName == "description" )
+ recipe.instructions += "\n\nDescription: " + el.text().stripWhiteSpace();
+ else if ( tagName == "preptime" )
+ readRecipemlPreptime( el );
+ else if ( tagName == "yield" ) {
+ TQDomNodeList yieldChildren = el.childNodes();
+ for ( unsigned j = 0; j < yieldChildren.count(); j++ ) {
+ TQDomElement y = yieldChildren.item( j ).toElement();
+ TQString tagName = y.tagName();
+ if ( tagName == "range" )
+ readRecipemlRange( y, recipe.yield.amount, recipe.yield.amount_offset );
+ else if ( tagName == "unit" )
+ recipe.yield.type = y.text();
+ else
+ kdDebug() << "Unknown tag within <yield>: " << y.tagName() << endl;
+ }
+ }
+ else
+ kdDebug() << "Unknown tag within <head>: " << el.tagName() << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlIngs( const TQDomElement& ings )
+{
+ TQDomNodeList l = ings.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ TQString tagName = el.tagName();
+
+ if ( tagName == "ing" )
+ readRecipemlIng( el );
+ else if ( tagName == "ing-div" ) //NOTE: this can have the "type" attribute
+ {
+ TQString header;
+ TQDomNodeList ingDiv = el.childNodes();
+ for ( unsigned j = 0; j < ingDiv.count(); j++ )
+ {
+ TQDomElement cEl = ingDiv.item( j ).toElement();
+ if ( cEl.tagName() == "title" )
+ header = cEl.text().stripWhiteSpace();
+ else if ( cEl.tagName() == "description" ) {} //TODO: what do we do with this?
+ else if ( cEl.tagName() == "ing" )
+ readRecipemlIng( cEl, 0, header );
+ else if ( tagName == "note" ) {} //TODO: what do we do with this?
+ else
+ kdDebug() << "Unknown tag within <ing-div>: " << cEl.tagName() << endl;
+ }
+ }
+ else if ( tagName == "note" ) {} //TODO: what do we do with this?
+ else
+ kdDebug() << "Unknown tag within <ingredients>: " << el.tagName() << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlIng( const TQDomElement& ing, Ingredient *ing_parent, const TQString &header )
+{
+ Ingredient new_ing;
+
+ TQDomNodeList ingChilds = ing.childNodes();
+
+ TQString name, unit, size, prep_method;
+ Ingredient quantity;
+ quantity.amount = 1;// default quantity assumed by RecipeML DTD
+
+ for ( unsigned j = 0; j < ingChilds.count(); j++ ) {
+ TQDomElement ingChild = ingChilds.item( j ).toElement();
+ TQString tagName = ingChild.tagName();
+
+ if ( tagName == "amt" ) {
+ TQDomNodeList amtChilds = ingChild.childNodes();
+
+ for ( unsigned k = 0; k < amtChilds.count(); k++ ) {
+ TQDomElement amtChild = amtChilds.item( k ).toElement();
+
+ if ( amtChild.tagName() == "qty" )
+ readRecipemlTQty( amtChild, quantity );
+ else if ( amtChild.tagName() == "size" )
+ size = amtChild.text().stripWhiteSpace();
+ else if ( amtChild.tagName() == "unit" )
+ unit = amtChild.text().stripWhiteSpace();
+ else
+ kdDebug() << "Unknown tag within <amt>: " << amtChild.tagName() << endl;
+ }
+ }
+ else if ( tagName == "item" ) {
+ name = ingChild.text().stripWhiteSpace();
+ if ( ing.attribute( "optional", "no" ) == "yes" )
+ prep_method = "(optional)";
+ }
+ else if ( tagName == "prep" ) { //FIXME: this overwrite the optional attribute
+ prep_method = ingChild.text().stripWhiteSpace();
+ }
+ else if ( tagName == "alt-ing" )
+ readRecipemlIng( ingChild, &new_ing, header );
+ else
+ kdDebug() << "Unknown tag within <ing>: " << ingChild.tagName() << endl;
+ }
+
+ if ( !size.isEmpty() )
+ unit.prepend( size + " " );
+
+ new_ing.name = name;
+ new_ing.units = Unit( unit, quantity.amount+quantity.amount_offset );
+ new_ing.amount = quantity.amount;
+ new_ing.amount_offset = quantity.amount_offset;
+ new_ing.group = header;
+ new_ing.prepMethodList = ElementList::split(",",prep_method);
+
+ if ( !ing_parent )
+ recipe.ingList.append(new_ing);
+ else
+ ing_parent->substitutes.append( new_ing );
+}
+
+void RecipeMLImporter::readRecipemlDirections( const TQDomElement& dirs )
+{
+ TQDomNodeList l = dirs.childNodes();
+
+ TQStringList directions;
+
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+
+ if ( el.tagName() == "step" )
+ directions.append( el.text().stripWhiteSpace() );
+ else
+ kdDebug() << "Unknown tag within <directions>: " << el.tagName() << endl;
+ }
+
+ TQString directionsText;
+
+ if ( directions.count() > 1 ) {
+ for ( unsigned i = 1; i <= directions.count(); i++ ) {
+ if ( i != 1 ) {
+ directionsText += "\n\n";
+ }
+
+ TQString sWith = TQString( "%1. " ).arg( i );
+ TQString text = directions[ i - 1 ];
+ if ( !text.stripWhiteSpace().startsWith( sWith ) )
+ directionsText += sWith;
+ directionsText += text;
+ }
+ }
+ else
+ directionsText = directions[ 0 ];
+
+ recipe.instructions = directionsText;
+}
+
+void RecipeMLImporter::readRecipemlMenu( const TQDomElement& menu_el )
+{
+ TQDomNodeList l = menu_el.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ TQString tagName = el.tagName();
+
+ if ( tagName == "head" )
+ readRecipemlHead( el );
+ else if ( tagName == "description" ) {} //TODO: what do we do with this?
+ else if ( tagName == "recipe" )
+ readRecipemlRecipe( el );
+ else
+ kdDebug() << "Unknown tag within <menu>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlSrcItems( const TQDomElement& sources )
+{
+ TQDomNodeList l = sources.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement srcitem = l.item( i ).toElement();
+ TQString tagName = srcitem.tagName();
+
+ if ( tagName == "srcitem" )
+ recipe.authorList.append( Element( srcitem.text().stripWhiteSpace() ) );
+ else
+ kdDebug() << "Unknown tag within <source>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlPreptime( const TQDomElement &preptime )
+{
+ TQDomNodeList l = preptime.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ TQDomElement el = l.item( i ).toElement();
+ TQString tagName = el.tagName();
+
+ if ( tagName == "time" ) {
+ int qty = 0;
+ TQString timeunit;
+
+ TQDomNodeList time_l = el.childNodes();
+ for ( unsigned i = 0 ; i < time_l.count(); i++ ) {
+ TQDomElement time_el = time_l.item( i ).toElement();
+ TQString time_tagName = time_el.tagName();
+
+ if ( time_tagName == "qty" )
+ qty = time_el.text().toInt();
+ else if ( time_tagName == "timeunit" )
+ timeunit = time_el.text();
+ else
+ kdDebug() << "Unknown tag within <time>: " << time_tagName << endl;
+ }
+
+ int minutes = 0;
+ int hours = 0;
+ if ( timeunit == "minutes" )
+ minutes = qty;
+ else if ( timeunit == "hours" )
+ hours = qty;
+ else
+ kdDebug() << "Unknown timeunit: " << timeunit << endl;
+
+ recipe.prepTime = TQTime( hours + minutes / 60, minutes % 60 );
+ }
+ else
+ kdDebug() << "Unknown tag within <preptime>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlTQty( const TQDomElement &qty, Ingredient &ing )
+{
+ TQDomNodeList qtyChilds = qty.childNodes();
+
+ for ( unsigned i = 0; i < qtyChilds.count(); i++ ) {
+ TQDomElement qtyChild = qtyChilds.item( i ).toElement();
+ TQString tagName = qtyChild.tagName();
+ if ( tagName == "range" )
+ readRecipemlRange( qtyChild, ing.amount, ing.amount_offset );
+ else if ( tagName.isEmpty() )
+ ing.amount = MixedNumber::fromString( qty.text() ).toDouble();
+ else
+ kdDebug() << "Unknown tag within <qty>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlRange( const TQDomElement& range, double &amount, double &amount_offset )
+{
+ TQDomNodeList rangeChilds = range.childNodes();
+ double q1 = 1, q2 = 0; // default quantity assumed by RecipeML DTD
+ for ( unsigned j = 0; j < rangeChilds.count(); j++ ) {
+ TQDomElement rangeChild = rangeChilds.item( j ).toElement();
+ TQString subTagName = rangeChild.tagName();
+ if ( subTagName == "q1" )
+ q1 = MixedNumber::fromString( rangeChild.text() ).toDouble();
+ else if ( subTagName == "q2" )
+ q2 = MixedNumber::fromString( rangeChild.text() ).toDouble();
+ else
+ kdDebug() << "Unknown tag within <range>: " << subTagName << endl;
+ }
+
+ amount = q1;
+ amount_offset = q2-q1;
+}
diff --git a/src/importers/recipemlimporter.h b/src/importers/recipemlimporter.h
new file mode 100644
index 0000000..51c6f42
--- /dev/null
+++ b/src/importers/recipemlimporter.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Richard L�rk�ng *
+* *
+* 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 RECIPEMLIMPORTER_H
+#define RECIPEMLIMPORTER_H
+
+#include "baseimporter.h"
+#include "datablocks/ingredient.h"
+#include "datablocks/recipe.h"
+
+#include <tqdom.h>
+
+/** Class to import the RecipeML, XML-based file format.
+ * More info at http://www.formatdata.com/recipeml
+ *
+ * @author Jason Kivlighn
+ */
+class RecipeMLImporter : public BaseImporter
+{
+public:
+ RecipeMLImporter();
+ virtual ~RecipeMLImporter();
+
+protected:
+ void parseFile( const TQString& filename );
+
+private:
+ void readRecipemlDirections( const TQDomElement& dirs );
+ void readRecipemlHead( const TQDomElement& head );
+ void readRecipemlIng( const TQDomElement& ing1, Ingredient *ing2 = 0, const TQString &header = TQString::null );
+ void readRecipemlIngs( const TQDomElement& ings );
+ void readRecipemlMenu( const TQDomElement& menu );
+ void readRecipemlSrcItems( const TQDomElement& sources );
+ void readRecipemlRecipe( const TQDomElement& recipe );
+ void readRecipemlPreptime( const TQDomElement &preptime );
+ void readRecipemlTQty( const TQDomElement &qty, Ingredient &ing );
+ void readRecipemlRange( const TQDomElement& range1, double &range2, double &range_offset );
+
+ Recipe recipe;
+};
+
+#endif //RECIPEMLIMPORTER_H
diff --git a/src/importers/rezkonvimporter.cpp b/src/importers/rezkonvimporter.cpp
new file mode 100644
index 0000000..58e59c0
--- /dev/null
+++ b/src/importers/rezkonvimporter.cpp
@@ -0,0 +1,291 @@
+/***************************************************************************
+* 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 "rezkonvimporter.h"
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kdebug.h>
+
+#include <tqfile.h>
+#include <tqregexp.h>
+#include <tqtextstream.h>
+
+#include "datablocks/mixednumber.h"
+
+RezkonvImporter::RezkonvImporter() : BaseImporter()
+{}
+
+RezkonvImporter::~RezkonvImporter()
+{}
+
+void RezkonvImporter::parseFile( const TQString &filename )
+{
+ TQFile input( filename );
+
+ if ( input.open( IO_ReadOnly ) ) {
+ TQTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ TQString line;
+
+ while ( !stream.atEnd() ) {
+ line = stream.readLine();
+
+ if ( line.contains( TQRegExp( "^=====.*REZKONV.*" ) ) ) {
+ TQStringList raw_recipe;
+ while ( !( line = stream.readLine() ).contains( TQRegExp( "^=====\\s*$" ) ) && !stream.atEnd() )
+ raw_recipe << line;
+
+ readRecipe( raw_recipe );
+ }
+ }
+
+ if ( fileRecipeCount() == 0 )
+ setErrorMsg( i18n( "No recipes found in this file." ) );
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+void RezkonvImporter::readRecipe( const TQStringList &raw_recipe )
+{
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ Recipe recipe;
+
+ TQStringList::const_iterator text_it = raw_recipe.begin();
+ m_end_it = raw_recipe.end();
+
+ //title (Titel)
+ text_it++;
+ recipe.title = ( *text_it ).mid( ( *text_it ).find( ":" ) + 1, ( *text_it ).length() ).stripWhiteSpace();
+ kdDebug() << "Found title: " << recipe.title << endl;
+
+ //categories (Kategorien):
+ text_it++;
+ TQStringList categories = TQStringList::split( ',', ( *text_it ).mid( ( *text_it ).find( ":" ) + 1, ( *text_it ).length() ) );
+ for ( TQStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat;
+ new_cat.name = TQString( *it ).stripWhiteSpace();
+ kdDebug() << "Found category: " << new_cat.name << endl;
+ recipe.categoryList.append( new_cat );
+ }
+
+ //yield (Menge)
+ text_it++;
+ //get the number between the ":" and the next space after it
+ TQString yield_str = ( *text_it ).stripWhiteSpace();
+ yield_str.remove( TQRegExp( "^Menge:\\s*" ) );
+ int sep_index = yield_str.find( ' ' );
+ if ( sep_index != -1 )
+ recipe.yield.type = yield_str.mid( sep_index+1 );
+ readRange( yield_str.mid( 0, sep_index ), recipe.yield.amount, recipe.yield.amount_offset );
+ kdDebug() << "Found yield: " << recipe.yield.amount << endl;
+
+ bool is_sub = false;
+ bool last_line_empty = false;
+ text_it++;
+ while ( text_it != raw_recipe.end() ) {
+ if ( ( *text_it ).isEmpty() ) {
+ last_line_empty = true;
+ text_it++;
+ continue;
+ }
+
+ if ( ( *text_it ).contains( TQRegExp( "^=====.*=$" ) ) ) //is a header
+ {
+ if ( ( *text_it ).contains( "quelle", false ) )
+ {
+ loadReferences( text_it, recipe );
+ break; //reference lines are the last before the instructions
+ }
+ else
+ loadIngredientHeader( *text_it, recipe );
+ }
+
+ //if it has no more than two spaces followed by a non-digit
+ //then we'll assume it is a direction line
+ else if ( last_line_empty && ( *text_it ).contains( TQRegExp( "^\\s{0,2}[^\\d\\s=]" ) ) )
+ break;
+ else
+ loadIngredient( *text_it, recipe, is_sub );
+
+ last_line_empty = false;
+ text_it++;
+ }
+
+ loadInstructions( text_it, recipe );
+
+ add
+ ( recipe );
+
+ current_header = TQString::null;
+}
+
+void RezkonvImporter::loadIngredient( const TQString &string, Recipe &recipe, bool &is_sub )
+{
+ Ingredient new_ingredient;
+ new_ingredient.amount = 0; //amount not required, so give default of 0
+
+ TQRegExp cont_test( "^-{1,2}" );
+ if ( string.stripWhiteSpace().contains( cont_test ) ) {
+ TQString name = string.stripWhiteSpace();
+ name.remove( cont_test );
+ kdDebug() << "Appending to last ingredient: " << name << endl;
+ if ( !recipe.ingList.isEmpty() ) //so it doesn't crash when the first ingredient appears to be a continuation of another
+ ( *recipe.ingList.at( recipe.ingList.count() - 1 ) ).name += " " + name;
+
+ return ;
+ }
+
+ //amount
+ if ( !string.mid( 0, 7 ).stripWhiteSpace().isEmpty() )
+ readRange( string.mid( 0, 7 ), new_ingredient.amount, new_ingredient.amount_offset );
+
+ //unit
+ TQString unit_str = string.mid( 8, 9 ).stripWhiteSpace();
+ new_ingredient.units = Unit( unit_str, new_ingredient.amount );
+
+ //name and preparation method
+ new_ingredient.name = string.mid( 18, string.length() - 18 ).stripWhiteSpace();
+
+ //separate out the preparation method
+ TQString name_and_prep = new_ingredient.name;
+ int separator_index = name_and_prep.find( "," );
+ if ( separator_index != -1 ) {
+ new_ingredient.name = name_and_prep.mid( 0, separator_index ).stripWhiteSpace();
+ new_ingredient.prepMethodList = ElementList::split(",",name_and_prep.mid( separator_index + 1, name_and_prep.length() ).stripWhiteSpace() );
+ }
+
+ //header (if present)
+ new_ingredient.group = current_header;
+
+ bool last_is_sub = is_sub;
+ if ( new_ingredient.prepMethodList.last().name == "or" ) {
+ new_ingredient.prepMethodList.pop_back();
+ is_sub = true;
+ }
+ else
+ is_sub = false;
+
+ if ( last_is_sub )
+ ( *recipe.ingList.at( recipe.ingList.count() - 1 ) ).substitutes.append(new_ingredient);
+ else
+ recipe.ingList.append( new_ingredient );
+}
+
+void RezkonvImporter::loadIngredientHeader( const TQString &string, Recipe &/*recipe*/ )
+{
+ TQString header = string;
+ header.remove( TQRegExp( "^=*" ) ).remove( TQRegExp( "=*$" ) );
+ header = header.stripWhiteSpace();
+
+ kdDebug() << "found ingredient header: " << header << endl;
+
+ current_header = header;
+}
+
+void RezkonvImporter::loadInstructions( TQStringList::const_iterator &text_it, Recipe &recipe )
+{
+ TQString instr;
+ TQRegExp rx_title( "^:{0,1}\\s*O-Titel\\s*:" );
+ TQString line;
+ while ( text_it != m_end_it ) {
+ line = *text_it;
+
+ //titles longer than the line width are rewritten here
+ if ( line.contains( rx_title ) ) {
+ line.remove( rx_title );
+ recipe.title = line.stripWhiteSpace();
+
+ TQRegExp rx_line_cont( ":\\s*>{0,1}\\s*:" );
+ while ( ( line = *text_it ).contains( rx_line_cont ) ) {
+ line.remove( rx_line_cont );
+ recipe.title += line;
+
+ text_it++;
+ }
+ kdDebug() << "Found long title: " << recipe.title << endl;
+ }
+ else {
+ if ( line.isEmpty() )
+ instr += "\n\n";
+
+ instr += line;
+ }
+
+ text_it++;
+ }
+
+ recipe.instructions = instr;
+}
+
+void RezkonvImporter::loadReferences( TQStringList::const_iterator &text_it, Recipe &recipe )
+{
+ kdDebug() << "Found source header" << endl;
+
+ while ( text_it != m_end_it ) {
+ text_it++;
+ TQRegExp rx_line_begin( "^\\s*-{0,2}\\s*" );
+
+ TQRegExp rx_creation_date = TQRegExp( "^\\s*-{0,2}\\s*Erfasst \\*RK\\*", false );
+ if ( ( *text_it ).contains( rx_creation_date ) ) // date followed by typist
+ {
+ TQString date = *text_it;
+ date.remove( rx_creation_date ).remove( TQRegExp( " von\\s*$" ) );
+
+ // Date is given as DD.MM.YY
+ TQString s = date.section( ".", 0, 0 );
+ int day = s.toInt();
+
+ s = date.section( ".", 1, 1 );
+ int month = s.toInt();
+
+ s = date.section( ".", 2, 2 );
+ int year = s.toInt();
+ year += 1900;
+ if ( year < 1970 )
+ year += 100; //we'll assume nothing has been created before 1970 (y2k issues :p)
+
+ recipe.ctime = TQDate(year,month,day);
+
+ #if 0
+ //typist
+ text_it++;
+ TQString typist = = *text_it;
+ typist.remove( rx_line_begin );
+ #endif
+
+ }
+ else //everything else is an author
+ {
+ if ( ( *text_it ).contains( rx_line_begin ) ) {
+ TQString author = *text_it;
+ author.remove( rx_line_begin );
+
+ recipe.authorList.append( Element( author ) );
+ }
+ else
+ break;
+ }
+ }
+}
+
+void RezkonvImporter::readRange( const TQString &range_str, double &amount, double &amount_offset )
+{
+ TQString from = range_str.section( '-', 0, 0 );
+ TQString to = range_str.section( '-', 1, 1 );
+
+ amount = MixedNumber::fromString( from ).toDouble();
+
+ if ( !to.stripWhiteSpace().isEmpty() )
+ amount_offset = MixedNumber::fromString( to ).toDouble() - amount;
+}
diff --git a/src/importers/rezkonvimporter.h b/src/importers/rezkonvimporter.h
new file mode 100644
index 0000000..7f22255
--- /dev/null
+++ b/src/importers/rezkonvimporter.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+* 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 REZKONVIMPORTER_H
+#define REZKONVIMPORTER_H
+
+#include <tqstringlist.h>
+
+#include "baseimporter.h"
+#include "datablocks/recipe.h"
+
+class RezkonvImporter : public BaseImporter
+{
+public:
+ RezkonvImporter();
+ ~RezkonvImporter();
+
+protected:
+ void parseFile( const TQString &filename );
+
+private:
+ void loadIngredient( const TQString &line, Recipe & recipe, bool &is_sub );
+ void loadIngredientHeader( const TQString &line, Recipe & recipe );
+ void loadInstructions( TQStringList::const_iterator &raw_text, Recipe & recipe );
+ void loadReferences( TQStringList::const_iterator &raw_text, Recipe & recipe );
+
+ void readRange( const TQString &, double &amount, double &amount_offset );
+ void readRecipe( const TQStringList &raw_text );
+
+ TQStringList::const_iterator m_end_it;
+
+ TQString current_header;
+};
+
+#endif //REZKONVIMPORTER_H