Beispiel #1
0
    def parse_dish(self, dish_str):
        # ingredients
        dish_ingredients = Ingredients("mediziner-mensa")
        matches = re.findall(self.ingredients_regex, dish_str)
        while len(matches) > 0:
            for x in matches:
                if len(x) > 0:
                    dish_ingredients.parse_ingredients(x[0])
            dish_str = re.sub(self.ingredients_regex, " ", dish_str)
            matches = re.findall(self.ingredients_regex, dish_str)
        dish_str = re.sub(r"\s+", " ", dish_str).strip()
        dish_str = dish_str.replace(" , ", ", ")

        # price
        dish_price = Prices()
        for x in re.findall(self.price_regex, dish_str):
            if len(x) > 0:
                dish_price = Prices(
                    Price(
                        float(x[0].replace("€", "").replace(",",
                                                            ".").strip())))
        dish_str = re.sub(self.price_regex, "", dish_str)

        return Dish(dish_str, dish_price, dish_ingredients.ingredient_set,
                    "Tagesgericht")
Beispiel #2
0
    def __getPrice(location: str, dish: Tuple[str, str, str, str, str]):
        if "Self-Service" in dish[0] or location == "mensa-garching":
            if dish[4] == "0":  # Meat
                prices: Prices = StudentenwerkMenuParser.prices_self_service_classic
                # Add a base price to the dish
                if "Fi" in dish[2]:  # Fish
                    prices.setBasePrice(
                        StudentenwerkMenuParser.prices_self_service_base[2])
                else:  # Sausage and meat. TODO: Find a way to distinguish between sausage and meat
                    prices.setBasePrice(
                        StudentenwerkMenuParser.prices_self_service_base[1])
                return prices
            elif dish[4] == "1":  # Vegetarian
                return StudentenwerkMenuParser.prices_self_service_classic
            elif dish[4] == "2":  # Vegan
                return StudentenwerkMenuParser.prices_self_service_vegan
            else:
                pass

        if location == "mensa-leopoldstr":
            return StudentenwerkMenuParser.prices_mesa_leopoldstr.get(
                dish[0], Prices())

        # Fall back to the old price
        return StudentenwerkMenuParser.prices_mesa_weihenstephan_mensa_lothstrasse.get(
            dish[0], Prices())
Beispiel #3
0
    def test_Should_Add_Dish_to_Canteen(self):
        canteen = LazyBuilder()
        dateobj = date(2017, 3, 27)
        dish = Dish("Gulasch vom Schwein", Prices(Price(1.9)),
                    set(["S", "Gl", "GlG", "GlW", "Kn", "Mi"]), "Tagesgericht")

        openmensa.addDishToCanteen(dish, dateobj, canteen)
        meal = canteen._days[dateobj]['Speiseplan'][0]
        self.assertEqual(meal[0], "Gulasch vom Schwein")
        self.assertEqual(meal[2], {'other': 190})
