|
|
@@ -155,9 +155,11 @@ object Parser {
|
|
|
("iheartvegetables.com" -> Parser.tastyRecipes),
|
|
|
("seriouseats.com" -> Parser.seriousEats),
|
|
|
("greatist.com" -> Parser.greatist),
|
|
|
- ("dimitrasdishes.com" -> Parser.dimitrasDishes),
|
|
|
+ ("dimitrasdishes.com" -> Parser.mvCreate),
|
|
|
("jif.com" -> Parser.jif),
|
|
|
- ("kingarthurbaking.com" -> Parser.kingArthurBaking)
|
|
|
+ ("kingarthurbaking.com" -> Parser.kingArthurBaking),
|
|
|
+ ("tasteasianfood.com" -> Parser.mvCreate),
|
|
|
+ ("lovefood.com" -> Parser.loveFood)
|
|
|
)
|
|
|
|
|
|
private val frequentParsers = Seq.from(
|
|
|
@@ -242,6 +244,40 @@ object Parser {
|
|
|
texts("div.wprm-recipe-instruction-text")
|
|
|
) _
|
|
|
|
|
|
+ def mvCreate: ParserFn = Parser(
|
|
|
+ text("*.mv-create-title-primary"),
|
|
|
+ text("span.mv-create-nutrition-serving-size").map(_.toFloatOption),
|
|
|
+ Some(text("div.mv-create-time-prep > span.mv-time-minutes")),
|
|
|
+ Some(text("div.mv-create-time-active > span.mv-time-minutes")),
|
|
|
+ texts("div.mv-create-ingredients > ul > li").map(
|
|
|
+ _.map(_
|
|
|
+ .replace("and", "")
|
|
|
+ .replaceAll("\u00BD", "1/2")
|
|
|
+ .replaceAll("\u00BC", "1/4")
|
|
|
+ .replaceAll("\u00BE", "3/4")
|
|
|
+ .replaceAll("\u2150", "1/7")
|
|
|
+ .replaceAll("\u2151", "1/9")
|
|
|
+ .replaceAll("\u2152", "1/10")
|
|
|
+ .replaceAll("\u2153", "1/3")
|
|
|
+ .replaceAll("\u2154", "2/3")
|
|
|
+ .replaceAll("\u2155", "1/5")
|
|
|
+ .replaceAll("\u2156", "2/5")
|
|
|
+ .replaceAll("\u2157", "3/5")
|
|
|
+ .replaceAll("\u2158", "4/5")
|
|
|
+ .replaceAll("\u2159", "1/6")
|
|
|
+ .replaceAll("\u215A", "5/6")
|
|
|
+ .replaceAll("\u215B", "1/8")
|
|
|
+ .replaceAll("\u215C", "3/8")
|
|
|
+ .replaceAll("\u215D", "5/8")
|
|
|
+ .replaceAll("\u215E", "7/8")
|
|
|
+ .replaceAll("\u215F", "1/")
|
|
|
+ .replaceAll("\u00F1", "n")
|
|
|
+ .trim
|
|
|
+ ).map(_parseIngredient _)
|
|
|
+ ),
|
|
|
+ texts("div.mv-create-instructions > ol > li")
|
|
|
+ ) _
|
|
|
+
|
|
|
def tastyRecipes: ParserFn = Parser(
|
|
|
text("h2.tasty-recipes-title"),
|
|
|
text("span.tasty-recipes-yield")
|
|
|
@@ -333,40 +369,6 @@ object Parser {
|
|
|
texts("article.article-body > ol > li")
|
|
|
) _
|
|
|
|
|
|
- def dimitrasDishes: ParserFn = Parser(
|
|
|
- text("h2.mv-create-title-primary"),
|
|
|
- text("div.mv-create-time-yield > span").map(_.toFloatOption),
|
|
|
- None,
|
|
|
- None,
|
|
|
- texts("div.mv-create-ingredients > ul > li").map(
|
|
|
- _.map(_
|
|
|
- .replace("and", "")
|
|
|
- .replaceAll("\u00BD", "1/2")
|
|
|
- .replaceAll("\u00BC", "1/4")
|
|
|
- .replaceAll("\u00BE", "3/4")
|
|
|
- .replaceAll("\u2150", "1/7")
|
|
|
- .replaceAll("\u2151", "1/9")
|
|
|
- .replaceAll("\u2152", "1/10")
|
|
|
- .replaceAll("\u2153", "1/3")
|
|
|
- .replaceAll("\u2154", "2/3")
|
|
|
- .replaceAll("\u2155", "1/5")
|
|
|
- .replaceAll("\u2156", "2/5")
|
|
|
- .replaceAll("\u2157", "3/5")
|
|
|
- .replaceAll("\u2158", "4/5")
|
|
|
- .replaceAll("\u2159", "1/6")
|
|
|
- .replaceAll("\u215A", "5/6")
|
|
|
- .replaceAll("\u215B", "1/8")
|
|
|
- .replaceAll("\u215C", "3/8")
|
|
|
- .replaceAll("\u215D", "5/8")
|
|
|
- .replaceAll("\u215E", "7/8")
|
|
|
- .replaceAll("\u215F", "1/")
|
|
|
- .replaceAll("\u00F1", "n")
|
|
|
- .trim
|
|
|
- ).map(_parseIngredient _)
|
|
|
- ),
|
|
|
- texts("div.mv-create-instructions > ol > li")
|
|
|
- ) _
|
|
|
-
|
|
|
def jif: ParserFn = Parser(
|
|
|
text("h1.recipe-name"),
|
|
|
elementList("div.recipe-breakdown-step").map(
|
|
|
@@ -445,6 +447,51 @@ object Parser {
|
|
|
texts("div.field field--recipe-steps > ol > li > p")
|
|
|
) _
|
|
|
|
|
|
+ def loveFood: ParserFn = Parser(
|
|
|
+ text("h1.post__title"),
|
|
|
+ elementList("div.layout__item.u-1/2-lap > ul > li").map(
|
|
|
+ _.filter((listItem) => (listItem >?> text("strong")) == Some("Serves:"))
|
|
|
+ .map(_ >> text)
|
|
|
+ .head
|
|
|
+ ).map("Serves: \\D*(\\d+).*".r.findFirstMatchIn(_).map(_.group(1).toFloat)),
|
|
|
+ Some(elementList("div.layout__item.u-1/2-lap > ul > li").map(
|
|
|
+ _.filter((listItem) => (listItem >?> text("strong")) == Some("Preparation Time:"))
|
|
|
+ .map(_ >> text)
|
|
|
+ .head
|
|
|
+ ).map("Preparation Time: \\D*(\\d+).*".r.findFirstMatchIn(_).map(_.group(1)).getOrElse(""))),
|
|
|
+ Some(elementList("div.layout__item.u-1/2-lap > ul > li").map(
|
|
|
+ _.filter((listItem) => (listItem >?> text("strong")) == Some("Cooking Time:"))
|
|
|
+ .map(_ >> text)
|
|
|
+ .head
|
|
|
+ ).map("Cooking Time: \\D*(\\d+).*".r.findFirstMatchIn(_).map(_.group(1)).getOrElse(""))),
|
|
|
+ texts("ul[name='ingredients-metric'] > li")
|
|
|
+ .map(_.map(_
|
|
|
+ .replaceAll("\u00BD", "1/2")
|
|
|
+ .replaceAll("\u00BC", "1/4")
|
|
|
+ .replaceAll("\u00BE", "3/4")
|
|
|
+ .replaceAll("\u2150", "1/7")
|
|
|
+ .replaceAll("\u2151", "1/9")
|
|
|
+ .replaceAll("\u2152", "1/10")
|
|
|
+ .replaceAll("\u2153", "1/3")
|
|
|
+ .replaceAll("\u2154", "2/3")
|
|
|
+ .replaceAll("\u2155", "1/5")
|
|
|
+ .replaceAll("\u2156", "2/5")
|
|
|
+ .replaceAll("\u2157", "3/5")
|
|
|
+ .replaceAll("\u2158", "4/5")
|
|
|
+ .replaceAll("\u2159", "1/6")
|
|
|
+ .replaceAll("\u215A", "5/6")
|
|
|
+ .replaceAll("\u215B", "1/8")
|
|
|
+ .replaceAll("\u215C", "3/8")
|
|
|
+ .replaceAll("\u215D", "5/8")
|
|
|
+ .replaceAll("\u215E", "7/8")
|
|
|
+ .replaceAll("\u215F", "1/")
|
|
|
+ .replaceAll("\u00F1", "n")
|
|
|
+ .trim
|
|
|
+ ))
|
|
|
+ .map(_.map(_parseIngredient _)),
|
|
|
+ texts("div.content__step-by-step > ol > li")
|
|
|
+ ) _
|
|
|
+
|
|
|
private def _parseFraction(fractionLine: String) = {
|
|
|
val fractionPattern = raw"(\d+)/(\d+)[\d-_]*".r
|
|
|
val mixedFractionPattern = raw"(\d+)\w+(\d+)/(\d+)[\d-_]*".r
|