diff options
Diffstat (limited to 'src/importers/mmfimporter.cpp')
-rw-r--r-- | src/importers/mmfimporter.cpp | 336 |
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; +} + |