aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Krahl <me@robin-krahl.de>2016-03-08 04:43:39 +0100
committerRobin Krahl <me@robin-krahl.de>2016-03-08 04:43:39 +0100
commit1648e81474b552ba499102e13f9f3ed863501757 (patch)
tree5bab6daabf8ee746efd9fbab3cdfabc6c498cfcc
parent745c643c2f900c8efeb0ac0a8ea3a519b3a790ac (diff)
downloaddbfp-1648e81474b552ba499102e13f9f3ed863501757.tar.gz
dbfp-1648e81474b552ba499102e13f9f3ed863501757.tar.bz2
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.
-rw-r--r--.gitignore1
-rw-r--r--Makefile8
-rw-r--r--README6
-rw-r--r--dbfp.c210
-rw-r--r--dbfp.h18
-rw-r--r--dbfp_check.c65
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 <curl/curl.h>
#include <errno.h>
+#include <expat.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#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 <stddef.h>
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 <check.h>
+#include <stdio.h>
#include <stdlib.h>
#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);