add a tool to print the tree of it - ics2txt - convert icalendar .ics file to plain text HTML git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt DIR Log DIR Files DIR Refs DIR Tags DIR README --- DIR commit d4d55c6876bf51dd555a0dbfae0316343d44997e DIR parent 8248ba97aa609be30e0ecf481d93e59a9876afcd HTML Author: Josuah Demangeon <me@josuah.net> Date: Sun, 28 Jun 2020 18:44:32 +0200 add a tool to print the tree of it Diffstat: M .gitignore | 1 + M Makefile | 2 +- A ics2tree.c | 100 +++++++++++++++++++++++++++++++ M ics2tsv.c | 25 +++++++++---------------- M src/ical.c | 249 +++++++++++++++++++++++-------- M src/ical.h | 65 ++++++++++++++++++------------- M src/map.c | 16 +++++++++------- M src/map.h | 4 ++-- 8 files changed, 348 insertions(+), 114 deletions(-) --- DIR diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ *.o ics2tsv +ics2tree DIR diff --git a/Makefile b/Makefile @@ -11,7 +11,7 @@ MANPREFIX = ${PREFIX}/man SRC = src/ical.c src/map.c src/util.c src/log.c HDR = src/ical.h src/map.h src/util.h src/log.h OBJ = ${SRC:.c=.o} -BIN = ics2tsv +BIN = ics2tsv ics2tree all: ${BIN} DIR diff --git a/ics2tree.c b/ics2tree.c @@ -0,0 +1,100 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ical.h" +#include "log.h" +#include "util.h" + +void +print_ical_tree_param(struct map_entry *entry, int level) +{ + if (entry == NULL) + return; + for (int i = 0; i < level; i++) + printf(": "); + fprintf(stdout, "param %s=%s\n", entry->key, (char *)entry->value); +} + +void +print_ical_tree_value(struct ical_value *value, int level) +{ + if (value == NULL) + return; + for (int i = 0; i < level; i++) + printf(": "); + fprintf(stdout, "value %s:%s\n", value->name, value->value); + for (size_t i = 0; i < value->param.len; i++) + print_ical_tree_param(value->param.entry + i, level + 1); + print_ical_tree_value(value->next, level); +} + +void +print_ical_tree_vnode(struct ical_vnode *node, int level) +{ + if (node == NULL) + return; + for (int i = 0; i < level; i++) + printf(": "); + fprintf(stdout, "node %p %s child=%p next=%p\n", node, node->name, node->child, node->next); + for (size_t i = 0; i < node->values.len; i++) + print_ical_tree_value(node->values.entry[i].value, level + 1); + print_ical_tree_vnode(node->child, level + 1); + print_ical_tree_vnode(node->next, level); +} + +int +print_ical_tree(FILE *fp) +{ + struct ical_vcalendar vcal; + int e; + + if ((e = ical_read_vcalendar(&vcal, fp)) < 0) + die("reading ical file: %s", ical_strerror(e)); + + print_ical_tree_vnode(vcal.root, 0); + fprintf(stdout, ".\n"); + fflush(stdout); + + ical_free_vcalendar(&vcal); + return 0; +} + +void +print_header(void) +{ + char *fields[] = { "", NULL }; + + printf("%s\t%s", "beg", "end"); + + for (char **f = fields; *f != NULL; f++) { + fprintf(stdout, "\t%s", *f); + } + fprintf(stdout, "\n"); +} + +int +main(int argc, char **argv) +{ + print_header(); + + log_arg0 = *argv++; + + if (*argv == NULL) { + if (print_ical_tree(stdin) < 0) + die("converting stdin"); + } + + for (; *argv != NULL; argv++, argc--) { + FILE *fp; + + info("converting \"%s\"", *argv); + if ((fp = fopen(*argv, "r")) == NULL) + die("opening %s", *argv); + if (print_ical_tree(fp) < 0) + die("converting %s", *argv); + fclose(fp); + } + + return 0; +} DIR diff --git a/ics2tsv.c b/ics2tsv.c @@ -7,23 +7,16 @@ #include "util.h" int -print_ical_to_tsv(FILE *fp) +print_ical_tsv(FILE *fp) { - struct ical_contentline cl; - char *line = NULL, *ln = NULL; - size_t sz = 0; - ssize_t r; + struct ical_vcalendar vcal; + int e; - memset(&cl, 0, sizeof cl); + if ((e = ical_read_vcalendar(&vcal, fp)) < 0) + die("reading ical file: %s", ical_strerror(e)); - while ((r = ical_read_line(&line, &ln, &sz, fp)) > 0) { - debug("readling line \"%s\"", line); - if (ical_parse_contentline(&cl, line) < 0) - die("parsing line \"%s\"", line); - } - free(line); - free(ln); - return r; + ical_free_vcalendar(&vcal); + return 0; } void @@ -47,7 +40,7 @@ main(int argc, char **argv) log_arg0 = *argv++; if (*argv == NULL) { - if (print_ical_to_tsv(stdin) < 0) + if (print_ical_tsv(stdin) < 0) die("converting stdin"); } @@ -57,7 +50,7 @@ main(int argc, char **argv) info("converting \"%s\"", *argv); if ((fp = fopen(*argv, "r")) == NULL) die("opening %s", *argv); - if (print_ical_to_tsv(fp) < 0) + if (print_ical_tsv(fp) < 0) die("converting %s", *argv); fclose(fp); } DIR diff --git a/src/ical.c b/src/ical.c @@ -9,8 +9,10 @@ #include "util.h" +enum ical_err ical_errno; + int -ical_read_line(char **line, char **ln, size_t *sz, FILE *fp) +ical_getline(char **line, char **ln, size_t *sz, FILE *fp) { int c; void *v; @@ -35,113 +37,240 @@ ical_read_line(char **line, char **ln, size_t *sz, FILE *fp) return 1; } +char * +ical_strerror(int i) +{ + enum ical_err err = (i > 0) ? i : -i; + + switch (err) { + case ICAL_ERR_OK: + return "no error"; + case ICAL_ERR_SYSTEM: + return "system error"; + case ICAL_ERR_END_MISMATCH: + return "END: does not match its corresponding BEGIN:"; + case ICAL_ERR_MISSING_BEGIN: + return "unexpected content line before any BEGIN:"; + case ICAL_ERR_MIN_NESTED: + return "too many END: for the number of BEGIN:"; + case ICAL_ERR_MAX_NESTED: + return "maximum nesting level reached"; + case ICAL_ERR_LENGTH: + assert(!"used internally, should not happen"); + } + assert(!"unknown error code"); + return "not a valid ical error code"; +} + +struct ical_value * +ical_new_value(char const *line) +{ + struct ical_value *new; + size_t len; + + len = strlen(line); + if ((new = calloc(1, sizeof *new + len + 1)) == NULL) + return NULL; + memcpy(new->buf, line, len + 1); + return new; +} + +void +ical_free_value(struct ical_value *value) +{ + debug("free value %p (%s:%s)", value, value->name, value->value); + map_free(&value->param, free); + free(value); +} + int -ical_parse_contentline(struct ical_contentline *cl, char *line) +ical_parse_value(struct ical_value *value) { char *column, *equal, *param, *cp; - size_t sz; int e = errno; - if ((column = strchr(line, ':')) == NULL) + value->name = value->buf; + + if ((column = strchr(value->buf, ':')) == NULL) return -1; *column = '\0'; - if ((cl->value = strdup(column + 1)) == NULL) - return -1; + value->value = column + 1; - if ((cp = strchr(line, ';')) != NULL) - cp++; + if ((cp = strchr(value->buf, ';')) != NULL) + *cp++ = '\0'; while ((param = strsep(&cp, ";")) != NULL) { if ((equal = strchr(param, '=')) == NULL) return -1; *equal = '\0'; - if (map_set(&cl->param, param, equal + 1) < 0) + if (map_set(&value->param, param, equal + 1) < 0) return -1; } - sz = sizeof cl->name; - if (strlcpy(cl->name, line, sz) >= sz) - return errno=EMSGSIZE, -1; - assert(errno == e); return 0; } -int -ical_parse_tzid(struct ical_value *value, struct ical_contentline *cl) +struct ical_vnode * +ical_new_vnode(char const *name) { - return 0; + struct ical_vnode *new; + size_t sz; + + if ((new = calloc(1, sizeof *new)) == NULL) + return NULL; + sz = sizeof new->name; + if (strlcpy(new->name, name, sz) >= sz) { + errno = EMSGSIZE; + goto err; + } + return new; +err: + ical_free_vnode(new); + return NULL; } -int -ical_parse_date(struct ical_value *value, struct ical_contentline *cl) +static void +ical_free_vnode_value(void *v) { - return 0; + ical_free_value(v); +} + +void +ical_free_vnode(struct ical_vnode *node) +{ + if (node == NULL) + return; + debug("free vnode %p %s", node, node->name); + map_free(&node->values, ical_free_vnode_value); + ical_free_vnode(node->child); + ical_free_vnode(node->next); + free(node); } int -ical_parse_attribute(struct ical_value *value, struct ical_contentline *cl) +ical_push_nested(struct ical_vcalendar *vcal, struct ical_vnode *new) { + struct ical_vnode **node; + + node = vcal->nested; + for (int i = 0; *node != NULL; node++, i++) { + if (i >= ICAL_NESTED_MAX) + return -ICAL_ERR_MAX_NESTED; + } + node[0] = new; + node[1] = NULL; return 0; } +struct ical_vnode * +ical_pop_nested(struct ical_vcalendar *vcal) +{ + struct ical_vnode **node, **prev = vcal->nested, *old; + + for (prev = node = vcal->nested; *node != NULL; node++) { + vcal->current = *prev; + prev = node; + old = *node; + } + *prev = NULL; + if (vcal->nested[0] == NULL) + vcal->current = NULL; + return old; +} + int ical_begin_vnode(struct ical_vcalendar *vcal, char const *name) { - if (strcasecmp(name, "VCALENDAR")) - return 0; - return -1; + struct ical_vnode *new; + int e; + + if ((new = ical_new_vnode(name)) == NULL) + return -ICAL_ERR_SYSTEM; + if ((e = ical_push_nested(vcal, new)) < 0) + goto err; + if (vcal->root == NULL) { + vcal->root = new; + vcal->current = new; + } else { + new->next = vcal->current->child; + vcal->current->child = new; + vcal->current = new; + } + return 0; +err: + ical_free_vnode(new); + return e; } int ical_end_vnode(struct ical_vcalendar *vcal, char const *name) { - if (strcasecmp(name, "VCALENDAR")) - return 0; - return -1; + struct ical_vnode *old; + + if ((old = ical_pop_nested(vcal)) == NULL) + return -ICAL_ERR_MIN_NESTED; + if (strcasecmp(name, old->name) != 0) + return -ICAL_ERR_END_MISMATCH; + return 0; } int -ical_add_contentline(struct ical_vcalendar *vcal, struct ical_contentline *cl) +ical_push_value(struct ical_vcalendar *vcal, struct ical_value *new) { - struct ical_value value_buf, *value = &value_buf; - int i; - struct { - char *name; - enum ical_value_type type; - int (*fn)(struct ical_value *, struct ical_contentline *); - } map[] = { - { "DTSTART", ICAL_VALUE_TIME, ical_parse_date }, - { "DTEND", ICAL_VALUE_TIME, ical_parse_date }, - { "TZID", ICAL_VALUE_TIME, ical_parse_tzid }, - { NULL, ICAL_VALUE_ATTRIBUTE, ical_parse_attribute }, - }; - - if (strcasecmp(cl->name, "BEGIN") == 0) - return ical_begin_vnode(vcal, cl->value); - - if (strcasecmp(cl->name, "END") == 0) - return ical_end_vnode(vcal, cl->value); - - memset(value, 0, sizeof *value); - - for (i = 0; map[i].name == NULL; i++) - if (strcasecmp(cl->name, map[i].name) == 0) - break; - value->type = map[i].type; - if (map[i].fn(value, cl) < 0) - return -1; + if (strcasecmp(new->name, "BEGIN") == 0) { + int e = ical_begin_vnode(vcal, new->value); + ical_free_value(new); + return e; + } + if (strcasecmp(new->name, "END") == 0) { + int e = ical_end_vnode(vcal, new->value); + ical_free_value(new); + return e; + } + + if (vcal->current == NULL) + return -ICAL_ERR_MISSING_BEGIN; + + debug("new %p %s:%s", new, new->name, new->value); + new->next = map_get(&vcal->current->values, new->name); + if (map_set(&vcal->current->values, new->name, new) < 0) + return -ICAL_ERR_SYSTEM; + return 0; } -void -ical_free_value(struct ical_value *value) +int +ical_read_vcalendar(struct ical_vcalendar *vcal, FILE *fp) { - ; + char *line = NULL, *ln = NULL; + size_t sz = 0; + ssize_t r; + int e; + + memset(vcal, 0, sizeof *vcal); + + while ((r = ical_getline(&line, &ln, &sz, fp)) > 0) { + struct ical_value *new; + + if ((new = ical_new_value(line)) == NULL) { + e = -ICAL_ERR_SYSTEM; + goto err; + } + if ((e = ical_parse_value(new)) < 0) + goto err; + if ((e = ical_push_value(vcal, new)) < 0) + goto err; + } + e = (r == 0) ? 0 : -ICAL_ERR_SYSTEM; +err: + free(line); + free(ln); + return e; } void -ical_free_contentline(struct ical_contentline *cl) +ical_free_vcalendar(struct ical_vcalendar *vcal) { - map_free(&cl->param); - free(cl->value); + debug("free vcalendar"); + ical_free_vnode(vcal->root); } DIR diff --git a/src/ical.h b/src/ical.h @@ -6,36 +6,25 @@ #include "map.h" -#define ICAL_NEST_MAX 4 +#define ICAL_NESTED_MAX 4 -/* */ +enum ical_err { + ICAL_ERR_OK, + ICAL_ERR_SYSTEM, + ICAL_ERR_END_MISMATCH, + ICAL_ERR_MISSING_BEGIN, + ICAL_ERR_MIN_NESTED, + ICAL_ERR_MAX_NESTED, -struct ical_contentline { - char name[32], *value; - struct map param; -}; - -/* single value for an iCalendar element attribute */ - -enum ical_value_type { - ICAL_VALUE_TIME, ICAL_VALUE_ATTRIBUTE, -} type; - -union ical_value_union { - time_t *time; - char *str; -}; - -struct ical_value { - enum ical_value_type type; - union ical_value_union value; + ICAL_ERR_LENGTH, }; /* global propoerties for an iCalendar document as well as parsing state */ struct ical_vcalendar { time_t tzid; - char *stack[ICAL_NEST_MAX + 1]; + struct ical_vnode *root; + struct ical_vnode *nested[ICAL_NESTED_MAX + 1]; struct ical_vnode *current; }; @@ -44,14 +33,34 @@ struct ical_vcalendar { struct ical_vnode { char name[32]; time_t beg, end; - struct map properties; /* struct ical_value */ - struct ical_vnode *child, *next; + struct map values; /*(struct ical_value *)*/ + struct ical_vnode *child; + struct ical_vnode *next; +}; + +/* one line whith the whole content unfolded */ + +struct ical_value { + char *name, *value; + struct map param; + struct ical_value *next; + char buf[]; }; /** src/ical.c **/ -int ical_read_line(char **line, char **ln, size_t *sz, FILE *fp); -int ical_parse_contentline(struct ical_contentline *contentline, char *line); -void ical_init_contentline(struct ical_contentline *contentline); -void ical_free_contentline(struct ical_contentline *contentline); +int ical_getline(char **line, char **ln, size_t *sz, FILE *fp); +char * ical_strerror(int i); +struct ical_value * ical_new_value(char const *line); +void ical_free_value(struct ical_value *value); +int ical_parse_value(struct ical_value *value); +struct ical_vnode * ical_new_vnode(char const *name); +void ical_free_vnode(struct ical_vnode *node); +int ical_push_nested(struct ical_vcalendar *vcal, struct ical_vnode *new); +struct ical_vnode * ical_pop_nested(struct ical_vcalendar *vcal); +int ical_begin_vnode(struct ical_vcalendar *vcal, char const *name); +int ical_end_vnode(struct ical_vcalendar *vcal, char const *name); +int ical_push_value(struct ical_vcalendar *vcal, struct ical_value *new); +void ical_free_vcalendar(struct ical_vcalendar *vcal); +int ical_read_vcalendar(struct ical_vcalendar *vcal, FILE *fp); #endif DIR diff --git a/src/map.c b/src/map.c @@ -54,8 +54,7 @@ map_set(struct map *map, char *key, void *value) for (; e >= insert; e--) e[1].key = e[0].key; - if ((insert->key = strdup(key)) == NULL) - return -1; + insert->key = key; insert->value = value; return 0; @@ -90,16 +89,19 @@ map_init(struct map *map) } void -map_free_values(struct map *map) +map_free_keys(struct map *map) { for (size_t i = 0; i < map->len; i++) - free(map->entry[map->len - 1].value); + free(map->entry[i].key); } void -map_free(struct map *map) +map_free(struct map *map, void (*fn)(void *)) { - for (size_t i = 0; i < map->len; i++) - free(map->entry[map->len - 1].key); + if (fn != NULL) { + for (size_t i = 0; i < map->len; i++) + fn(map->entry[i].value); + } free(map->entry); + map->len = 0; } DIR diff --git a/src/map.h b/src/map.h @@ -18,7 +18,7 @@ void * map_get(struct map *map, char *key); int map_set(struct map *map, char *key, void *value); int map_del(struct map *map, char *key); void map_init(struct map *map); -void map_free_values(struct map *map); -void map_free(struct map *map); +void map_free_keys(struct map *map); +void map_free(struct map *map, void (*fn)(void *)); #endif