Skip to content

Commit

Permalink
feat: Added support for eat this (#65)
Browse files Browse the repository at this point in the history
* refactor: origin is used instead of host to identify recipe website

* docs: fixed changed paths

* feat: Added Eat This parsing script

* feat: improved amount parsing

* feat: Added support for special html structures

* feat: Added not supported url feedback

* fix: fixed servings regex

* test: Added Eat this! tests

* fix: use regex splitting instead

* feat: Improved amount and unit parsing

* feat: IngredientParsingResult now has list of ingredients

* feat: ingredients separated with '+' are now parsed

* test: adjusted tests

* test: Added test for unsupported Eat this! website

* refactor: bianca zapatka and eat this use same ingredient parser

* refactor: removed kptnCook warning message

* refactor: reduced duplicate code

* refactor: reduced method length
  • Loading branch information
Slartibartfass2 authored Feb 27, 2023
1 parent 461807e commit 5b73e04
Show file tree
Hide file tree
Showing 15 changed files with 864 additions and 188 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ Don't forget to give the project a star! Thanks again!
### Adding support for a recipe website
1. Add a recipe script file in `./lib/src/recipe-scripts`.
1. Add a recipe script file in `./lib/src/recipe_scripts`.
2. Add a parsing method which takes a `Document` and a `RecipeParsingJob` and returns a `RecipeParsingResult`.
3. Register the url host and the parsing method in the `_recipeParseMethodMap` map in `./lib/src/recipe_controller.dart`.
4. Add tests in `./test/script_test` to ensure the parsing works.
4. Add tests in `./test/recipe_scripts_tests` to ensure the parsing works.
<p align="right">(<a href="#readme-top">back to top</a>)</p>
Expand Down
4 changes: 2 additions & 2 deletions lib/src/models/ingredient_parsing_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class IngredientParsingResult with _$IngredientParsingResult {
/// Creates [IngredientParsingResult] object.
const factory IngredientParsingResult({
/// Optionally parsed ingredient.
Ingredient? ingredient,
@Default([]) List<Ingredient> ingredients,

/// Additional informations about the parsing.
required List<MetaDataLog> metaDataLogs,
@Default([]) List<MetaDataLog> metaDataLogs,
}) = _IngredientParsingResult;
}
10 changes: 6 additions & 4 deletions lib/src/recipe_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import 'models/recipe.dart';
import 'models/recipe_parsing_job.dart';
import 'models/recipe_parsing_result.dart';
import 'recipe_scripts/bianca_zapatka.dart';
import 'recipe_scripts/eat_this.dart';
import 'recipe_scripts/kptncook.dart';
import 'recipe_scripts/recipe_scripts_helper.dart';

final Map<String, RecipeParsingResult Function(Document, RecipeParsingJob)>
_recipeParseMethodMap = {
'mobile.kptncook.com': parseKptnCookRecipe,
'biancazapatka.com': parseBiancaZapatkaRecipe,
'http://mobile.kptncook.com': parseKptnCookRecipe,
'https://biancazapatka.com': parseBiancaZapatkaRecipe,
'https://www.eat-this.org': parseEatThisRecipe,
};

/// Collects recipes from the websites in the passed [recipeParsingJobs].
Expand Down Expand Up @@ -56,7 +58,7 @@ Future<RecipeParsingResult> _collectRecipe(

var document = parse(response.body);

var parseMethod = _recipeParseMethodMap[recipeParsingJob.url.host];
var parseMethod = _recipeParseMethodMap[recipeParsingJob.url.origin];

return parseMethod!.call(document, recipeParsingJob);
}
Expand All @@ -65,4 +67,4 @@ Future<RecipeParsingResult> _collectRecipe(
///
/// Supported means that there's a parsing script available which can be used
/// to collect the ingredients of the recipe.
bool isUrlSupported(Uri url) => _recipeParseMethodMap.containsKey(url.host);
bool isUrlSupported(Uri url) => _recipeParseMethodMap.containsKey(url.origin);
100 changes: 16 additions & 84 deletions lib/src/recipe_scripts/bianca_zapatka.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import 'package:html/dom.dart';

import '../models/ingredient.dart';
import '../models/ingredient_parsing_result.dart';
import '../models/meta_data_log.dart';
import '../models/recipe.dart';
import '../models/recipe_parsing_job.dart';
import '../models/recipe_parsing_result.dart';
import 'parsing_helper.dart';
import 'recipe_scripts_helper.dart';
import 'wordpress_ingredient_parsing.dart';

/// Parses a [Document] from the Bianca Zapatka website to a recipe.
RecipeParsingResult parseBiancaZapatkaRecipe(
Expand All @@ -31,34 +30,12 @@ RecipeParsingResult parseBiancaZapatkaRecipe(
var recipeServings = int.parse(servingsElements.first.text);
var servingsMultiplier = recipeParsingJob.servings / recipeServings;

var ingredientParsingResults = ingredientContainers
.map(
(element) => parseIngredient(
element,
servingsMultiplier,
recipeParsingJob.url.toString(),
language: recipeParsingJob.language,
),
)
.toList();

var logs = ingredientParsingResults
.map((result) => result.metaDataLogs)
.expand((metaDataLogs) => metaDataLogs)
.toList();

var ingredients = ingredientParsingResults
.map((result) => result.ingredient)
.whereType<Ingredient>()
.toList();

return RecipeParsingResult(
recipe: Recipe(
ingredients: ingredients,
name: recipeName,
servings: recipeParsingJob.servings,
),
metaDataLogs: logs,
return createResultFromIngredientParsing(
ingredientContainers,
recipeParsingJob,
servingsMultiplier,
recipeName,
parseIngredient,
);
}

Expand All @@ -69,59 +46,14 @@ RecipeParsingResult parseBiancaZapatkaRecipe(
/// If the parsing fails the ingredient in [IngredientParsingResult] will be
/// null and a suitable log will be returned.
IngredientParsingResult parseIngredient(
Element ingredientElement,
Element element,
double servingsMultiplier,
String recipeUrl, {
String recipeUrl,
String? language,
}) {
var amount = 0.0;
var unit = "";
var name = "";

var nameElements =
ingredientElement.getElementsByClassName("wprm-recipe-ingredient-name");
if (nameElements.isNotEmpty) {
var nameElement = nameElements.first;
// Sometimes the name has a url reference in a <a> tag
if (nameElement.children.isNotEmpty) {
name = nameElement.children.map((e) => e.text).join();
} else {
name = nameElement.text.trim();
}
} else {
return createFailedIngredientParsingResult(recipeUrl);
}

var logs = <MetaDataLog>[];

var amountElements =
ingredientElement.getElementsByClassName("wprm-recipe-ingredient-amount");
if (amountElements.isNotEmpty) {
var amountElement = amountElements.first;
var amountString = amountElement.text.trim();
var parsedAmount = tryParseAmountString(amountString, language: language);
if (parsedAmount != null) {
amount = parsedAmount * servingsMultiplier;
} else {
logs.add(
createFailedAmountParsingMetaDataLog(
recipeUrl,
amountString,
name,
),
);
}
}

var unitElements =
ingredientElement.getElementsByClassName("wprm-recipe-ingredient-unit");
if (unitElements.isNotEmpty) {
var unitElement = unitElements.first;
unit = unitElement.text.trim();
}

return IngredientParsingResult(
ingredient: Ingredient(amount: amount, unit: unit, name: name),
metaDataLogs: logs,
);
}
) =>
parseWordPressIngredient(
element,
servingsMultiplier,
recipeUrl,
language,
);
Loading

0 comments on commit 5b73e04

Please sign in to comment.