URI: 
       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