Beispiel #4
0
    def get_menus(self, text, year, week_number):
        menus = {}
        lines = text.splitlines()
        count = 0
        # remove headline etc.
        for line in lines:
            # Find the line which is the header of the table and includes the day of week
            line_shrink = line.replace(" ", "").replace("\n", "").lower()
            # Note we do not include 'montag' und 'freitag' since they are also used in the line before the table
            # header to indicate the range of the week “Monday … until Friday _”
            if any(x in line_shrink
                   for x in ('dienstag', 'mittwoch', 'donnerstag')):
                break

            count += 1

        else:
            warn(
                "NotImplemented: IPP parsing failed. Menu text is not a weekly menu. First line: '{}'"
                .format(lines[0]))
            return None

        lines = lines[count:]
        weekdays = lines[0]

        # The column detection is done through the string "Tagessuppe siehe Aushang" which is at the beginning of
        # every column. However, due to center alignment the column do not begin at the 'T' character and broader
        # text in the column might be left of this character, which then gets truncated. But the gap between the 'T'
        # and the '€' character of the previous column¹ — the real beginning of the current column — is always three,
        # which will be subtracted here. Monday is the second column, so the value should never become negative
        # although it is handled here.
        # ¹or 'e' of "Internationale Küche" if it is the monday column

        # find lines which match the regex
        # lines[1:] == exclude the weekday line which also can contain `Geschlossen`
        soup_lines_iter = (x for x in lines[1:]
                           if self.split_days_regex.search(x))

        soup_line1 = next(soup_lines_iter)
        soup_line2 = next(soup_lines_iter, '')

        # Sometimes on closed days, the keywords are written instead of the week of day instead of the soup line
        positions1 = [
            (max(a.start() - 3, 0), a.end())
            for a in list(re.finditer(self.split_days_regex_closed, weekdays))
        ]

        positions2 = [(max(a.start() - 3, 0), a.end()) for a in list(
            re.finditer(self.split_days_regex_soup_one_line, soup_line1))]
        # In the second line there is just 'Aushang' (two lines "Tagessuppe siehe Aushang" or
        # closed days ("Geschlossen", "Feiertag")
        positions3 = [(max(a.start() - 14, 0), a.end() + 3) for a in list(
            re.finditer(self.split_days_regex_soup_two_line, soup_line2))]
        # closed days ("Geschlossen", "Feiertag", …) can be in first line and second line
        positions4 = [
            (max(a.start() - 3, 0), a.end()) for a in
            list(re.finditer(self.split_days_regex_closed, soup_line1)) +
            list(re.finditer(self.split_days_regex_closed, soup_line2))
        ]

        if positions3:  # Two lines "Tagessuppe siehe Aushang"
            soup_line_index = lines.index(soup_line2)
        else:
            soup_line_index = lines.index(soup_line1)

        positions = sorted(positions1 + positions2 + positions3 + positions4)

        if len(positions) != 5:
            warn(
                "IPP PDF parsing of week {} in year {} failed. Only {} of 5 columns detected."
                .format(week_number, year, len(positions)))
            return None

        pos_mon = positions[0][0]
        pos_tue = positions[1][0]
        pos_wed = positions[2][0]
        pos_thu = positions[3][0]
        pos_fri = positions[4][0]

        lines_weekdays = {
            "mon": "",
            "tue": "",
            "wed": "",
            "thu": "",
            "fri": ""
        }
        # it must be lines[3:] instead of lines[2:] or else the menus would start with "Preis ab 0,90€" (from the
        # soups) instead of the first menu, if there is a day where the bistro is closed.
        for line in lines[soup_line_index + 3:]:
            lines_weekdays["mon"] += " " + line[pos_mon:pos_tue].replace(
                "\n", " ")
            lines_weekdays["tue"] += " " + line[pos_tue:pos_wed].replace(
                "\n", " ")
            lines_weekdays["wed"] += " " + line[pos_wed:pos_thu].replace(
                "\n", " ")
            lines_weekdays["thu"] += " " + line[pos_thu:pos_fri].replace(
                "\n", " ")
            lines_weekdays["fri"] += " " + line[pos_fri:].replace("\n", " ")

        for key in lines_weekdays:
            # Appends `?€` to „Überraschungsmenü“ if it do not have a price. The second '€' is a separator for the
            # later split
            lines_weekdays[key] = self.surprise_without_price_regex.sub(
                r"\g<1>?€ € \g<2>", lines_weekdays[key])
            # get rid of two-character umlauts (e.g. SMALL_LETTER_A+COMBINING_DIACRITICAL_MARK_UMLAUT)
            lines_weekdays[key] = unicodedata.normalize(
                "NFKC", lines_weekdays[key])
            # remove multi-whitespaces
            lines_weekdays[key] = ' '.join(lines_weekdays[key].split())
            # get all dish including name and price
            dish_names_price = re.findall(self.dish_regex,
                                          lines_weekdays[key] + ' ')
            # create dish types
            # since we have the same dish types every day we can use them if there are 4 dishes available
            if len(dish_names_price) == 4:
                dish_types = [
                    "Veggie", "Traditionelle Küche", "Internationale Küche",
                    "Specials"
                ]
            else:
                dish_types = ["Tagesgericht"] * len(dish_names_price)

            # create ingredients
            # all dishes have the same ingridients
            ingredients = Ingredients("ipp-bistro")
            ingredients.parse_ingredients("Mi,Gl,Sf,Sl,Ei,Se,4")
            # create list of Dish objects
            counter = 0
            dishes = []
            for (dish_name, price) in dish_names_price:
                dishes.append(
                    Dish(dish_name.strip(),
                         Prices(Price(price.replace(',', '.').strip())),
                         ingredients.ingredient_set, dish_types[counter]))
                counter += 1
            date = self.get_date(year, week_number,
                                 self.weekday_positions[key])
            # create new Menu object and add it to dict
            menu = Menu(date, dishes)
            # remove duplicates
            menu.remove_duplicates()
            menus[date] = menu

        return menus
