From 47d6867cc86d5239fea690565abe84c526edaa5b Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 1 Jun 2016 23:44:11 +0200 Subject: dbfp: implement dbfp_query_departure Retrieves the next departures of a given station. The time handling needs to be checked, as the DB API always uses Europe/Berlin. --- dbfp.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- dbfp.h | 3 + dbfp_check.c | 68 ++++++++++++++++++++ 3 files changed, 270 insertions(+), 6 deletions(-) diff --git a/dbfp.c b/dbfp.c index 807ba19..b2d8074 100644 --- a/dbfp.c +++ b/dbfp.c @@ -27,10 +27,16 @@ struct location_data { struct dbfp_location *locations; }; +struct departure_data { + size_t n; + struct dbfp_departure *departures; +}; + struct object_data { const char *name; int (*read)(const char **, size_t, void *); void *value; + int required; }; static int dbfp_parse(void *contents, size_t len, size_t n, void *data); @@ -38,10 +44,16 @@ static int dbfp_request(struct dbfp *dbfp, char *service, char *query, XML_Parser parser, struct parse_data *pd); static int dbfp_url(struct dbfp *dbfp, char *service, char *query, char **url); static int dbfp_format_url(char **out, const char *format, ...); +static int dbfp_format_time(char **out, size_t n, const char *format, + const struct tm *tm); static int check_ele_error(const char *name, const char **atts); static int read_string(const char **atts, size_t idx, void *value); static int read_float(const char **atts, size_t idx, void *value); +static int read_datetime(const char **atts, size_t idx, void *value, + const char *format); +static int read_date(const char **atts, size_t idx, void *value); +static int read_time(const char **atts, size_t idx, void *value); static int read_object_indizes(const char **atts, struct object_data *data, size_t *indizes); static int read_object(const char **atts, struct object_data *data); @@ -49,6 +61,9 @@ static int read_object(const char **atts, struct object_data *data); static void location_start(void *data, const char *name, const char **atts); static void location_end(void *data, const char *name); +static void departure_start(void *data, const char *name, const char **atts); +static void departure_end(void *data, const char *name); + int dbfp_init(struct dbfp *dbfp, char *key) { int err = 0; @@ -168,6 +183,74 @@ void dbfp_departure_close(struct dbfp_departure *departure) free(departure->track); } +int dbfp_query_departure(struct dbfp *dbfp, struct dbfp_location *location, + char *location_id, time_t time, size_t *n, + struct dbfp_departure **departures) +{ + int err = 0; + char *query = 0; + char *query_time = NULL; + char *query_date = NULL; + XML_Parser parser = NULL; + struct departure_data dd = { .n = 0, .departures = NULL }; + struct parse_data pd = { .error = 0, .data = &dd }; + struct tm *tm; + + if (!dbfp || !n || !departures) + return EINVAL; + if ((!location && !location_id) || (location && !location->id)) + return EINVAL; + + *n = 0; + *departures = 0; + + if (time) { + tm = localtime(&time); + if (!tm) { + err = DBFP_ERROR_FORMAT; + goto cleanup; + } + + err = dbfp_format_time(&query_time, 5, "%H:%M", tm); + if (err) + goto cleanup; + + err = dbfp_format_time(&query_date, 10, "%Y-%m-%d", tm); + if (err) + goto cleanup; + + err = dbfp_format_url(&query, "id=%s&date=%s&time=%s", + location ? location->id : location_id, + query_date, query_time); + } else { + err = dbfp_format_url(&query, "id=%s", + location ? location->id : location_id); + } + if (err) + goto cleanup; + + parser = XML_ParserCreateNS(NULL, '\0'); + XML_SetElementHandler(parser, departure_start, departure_end); + + err = dbfp_request(dbfp, "departureBoard", query, parser, &pd); + if (err) + goto cleanup; + + *n = dd.n; + *departures = dd.departures; + +cleanup: + // TODO check wheterh the elements of dd.departures should be closed + if (err) + free(dd.departures); + XML_ParserFree(parser); + free(query_time); + free(query_date); + free(query); + + return err; +} + static int dbfp_parse(void *contents, size_t len, size_t n, void *data) { XML_Parser parser = (XML_Parser)data; @@ -251,6 +334,26 @@ static int dbfp_format_url(char **out, const char *format, ...) return 0; } +static int dbfp_format_time(char **out, size_t n, const char *format, + const struct tm *tm) +{ + size_t len; + + *out = malloc((n + 1) * sizeof(**out)); + if (!*out) + return ENOMEM; + + /* TODO: check timezone */ + len = strftime(*out, n + 1, format, tm); + if (len != n) { + free(*out); + *out = NULL; + return DBFP_ERROR_FORMAT; + } + + return 0; +} + static int check_ele_error(const char *name, const char **atts) { if (strcmp(name, "Error") == 0) { @@ -284,6 +387,31 @@ static int read_float(const char **atts, size_t idx, void *value) return 0; } +static int read_datetime(const char **atts, size_t idx, void *value, + const char *format) +{ + struct tm *tm = (struct tm *)value; + + /* + * TODO: check whether it is safe to rely on the fact that strptime + * does not touch the unhandled values + */ + if (!strptime(atts[idx], format, tm)) + return DBFP_ERROR_STRUCTURE; + + return 0; +} + +static int read_date(const char **atts, size_t idx, void *value) +{ + return read_datetime(atts, idx, value, "%Y-%m-%d"); +} + +static int read_time(const char **atts, size_t idx, void *value) +{ + return read_datetime(atts, idx, value, "%H:%M"); +} + static int read_object_indizes(const char **atts, struct object_data *data, size_t *indizes) { @@ -303,7 +431,11 @@ static int read_object_indizes(const char **atts, struct object_data *data, } for (i = 0; data[i].name; i++) { - if (!indizes[i]) + /* + * allow some items to be missing due to the type/tyoe/tyte + * confusion + */ + if (!indizes[i] && data[i].required) return DBFP_ERROR_STRUCTURE; } @@ -329,6 +461,8 @@ static int read_object(const char **atts, struct object_data *data) goto cleanup; for (i = 0; data[i].name; i++) { + if (!indizes[i]) + continue; err = data[i].read(atts, indizes[i], data[i].value); if (err) goto cleanup; @@ -347,11 +481,11 @@ static void location_start(void *data, const char *name, const char **atts) struct location_data *ld = (struct location_data *)pd->data; struct dbfp_location location = { NULL, NULL, 0, 0 }; struct object_data od[] = { - { "name", &read_string, &location.name }, - { "id", &read_string, &location.id }, - { "lon", &read_float, &location.lon }, - { "lat", &read_float, &location.lat }, - { NULL, NULL, NULL } + { "name", &read_string, &location.name, 1 }, + { "id", &read_string, &location.id, 1 }, + { "lon", &read_float, &location.lon, 1 }, + { "lat", &read_float, &location.lat, 1 }, + { NULL, NULL, NULL, 0 } }; err = check_ele_error(name, atts); @@ -385,3 +519,62 @@ cleanup: static void location_end(void *data, const char *name) { } + +static void departure_start(void *data, const char *name, const char **atts) +{ + int err = 0; + struct parse_data *pd = (struct parse_data *)data; + struct departure_data *dd = (struct departure_data *)pd->data; + struct tm tm; + struct dbfp_departure departure = { NULL, NULL, NULL, NULL, 0, NULL, + NULL }; + struct object_data od[] = { + { "name", &read_string, &departure.name, 1 }, + { "type", &read_string, &departure.type, 0 }, + { "tyte", &read_string, &departure.type, 0 }, + { "tyoe", &read_string, &departure.type, 0 }, + { "stopid", &read_string, &departure.stopid, 1 }, + { "stop", &read_string, &departure.stop, 1 }, + { "date", &read_date, &tm, 1 }, + { "time", &read_time, &tm, 1 }, + { "direction", &read_string, &departure.direction, 1 }, + { "track", &read_string, &departure.track, 1 }, + { NULL, NULL, NULL, 0 } + }; + + err = check_ele_error(name, atts); + if (err) + goto cleanup; + + if (strcmp(name, "Departure") != 0) + goto cleanup; + + memset(&tm, 0, sizeof(tm)); + + err = read_object(atts, od); + if (err) + goto cleanup; + + dd->departures = realloc(dd->departures, + (dd->n + 1) * sizeof(*dd->departures)); + if (!dd->departures) { + err = ENOMEM; + goto cleanup; + } + + /* TODO: check timezone */ + departure.time = mktime(&tm); + + dd->departures[dd->n] = departure; + dd->n++; + +cleanup: + if (err) { + dbfp_departure_close(&departure); + pd->error = err; + } +} + +static void departure_end(void *data, const char *name) +{ +} diff --git a/dbfp.h b/dbfp.h index e1e1247..02b724d 100644 --- a/dbfp.h +++ b/dbfp.h @@ -49,5 +49,8 @@ int dbfp_query_location_name(struct dbfp *dbfp, char *input, size_t *n, struct dbfp_location **locations); void dbfp_departure_close(struct dbfp_departure *departure); +int dbfp_query_departure(struct dbfp *dbfp, struct dbfp_location *location, + char *location_id, time_t time, size_t *n, + struct dbfp_departure **departures); #endif /* DBFP_H_ */ diff --git a/dbfp_check.c b/dbfp_check.c index f816cfc..bda63e5 100644 --- a/dbfp_check.c +++ b/dbfp_check.c @@ -16,6 +16,11 @@ static void assert_location_eq(size_t n, struct dbfp_location *locations, size_t i, const char *name, const char *id, float lon, float lat); +static void assert_departure_eq(size_t n, struct dbfp_departure *departures, + size_t i, const char *name, const char *type, + const char *stopid, const char *stop, time_t time, + const char *direction, const char *track); + START_TEST(test_dbfp_create) { struct dbfp dbfp; @@ -85,6 +90,47 @@ START_TEST(test_dbfp_location_simple) } END_TEST +START_TEST(test_dbfp_departure_simple) +{ + int err; + struct dbfp dbfp; + struct dbfp_departure *departures = NULL; + struct tm tm = { .tm_year = 116, .tm_mon = 5, .tm_mday = 1, + .tm_hour = 17, .tm_min = 42 }; + time_t time = mktime(&tm); + size_t n; + size_t i; + + err = dbfp_init(&dbfp, api_key); + ck_assert_int_eq(err, 0); + + err = dbfp_query_departure(&dbfp, NULL, "8000105", 0, &n, &departures); + ck_assert_int_eq(err, 0); + ck_assert_int_gt(n, 0); + for (i = 0; i < n; i++) + dbfp_departure_close(&departures[i]); + free(departures); + + err = dbfp_query_departure(&dbfp, NULL, "8000105", time, &n, + &departures); + ck_assert_int_eq(err, 0); + tm.tm_hour++; + time = mktime(&tm); + assert_departure_eq(n, departures, 0, "ICE 1556", "ICE", "8000105", + "Frankfurt(Main)Hbf", time, "Wiesbaden Hbf", "6"); + tm.tm_min = 49; + time = mktime(&tm); + assert_departure_eq(n, departures, 1, "IC 2274", "IC", "8000105", + "Frankfurt(Main)Hbf", time, "Kassel-Wilhelmshöhe", + "11"); + for (i = 0; i < n; i++) + dbfp_departure_close(&departures[i]); + free(departures); + + dbfp_close(&dbfp); +} +END_TEST + int main(int argc, char **argv) { int num_failed = 0; @@ -92,6 +138,7 @@ int main(int argc, char **argv) SRunner *sr; TCase *tc_basic; TCase *tc_location; + TCase *tc_departure; api_key = getenv("DBFP_API_KEY"); if (!api_key) { @@ -111,6 +158,10 @@ int main(int argc, char **argv) tcase_add_test(tc_location, test_dbfp_location_simple); suite_add_tcase(s, tc_location); + tc_departure = tcase_create("departure"); + tcase_add_test(tc_departure, test_dbfp_departure_simple); + suite_add_tcase(s, tc_departure); + srunner_run_all(sr, CK_ENV); num_failed = srunner_ntests_failed(sr); srunner_free(sr); @@ -130,3 +181,20 @@ static void assert_location_eq(size_t n, struct dbfp_location *locations, ck_assert(locations[i].lon == lon); ck_assert(locations[i].lat == lat); } + +static void assert_departure_eq(size_t n, struct dbfp_departure *departures, + size_t i, const char *name, const char *type, + const char *stopid, const char *stop, time_t time, + const char *direction, const char *track) +{ + ck_assert_ptr_ne(departures, NULL); + ck_assert_uint_lt(i, n); + + ck_assert_str_eq(departures[i].name, name); + ck_assert_str_eq(departures[i].type, type); + ck_assert_str_eq(departures[i].stopid, stopid); + ck_assert_str_eq(departures[i].stop, stop); + ck_assert_uint_eq(departures[i].time, time); + ck_assert_str_eq(departures[i].direction, direction); + ck_assert_str_eq(departures[i].track, track); +} -- cgit v1.2.1