add a tool to print the tree of it - ics2txt - convert icalendar .ics file to p… | |
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
--- | |
commit d4d55c6876bf51dd555a0dbfae0316343d44997e | |
parent 8248ba97aa609be30e0ecf481d93e59a9876afcd | |
Author: Josuah Demangeon <[email protected]> | |
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(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -1,2 +1,3 @@ | |
*.o | |
ics2tsv | |
+ics2tree | |
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} | |
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, nod… | |
+ 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; | |
+} | |
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); | |
} | |
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 *… | |
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); | |
} | |
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 | |
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; | |
} | |
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 |