From 1648e81474b552ba499102e13f9f3ed863501757 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 8 Mar 2016 04:43:39 +0100 Subject: add rudimentary location support The function dbfp_query_location_name performs the location.name query and returns a list of all stations that match the search term. The implementation has two flaws: there is no URL encoding, and malformatted query results will not lead to a meaningful error message. There are two new test cases: basic tests some basic functionality, as setting the API key and accessing the API. location adds a simple test for the location.name query. --- .gitignore | 1 + Makefile | 8 ++- README | 6 ++ dbfp.c | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ dbfp.h | 18 ++++- dbfp_check.c | 65 ++++++++++++++++++ 6 files changed, 305 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0aa9426..83cf998 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp *.o *.so +dbfp_check diff --git a/Makefile b/Makefile index 7b67ed2..e802cd4 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ endif CHECK_CFLAGS = $(shell pkg-config check --cflags) CHECK_LDLIBS = $(shell pkg-config check --libs) +CURL_LDLIBS = -lcurl +EXPAT_LDLIBS = -lexpat .PHONY: all check clean @@ -24,9 +26,11 @@ clean: $(RM) dbfp_check $(RM) dbfp.o dbfp_check.o -libdbfp.so: LDFLAGS = -shared +libdbfp.so: LDFLAGS += -shared +libdbfp.so: LDLIBS += $(CURL_LDLIBS) +libdbfp.so: LDLIBS += $(EXPAT_LDLIBS) libdbfp.so: dbfp.o - $(CC) $(LDFLAGS) -o $@ $^ + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) dbfp_check: LDLIBS += $(CHECK_LDLIBS) dbfp_check: LDLIBS += -ldbfp -L. diff --git a/README b/README index ef0ee05..6e0d563 100644 --- a/README +++ b/README @@ -5,6 +5,12 @@ This C library provides an interface to the timetable API of the Deutsche Bahn (*Fahrplan-API*). This API exposes information on stations, departures, arrivals and routes. +Dependencies +------------ + + - libcurl + - libexpat + Compiling and installation -------------------------- diff --git a/dbfp.c b/dbfp.c index 0bba365..41e8abc 100644 --- a/dbfp.c +++ b/dbfp.c @@ -6,10 +6,36 @@ #include "dbfp.h" +#include #include +#include +#include #include #include +#define DBFP_BASE_URL "http://open-api.bahn.de/bin/rest.exe" +#define DBFP_URL_FORMAT "%s/%s?authKey=%s&%s" +#define DBFP_URL_LEN 8000 + +struct parse_data { + struct dbfp_status *status; + void *data; +}; + +struct loc_data { + size_t n; + struct dbfp_location *locs; +}; + +static int dbfp_parse(void *contents, size_t len, size_t n, void *data); +static void dbfp_request(struct dbfp *dbfp, char *service, char *query, + XML_Parser parser, struct dbfp_status *status); +static int dbfp_url(struct dbfp *dbfp, char *service, char *query, char **url); + +static void handle_loc(struct loc_data *ld, const char **atts); +static void loc_ele_start(void *data, const char *name, const char **atts); +static void loc_ele_end(void *data, const char *name); + int dbfp_init(struct dbfp *dbfp, char *key) { if (!dbfp || !key) @@ -29,3 +55,187 @@ void dbfp_close(struct dbfp *dbfp) free(dbfp->key); } + +struct dbfp_status dbfp_query_location_name(struct dbfp *dbfp, char *input, + size_t *n, struct dbfp_location **out) +{ + struct dbfp_status status = { 0, 0, 0, 0, 0 }; + char *query = NULL; + int len; + struct parse_data pd; + struct loc_data ld; + XML_Parser parser = NULL; + + if (!dbfp || !input) { + status.run_error = EINVAL; + goto cleanup; + } + + /* + * TODO(robin.krahl): improve query building and implement HTML + * encoding. + */ + query = malloc(DBFP_URL_LEN); + if (!query) { + status.run_error = ENOMEM; + goto cleanup; + } + len = snprintf(query, DBFP_URL_LEN, "input=%s", input); + if (len < 0 || len >= DBFP_URL_LEN) { + status.run_error = -1; + goto cleanup; + } + + pd.status = &status; + pd.data = &ld; + ld.n = 0; + ld.locs = NULL; + + parser = XML_ParserCreateNS(NULL, '\0'); + XML_SetUserData(parser, &pd); + XML_SetElementHandler(parser, loc_ele_start, loc_ele_end); + + dbfp_request(dbfp, "location.name", query, parser, &status); + + if (ld.n && ld.locs) { + if (n) + *n = ld.n; + if (out) + *out = ld.locs; + else + free(ld.locs); + } else { + free(ld.locs); + } + +cleanup: + XML_ParserFree(parser); + free(query); + + if (status.run_error || status.parse_error || status.curl_error || + status.api_error) + status.error = 1; + + return status; +} + +static int dbfp_parse(void *contents, size_t len, size_t n, void *data) +{ + XML_Parser parser = (XML_Parser)data; + struct parse_data *pd; + size_t size = len * n; + + pd = (struct parse_data *)XML_GetUserData(parser); + + if (!pd->status->parse_error && !XML_Parse(parser, contents, size, 0)) { + pd->status->parse_error = XML_GetErrorCode(parser); + } + + return size; +} + +static void dbfp_request(struct dbfp *dbfp, char *service, char *query, + XML_Parser parser, struct dbfp_status *status) +{ + char *url; + CURL *curl = NULL; + + status->run_error = dbfp_url(dbfp, service, query, &url); + if (status->run_error) + goto cleanup; + + curl_global_init(CURL_GLOBAL_ALL ^ CURL_GLOBAL_SSL); + curl = curl_easy_init(); + if (!curl) { + status->run_error = -1; + goto cleanup; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dbfp_parse); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)parser); + + status->curl_error = curl_easy_perform(curl); + + if (status->parse_error) + fprintf(stderr, "Parse error %d: %s\n", status->parse_error, + XML_ErrorString(status->parse_error)); + +cleanup: + if (curl) + curl_easy_cleanup(curl); + curl_global_cleanup(); + free(url); +} + +static int dbfp_url(struct dbfp *dbfp, char *service, char *query, char **url) +{ + int len; + + *url = malloc(DBFP_URL_LEN); + if (!*url) + return ENOMEM; + + len = snprintf(*url, DBFP_URL_LEN, DBFP_URL_FORMAT, DBFP_BASE_URL, + service, dbfp->key, query); + if (len < 0 || len >= DBFP_URL_LEN) { + free(*url); + *url = NULL; + return -1; + } + + return 0; +} + +static void handle_loc(struct loc_data *ld, const char **atts) +{ + char *name = NULL; + char *id = NULL; + size_t i; + + /* + * TODO(robin.krahl): fix error handling for the cases that a) + * name or id is not set and b) realloc fails. + */ + + for (i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "name") == 0) + name = strdup(atts[i + 1]); + else if (strcmp(atts[i], "id") == 0) + id = strdup(atts[i + 1]); + } + + if (name && id) { + ld->locs = realloc(ld->locs, (ld->n + 1) * sizeof(*ld->locs)); + if (ld->locs) { + ld->n++; + ld->locs[ld->n - 1].name = name; + ld->locs[ld->n - 1].id = id; + } + } else { + free(name); + free(id); + } +} + +static void loc_ele_start(void *data, const char *name, const char **atts) +{ + struct parse_data *pd = (struct parse_data *)data; + struct loc_data *ld = (struct loc_data *)pd->data; + size_t i; + + if (strcmp(name, "Error") == 0) { + for (i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "code")) { + fprintf(stderr, "err code %s\n", atts[i + 1]); + break; + } + } + } else if (strcmp(name, "StopLocation") == 0) { + handle_loc(ld, atts); + } +} + +static void loc_ele_end(void *data, const char *name) +{ +} diff --git a/dbfp.h b/dbfp.h index 2efd5e1..7c786c7 100644 --- a/dbfp.h +++ b/dbfp.h @@ -7,13 +7,29 @@ #ifndef DBFP_H_ #define DBFP_H_ -#define DBFP_BASE_URL "http://open-api.bahn.de/bin/rest.exe" +#include struct dbfp { char *key; }; +struct dbfp_status { + int error; + int run_error; + int parse_error; + int curl_error; + int api_error; +}; + +struct dbfp_location { + char *name; + char *id; +}; + int dbfp_init(struct dbfp *dbfp, char *key); void dbfp_close(struct dbfp *dbfp); +struct dbfp_status dbfp_query_location_name(struct dbfp *dbfp, char *input, + size_t *n, struct dbfp_location **out); + #endif /* DBFP_H_ */ diff --git a/dbfp_check.c b/dbfp_check.c index 35daaef..067918f 100644 --- a/dbfp_check.c +++ b/dbfp_check.c @@ -5,10 +5,13 @@ */ #include +#include #include #include "dbfp.h" +static char *api_key; + START_TEST(test_dbfp_create) { struct dbfp dbfp; @@ -21,20 +24,82 @@ START_TEST(test_dbfp_create) } END_TEST +START_TEST(test_dbfp_access) +{ + struct dbfp dbfp; + struct dbfp_status status; + int err; + + err = dbfp_init(&dbfp, api_key); + ck_assert_int_eq(err, 0); + + status = dbfp_query_location_name(&dbfp, "", NULL, NULL); + ck_assert_int_eq(status.error, 0); + ck_assert_int_eq(status.run_error, 0); + ck_assert_int_eq(status.parse_error, 0); + ck_assert_int_eq(status.curl_error, 0); + ck_assert_int_eq(status.api_error, 0); + + dbfp_close(&dbfp); +} +END_TEST + +START_TEST(test_dbfp_location_simple) +{ + struct dbfp dbfp; + struct dbfp_status status; + int err; + size_t n; + struct dbfp_location *locs = NULL; + + err = dbfp_init(&dbfp, api_key); + ck_assert_int_eq(err, 0); + + status = dbfp_query_location_name(&dbfp, "freiburg", &n, &locs); + ck_assert_int_eq(status.error, 0); + ck_assert_int_eq(status.run_error, 0); + ck_assert_int_eq(status.parse_error, 0); + ck_assert_int_eq(status.curl_error, 0); + ck_assert_int_eq(status.api_error, 0); + + ck_assert_int_gt(n, 0); + ck_assert_ptr_ne(locs, NULL); + + ck_assert_str_eq(locs[0].name, "Freiburg(Breisgau) Hbf"); + ck_assert_str_eq(locs[0].id, "008000107"); + + free(locs); + + dbfp_close(&dbfp); +} +END_TEST + int main(int argc, char **argv) { int num_failed = 0; Suite *s; SRunner *sr; TCase *tc_basic; + TCase *tc_location; + + api_key = getenv("DBFP_API_KEY"); + if (!api_key) { + fprintf(stderr, "DBFP_API_KEY not set\n"); + return EXIT_FAILURE; + } s = suite_create("dbfp"); sr = srunner_create(s); tc_basic = tcase_create("basic"); tcase_add_test(tc_basic, test_dbfp_create); + tcase_add_test(tc_basic, test_dbfp_access); suite_add_tcase(s, tc_basic); + tc_location = tcase_create("location"); + tcase_add_test(tc_location, test_dbfp_location_simple); + suite_add_tcase(s, tc_location); + srunner_run_all(sr, CK_ENV); num_failed = srunner_ntests_failed(sr); srunner_free(sr); -- cgit v1.2.1