UNPKG

recipe-ingredient-parser-v3

Version:

Natural language parser for recipes and ingredient lists, incl. combining ingredients

136 lines (122 loc) 4.57 kB
import { numbersMap } from './numbers'; export function convertFromFraction(value: string) { // number comes in, for example: 1 1/3 if (value && value.split(' ').length > 1) { const [whole, fraction] = value.split(' '); const [a, b] = fraction.split('/'); const remainder = parseFloat(a) / parseFloat(b); const wholeAndFraction = parseInt(whole) ? parseInt(whole) + remainder : remainder; return keepTwoDecimals(wholeAndFraction); } else if (!value || value.split('-').length > 1) { return value; } else { const [a, b] = value.split('/'); return b ? keepTwoDecimals(parseFloat(a) / parseFloat(b)) : a; } } export function getFirstMatch(line: string, regex: RegExp) { const match = line.match(regex); return (match && match[0]) || ''; } const unicodeObj: { [key: string]: string } = { '½': '1/2', '⅓': '1/3', '⅔': '2/3', '¼': '1/4', '¾': '3/4', '⅕': '1/5', '⅖': '2/5', '⅗': '3/5', '⅘': '4/5', '⅙': '1/6', '⅚': '5/6', '⅐': '1/7', '⅛': '1/8', '⅜': '3/8', '⅝': '5/8', '⅞': '7/8', '⅑': '1/9', '⅒': '1/10' }; export function text2num(s: string, language: string) { const a = s.toString().split(/[\s-]+/); let values: number[] = [0, 0]; a.forEach(x => { values = feach(x, values[0], values[1], language) }); if (values[0] + values[1] < 0) return null else return values[0] + values[1]; } export function feach(w: string, g: number, n: number, language: string) { let number = numbersMap.get(language) let small = number[0] let magnitude = number[1] var x = small[w]; if (x != null) { g = g + x; } else if (100 == magnitude[w]) { if (g > 0) g = g * 100; else g = 100 } else { x = magnitude[w]; if (x != null) { n = n + g * x g = 0; } else return [-1, -1] } return [g, n] } export function findQuantityAndConvertIfUnicode(ingredientLine: string, language: string) { const numericAndFractionRegex = /\b(\d+\/\d+|\d+\s\d+\/\d+|\d+\.\d+|\d+)\b/g; // consider only the numbers that have space and not those attached to words, such as "petra9" //const numericRangeWithSpaceRegex = /^(\d+\-\d+)|^(\d+\s\-\s\d+)|^(\d+\sto\s\d+)/g; // for ex: "1 to 2" or "1 - 2" const unicodeFractionRegex = /\d*[^\u0000-\u007F]+/g; const onlyUnicodeFraction = /[^\u0000-\u007F]+/g; const wordUntilSpace = /[^\s]+/g; // found a unicode quantity inside our regex, for ex: '⅝' if (ingredientLine.match(unicodeFractionRegex)) { const numericPart = getFirstMatch(ingredientLine, numericAndFractionRegex); const unicodePart = getFirstMatch(ingredientLine, numericPart ? onlyUnicodeFraction : unicodeFractionRegex); // If there's a match for the unicodePart in our dictionary above if (unicodeObj[unicodePart]) { return [`${numericPart} ${unicodeObj[unicodePart]}`, ingredientLine.replace(getFirstMatch(ingredientLine, unicodeFractionRegex), '').trim()]; } } // found a quantity range, for ex: "2 to 3" // if (ingredientLine.match(numericRangeWithSpaceRegex)) { // const quantity = getFirstMatch(ingredientLine, numericRangeWithSpaceRegex).replace('to', '-').split(' ').join(''); // const restOfIngredient = ingredientLine.replace(getFirstMatch(ingredientLine, numericRangeWithSpaceRegex), '').trim(); // return [ingredientLine.match(numericRangeWithSpaceRegex) && quantity, restOfIngredient]; // } // found a numeric/fraction quantity, for example: "1 1/3" if (ingredientLine.match(numericAndFractionRegex)) { const quantity = getFirstMatch(ingredientLine, numericAndFractionRegex); const restOfIngredient = ingredientLine.replace(getFirstMatch(ingredientLine, numericAndFractionRegex), '').trim() return [ingredientLine.match(numericAndFractionRegex) && quantity, restOfIngredient]; } else if (ingredientLine.match(wordUntilSpace)) { const quantity = getFirstMatch(ingredientLine, wordUntilSpace); const quantityNumber = text2num(quantity.toLowerCase(), language) if (quantityNumber) { const restOfIngredient = ingredientLine.replace(getFirstMatch(ingredientLine, wordUntilSpace), '').trim() return [ingredientLine.match(wordUntilSpace) && quantityNumber + '', restOfIngredient]; } else return [null, ingredientLine]; } // no parse-able quantity found else { return [null, ingredientLine]; } } function keepTwoDecimals(val: number) { const strVal = val.toString(); return strVal.split('.')[0] + '.' + strVal.split('.')[1].substring(0, 2); }