├── requirements.txt ├── styles.css ├── README.md ├── flask_app.py ├── tests.py ├── LICENSE ├── restaurants.txt ├── main.py └── parser.py /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4 2 | requests 3 | flask 4 | flask-caching 5 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | height:100%; 3 | font-size:14px; 4 | font-family:Arial, Helvetica, Sans-serif; 5 | letter-spacing:0.01em; 6 | } 7 | 8 | div.title { 9 | font-weight: bold; 10 | } 11 | 12 | div.endnote { 13 | font-size:10px; 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lunch-parser 2 | ============ 3 | 4 | Scripts useful for parsing the menus of the restaurants near Campus Solna of Karolinska Institutet and BMC of Uppsala University and generating a simple html page. 5 | 6 | Usage: 7 | python3 main.py restaurant_name > index.html 8 | 9 | The supported restaurants are also listed when you run main.py without any arguments. 10 | 11 | If new restaurants are added, add the parser function to `parser.py`, the relevant keyword and function name to `MAPPER` in `main.py`, and URLs etc to `restaurants.txt`. 12 | 13 | An experimental version can be run via flask `FLASK_APP=flask_app.py flask run`, with the ki menus listed under /ki and uu under /uu. -------------------------------------------------------------------------------- /flask_app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_caching import Cache 3 | 4 | import main 5 | 6 | app = Flask(__name__) 7 | cache = Cache(app, config={'CACHE_TYPE': 'simple'}) 8 | 9 | @app.route('/') 10 | def display_available(): 11 | content = ('' + 12 | '' + 13 | 'Restaurant Menu Parser' + 14 | '' + 15 | '' + 16 | '

KI (Solna)

' + 17 | '

UU (BMC)

' + 18 | '' + 19 | '') 20 | return content 21 | 22 | @app.route('/ki') 23 | @cache.cached(timeout=3600) 24 | def make_menu_ki(): 25 | return main.gen_ki_menu() 26 | 27 | @app.route('/uu') 28 | @cache.cached(timeout=3600) 29 | def make_menu_uu(): 30 | return main.gen_uu_menu() 31 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import main as mn 4 | import parser as ps 5 | 6 | def test_read_restaurants() : 7 | import tempfile 8 | import os 9 | 10 | text = '''#identifier Name URL Menu URL Open Streetmap 11 | 61an Restaurang 61:an http://gastrogate.com/restaurang/61an/ http://gastrogate.com/restaurang/61an/page/3/ https://www.openstreetmap.org/#map=19/59.22071/17.93717 12 | alfred Alfreds restaurang http://www.alfredsrestaurang.se/ http://www.alfredsrestaurang.se/ https://www.openstreetmap.org/#map=19/59.21944/17.94074 13 | arom Café Arom http://aromsh.se/ http://aromsh.se/?page_id=13 http://www.openstreetmap.org/#map=18/59.21955/17.94160''' 14 | 15 | file_name = tempfile.mkstemp()[1] 16 | with open(file_name, 'w') as f: 17 | f.write(text) 18 | 19 | answer = [['61an', 'Restaurang 61:an', 'http://gastrogate.com/restaurang/61an/', 'http://gastrogate.com/restaurang/61an/page/3/' , 'https://www.openstreetmap.org/#map=19/59.22071/17.93717'], 20 | ['alfred', 'Alfreds restaurang', 'http://www.alfredsrestaurang.se/', 'http://www.alfredsrestaurang.se/', 'https://www.openstreetmap.org/#map=19/59.21944/17.94074'], 21 | ['arom', 'Café Arom', 'http://aromsh.se/', 'http://aromsh.se/?page_id=13', 'http://www.openstreetmap.org/#map=18/59.21955/17.94160']] 22 | 23 | assert mn.read_restaurants(file_name) == answer 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018, Linus Östberg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of kimenu nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /restaurants.txt: -------------------------------------------------------------------------------- 1 | # identifier Name URL Menu URL Open Streetmap 2 | # KI 3 | glada Den Glada Restaurangen http://www.dengladarestaurangen.se/ http://www.dengladarestaurangen.se/#!meny/c30g http://www.openstreetmap.org/#map=19/59.35123/18.03006 4 | haga Haga gatukök http://orenib.se/haga_gk2.pdf http://orenib.se/haga_gk2.pdf https://www.openstreetmap.org/#map=19/59.34931/18.02095 5 | hjulet Restaurang Hjulet http://gastrogate.com/restaurang/restauranghjulet/ http://gastrogate.com/restaurang/restauranghjulet/page/3/ https://www.openstreetmap.org/#map=19/59.34508/18.02423 6 | jons Jöns Jacob http://gastrogate.com/restaurang/jonsjacob/ https://jonsjacob.gastrogate.com/lunch/ https://www.openstreetmap.org/#map=19/59.34673/18.02465 7 | jorpes Café Erik Jorpes http://restaurang-ns.com/cafe-erik-jorpes/ http://restaurang-ns.com/cafe-erik-jorpes/ https://www.openstreetmap.org/#map=19/59.34851/18.02721 8 | karolina Restaurang Karolina http://gastrogate.com/restaurang/ksrestaurangen/ https://ksrestaurangen.gastrogate.com/lunch/ https://www.openstreetmap.org/#map=19/59.35224/18.03103 9 | livet Livet [Restaurant] https://www.livetbrand.com/livet-restaurant/ https://www.livetbrand.com/livet-restaurant/ https://www.openstreetmap.org/#map=19/59.34853/18.02989 10 | mollan Mollan Asian kök http://mollanasiankok.se/ http://mollanasiankok.se/vecka%20meny.html https://www.openstreetmap.org/#map=19/59.34836/18.02650 11 | svarta Svarta Räfven http://restaurang-ns.com/svarta-rafven/ http://restaurang-ns.com/svarta-rafven/ https://www.openstreetmap.org/#map=19/59.34851/18.02804 12 | subway Subway http://subway.se/sv/hem/ http://subway.se/sv/hem/ https://www.openstreetmap.org/#map=19/59.35084/18.02433 13 | nanna Restaurang Nanna Svartz http://restaurang-ns.com/restaurang-nanna-svartz/ http://restaurang-ns.com/restaurang-nanna-svartz/ https://www.openstreetmap.org/#map=19/59.34848/18.02807 14 | # UU 15 | bikupan Restaurang Bikupan http://www.hors.se/restaurang/restaurang-bikupan/ http://www.hors.se/restaurang/restaurang-bikupan/ https://www.openstreetmap.org/#map=18/59.84186/17.63407 16 | dufva Sven Dufva http://svendufva.se/ http://svendufva.se/#lunch https://www.openstreetmap.org/#map=19/59.84298/17.64096 17 | hubben Restaurang Hubben https://vasakronan.foodbycoor.se/hubben https://vasakronan.foodbycoor.se/hubben/restaurangen/restaurangens-meny https://www.openstreetmap.org/#map=18/59.84334/17.64074 18 | rudbeck Bistro Rudbeck http://www.compass-group.se/restauranger/Bistro-Rudbeck-Uppsala-/ https://eurest.mashie.com/public/menu/restaurang+kannan/cb061efe?country=se https://www.openstreetmap.org/#map=19/59.84518/17.63968 19 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2014-2018, Linus Östberg 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of kimenu nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | ''' 31 | Main script for choosing what restaurant parsers to use 32 | ''' 33 | 34 | import os 35 | import sys 36 | 37 | import parser as ps 38 | 39 | __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) 40 | REST_FILENAME = os.path.join(__location__, 'restaurants.txt') 41 | 42 | 43 | # works as ordered dict as well, but must be _ordered_ 44 | MAPPER = (('jorpes', ps.parse_jorpes), ('glada', ps.parse_glada), 45 | ('haga', ps.parse_haga), ('hjulet', ps.parse_hjulet), 46 | ('jons', ps.parse_jons), ('karolina', ps.parse_karolina), 47 | ('livet', ps.parse_livet), ('mollan', ps.parse_mollan), 48 | ('nanna', ps.parse_nanna), ('svarta', ps.parse_svarta), 49 | ('subway', ps.parse_subway), ('bikupan', ps.parse_bikupan), 50 | ('dufva', ps.parse_dufva), ('hubben', ps.parse_hubben), 51 | ('rudbeck', ps.parse_rudbeck)) 52 | 53 | 54 | def activate_parsers(restaurants, restaurant_data): 55 | ''' 56 | Run the wanted parsers 57 | ''' 58 | output = [] 59 | for i in range(len(MAPPER)): 60 | if MAPPER[i][0] in restaurants: 61 | try: 62 | to_use = restaurant_data[[x[0] for x in restaurant_data].index( 63 | [x[0] for x in MAPPER][i])] 64 | output.append('\n'.join(MAPPER[i][1](to_use))) 65 | except Exception as err: 66 | sys.stderr.write('E in {}: {}\n'.format(MAPPER[i][0], err)) 67 | return '\n'.join(output) 68 | 69 | 70 | def gen_ki_menu(): 71 | ''' 72 | Generate a menu for restaurants at KI 73 | ''' 74 | restaurant_data = read_restaurants(open(REST_FILENAME).read()) 75 | rest_names = [x[0] for x in MAPPER[:11]] 76 | 77 | output = '' 78 | output += '\n'.join(page_start(ps.get_weekday(), str(ps.get_day()), ps.get_month())) 79 | output += activate_parsers(rest_names, restaurant_data) 80 | output += '\n'.join(page_end()) 81 | return output 82 | 83 | 84 | def gen_uu_menu(): 85 | ''' 86 | Generate a menu for restaurants at UU 87 | ''' 88 | restaurant_data = read_restaurants(open(REST_FILENAME).read()) 89 | rest_names = [x[0] for x in MAPPER[11:]] 90 | 91 | output = '' 92 | output += '\n'.join(page_start(ps.get_weekday(), str(ps.get_day()), ps.get_month())) 93 | output += activate_parsers(rest_names, restaurant_data) 94 | output += '\n'.join(page_end()) 95 | 96 | sys.stderr.write(output) 97 | return output 98 | 99 | 100 | def page_end(): 101 | ''' 102 | Print the closure of tags etc 103 | ''' 104 | lines = list() 105 | lines.append('
Code available at ' + 106 | '' + 107 | 'Github. Patches are very welcome.
') 108 | lines.append('') 109 | lines.append('') 110 | return lines 111 | 112 | 113 | def page_start(weekday, day, month): 114 | ''' 115 | Print the initialisation of the page 116 | ''' 117 | lines = list() 118 | lines.append('') 119 | lines.append('') 120 | date = ps.fix_for_html(weekday.capitalize() + ' ' + str(day) + ' ' + str(month)) 121 | lines.append('Dagens mat - {}'.format(date)) 122 | lines.append('') 123 | lines.append('') 124 | lines.append('') 125 | lines.append('') 126 | # page formatting 127 | lines.append('') 128 | return lines 129 | 130 | 131 | def parse_restaurant_names(rest_names): 132 | ''' 133 | Decide what restaurants to generate menus for 134 | ''' 135 | restaurants = list() 136 | for param in rest_names: 137 | if param not in (x[0] for x in MAPPER): 138 | raise ValueError('{} not a valid restaurant'.format(param)) 139 | restaurants.append(param.lower()) 140 | return restaurants 141 | 142 | 143 | def print_usage(supported): 144 | ''' 145 | Print description of syntax 146 | ''' 147 | sys.stderr.write('Usage: {} restaurant1 [restaurant2] \n'.format(sys.argv[0])) 148 | sys.stderr.write('Supported restaurants: {}\n'.format(', '.join(sorted(supported)))) 149 | sys.stderr.write('write all to generate all supported restaurants\n') 150 | 151 | 152 | def read_restaurants(intext): 153 | ''' 154 | Read the list of restaurants 155 | Read a tsv file with the columns: 156 | [0] identifier [1] Name [2] URL [3] Menu URL [4] OSM URL 157 | ''' 158 | restaurants = list() 159 | for line in intext.split('\n'): 160 | if not line or line[0] == '#': 161 | continue 162 | restaurants.append(line.rstrip().split('\t')) 163 | return restaurants 164 | 165 | 166 | if __name__ == '__main__': 167 | if len(sys.argv) < 2 or '-h' in sys.argv: 168 | print_usage((x[0] for x in MAPPER)) 169 | sys.exit() 170 | 171 | RESTAURANT_DATA = read_restaurants(open(REST_FILENAME).read()) 172 | if 'all' in sys.argv[1:]: 173 | REST_NAMES_IN = (x[0] for x in MAPPER) 174 | else: 175 | REST_NAMES_IN = [param for param in sys.argv[1:] if param != '-r'] 176 | 177 | try: 178 | REST_NAMES = parse_restaurant_names(REST_NAMES_IN) 179 | except ValueError as err: 180 | sys.stderr.write('E: {}'.format(err)) 181 | print_usage((x[0] for x in MAPPER)) 182 | sys.exit(1) 183 | 184 | # print the menus 185 | print('\n'.join(page_start(ps.get_weekday(), str(ps.get_day()), ps.get_month()))) 186 | print(activate_parsers(REST_NAMES, RESTAURANT_DATA)) 187 | print('\n'.join(page_end())) 188 | -------------------------------------------------------------------------------- /parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2014-2018, Linus Östberg 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of kimenu nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | ''' 31 | Parsers of the menu pages for the restaurants at Karolinska Institutet 32 | ''' 33 | 34 | from datetime import date 35 | import sys 36 | 37 | import requests 38 | from bs4 import BeautifulSoup 39 | 40 | 41 | def fix_for_html(text): 42 | ''' 43 | HTML formatting of characters 44 | ''' 45 | text = text.replace('ö', 'ö') 46 | text = text.replace('Ö', 'Ö') 47 | text = text.replace('å', 'å') 48 | text = text.replace('Å', 'Å') 49 | text = text.replace('ä', 'ä') 50 | text = text.replace('Ä', 'Ä') 51 | text = text.replace('é', 'é') 52 | text = text.replace('è', 'è') 53 | text = text.replace('í', 'í') 54 | text = text.replace('ì', 'ì') 55 | text = text.replace('à', 'à') 56 | text = text.replace('á', 'á') 57 | text = text.replace('ô', 'ô') 58 | text = text.replace('ü', 'ü') 59 | text = text.replace('Ä', 'Ä') 60 | text = text.replace('´', ''') 61 | text = text.replace('`', ''') 62 | text = text.replace('ç', 'ç') 63 | text = text.replace('”', '"') 64 | text = text.replace('è', 'è') 65 | text = text.replace('ä', 'ä') 66 | text = text.replace('Ö', 'Ä') 67 | text = text.replace('Ä', 'Ä') 68 | text = text.replace('ö', 'ö') 69 | text = text.replace('é', 'é') 70 | text = text.replace('Ã¥', 'å') 71 | text = text.replace('Å', 'Å') 72 | text = text.replace('â', '—') 73 | # Karolina 74 | text = text.replace('å', 'å') 75 | text = text.replace('ä', 'ä') 76 | text = text.replace('ö', 'ö') 77 | text = text.replace('Ä', 'Ä') 78 | 79 | text = text.strip() 80 | 81 | return text 82 | 83 | 84 | ### date management start ### 85 | def get_day(): 86 | ''' 87 | Today as digit 88 | ''' 89 | return date.today().day 90 | 91 | 92 | def get_monthdigit(): 93 | ''' 94 | Month as digit 95 | ''' 96 | return date.today().month 97 | 98 | 99 | def get_month(): 100 | ''' 101 | Month name 102 | ''' 103 | months = {1: 'januari', 2: 'februari', 3: 'mars', 4: 'april', 104 | 5: 'maj', 6: 'juni', 7: 'juli', 8: 'augusti', 105 | 9: 'september', 10: 'oktober', 11: 'november', 12: 'december'} 106 | 107 | return months[get_monthdigit()] 108 | 109 | 110 | def get_week(): 111 | ''' 112 | Week number 113 | ''' 114 | return date.today().isocalendar()[1] 115 | 116 | 117 | def get_weekday(lang='sv', tomorrow=False): 118 | ''' 119 | Day name in swedish(sv) or english (en) 120 | ''' 121 | wdigit = get_weekdigit() 122 | if tomorrow: 123 | wdigit += 1 124 | if lang == 'sv': 125 | weekdays = {0: 'måndag', 1: 'tisdag', 2: 'onsdag', 3: 'torsdag', 126 | 4: 'fredag', 5: 'lördag', 6: 'söndag', 7: 'måndag'} 127 | if lang == 'en': 128 | weekdays = {0: 'monday', 1: 'tuesday', 2: 'wednesday', 3: 'thursday', 129 | 4: 'friday', 5: 'saturday', 6: 'sunday', 7: 'monday'} 130 | return weekdays[wdigit] 131 | 132 | 133 | def get_weekdigit(): 134 | ''' 135 | Get digit for week (monday = 0) 136 | ''' 137 | return date.today().weekday() 138 | 139 | 140 | def get_year(): 141 | ''' 142 | Year as number 143 | ''' 144 | return date.today().year 145 | ### date management end ### 146 | 147 | ### parsers start ### 148 | def parse_bikupan(resdata): 149 | ''' 150 | Parse the menu of Restaurang Bikupan 151 | ''' 152 | lines = list() 153 | lines += restaurant_start(fix_for_html(resdata[1]), 'Uppsala', 154 | resdata[2], resdata[4]) 155 | 156 | page_req = requests.get(resdata[3]) 157 | if page_req.status_code != 200: 158 | raise IOError('Bad HTTP responce code') 159 | 160 | soup = BeautifulSoup(page_req.text, 'html.parser') 161 | relevant = soup.find("div", {"class": "col-md-3 hors-menu text-center"}) 162 | dishes = relevant.find_all("div", {"class": "col-xs-10 text-left"}) 163 | for dish in dishes: 164 | lines.append(dish.get_text().strip().replace('\n', ' ') + '
') 165 | lines += restaurant_end() 166 | return lines 167 | 168 | 169 | def parse_dufva(resdata): 170 | ''' 171 | Parse the menu of Sven Dufva 172 | ''' 173 | lines = list() 174 | lines += restaurant_start(fix_for_html(resdata[1]), 'Uppsala', 175 | resdata[2], resdata[4]) 176 | 177 | page_req = requests.get(resdata[3]) 178 | if page_req.status_code != 200: 179 | raise IOError('Bad HTTP responce code') 180 | 181 | soup = BeautifulSoup(page_req.text, 'html.parser') 182 | relevant = soup.find("div", {"id": "post"}) 183 | menu_lines = relevant.get_text().split('\n') 184 | dag = get_weekday() 185 | started = False 186 | for line in menu_lines: 187 | if not line: 188 | continue 189 | if line.lower() == dag: 190 | started = True 191 | continue 192 | if started: 193 | if line[0] != '-': 194 | lines.append(line.strip() + '
') 195 | else: 196 | break 197 | lines += restaurant_end() 198 | return lines 199 | 200 | 201 | def parse_glada(resdata): 202 | ''' 203 | Parse the menu of Glada restaurangen 204 | ''' 205 | lines = list() 206 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 207 | resdata[2], resdata[4]) 208 | 209 | # No way I'll parse this one. If anyone actually wants to, I'd be happy to accept a patch. 210 | 211 | lines += restaurant_end() 212 | return lines 213 | 214 | 215 | def parse_haga(resdata): 216 | ''' 217 | Print a link to the menu of Haga gatukök 218 | ''' 219 | lines = list() 220 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 221 | resdata[2], resdata[4]) 222 | lines += restaurant_end() 223 | return lines 224 | 225 | 226 | def parse_hjulet(resdata): 227 | ''' 228 | Parse the menu of Restaurang Hjulet 229 | ''' 230 | lines = list() 231 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 232 | resdata[2], resdata[4]) 233 | try: 234 | page_req = requests.get(resdata[3]) 235 | if page_req.status_code != 200: 236 | raise IOError('Bad HTTP responce code') 237 | 238 | soup = BeautifulSoup(page_req.text, 'html.parser') 239 | days = soup.find('table', {'class':'table lunch_menu animation'}) 240 | dishes = days.find('td', {'class':'td_title'}) 241 | lines.append(dishes.get_text().strip().replace('\n', '
')) 242 | except Exception as err: 243 | sys.stderr.write(err) 244 | lines += restaurant_end() 245 | 246 | return lines 247 | 248 | 249 | def parse_hubben(resdata): 250 | ''' 251 | Parse the menu of Restaurang Hubben 252 | ''' 253 | lines = list() 254 | lines += restaurant_start(fix_for_html(resdata[1]), 'Uppsala', 255 | resdata[2], resdata[4]) 256 | 257 | page_req = requests.get(resdata[3]) 258 | if page_req.status_code != 200: 259 | raise IOError('Bad HTTP responce code') 260 | 261 | soup = BeautifulSoup(page_req.text, 'html.parser') 262 | days = soup.find_all("div", {"class": "day"}) 263 | current = days[get_weekdigit()] 264 | dishes = current.find_all('div', {'class': 'element description col-md-4 col-print-5'}) 265 | for dish in dishes: 266 | lines.append(dish.get_text().strip().replace('\n', ' ') + '
') 267 | lines += restaurant_end() 268 | return lines 269 | 270 | 271 | def parse_jons(resdata): 272 | ''' 273 | Parse the menu of Jöns Jacob 274 | ''' 275 | lines = list() 276 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 277 | resdata[2], resdata[4]) 278 | 279 | page_req = requests.get(resdata[3]) 280 | if page_req.status_code != 200: 281 | raise IOError('Bad HTTP responce code') 282 | 283 | soup = BeautifulSoup(page_req.text, 'html.parser') 284 | days = soup.find('table', {'class':'table lunch_menu animation'}) 285 | day = days.find('tbody', {'class':'lunch-day-content'}) 286 | dishes = day.find_all('td', {'class':'td_title'}) 287 | for dish in dishes: 288 | lines.append(dish.get_text().strip().split('\n')[1] + '
') 289 | 290 | lines += restaurant_end() 291 | return lines 292 | 293 | 294 | def parse_jorpes(resdata): 295 | ''' 296 | Parse the menu of Resturang Jorpes 297 | ''' 298 | lines = list() 299 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 300 | resdata[2], resdata[4]) 301 | lines += restaurant_end() 302 | return lines 303 | 304 | 305 | def parse_karolina(resdata): 306 | ''' 307 | Parse the menu of Restaurang Karolina 308 | ''' 309 | lines = list() 310 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 311 | resdata[2], resdata[4]) 312 | 313 | try: 314 | page_req = requests.get(resdata[3]) 315 | if page_req.status_code != 200: 316 | raise IOError('Bad HTTP responce code') 317 | 318 | soup = BeautifulSoup(page_req.text, 'html.parser') 319 | days = soup.find('table', {'class':'table lunch_menu animation'}) 320 | day = days.find('tbody', {'class':'lunch-day-content'}) 321 | dishes = day.find_all('td', {'class':'td_title'}) 322 | for dish in dishes: 323 | lines.append(dish.get_text().strip().split(':')[1] + '
') 324 | 325 | except Exception as err: 326 | sys.stderr.write(err) 327 | 328 | lines += restaurant_end() 329 | return lines 330 | 331 | 332 | def parse_livet(resdata): 333 | ''' 334 | Parse the menu of Livet [restaurant] 335 | ''' 336 | lines = list() 337 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 338 | resdata[2], resdata[4]) 339 | 340 | try: 341 | page_req = requests.get(resdata[3]) 342 | if page_req.status_code != 200: 343 | raise IOError('Bad HTTP responce code') 344 | 345 | soup = BeautifulSoup(page_req.text, 'html.parser') 346 | days = soup.find('div', {'class':'property--xhtml-string'}) 347 | started = False 348 | for row in days.find_all('p'): 349 | if get_weekday() in row.get_text().lower(): 350 | started = True 351 | continue 352 | if get_weekday(tomorrow=True) in row.get_text().lower(): 353 | break 354 | if started: 355 | dish = row.find('b') 356 | dish_text = dish.get_text().replace('\xa0', '') 357 | if dish_text: 358 | lines.append(dish_text + '
') 359 | 360 | 361 | except Exception as err: 362 | sys.stderr.write('E: Livet: {}'.format(err)) 363 | 364 | lines += restaurant_end() 365 | return lines 366 | 367 | 368 | def parse_mollan(resdata): 369 | ''' 370 | Parse the menu of Mollan 371 | ''' 372 | lines = list() 373 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 374 | resdata[2], resdata[4]) 375 | 376 | # To be fixed some day. Not fun. 377 | page_req = requests.get(resdata[3]) 378 | if page_req.status_code != 200: 379 | raise IOError('Bad HTTP responce code') 380 | soup = BeautifulSoup(page_req.text, 'html.parser') 381 | relevant = soup.find_all('span', {'class': 'mobile-undersized-upper'}) 382 | wday = fix_for_html(get_weekday()) 383 | started = False 384 | for tag in relevant: 385 | if 'bold' in tag['style']: 386 | if wday in tag.get_text().lower(): 387 | started = True 388 | continue 389 | if started: 390 | break 391 | if started: 392 | lines.append(fix_for_html(tag.get_text()) + '
') 393 | 394 | lines += restaurant_end() 395 | 396 | return lines 397 | 398 | 399 | def parse_nanna(resdata): 400 | ''' 401 | Parse the menu of Nanna Svartz 402 | ''' 403 | lines = list() 404 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 405 | resdata[2], resdata[4]) 406 | 407 | # will fix some day. Not fun. 408 | #page_req = requests.get(resdata[3]) 409 | #if page_req.status_code != 200: 410 | # raise IOError('Bad HTTP responce code') 411 | 412 | lines += restaurant_end() 413 | return lines 414 | 415 | 416 | def parse_rudbeck(resdata): 417 | ''' 418 | Parse the menu of Bistro Rudbeck 419 | ''' 420 | lines = list() 421 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 422 | resdata[2], resdata[4]) 423 | 424 | page_req = requests.get(resdata[3]) 425 | if page_req.status_code != 200: 426 | raise IOError('Bad HTTP responce code') 427 | 428 | soup = BeautifulSoup(page_req.text, 'html.parser') 429 | days = soup.find_all('div', {'class':'container-fluid no-print'}) 430 | day = days[get_weekdigit()] 431 | dishes = day.find_all('span')[3:] 432 | for dish in dishes: 433 | lines.append(dish.get_text().strip() + '
') 434 | 435 | lines += restaurant_end() 436 | return lines 437 | 438 | 439 | def parse_subway(resdata): 440 | ''' 441 | Print info about Subway 442 | ''' 443 | lines = list() 444 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 445 | resdata[2], resdata[4]) 446 | lines += restaurant_end() 447 | return lines 448 | 449 | 450 | def parse_svarta(resdata): 451 | ''' 452 | Parse the menu of Svarta Räfven 453 | ''' 454 | lines = list() 455 | lines += restaurant_start(fix_for_html(resdata[1]), 'Solna', 456 | resdata[2], resdata[4]) 457 | 458 | # page_req = requests.get(resdata[3]) 459 | # soup = BeautifulSoup(page_req.text, 'html.parser') 460 | 461 | lines += restaurant_end() 462 | return lines 463 | 464 | ### parsers end ### 465 | 466 | def restaurant_end(): 467 | ''' 468 | Finish the tags after the listing of the menu of a restaurant 469 | ''' 470 | lines = list() 471 | lines.append('

') 472 | lines.append('') 473 | return lines 474 | 475 | 476 | def restaurant_start(restaurant, location, home_url, mapurl): 477 | '''' 478 | Start the listing of the menu of a restaurant 479 | ''' 480 | lines = list() 481 | lines.append(''.format(restaurant)) 482 | lines.append('
{rest}'.format(rest=restaurant, 483 | url=home_url) + 484 | ' ({loc})
'.format(loc=location, 485 | murl=mapurl)) 486 | lines.append('