Beispiel #5
0
 def _parse_price(prices: Dict[str, float]) -> Prices:
     return Prices(
         **{key: Price(base_price=val)
            for key, val in prices.items()})
Beispiel #6
0
    def get_menus(self, text, year, week_number):
        menus = {}
        lines = text.splitlines()
        count = 0
        # remove headline etc.
        for line in lines:
            if line.replace(" ", "").replace(
                    "\n",
                    "").lower() == "montagdienstagmittwochdonnerstagfreitag":
                break

            count += 1

        lines = lines[count:]
        # we assume that the weeksdays are now all in the first line
        pos_mon = lines[0].find("Montag")
        pos_tue = lines[0].find("Dienstag")
        pos_wed = lines[0].find("Mittwoch")
        pos_thu = lines[0].find("Donnerstag")
        pos_fri = lines[0].find("Freitag")

        # The text is formatted as table using whitespaces. Hence, we need to get those parts of each line that refer
        #  to the respective week day
        lines_weekdays = {
            "mon": "",
            "tue": "",
            "wed": "",
            "thu": "",
            "fri": ""
        }
        for line in lines:
            lines_weekdays["mon"] += " " + line[pos_mon:pos_tue].replace(
                "\n", " ").replace("Montag", "")
            lines_weekdays["tue"] += " " + line[pos_tue:pos_wed].replace(
                "\n", " ").replace("Dienstag", "")
            lines_weekdays["wed"] += " " + line[pos_wed:pos_thu].replace(
                "\n", " ").replace("Mittwoch", "")
            lines_weekdays["thu"] += " " + line[pos_thu:pos_fri].replace(
                "\n", " ").replace("Donnerstag", "")
            lines_weekdays["fri"] += " " + line[pos_fri:].replace(
                "\n", " ").replace("Freitag", "")

        # currently, up to 5 dishes are on the menu
        num_dishes = 5
        line_aktion = []
        if year < 2018:
            # in older versions of the FMI Bistro menu, the Aktionsgericht was the same for the whole week
            num_dishes = 3
            line_aktion = [s for s in lines if "Aktion" in s]
            if len(line_aktion) == 1:
                line_aktion_pos = lines.index(line_aktion[0]) - 2
                aktionsgericht = ' '.join(
                    lines[line_aktion_pos:line_aktion_pos + 3])
                aktionsgericht = aktionsgericht \
                    .replace("Montag – Freitag", "") \
                    .replace("Tagessuppe täglich wechselndes Angebot", "") \
                    .replace("ab € 1,00", "") \
                    .replace("Aktion", "")
                num_dishes += aktionsgericht.count('€')
                for key in lines_weekdays:
                    lines_weekdays[
                        key] = aktionsgericht + ", " + lines_weekdays[key]

        # Process menus for each day
        for key in lines_weekdays:
            # stop parsing day when bistro is closed at that day
            if "geschlossen" in lines_weekdays[key].lower():
                continue

            # extract all allergens
            dish_allergens = []
            for x in re.findall(self.allergens_regex, lines_weekdays[key]):
                if len(x) > 0:
                    dish_allergens.append(
                        re.sub(r"((Allergene:)|\s|\n)*", "", x[0]))
                else:
                    dish_allergens.append("")
            lines_weekdays[key] = re.sub(self.allergens_regex, "",
                                         lines_weekdays[key])
            # get rid of two-character umlauts (e.g. SMALL_LETTER_A+COMBINING_DIACRITICAL_MARK_UMLAUT)
            lines_weekdays[key] = unicodedata.normalize(
                "NFKC", lines_weekdays[key])
            # remove multi-whitespaces
            lines_weekdays[key] = ' '.join(lines_weekdays[key].split())

            # remove no allergens indicator
            lines_weekdays[key] = lines_weekdays[key].replace("./.", "")
            # get all dish including name and price
            dish_names = re.findall(self.dish_regex, lines_weekdays[key])
            # get dish prices
            prices = re.findall(self.price_regex, ' '.join(dish_names))
            # convert prices to float
            prices = [
                Prices(
                    Price(
                        float(
                            price.replace("€", "").replace(",", ".").strip())))
                for price in prices
            ]
            # remove price and commas from dish names
            dish_names = [
                re.sub(self.price_regex, "", dish).replace(",", "").strip()
                for dish in dish_names
            ]
            # create list of Dish objects; only take first 3/4 as the following dishes are corrupt and not necessary
            dishes = []
            for (dish_name, price,
                 dish_allergen) in list(zip(dish_names, prices,
                                            dish_allergens)):
                # filter empty dishes
                if dish_name:
                    ingredients = Ingredients("fmi-bistro")
                    ingredients.parse_ingredients(dish_allergen)
                    dishes.append(
                        Dish(dish_name, price, ingredients.ingredient_set,
                             "Tagesgericht"))
            dishes = dishes[:num_dishes]
            date = self.get_date(year, week_number,
                                 self.weekday_positions[key])
            # create new Menu object and add it to dict
            menu = Menu(date, dishes)
            # remove duplicates
            menu.remove_duplicates()
            menus[date] = menu

        return menus
