Compare commits
No commits in common. "master" and "5a097a5ce799a45f762bd832b6b82eeb239579dd" have entirely different histories.
master
...
5a097a5ce7
4
.gitignore
vendored
4
.gitignore
vendored
@ -4,7 +4,3 @@ menuprint
|
||||
.lvimrc
|
||||
example.*
|
||||
dhparam*
|
||||
.cache
|
||||
.clang-format
|
||||
build
|
||||
compile_commands.json
|
||||
|
@ -1,42 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
enable_language(CXX)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBXML REQUIRED libxml-2.0)
|
||||
pkg_check_modules(LIBXMLPP REQUIRED libxml++-3.0)
|
||||
find_library(Restbed restbed
|
||||
PATHS /usr/lib)
|
||||
find_library(Curl curl
|
||||
PATHS /usr/lib)
|
||||
|
||||
include_directories(/usr/local/include /opt/homebrew/include)
|
||||
link_directories(/opt/homebrew/lib)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g")
|
||||
|
||||
project(LunchRest)
|
||||
|
||||
list(APPEND Restaurants
|
||||
restaurants/ukarla.cpp
|
||||
restaurants/tao.cpp
|
||||
restaurants/mahostina.cpp
|
||||
restaurants/menicka.cpp
|
||||
)
|
||||
|
||||
add_executable(lunchrest)
|
||||
|
||||
target_sources(lunchrest
|
||||
PRIVATE ${Restaurants}
|
||||
PRIVATE main.cpp
|
||||
PRIVATE meal.cpp
|
||||
PRIVATE menu.cpp
|
||||
PRIVATE network/network.cpp
|
||||
PRIVATE environment.cpp
|
||||
PRIVATE restaurants/functions.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(lunchrest ${LIBXML_LIBRARIES} ${LIBXMLPP_LIBRARIES} ${Restbed} ${Curl})
|
||||
target_include_directories(lunchrest PUBLIC ${LIBXML_INCLUDE_DIRS} ${LIBXMLPP_INCLUDE_DIRS})
|
44
Makefile
Normal file
44
Makefile
Normal file
@ -0,0 +1,44 @@
|
||||
CXX ?= g++
|
||||
CFLAGS ?= -O2 -Wall -Wextra `pkg-config libxml-2.0 --cflags` `pkg-config libxml++-3.0 --cflags` -g
|
||||
PREFIX ?= /usr/local/bin
|
||||
LDFLAGS ?= -lcurl -lrestbed `pkg-config libxml-2.0 --libs` `pkg-config libxml++-3.0 --libs`
|
||||
|
||||
PARSERS = udrevaka.o padagali.o lightofindia.o ukarla.o alcapone.o plac.o zo.o suzzies.o
|
||||
|
||||
.PHONY: default
|
||||
default: menuprint
|
||||
|
||||
menuprint: main.o meal.o menu.o network.o restaurants.o environment.o $(PARSERS)
|
||||
$(CXX) $(CFLAGS) -o $@ $^ ${LDFLAGS}
|
||||
|
||||
main.o: main.cpp restaurant.hpp menu.hpp meal.hpp restaurants/restaurants.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
meal.o: meal.cpp meal.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
menu.o: menu.cpp menu.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
network.o: network/network.cpp network/network.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
restaurants.o: restaurants/restaurants.cpp restaurants/restaurants.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
environment.o: environment.cpp environment.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
udrevaka.o: restaurants/udrevaka.cpp restaurants.o restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
padagali.o: restaurants/padagali.cpp restaurants.o restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
lightofindia.o: restaurants/lightofindia.cpp restaurants.o restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
ukarla.o: restaurants/ukarla.cpp restaurants.o restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
alcapone.o: restaurants/alcapone.cpp restaurants.o restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
plac.o: restaurants/plac.cpp restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
zo.o: restaurants/zo.cpp restaurants/restaurants.hpp network/network.hpp htmlparser.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
suzzies.o: restaurants/suzzies.cpp restaurants/restaurants.hpp network/network.hpp environment.hpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -Rf *.o menuprint
|
103
main.cpp
103
main.cpp
@ -1,8 +1,7 @@
|
||||
#include "environment.hpp"
|
||||
#include "meal.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "restaurant.hpp"
|
||||
#include "restaurants/menicka.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "meal.hpp"
|
||||
#include "restaurants/restaurants.hpp"
|
||||
|
||||
#include <fstream>
|
||||
@ -15,13 +14,11 @@
|
||||
|
||||
std::map<std::string, std::unique_ptr<LunchRest::Restaurant>> restaurants;
|
||||
|
||||
void sendResponse(const std::string &response, int status_code,
|
||||
const std::shared_ptr<restbed::Session> session,
|
||||
const std::string &content_type = "text/plain") {
|
||||
session->close(status_code, response,
|
||||
{ { "Content-Length", std::to_string(response.length()) },
|
||||
{ "Access-Control-Allow-Origin", "*" },
|
||||
{ "Content-Type", content_type } });
|
||||
std::string file_info_name = "data_backup";
|
||||
struct tm last_save;
|
||||
|
||||
void sendResponse(const std::string &response, int status_code, const std::shared_ptr< restbed::Session > session) {
|
||||
session->close(status_code, response, { { "Content-Length", std::to_string(response.length()) }, { "Access-Control-Allow-Origin", "*" } });
|
||||
}
|
||||
|
||||
void refresh( const std::shared_ptr< restbed::Session > session ) {
|
||||
@ -31,14 +28,13 @@ void refresh(const std::shared_ptr<restbed::Session> session) {
|
||||
sendResponse( "Refreshed menus!", restbed::OK, session );
|
||||
}
|
||||
|
||||
std::string get_all_json(const std::vector<int> &days = { 0, 1, 2, 3, 4, 5,
|
||||
6 }) {
|
||||
std::string get_all_json() {
|
||||
std::stringstream ss{};
|
||||
bool atleastonerest = false;
|
||||
ss << "[";
|
||||
for(auto &restaurant : restaurants) {
|
||||
atleastonerest = true;
|
||||
ss << restaurant.second->jsonify(days) << ",";
|
||||
ss << restaurant.second->jsonify() << ",";
|
||||
}
|
||||
if(atleastonerest)
|
||||
ss.seekp(-1, ss.cur);
|
||||
@ -46,8 +42,25 @@ std::string get_all_json(const std::vector<int> &days = { 0, 1, 2, 3, 4, 5,
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void backupData() {
|
||||
time_t now = time(NULL);
|
||||
struct tm *local = localtime(&now);
|
||||
if(last_save.tm_year == local->tm_year && last_save.tm_mon == local->tm_mon && last_save.tm_mday == local->tm_mday)
|
||||
return;
|
||||
|
||||
std::ofstream output_file;
|
||||
output_file.open(file_info_name, std::ios_base::app);
|
||||
auto write_buffer = get_all_json();
|
||||
write_buffer += "\n";
|
||||
output_file.write(write_buffer.c_str(), write_buffer.size());
|
||||
|
||||
last_save.tm_year = local->tm_year;
|
||||
last_save.tm_mon = local->tm_mon;
|
||||
last_save.tm_mday = local->tm_mday;
|
||||
}
|
||||
|
||||
void get_all( const std::shared_ptr< restbed::Session > session ) {
|
||||
sendResponse(get_all_json(), restbed::OK, session, "application/json");
|
||||
sendResponse(get_all_json(), restbed::OK, session);
|
||||
}
|
||||
|
||||
void get( const std::shared_ptr< restbed::Session > session ) {
|
||||
@ -77,43 +90,52 @@ void get(const std::shared_ptr<restbed::Session> session) {
|
||||
restaurant = query_param.second;
|
||||
}
|
||||
if(day == -1 && restaurant == "") {
|
||||
sendResponse("DIDN'T SPECIFY DAY OR RESTAURANT", restbed::BAD_REQUEST,
|
||||
session);
|
||||
sendResponse("DIDN'T SPECIFY DAY OR RESTAURANT", restbed::BAD_REQUEST, session);
|
||||
return;
|
||||
}
|
||||
|
||||
if(restaurant != "" && restaurants.find(restaurant) == restaurants.end()) {
|
||||
sendResponse("YOU DIDN'T SPECIFY A VALID RESTAURANT!",
|
||||
restbed::BAD_REQUEST, session);
|
||||
sendResponse("YOU DIDN'T SPECIFY A VALID RESTAURANT!", restbed::BAD_REQUEST, session);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string response{};
|
||||
std::stringstream ss{};
|
||||
if(day != -1 && restaurant != "") {
|
||||
response = "[" + restaurants[restaurant]->jsonify({ day }) + "]";
|
||||
ss << "[" << restaurants[restaurant]->jsonify({day}) << "]";
|
||||
} else if(day != -1) {
|
||||
response = get_all_json({ day });
|
||||
} else if (restaurant != "") {
|
||||
response = "[" + restaurants[restaurant]->jsonify() + "]";
|
||||
ss << "[";
|
||||
bool atleastone = false;
|
||||
for(auto &restaurant : restaurants) {
|
||||
atleastone = true;
|
||||
ss << restaurant.second->jsonify({day}) << ",";
|
||||
}
|
||||
sendResponse(response, restbed::OK, session, "application/json");
|
||||
if(atleastone)
|
||||
ss.seekp(-1, ss.cur);
|
||||
ss << "]";
|
||||
} else if(restaurant != "") {
|
||||
ss << "[" << restaurants[restaurant]->jsonify() << "]";
|
||||
}
|
||||
sendResponse(ss.str(), restbed::OK, session);
|
||||
}
|
||||
|
||||
int main(int /*UNUSED*/, char ** /*UNUSED*/, char **env) {
|
||||
int main(int argc, char **argv, char **env) {
|
||||
setupEnv(env);
|
||||
restaurants["udrevaka"] = std::make_unique<LunchRest::MenickaRestaurant>("https://www.menicka.cz/2752-u-drevaka-beergrill.html", "U Dřeváka");
|
||||
restaurants["padagali"] = std::make_unique<LunchRest::MenickaRestaurant>("https://www.menicka.cz/4116-padagali.html", "Padagali");
|
||||
restaurants["lightofindia"] = std::make_unique<LunchRest::MenickaRestaurant>("https://www.menicka.cz/5448-light-of-india.html", "Light of India");
|
||||
restaurants["udrevaka"] = std::make_unique<LunchRest::UDrevakaRestaurant>();
|
||||
restaurants["padagali"] = std::make_unique<LunchRest::PadagaliRestaurant>();
|
||||
restaurants["lightofindia"] = std::make_unique<LunchRest::LightOfIndiaRestaurant>();
|
||||
restaurants["ukarla"] = std::make_unique<LunchRest::UKarlaRestaurant>();
|
||||
restaurants["alcapone"] = std::make_unique<LunchRest::MenickaRestaurant>("https://www.menicka.cz/2609-pizzeria-al-capone.html", "Al Capone");
|
||||
restaurants["suzies"] = std::make_unique<LunchRest::MenickaRestaurant>("https://www.menicka.cz/3830-suzies-steak-pub.html", "Suzies");
|
||||
restaurants["tao"] = std::make_unique<LunchRest::TaoRestaurant>();
|
||||
restaurants["mahostina"] = std::make_unique<LunchRest::MahostinaRestaurant>();
|
||||
restaurants["divabara"] = std::make_unique<LunchRest::MenickaRestaurant>("https://www.menicka.cz/6468-diva-bara.html", "Divá Bára");
|
||||
restaurants["alcapone"] = std::make_unique<LunchRest::AlCaponeRestaurant>();
|
||||
restaurants["plac"] = std::make_unique<LunchRest::PlacRestaurant>();
|
||||
restaurants["zo"] = std::make_unique<LunchRest::ZoRestaurant>();
|
||||
restaurants["suzzies"] = std::make_unique<LunchRest::SuzziesRestaurant>();
|
||||
std::cout << "Initial parsing" << std::endl;
|
||||
for(auto &restaurant : restaurants)
|
||||
restaurant.second->parse();
|
||||
std::cout << "Finished parsing" << std::endl;
|
||||
last_save.tm_year = 0;
|
||||
last_save.tm_mon = 0;
|
||||
last_save.tm_mday = 0;
|
||||
backupData();
|
||||
|
||||
restbed::Service service;
|
||||
|
||||
@ -132,20 +154,17 @@ int main(int /*UNUSED*/, char ** /*UNUSED*/, char **env) {
|
||||
refreshserv->set_method_handler( "GET", refresh );
|
||||
service.publish(refreshserv);
|
||||
|
||||
/* auto ssl_settings = std::make_shared<restbed::SSLSettings>();
|
||||
auto ssl_settings = std::make_shared<restbed::SSLSettings>();
|
||||
ssl_settings->set_http_disabled(true);
|
||||
ssl_settings->set_private_key(restbed::Uri(
|
||||
"file:///home/zvon/data/programming/lunch-rest/example.key"));
|
||||
ssl_settings->set_certificate(restbed::Uri(
|
||||
"file:///home/zvon/data/programming/lunch-rest/example.crt"));
|
||||
ssl_settings->set_temporary_diffie_hellman(restbed::Uri(
|
||||
"file:///home/zvon/data/programming/lunch-rest/dhparams.pem"));
|
||||
ssl_settings->set_port(1985);*/
|
||||
ssl_settings->set_private_key(restbed::Uri("file:///home/zvon/data/programming/lunch-rest/example.key"));
|
||||
ssl_settings->set_certificate(restbed::Uri("file:///home/zvon/data/programming/lunch-rest/example.crt"));
|
||||
ssl_settings->set_temporary_diffie_hellman(restbed::Uri("file:///home/zvon/data/programming/lunch-rest/dhparams.pem"));
|
||||
ssl_settings->set_port(1985);
|
||||
|
||||
auto settings = std::make_shared< restbed::Settings >();
|
||||
settings->set_port(1984);
|
||||
settings->set_default_header( "Connection", "close" );
|
||||
// settings->set_ssl_settings(ssl_settings);
|
||||
settings->set_ssl_settings(ssl_settings);
|
||||
|
||||
service.start(settings);
|
||||
|
||||
|
55
restaurants/alcapone.cpp
Normal file
55
restaurants/alcapone.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
|
||||
std::string removeAlergens(const std::string &name) {
|
||||
int pos = name.length() - 1;
|
||||
while(name[pos] == ',' || std::isdigit(name[pos]))
|
||||
pos--;
|
||||
return name.substr(0,pos);
|
||||
}
|
||||
|
||||
void LunchRest::AlCaponeRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto rows = root.find("//table[@class='table table-responsive']/tbody/tr");
|
||||
int cur_day = -1;
|
||||
for(auto &row : rows) {
|
||||
if(row->find("./td[@class='bg1']").size() != 0) {
|
||||
std::string day = nodeToText(row->find("./td[@class='bg1']/text()")[0]);
|
||||
if(day.find("pondělí") != std::string::npos)
|
||||
cur_day = 0;
|
||||
else if(day.find("úterý") != std::string::npos)
|
||||
cur_day = 1;
|
||||
else if(day.find("středa") != std::string::npos)
|
||||
cur_day = 2;
|
||||
else if(day.find("čtvrtek") != std::string::npos)
|
||||
cur_day = 3;
|
||||
else if(day.find("pátek") != std::string::npos)
|
||||
cur_day = 4;
|
||||
else if(day.find("sobota") != std::string::npos)
|
||||
cur_day = 5;
|
||||
else if(day.find("neděle") != std::string::npos)
|
||||
cur_day = 6;
|
||||
menus[cur_day].setInvalidMenu(false);
|
||||
continue;
|
||||
}
|
||||
if(cur_day < 0)
|
||||
continue;
|
||||
auto menu_info = row->find("./td/text()");
|
||||
auto meal_data = row->find("./td/h3/text()");
|
||||
std::string menu = nodeToText(menu_info[0]);
|
||||
std::string name = nodeToText(meal_data[0]);
|
||||
name = removeAlergens(name);
|
||||
if(menu == "Polévka") {
|
||||
menus[cur_day].addMeal(true, name, "", 0);
|
||||
} else {
|
||||
menus[cur_day].addMeal(false, name, "", std::stoi(nodeToText(meal_data[1])));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
#ifndef LUNCH_REST_FUNCTIONS
|
||||
#define LUNCH_REST_FUNCTIONS
|
||||
#include <string>
|
||||
#include <libxml++/libxml++.h>
|
||||
|
||||
namespace LunchRest {
|
||||
std::string nodeToText(xmlpp::Node *node);
|
||||
std::string trim(const std::string &input);
|
||||
}
|
||||
#endif
|
44
restaurants/lightofindia.cpp
Normal file
44
restaurants/lightofindia.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
|
||||
bool isWhiteSpaceOnly(const std::string &text) {
|
||||
if(text == "")
|
||||
return true;
|
||||
for(auto &x : text) {
|
||||
if(!std::isspace(x))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LunchRest::LightOfIndiaRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto container = root.find("//div[@id='content_container']")[0];
|
||||
auto texts = container->find(".//td/text()");
|
||||
int index = -1;
|
||||
for(auto text : texts) {
|
||||
std::string text_text = nodeToText(text);
|
||||
if(isWhiteSpaceOnly(text_text) || text_text.find("Week") != std::string::npos)
|
||||
continue;
|
||||
if(text_text[0] == '1')
|
||||
index++;
|
||||
auto end = text_text.find(')');
|
||||
if(end == std::string::npos)
|
||||
continue;
|
||||
auto possible_end = text_text.find('g', end);
|
||||
if(possible_end != std::string::npos)
|
||||
end = possible_end;
|
||||
std::string name = text_text.substr(4, end - 3);
|
||||
int price = std::stoi(text_text.substr(end+1));
|
||||
bool soup = name.find("soup") == std::string::npos ? false : true;
|
||||
menus[index].addMeal(soup, name, "", price);
|
||||
menus[index].setInvalidMenu(false);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
#include "functions.hpp"
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
#include <iostream>
|
||||
|
||||
void LunchRest::MahostinaRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if (html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
try {
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto today_lists = root.find(
|
||||
"//div[@id='dnesnibasta-section']//ul[@data-rte-list='default']");
|
||||
if (today_lists.empty()) {
|
||||
std::cout << "No meals :(" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
time_t t = time(nullptr);
|
||||
tm *timePtr = localtime(&t);
|
||||
auto day = (timePtr->tm_wday + 6) % 7;
|
||||
|
||||
for (auto &meal : today_lists[0]->find("./li/p/text()")) {
|
||||
auto text = nodeToText(meal);
|
||||
auto price_end = text.find(",-");
|
||||
auto price_start = price_end - 1;
|
||||
while (text[price_start] >= '0' && text[price_start] <= '9') {
|
||||
price_start -= 1;
|
||||
}
|
||||
price_start += 1;
|
||||
menus[day].addMeal(
|
||||
false, text.substr(0, price_start - 1), "",
|
||||
std::stoi(text.substr(price_start, price_end - price_start)));
|
||||
}
|
||||
menus[day].setInvalidMenu(false);
|
||||
} catch (std::exception &/*UNUSED*/) {
|
||||
clearMenus();
|
||||
for(int i = 0; i < menus.size(); i++) {
|
||||
menus[i].addMeal(false, parseError, parseInfo, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
#include "menicka.hpp"
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
#include "functions.hpp"
|
||||
|
||||
std::pair<std::string, int> getNameAndPrice(xmlpp::Node *meal) {
|
||||
auto souptext = meal->find("./div[@class='polozka']/text()");
|
||||
auto soupprice = meal->find("./div[@class='cena']/text()");
|
||||
std::string name = "";
|
||||
int price = -1;
|
||||
if(souptext.size() != 0) {
|
||||
name = LunchRest::nodeToText(souptext[0]);
|
||||
}
|
||||
if(soupprice.size() != 0) {
|
||||
price = std::stoi(LunchRest::nodeToText(soupprice[0]));
|
||||
}
|
||||
|
||||
return {name, price};
|
||||
}
|
||||
|
||||
void LunchRest::MenickaRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
try {
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto days = root.find("//div[@class='menicka']");
|
||||
for(auto &day : days) {
|
||||
auto daytext = day->find("./div[@class='nadpis']/text()");
|
||||
auto soup = day->find("./ul/li[@class='polevka']");
|
||||
auto meals = day->find("./ul/li[@class='jidlo']");
|
||||
if(daytext.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto daystr = nodeToText(daytext[0]);
|
||||
int cur_day = 0;
|
||||
switch (daystr[0]) {
|
||||
case 'P':
|
||||
if (daystr[1] != 'o') {
|
||||
cur_day = 4;
|
||||
}
|
||||
break;
|
||||
case "Ú"[0]:
|
||||
cur_day = 1;
|
||||
break;
|
||||
case 'S':
|
||||
cur_day = 2;
|
||||
break;
|
||||
case "Č"[0]:
|
||||
cur_day = 3;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(soup.size() != 0) {
|
||||
auto soupData = getNameAndPrice(soup[0]);
|
||||
if(!soupData.first.empty()) {
|
||||
menus[cur_day].addMeal(Meal(true, soupData.first, "", soupData.second));
|
||||
}
|
||||
}
|
||||
for(auto &meal : meals) {
|
||||
auto mealData = getNameAndPrice(meal);
|
||||
if(!mealData.first.empty()) {
|
||||
menus[cur_day].addMeal(Meal(false, mealData.first, "", mealData.second));
|
||||
}
|
||||
}
|
||||
if(!menus[cur_day].getMeals().empty()) {
|
||||
menus[cur_day].setInvalidMenu(false);
|
||||
}
|
||||
}
|
||||
} catch (std::exception &/*UNUSED*/) {
|
||||
clearMenus();
|
||||
for(int i = 0; i < menus.size(); i++) {
|
||||
menus[i].addMeal(false, parseError, parseInfo, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifndef LUNCH_REST_MENICKA
|
||||
#define LUNCH_REST_MENICKA
|
||||
#include "../restaurant.hpp"
|
||||
|
||||
namespace LunchRest {
|
||||
class MenickaRestaurant : public Restaurant {
|
||||
public:
|
||||
MenickaRestaurant(const std::string &url, const std::string &name) : Restaurant(url, name) {}
|
||||
virtual ~MenickaRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
}
|
||||
#endif
|
53
restaurants/padagali.cpp
Normal file
53
restaurants/padagali.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
|
||||
int dayToNum(const std::string &day) {
|
||||
if(day.find("POND") != std::string::npos) {
|
||||
return 0;
|
||||
}
|
||||
if (day.find("ÚTER") != std::string::npos) {
|
||||
return 1;
|
||||
}
|
||||
if (day.find("STŘE") != std::string::npos) {
|
||||
return 2;
|
||||
}
|
||||
if (day.find("ČTVR") != std::string::npos) {
|
||||
return 3;
|
||||
}
|
||||
if (day.find("PÁTE") != std::string::npos) {
|
||||
return 4;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void LunchRest::PadagaliRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html.empty()) {
|
||||
return;
|
||||
}
|
||||
clearMenus();
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto days = root.find("//div[@class='glf-mor-restaurant-menu-category']");
|
||||
auto menu_index = dayToNum(nodeToText(days[0]->find("./h3/text()")[0]));
|
||||
if(menu_index == -1) {
|
||||
return;
|
||||
}
|
||||
auto max_div = 5 - menu_index;
|
||||
for(int i = 0; i < max_div; i++) {
|
||||
auto *day = days[i];
|
||||
auto meals = day->find("./div");
|
||||
for(auto &meal : meals) {
|
||||
auto info = meal->find("./div/div/div");
|
||||
std::string desc = nodeToText(info[1]->find("./text()")[0]);
|
||||
std::string name = nodeToText(info[0]->find("./h5/text()")[0]);
|
||||
int price = std::stoi(nodeToText(info[0]->find("./div/span/text()")[0]));
|
||||
bool soup = name.find("Soup") != std::string::npos;
|
||||
menus[menu_index].addMeal(soup, name, desc, price);
|
||||
menus[menu_index].setInvalidMenu(false);
|
||||
}
|
||||
menu_index++;
|
||||
}
|
||||
}
|
50
restaurants/plac.cpp
Normal file
50
restaurants/plac.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
|
||||
void LunchRest::PlacRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto pizzas = root.find("//div[@class='mt-c cf']//div[@class='mt-i cf']");
|
||||
if(pizzas.size() == 0)
|
||||
return;
|
||||
auto soups = pizzas[0]->find(".//div[@class='b b-text cf']");
|
||||
|
||||
int soup_price = std::stoi(nodeToText(soups[1]->find(".//strong/text()")[0]));
|
||||
|
||||
int cur_day = -1;
|
||||
for(auto &soup : soups[0]->find(".//p/text()")) {
|
||||
std::string soup_text = nodeToText(soup);
|
||||
auto soup_day = soup_text.substr(0,2);
|
||||
if(soup_day == "Po")
|
||||
cur_day = 0;
|
||||
if(cur_day < 0)
|
||||
continue;
|
||||
menus[cur_day].setInvalidMenu(false);
|
||||
auto soupname = soup_text.substr(5);
|
||||
while(std::isspace(soupname[0]))
|
||||
soupname = soupname.substr(1);
|
||||
menus[cur_day].addMeal(true, soupname, "", soup_price);
|
||||
cur_day++;
|
||||
if(cur_day > 4)
|
||||
break;
|
||||
}
|
||||
|
||||
for(unsigned long int i = 2; i < pizzas.size(); i++) {
|
||||
auto content = pizzas[i]->find(".//div[@class='b b-text cf']");
|
||||
auto name_candidates = content[0]->find(".//h3/text()");
|
||||
std::string name = nodeToText(name_candidates[0]);
|
||||
auto desc_candidates = content[0]->find(".//p//text()");
|
||||
std::string desc = nodeToText(desc_candidates[0]);
|
||||
auto price_candidates = content[1]->find(".//p/strong/text()");
|
||||
int price = std::stoi(nodeToText(price_candidates[0]));
|
||||
name = trim(name);
|
||||
desc = trim(desc);
|
||||
addPermanent(false, name, desc, price);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
#include "functions.hpp"
|
||||
#include "restaurants.hpp"
|
||||
|
||||
std::string LunchRest::nodeToText(xmlpp::Node *node) {
|
||||
return dynamic_cast<const xmlpp::ContentNode *>(node)->get_content();
|
@ -2,25 +2,57 @@
|
||||
#include <libxml++/libxml++.h>
|
||||
|
||||
namespace LunchRest {
|
||||
const std::string parseError = "Could not retreive menu";
|
||||
const std::string parseInfo = "Please contact the developer, likely the restaurant website changed and requires a new parser";
|
||||
std::string nodeToText(xmlpp::Node *node);
|
||||
std::string trim(const std::string &input);
|
||||
class UDrevakaRestaurant : public Restaurant {
|
||||
public:
|
||||
UDrevakaRestaurant() : Restaurant("https://udrevaka.cz/denni-menu/", "U Dřeváka") {}
|
||||
virtual ~UDrevakaRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
class PadagaliRestaurant : public Restaurant {
|
||||
public:
|
||||
PadagaliRestaurant() : Restaurant("https://padagali.cz/denni-menu/", "Padagali") {}
|
||||
virtual ~PadagaliRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
class LightOfIndiaRestaurant : public Restaurant {
|
||||
public:
|
||||
LightOfIndiaRestaurant() : Restaurant("http://lightofindia.cz/lang-en/denni-menu", "Light of India") {}
|
||||
virtual ~LightOfIndiaRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
class UKarlaRestaurant : public Restaurant {
|
||||
public:
|
||||
UKarlaRestaurant() : Restaurant("https://ukarlabrno.cz/denni-menu/", "U Karla") {}
|
||||
virtual ~UKarlaRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
class TaoRestaurant : public Restaurant {
|
||||
class AlCaponeRestaurant : public Restaurant {
|
||||
public:
|
||||
TaoRestaurant() : Restaurant("https://www.taorestaurant.cz/tydenni_menu/nabidka/", "Táo Viet Nam") {}
|
||||
virtual ~TaoRestaurant() = default;
|
||||
AlCaponeRestaurant() : Restaurant("https://www.pizzaalcapone.cz/cz/poledni-menu", "Al Capone") {}
|
||||
virtual ~AlCaponeRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
class MahostinaRestaurant : public Restaurant {
|
||||
class PlacRestaurant : public Restaurant {
|
||||
public:
|
||||
MahostinaRestaurant() : Restaurant("https://www.mahostina.cz/", "Má Hostina") {}
|
||||
virtual ~MahostinaRestaurant() = default;
|
||||
PlacRestaurant() : Restaurant("https://www.bistroplac.cz/poledni-nabidka/", "Bistro Plac") {}
|
||||
virtual ~PlacRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
};
|
||||
class ZoRestaurant : public Restaurant {
|
||||
public:
|
||||
ZoRestaurant();
|
||||
virtual ~ZoRestaurant() = default;
|
||||
virtual void parse() override {};
|
||||
};
|
||||
class SuzziesRestaurant : public Restaurant {
|
||||
public:
|
||||
SuzziesRestaurant() : Restaurant("http://suzies.cz/poledni-menu.html", "Suzzie's") {}
|
||||
virtual ~SuzziesRestaurant() = default;
|
||||
virtual void parse() override;
|
||||
private:
|
||||
std::string api_key;
|
||||
};
|
||||
} // end of namespace LunchRest
|
||||
|
||||
|
61
restaurants/suzzies.cpp
Normal file
61
restaurants/suzzies.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
|
||||
int weekDay(const std::string &date_str) {
|
||||
struct tm date_tm;
|
||||
strptime(date_str.c_str(), "%Y-%m-%d %H:%M:%S", &date_tm);
|
||||
return (date_tm.tm_wday + 6) % 7;
|
||||
}
|
||||
|
||||
void LunchRest::SuzziesRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto days = root.find("//div[@class='day']");
|
||||
for(auto &day : days) {
|
||||
auto *daynum = xmlGetProp(day->cobj(), (xmlChar*)"data-day");
|
||||
int cur_day = 0;
|
||||
switch(daynum[0]) {
|
||||
case '2':
|
||||
cur_day = 1;
|
||||
break;
|
||||
case '3':
|
||||
cur_day = 2;
|
||||
break;
|
||||
case '4':
|
||||
cur_day = 3;
|
||||
break;
|
||||
case '5':
|
||||
cur_day = 4;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for(auto &meal : day->find(".//div[@class='item']")) {
|
||||
Meal meal_obj{};
|
||||
auto type = trim(nodeToText(meal->find(".//h6/text()")[0]));
|
||||
auto name_nodes = meal->find(".//div[@class='title']/text()");
|
||||
auto text_nodes = meal->find(".//div[@class='text']/text()");
|
||||
auto price_nodes = meal->find(".//div[@class='price']/text()");
|
||||
if(price_nodes.size() > 0) {
|
||||
// not soup
|
||||
auto name = type;
|
||||
if(name_nodes.size() > 0)
|
||||
name += " - " + trim(nodeToText(name_nodes[0]));
|
||||
meal_obj.setName(name);
|
||||
if(text_nodes.size() > 0)
|
||||
meal_obj.setDesc(trim(nodeToText(text_nodes[0])));
|
||||
meal_obj.setPrice(std::stoi(trim(nodeToText(price_nodes[0]))));
|
||||
} else {
|
||||
meal_obj.setName(trim(nodeToText(name_nodes[0])));
|
||||
meal_obj.setSoup();
|
||||
}
|
||||
menus[cur_day].addMeal(meal_obj);
|
||||
}
|
||||
menus[cur_day].setInvalidMenu(false);
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
#include "functions.hpp"
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
#include <iostream>
|
||||
|
||||
std::pair<size_t, size_t> getPricePosFromText(const std::string &text) {
|
||||
size_t price_pos = 0, possible_price = 0;
|
||||
while (possible_price != std::string::npos) {
|
||||
price_pos = possible_price;
|
||||
possible_price = text.find('k', possible_price + 1);
|
||||
}
|
||||
if(price_pos == 0 || price_pos == std::string::npos) {
|
||||
return {-1, -1};
|
||||
}
|
||||
auto end_pos = price_pos;
|
||||
price_pos -= 1;
|
||||
while (text[price_pos] >= '0' && text[price_pos] <= '9') {
|
||||
price_pos -= 1;
|
||||
}
|
||||
price_pos += 1;
|
||||
return { price_pos, end_pos - price_pos };
|
||||
}
|
||||
|
||||
size_t getEndOfTextPos(const std::string &text, size_t price_pos) {
|
||||
auto end_pos = price_pos -= 1;
|
||||
while (text[end_pos] == '.' || text[end_pos] == ' ') {
|
||||
end_pos -= 1;
|
||||
}
|
||||
end_pos += 1;
|
||||
return end_pos;
|
||||
}
|
||||
|
||||
void LunchRest::TaoRestaurant::parse() {
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if (html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
try {
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto week_meals_html =
|
||||
root.find("//div[@class='ct-div-block']/div[@class='ct-div-block "
|
||||
"tydenni-menu-div']");
|
||||
if (week_meals_html.empty()) {
|
||||
std::cout << "No week meals :(" << std::endl;
|
||||
return;
|
||||
}
|
||||
auto daily_meals_html =
|
||||
root.find("//div[@class='ct-section-inner-wrap']/"
|
||||
"div[@class='ct-div-block tydenni-menu-div']");
|
||||
if (daily_meals_html.empty()) {
|
||||
std::cout << "No daily meals :(" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Meal> week_meals{};
|
||||
for (auto &meal : week_meals_html) {
|
||||
auto texts = meal->find(".//span/text()");
|
||||
if (!texts.empty()) {
|
||||
auto text = nodeToText(texts[0]);
|
||||
auto price_positions = getPricePosFromText(text);
|
||||
if(price_positions.first == (size_t)-1) {
|
||||
week_meals.emplace_back(false, text, "", 0);
|
||||
continue;
|
||||
}
|
||||
auto end_pos = getEndOfTextPos(text, price_positions.first);
|
||||
week_meals.emplace_back(
|
||||
false, text.substr(0, end_pos), "",
|
||||
std::stoi(text.substr(price_positions.first,
|
||||
price_positions.second)));
|
||||
}
|
||||
}
|
||||
for (auto &meal : daily_meals_html) {
|
||||
auto texts = meal->find(".//span/text()");
|
||||
auto days = meal->find(".//div/b/text()");
|
||||
if (!texts.empty() && !days.empty()) {
|
||||
auto text = nodeToText(texts[0]);
|
||||
auto day = nodeToText(days[0]);
|
||||
auto price_positions = getPricePosFromText(text);
|
||||
if(price_positions.first == (size_t)-1) {
|
||||
continue;
|
||||
}
|
||||
auto end_pos = getEndOfTextPos(text, price_positions.first);
|
||||
|
||||
int cur_day = 0;
|
||||
switch (day[0]) {
|
||||
case 'P':
|
||||
if (day[1] != 'o') {
|
||||
cur_day = 4;
|
||||
}
|
||||
break;
|
||||
case "Ú"[0]:
|
||||
cur_day = 1;
|
||||
break;
|
||||
case 'S':
|
||||
cur_day = 2;
|
||||
break;
|
||||
case "Č"[0]:
|
||||
cur_day = 3;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Meal cur_meal(false, text.substr(0, end_pos), "",
|
||||
std::stoi(text.substr(price_positions.first,
|
||||
price_positions.second)));
|
||||
menus[cur_day].addMeal(cur_meal);
|
||||
for (auto &meal : week_meals) {
|
||||
menus[cur_day].addMeal(meal);
|
||||
}
|
||||
menus[cur_day].setInvalidMenu(false);
|
||||
}
|
||||
}
|
||||
} catch (std::exception &/*UNUSED*/) {
|
||||
clearMenus();
|
||||
for(int i = 0; i < menus.size(); i++) {
|
||||
menus[i].addMeal(false, parseError, parseInfo, 0);
|
||||
}
|
||||
}
|
||||
}
|
36
restaurants/udrevaka.cpp
Normal file
36
restaurants/udrevaka.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
|
||||
void LunchRest::UDrevakaRestaurant::parse() {
|
||||
int menu_index = 0;
|
||||
Request r;
|
||||
auto html = r.get(_url);
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto days = root.find("//li[@class='item-day']");
|
||||
for(auto &day : days) {
|
||||
auto meals = day->find("./div[@class='row']");
|
||||
for(auto &meal : meals) {
|
||||
auto divs = meal->find(".//div/text()");
|
||||
Meal meal_obj{};
|
||||
std::string name = trim(nodeToText(divs[0]));
|
||||
auto soup_pos = name.find("Polévka");
|
||||
if(soup_pos != std::string::npos) {
|
||||
meal_obj.setSoup();
|
||||
meal_obj.setName(name.substr(10, name.find('(') - 11));
|
||||
} else {
|
||||
meal_obj.setName(name.substr(3, name.find('(') - 4));
|
||||
}
|
||||
if(divs.size() > 1) {
|
||||
meal_obj.setPrice(std::stoi(trim(nodeToText(divs[1]))));
|
||||
}
|
||||
menus[menu_index].addMeal(meal_obj);
|
||||
menus[menu_index].setInvalidMenu(false);
|
||||
}
|
||||
menu_index++;
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
#include "functions.hpp"
|
||||
#include "restaurants.hpp"
|
||||
#include "../network/network.hpp"
|
||||
#include "../htmlparser.hpp"
|
||||
@ -10,7 +9,6 @@ void LunchRest::UKarlaRestaurant::parse() {
|
||||
if(html == "")
|
||||
return;
|
||||
clearMenus();
|
||||
try {
|
||||
HtmlParser hparse(html);
|
||||
auto &root = hparse.getRoot();
|
||||
auto days = root.find("//li[@class='item-day']");
|
||||
@ -21,10 +19,6 @@ void LunchRest::UKarlaRestaurant::parse() {
|
||||
for(auto &meal : meals) {
|
||||
auto soup = false;
|
||||
auto texts = meal->find("./div/text()");
|
||||
if(texts.empty()) {
|
||||
menus[menu_index].addMeal(false, parseError, parseInfo, 0);
|
||||
continue;
|
||||
}
|
||||
std::string name = trim(nodeToText(texts[0]));
|
||||
if(name[0] == 'P') {
|
||||
soup = true;
|
||||
@ -40,10 +34,4 @@ void LunchRest::UKarlaRestaurant::parse() {
|
||||
}
|
||||
menu_index++;
|
||||
}
|
||||
} catch (std::exception &/*UNUSED*/) {
|
||||
clearMenus();
|
||||
for(int i = 0; i < menus.size(); i++) {
|
||||
menus[i].addMeal(false, parseError, parseInfo, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
restaurants/zo.cpp
Normal file
31
restaurants/zo.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "restaurants.hpp"
|
||||
|
||||
LunchRest::ZoRestaurant::ZoRestaurant() : Restaurant("", "Zo!") {
|
||||
clearMenus();
|
||||
addPermanent(true, "Pho Bo", "Rýžové nudle pho v hovězím vývaru, hovězí maso, vietnamské bylinky", 120);
|
||||
addPermanent(true, "Pho Bo Tai Lan", "Rýžové nudle pho v hovězím vývaru, RESTOVANÉ hovězí maso, vietnamské bylinky", 120);
|
||||
addPermanent(true, "Bun Bo Hue", "Rýžové nudle bun v hovězím vývaru po Hue, masové kuličky, krevetová pasta", 120);
|
||||
addPermanent(true, "Bun Tom", "Rýžové nudle bun s krevety", 120);
|
||||
addPermanent(true, "Bun Dau", "Rýžové nudle bun v zeleninovém vývaru, tofu", 120);
|
||||
addPermanent(true, "Pho Bo Bam", "Rýžové nudle pho, syrové nasekané hovězí maso zalité hovězím vývarem, vietnamské bylinky", 135);
|
||||
addPermanent(true, "Bun Moc", "Rýžové nudle bun, kuřecí vývar s masovými kuličky, houbami a bylinkami", 125);
|
||||
addPermanent(false, "Bun Bo Nam Bo", "Rýžové nudle bun, hovězí maso, vietnamské bylinky, sladkokyselá zálivka", 135);
|
||||
addPermanent(false, "Bun Ga Tron", "Rýžové nudle bun s restovaným kuřecím masem, vietnamské bylinky, omáčka ZO!", 135);
|
||||
addPermanent(false, "Bun Dau Zo", "Podobné jako Bun Bo Nam Bo, ale s tofu", 120);
|
||||
addPermanent(false, "Bun Tom Tron", "Rýžové nudle bun s krevety, vietnamské bylinky, omáčka ZO!", 135);
|
||||
addPermanent(false, "Zo Bowl", "Miska plná rýžových nudlí a zeleniny, hrášek, houby shitake, brokolice, mungo, arašídová omáčka", 135);
|
||||
addPermanent(false, "Mi Orizo", "Orestované nudle z naší kuchyně, Vyberte si: kuřecí (120), hovězí (130)", 120);
|
||||
addPermanent(false, "Ban Da Xao", "Orestované jižanské placaté rýžové nudle, Vyberte si: kuřecí (120), hovězí (135)", 120);
|
||||
addPermanent(false, "Mi Udozo", "Orestované udon nudle po našem stylu, Vyberte si: kuřecí, hovězí", 135);
|
||||
addPermanent(false, "Bun Ngan Nuong", "Grilované kachní maso, rýžové nudle bun, vietnamské bylinky, sojová omáčka", 155);
|
||||
addPermanent(false, "Bun Cha", "Grilované vepřové maso, rýžové nudle bun, vietnamské bylinky, sladkokyselá zálivka", 135);
|
||||
addPermanent(false, "Salát Bo Zo", "Smažené obalené kousky avokáda, zelenina, ředkev", 135);
|
||||
addPermanent(false, "Com Xuat dle šéfkuchaře", "Vyberte si základ (kuřecí, vepřové, hovězí, krevetové, tofu), my domyslíme zbytek, rýže, zelenina", 135);
|
||||
addPermanent(false, "Rýže s kari omáčkou", "Vyberte si: kuřecí, hovězí, krevety, tofu", 120);
|
||||
addPermanent(false, "Vitský", "Smažená kachna podávaná s bambusovými výhonky a rýží", 155);
|
||||
addPermanent(false, "Nem Ran 2ks", "Smažené rolky", 50);
|
||||
addPermanent(false, "Nem Cuon Tom 2ks", "Smažené rolky plněné krevetami", 50);
|
||||
addPermanent(false, "Nem Cuon Bo 2ks", "Nesmažené rolky plněné avokádem podle Zo", 70);
|
||||
addPermanent(false, "Nem Cuon Vege 2ks", "Nesmažené rolky plněné tofu", 50);
|
||||
addPermanent(false, "Nem Ran Vege 2ks", "Smažené rolky plněné zeleninou (bez masa)", 50);
|
||||
}
|
Loading…
Reference in New Issue
Block a user