summaryrefslogtreecommitdiffstats
path: root/src/importers/mmfimporter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/importers/mmfimporter.cpp')
-rw-r--r--src/importers/mmfimporter.cpp336
1 files changed, 336 insertions, 0 deletions
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;
+}
+