Beispiel #7
0
class StudentenwerkMenuParser(MenuParser):
    # Prices taken from: https://www.studentenwerk-muenchen.de/mensa/mensa-preise/

    # Base price for sausage, meat, fish
    prices_self_service_base: Tuple[float, float, float] = (0.55, 1.00, 1.50)
    # Meet and vegetarian base prices for Students, Staff, Guests
    prices_self_service_classic: Prices = Prices(Price(0, 0.75, "100g"),
                                                 Price(0, 0.90, "100g"),
                                                 Price(0, 1.05, "100g"))
    # Vegan, stew and soup prices for students, staff, guests
    prices_self_service_vegan: Prices = Prices(Price(0, 0.33, "100g"),
                                               Price(0, 0.55, "100g"),
                                               Price(0, 0.66, "100g"))

    # Students, Staff, Guests
    prices_mesa_leopoldstr: Dict[str, Prices] = {
        "Grüne Mensa":
        Prices(),
        "Vegan":
        Prices(Price(0, 0.33, "100g"), Price(0, 0.55, "100g"),
               Price(0, 0.66, "100g")),
        "Vegetarisch":
        Prices(Price(0, 0.75, "100g"), Price(0, 0.85, "100g"),
               Price(0, 0.95, "100g")),
        "Suppe":
        Prices(Price(0.55), Price(0.65), Price(0.80)),
        "Länder-Mensa":
        Prices(),
        "Länder Menü":
        Prices(Price(0, 0.75, "100g"), Price(0, 0.85, "100g"),
               Price(0, 0.95, "100g")),
        "Länder-Suppe":
        Prices(Price(0.55), Price(0.65), Price(0.80)),
        "Mensa Klassiker":
        Prices(),
        "Klassik Menü":
        Prices(Price(0, 0.85, "100g"), Price(0, 0.90, "100g"),
               Price(0, 1, "100g")),
        "Klassik Tellergericht":
        Prices(),
        "Klassik Suppe":
        Prices(Price(0.55), Price(0.65), Price(0.80)),
        "Mensa Spezial Pasta":
        Prices(),
        "Pasta-Menü":
        Prices(Price(0, 0.60, "100g"), Price(0, 0.70, "100g"),
               Price(0, 0.80, "100g")),
        "Beilage":
        Prices(Price(0.60), Price(0.77), Price(0.92)),
        "Aktionssalat 3":
        Prices(Price(0.80), Price(1.14), Price(1.34)),
        "Dessert":
        Prices(Price(0.60), Price(0.77), Price(0.92)),
        "Aktionsdessert 3":
        Prices(Price(0.80), Price(1.14), Price(1.34)),
        "Aktionsdessert 4":
        Prices(Price(1), Price(1.34), Price(1.54)),
        "Frische Säfte":
        Prices(Price(1.50), Price(1.50), Price(1.50))
    }

    # Students, Staff, Guests
    # Looks like those are the fallback prices
    prices_mesa_weihenstephan_mensa_lothstrasse: Dict[str, Tuple[
        Price, Price, Price]] = {
            "Tagesgericht 1":
            Prices(Price(1.00), Price(1.90), Price(2.40)),
            "Tagesgericht 2":
            Prices(Price(1.55), Price(2.25), Price(2.75)),
            "Tagesgericht 3":
            Prices(Price(1.90), Price(2.60), Price(3.10)),
            "Tagesgericht 4":
            Prices(Price(2.40), Price(2.95), Price(3.45)),
            "Suppe":
            Prices(Price(0.55), Price(0.65), Price(0.80)),
            "Stärkebeilagen":
            Prices(Price(0.60), Price(0.77), Price(0.92)),
            "Beilage":
            Prices(Price(0.60), Price(0.79), Price(0.94)),
            "Salatbuffet":
            Prices(Price(0, 0.85, "100g"), Price(0, 0.90, "100g"),
                   Price(0, 0.95, "100g")),
            "Obst":
            Prices(Price(0.80), Price(0.80), Price(0.80)),
            "Aktionsgericht 1":
            Prices(Price(1.55), Price(2.25), Price(2.75)),
            "Aktionsgericht 2":
            Prices(Price(1.90), Price(2.60), Price(3.10)),
            "Aktionsgericht 3":
            Prices(Price(2.40), Price(2.95), Price(3.45)),
            "Aktionsgericht 4":
            Prices(Price(2.60), Price(3.30), Price(3.80)),
            "Aktionsgericht 5":
            Prices(Price(2.80), Price(3.65), Price(4.15)),
            "Aktionsgericht 6":
            Prices(Price(3.00), Price(4.00), Price(4.50)),
            "Aktionsgericht 7":
            Prices(Price(3.20), Price(4.35), Price(4.85)),
            "Aktionsgericht 8":
            Prices(Price(3.50), Price(4.70), Price(5.20)),
            "Aktionsgericht 9":
            Prices(Price(4.00), Price(5.05), Price(5.55)),
            "Aktionsgericht 10":
            Prices(Price(4.50), Price(5.40), Price(5.90)),
            "Aktionsgericht 11":
            Prices(Price(5.50), Price(6.50), Price(7.20)),
            "Biogericht 1":
            Prices(Price(1.55), Price(2.25), Price(2.75)),
            "Biogericht 2":
            Prices(Price(1.90), Price(2.60), Price(3.10)),
            "Biogericht 3":
            Prices(Price(2.40), Price(2.95), Price(3.45)),
            "Biogericht 4":
            Prices(Price(2.60), Price(3.30), Price(3.80)),
            "Biogericht 5":
            Prices(Price(2.80), Price(3.65), Price(4.15)),
            "Biogericht 6":
            Prices(Price(3.00), Price(4.00), Price(4.50)),
            "Biogericht 7":
            Prices(Price(3.20), Price(4.35), Price(4.85)),
            "Biogericht 8":
            Prices(Price(3.50), Price(4.70), Price(5.20)),
            "Biogericht 9":
            Prices(Price(4.00), Price(5.05), Price(5.55)),
            "Biogericht 10":
            Prices(Price(4.50), Price(5.40), Price(5.90)),
            "Biogericht 11":
            Prices(Price(5.50), Price(6.50), Price(7.20)),
            "Biobeilage 1":
            Prices(Price(0.60), Price(0.79), Price(0.99)),
            "Biobeilage 2":
            Prices(Price(0.75), Price(0.94), Price(1.14)),
            "Biobeilage 3":
            Prices(Price(0.85), Price(1.14), Price(1.34)),
            "Biobeilage 4":
            Prices(Price(1.05), Price(1.34), Price(1.54)),
            "Biobeilage 6":
            Prices(Price(1.40), Price(1.60), Price(1.80)),
            "Aktionsbeilage 1":
            Prices(Price(0.60), Price(0.79), Price(0.99)),
            "Aktionsbeilage 2":
            Prices(Price(0.75), Price(0.94), Price(1.14)),
            "Aktionsbeilage 3":
            Prices(Price(0.85), Price(1.14), Price(1.34)),
            "Aktionsbeilage 4":
            Prices(Price(1.05), Price(1.34), Price(1.54)),
            "Aktionsbeilage 6":
            Prices(Price(1.40), Price(1.60), Price(1.80)),
        }

    @staticmethod
    def __getPrice(location: str, dish: Tuple[str, str, str, str, str]):
        if "Self-Service" in dish[0] or location == "mensa-garching":
            if dish[4] == "0":  # Meat
                prices: Prices = StudentenwerkMenuParser.prices_self_service_classic
                # Add a base price to the dish
                if "Fi" in dish[2]:  # Fish
                    prices.setBasePrice(
                        StudentenwerkMenuParser.prices_self_service_base[2])
                else:  # Sausage and meat. TODO: Find a way to distinguish between sausage and meat
                    prices.setBasePrice(
                        StudentenwerkMenuParser.prices_self_service_base[1])
                return prices
            elif dish[4] == "1":  # Vegetarian
                return StudentenwerkMenuParser.prices_self_service_classic
            elif dish[4] == "2":  # Vegan
                return StudentenwerkMenuParser.prices_self_service_vegan
            else:
                pass

        if location == "mensa-leopoldstr":
            return StudentenwerkMenuParser.prices_mesa_leopoldstr.get(
                dish[0], Prices())

        # Fall back to the old price
        return StudentenwerkMenuParser.prices_mesa_weihenstephan_mensa_lothstrasse.get(
            dish[0], Prices())

    # Some of the locations do not use the general Studentenwerk system and do not have a location id.
    # It differs how they publish their menus — probably everyone needs an own parser.
    # For documentation they are in the list but commented out.
    location_id_mapping: Dict[str, int] = {
        "mensa-arcisstr": 421,
        "mensa-arcisstrasse": 421,  # backwards compatibility
        "mensa-garching": 422,
        "mensa-leopoldstr": 411,
        "mensa-lothstr": 431,
        "mensa-martinsried": 412,
        "mensa-pasing": 432,
        "mensa-weihenstephan": 423,
        "stubistro-arcisstr": 450,
        # "stubistro-benediktbeuern": ,
        "stubistro-goethestr": 418,
        "stubistro-großhadern": 414,
        "stubistro-grosshadern": 414,
        "stubistro-rosenheim": 441,
        "stubistro-schellingstr": 416,
        # "stubistro-schillerstr": ,
        "stucafe-adalbertstr": 512,
        "stucafe-akademie-weihenstephan": 526,
        # "stucafe-audimax" ,
        "stucafe-boltzmannstr": 527,
        "stucafe-garching": 524,
        # "stucafe-heßstr": ,
        "stucafe-karlstr": 532,
        # "stucafe-leopoldstr": ,
        # "stucafe-olympiapark": ,
        "stucafe-pasing": 534,
        # "stucafe-weihenstephan": ,
    }

    base_url: str = "http://www.studentenwerk-muenchen.de/mensa/speiseplan/speiseplan_{}_-de.html"

    def parse(self, location: str):
        """`location` can be either the numeric location id or its string alias as defined in `location_id_mapping`"""
        try:
            location_id: int = int(location)
        except ValueError:
            try:
                location_id = self.location_id_mapping[location]
            except KeyError:
                print(
                    "Location {} not found. Choose one of {}.".format(
                        location, ', '.join(self.location_id_mapping.keys())),
                    sys.stderr)
                return None

        page_link: str = self.base_url.format(location_id)

        page: requests.Response = requests.get(page_link)
        tree: html.Element = html.fromstring(page.content)
        return self.get_menus(tree, location)

    def get_menus(self, page: html.Element, location: str):
        # initialize empty dictionary
        menus: Dict[date, Menu] = dict()
        # convert passed date to string
        # get all available daily menus
        daily_menus: html.Element = self.__get_daily_menus_as_html(page)

        # iterate through daily menus
        for daily_menu in daily_menus:
            # get html representation of current menu
            menu_html = html.fromstring(html.tostring(daily_menu))
            # get the date of the current menu; some string modifications are necessary
            current_menu_date_str = menu_html.xpath("//strong/text()")[0]
            # parse date
            try:
                current_menu_date: date = util.parse_date(
                    current_menu_date_str)
            except ValueError:
                print(
                    "Warning: Error during parsing date from html page. Problematic date: %s"
                    % current_menu_date_str)
                # continue and parse subsequent menus
                continue
            # parse dishes of current menu
            dishes: List[Dish] = self.__parse_dishes(menu_html, location)
            # create menu object
            menu: Menu = Menu(current_menu_date, dishes)
            # add menu object to dictionary using the date as key
            menus[current_menu_date] = menu

        # return the menu for the requested date; if no menu exists, None is returned
        return menus

    @staticmethod
    def __get_daily_menus_as_html(page):
        # obtain all daily menus found in the passed html page by xpath query
        daily_menus: page.xpath = page.xpath(
            "//div[@class='c-schedule__item']")
        return daily_menus

    @staticmethod
    def __parse_dishes(menu_html, location):
        # obtain the names of all dishes in a passed menu
        dish_names: List[str] = [
            dish.rstrip() for dish in menu_html.xpath(
                "//p[@class='js-schedule-dish-description']/text()")
        ]
        # make duplicates unique by adding (2), (3) etc. to the names
        dish_names = util.make_duplicates_unique(dish_names)
        # obtain the types of the dishes (e.g. 'Tagesgericht 1')
        dish_types: List[str] = [
            type.text if type.text else ''
            for type in menu_html.xpath("//span[@class='stwm-artname']")
        ]
        # obtain all ingredients
        dish_markers_additional: List[str] = menu_html.xpath(
            "//li[contains(@class, 'c-schedule__list-item  u-clearfix  clearfix  js-menu__list-item')]/@data-essen-zusatz"
        )
        dish_markers_allergen: List[str] = menu_html.xpath(
            "//li[contains(@class, 'c-schedule__list-item  u-clearfix  clearfix  js-menu__list-item')]/@data-essen-allergene"
        )
        dish_markers_type: List[str] = menu_html.xpath(
            "//li[contains(@class, 'c-schedule__list-item  u-clearfix  clearfix  js-menu__list-item')]/@data-essen-typ"
        )
        dish_markers_meetless: List[str] = menu_html.xpath(
            "//li[contains(@class, 'c-schedule__list-item  u-clearfix  clearfix  js-menu__list-item')]/@data-essen-fleischlos"
        )

        # create dictionary out of dish name and dish type
        dishes_dict: Dict[str, Tuple[str, str, str, str, str]] = dict()
        dishes_tup: zip = zip(dish_names, dish_types, dish_markers_additional,
                              dish_markers_allergen, dish_markers_type,
                              dish_markers_meetless)
        for dish_name, dish_type, dish_marker_additional, dish_marker_allergen, dish_marker_type, dish_marker_meetless in dishes_tup:
            dishes_dict[dish_name] = (dish_type, dish_marker_additional,
                                      dish_marker_allergen, dish_marker_type,
                                      dish_marker_meetless)

        # create Dish objects with correct prices; if price is not available, -1 is used instead
        dishes: List[Dish] = list()
        for name in dishes_dict:
            if not dishes_dict[name] and dishes:
                # some dishes are multi-row. That means that for the same type the dish is written in multiple rows.
                # From the second row on the type is then just empty. In that case, we just use the price and
                # ingredients of the previous dish.
                dishes.append(
                    Dish(name, dishes[-1].prices, dishes[-1].ingredients,
                         dishes[-1].dish_type))
            else:
                dish_ingredients: Ingredients = Ingredients(location)
                # parse ingredients
                dish_ingredients.parse_ingredients(dishes_dict[name][1])
                dish_ingredients.parse_ingredients(dishes_dict[name][2])
                dish_ingredients.parse_ingredients(dishes_dict[name][3])
                # find price
                price: Price = StudentenwerkMenuParser.__getPrice(
                    location, dishes_dict[name])
                # create dish
                dishes.append(
                    Dish(name, price, dish_ingredients.ingredient_set,
                         dishes_dict[name][0]))

        return dishes
Beispiel #8
0
    def test_Should_Add_Week_to_Canteen(self):
        date_mon2 = date(2017, 11, 6)
        date_tue2 = date(2017, 11, 7)
        date_wed2 = date(2017, 11, 8)
        date_thu2 = date(2017, 11, 9)
        date_fri2 = date(2017, 11, 10)
        dish_aktion2 = Dish(
            "Pochiertes Lachsfilet mit Dillsoße dazu Minze-Reis",
            Prices(Price(6.5)), set(["Sl", "Mi"]), "Tagesgericht")
        dish1_mon2 = Dish("Dampfkartoffeln mit Zucchinigemüse",
                          Prices(Price(3.6)), set(["Sl"]), "Tagesgericht")
        dish2_mon2 = Dish("Valess-Schnitzel mit Tomaten-Couscous",
                          Prices(Price(4.3)), set(["Sl", "Gl", "Ei", "Mi"]),
                          "Tagesgericht")
        dish3_mon2 = Dish("Kasslerpfanne mit frischen Champignons und Spätzle",
                          Prices(Price(4.9)), set(["Sl", "Mi"]),
                          "Tagesgericht")
        dish1_tue2 = Dish("Gemüsereispfanne mit geräuchertem Tofu",
                          Prices(Price(3.6)), set(["Sl"]), "Tagesgericht")
        dish2_tue2 = Dish(
            "Schweineschnitzel in Karottenpanade mit Rosmarin- Risoleekartoffeln",
            Prices(Price(5.3)), set(["Sl", "Gl", "Ei"]), "Tagesgericht")
        dish1_wed2 = Dish("Spaghetti al Pomodoro", Prices(Price(3.6)),
                          set(["Sl", "Gl"]), "Tagesgericht")
        dish2_wed2 = Dish(
            "Krustenbraten vom Schwein mit Kartoffelknödel und Krautsalat",
            Prices(Price(5.3)), set(["Sl", "Gl"]), "Tagesgericht")
        dish1_thu2 = Dish("Red-Thaicurrysuppe mit Gemüse und Kokosmilch",
                          Prices(Price(2.9)), set(["Sl"]), "Tagesgericht")
        dish2_thu2 = Dish("Senf-Eier mit Salzkartoffeln", Prices(Price(3.8)),
                          set(["Sl", "Sf", "Mi"]), "Tagesgericht")
        dish3_thu2 = Dish("Putengyros mit Zaziki und Tomatenreis",
                          Prices(Price(5.3)), set(["Sl", "Mi"]),
                          "Tagesgericht")
        dish1_fri2 = Dish("Spiralnudeln mit Ratatouillegemüse",
                          Prices(Price(3.6)), set(["Gl"]), "Tagesgericht")
        dish2_fri2 = Dish("Milchreis mit warmen Sauerkirschen",
                          Prices(Price(3)), set(["Mi"]), "Tagesgericht")
        dish3_fri2 = Dish("Lasagne aus Seelachs und Blattspinat",
                          Prices(Price(5.3)), set(["Sl", "Gl", "Mi"]),
                          "Tagesgericht")
        menu_mon2 = Menu(date_mon2,
                         [dish_aktion2, dish1_mon2, dish2_mon2, dish3_mon2])
        menu_tue2 = Menu(date_tue2, [dish_aktion2, dish1_tue2, dish2_tue2])
        menu_wed2 = Menu(date_wed2, [dish_aktion2, dish1_wed2, dish2_wed2])
        menu_thu2 = Menu(date_thu2,
                         [dish_aktion2, dish1_thu2, dish2_thu2, dish3_thu2])
        menu_fri2 = Menu(date_fri2,
                         [dish_aktion2, dish1_fri2, dish2_fri2, dish3_fri2])
        week = Week(45, 2017,
                    [menu_mon2, menu_tue2, menu_wed2, menu_thu2, menu_fri2])
        week = {}
        week[date_mon2] = menu_mon2
        week[date_tue2] = menu_tue2
        week[date_wed2] = menu_wed2
        week[date_thu2] = menu_thu2
        week[date_fri2] = menu_fri2
        weeks = Week.to_weeks(week)

        canteen = openmensa.weeksToCanteenFeed(weeks)
        self.assertEqual(canteen.hasMealsFor(date_mon2), True)
        self.assertEqual(canteen.hasMealsFor(date_tue2), True)
        self.assertEqual(canteen.hasMealsFor(date_wed2), True)
        self.assertEqual(canteen.hasMealsFor(date_thu2), True)
        self.assertEqual(canteen.hasMealsFor(date_fri2), True)

        canteen_wed2 = canteen._days[date_wed2]['Speiseplan']
        self.assertEqual(
            canteen_wed2[0],
            ("Pochiertes Lachsfilet mit Dillsoße dazu Minze-Reis", [], {
                'other': 650
            }))
        self.assertEqual(canteen_wed2[1], ("Spaghetti al Pomodoro", [], {
            'other': 360
        }))
        self.assertEqual(
            canteen_wed2[2],
            ("Krustenbraten vom Schwein mit Kartoffelknödel und Krautsalat",
             [], {
                 'other': 530
             }))