ical: improve and simplify line parsing - ics2txt - convert icalendar .ics file to plain text
(HTM) git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
---
(DIR) commit 92a5d0067b717710eb607c0465a8a60d4b4c8655
(DIR) parent d10df705caaa2ca4e3229af6d5ec76e0f0d301da
(HTM) Author: Josuah Demangeon <me@josuah.net>
Date: Wed, 16 Jun 2021 23:13:22 +0200
ical: improve and simplify line parsing
Diffstat:
M Makefile | 2 +-
M bin/ics2tsv | 2 +-
M ical.c | 152 +++++++++++++++++++------------
M ical.h | 25 ++++++++++++++++++-------
M ics2tree.c | 8 ++++----
A ics2tsv.c | 99 +++++++++++++++++++++++++++++++
M tcal.5 | 11 +++++------
7 files changed, 220 insertions(+), 79 deletions(-)
---
(DIR) diff --git a/Makefile b/Makefile
@@ -10,7 +10,7 @@ MANPREFIX = ${PREFIX}/man
SRC = ical.c base64.c util.c
HDR = ical.h base64.h util.h
OBJ = ${SRC:.c=.o}
-BIN = ics2tree
+BIN = ics2tree ics2tsv
MAN1 = ics2txt.1
MAN5 = tcal.5
(DIR) diff --git a/bin/ics2tsv b/bin/ics2tsv
@@ -126,7 +126,7 @@ sub("^ ", "") {
next
if (content["name"] == "TZID") {
- ical_set_tzid(content["value"])
+ ical_set_tz(content["value"])
} else if (DT[content["name"]]) {
vevent[content["name"]] = ical_to_epoch(content, params)
} else {
(DIR) diff --git a/ical.c b/ical.c
@@ -11,14 +11,20 @@
#include "util.h"
#include "base64.h"
-#define Xstrlcpy(d, s) (strlcpy((d), (s), sizeof(d)) < sizeof(d))
-#define Xstrlcat(d, s) (strlcat((d), (s), sizeof(d)) < sizeof(d))
-
-/* helpers: common utilities to call within the p->fn() callbacks as
- * well as in the code below */
+char *ical_block_name[ICAL_BLOCK_OTHER + 1] = {
+ [ICAL_BLOCK_VEVENT] = "VEVENT",
+ [ICAL_BLOCK_VTODO] = "VTODO",
+ [ICAL_BLOCK_VJOURNAL] = "VJOURNAL",
+ [ICAL_BLOCK_VFREEBUSY] = "VFREEBUSY",
+ [ICAL_BLOCK_VALARM] = "VALARM",
+ [ICAL_BLOCK_OTHER] = NULL,
+};
+
+/* valuel helpers: common utilities to call within the p->fn()
+ * callbacks as well as in the code below */
int
-ical_error(IcalParser *p, char const *msg)
+ical_err(IcalParser *p, char *msg)
{
p->errmsg = msg;
return -1;
@@ -36,7 +42,7 @@ ical_get_value(IcalParser *p, char *s, size_t *len)
*len = strlen(s);
if (p->base64)
if (base64_decode(s, len, s, len) < 0)
- return ical_error(p, "invalid base64 data");
+ return ical_err(p, "invalid base64 data");
return 0;
}
@@ -55,7 +61,7 @@ ical_get_time(IcalParser *p, char *s, time_t *t)
/* date */
for (int i = 0; i < 8; i++)
if (!isdigit(s[i]))
- return ical_error(p, "invalid date format");
+ return ical_err(p, "invalid date format");
tm.tm_year = N(0,1000) + N(1,100) + N(2,10) + N(3,1) - 1900;
tm.tm_mon = N(4,10) + N(5,1) - 1;
tm.tm_mday = N(6,10) + N(7,1);
@@ -66,7 +72,7 @@ ical_get_time(IcalParser *p, char *s, time_t *t)
s++;
for (int i = 0; i < 6; i++)
if (!isdigit(s[i]))
- return ical_error(p, "invalid time format");
+ return ical_err(p, "invalid time format");
tm.tm_hour = N(0,10) + N(1,1);
tm.tm_min = N(2,10) + N(3,1);
tm.tm_sec = N(4,10) + N(5,1);
@@ -74,8 +80,10 @@ ical_get_time(IcalParser *p, char *s, time_t *t)
tzid = "UTC";
}
+#undef N
+
if ((*t = tztime(&tm, tzid)) == (time_t)-1)
- return ical_error(p, "could not convert time");
+ return ical_err(p, "could not convert time");
return 0;
}
@@ -84,21 +92,21 @@ ical_get_time(IcalParser *p, char *s, time_t *t)
* processing time zones definition or prepare base64 decoding, and
* permit to only have parsing code left to parsing functions */
-int
+static int
hook_entry_name(IcalParser *p, char *name)
{
(void)p; (void)name;
return 0;
}
-int
+static int
hook_param_name(IcalParser *p, char *name)
{
(void)p; (void)name;
return 0;
}
-int
+static int
hook_param_value(IcalParser *p, char *name, char *value)
{
if (strcasecmp(name, "ENCODING") == 0)
@@ -110,38 +118,53 @@ hook_param_value(IcalParser *p, char *name, char *value)
return 0;
}
-int
+static int
hook_entry_value(IcalParser *p, char *name, char *value)
{
if (strcasecmp(name, "TZID") == 0)
- if (!Xstrlcpy(p->current->tzid, value))
- return ical_error(p, "TZID: name too large");
+ if (strlcpy(p->current->tzid, value, sizeof p->current->tzid) >=
+ sizeof p->current->tzid)
+ return ical_err(p, "TZID: name too large");
p->tzid = NULL;
return 0;
}
-int
+static int
hook_block_begin(IcalParser *p, char *name)
{
p->current++;
memset(p->current, 0, sizeof(*p->current));
if (ical_get_level(p) >= ICAL_STACK_SIZE)
- return ical_error(p, "max recurion reached");
- if (!Xstrlcpy(p->current->name, name))
- return ical_error(p, "value too large");
+ return ical_err(p, "max recurion reached");
+ if (strlcpy(p->current->name, name, sizeof p->current->name) >=
+ sizeof p->current->name)
+ return ical_err(p, "value too large");
+
+ for (int i = 0; ical_block_name[i] != NULL; i++) {
+ if (strcasecmp(ical_block_name[i], name) == 0) {
+ if (p->block != ICAL_BLOCK_OTHER)
+ return ical_err(p, "BEGIN:V* in BEGIN:V*");
+ p->block = i;
+ }
+ }
+
return 0;
}
-int
+static int
hook_block_end(IcalParser *p, char *name)
{
if (strcasecmp(p->current->name, name) != 0)
- return ical_error(p, "mismatching BEGIN: and END:");
+ return ical_err(p, "mismatching BEGIN: and END:");
p->current--;
if (p->current < p->stack)
- return ical_error(p, "more END: than BEGIN:");
+ return ical_err(p, "more END: than BEGIN:");
+
+ if (ical_block_name[p->block] != NULL &&
+ strcasecmp(ical_block_name[p->block], name) == 0)
+ p->block = ICAL_BLOCK_OTHER;
return 0;
}
@@ -162,7 +185,7 @@ ical_parse_value(IcalParser *p, char **sp, char *name)
while (!iscntrl(*s) && *s != '"')
s++;
if (*s != '"')
- return ical_error(p, "missing '\"'");
+ return ical_err(p, "missing '\"'");
*s++ = '\0';
} else {
val = s;
@@ -188,7 +211,7 @@ ical_parse_param(IcalParser *p, char **sp)
do {
for (name = s; isalnum(*s) || *s == '-'; s++);
if (s == name || (*s != '='))
- return ical_error(p, "invalid parameter name");
+ return ical_err(p, "invalid parameter name");
*s++ = '\0';
if ((err = hook_param_name(p, name)) != 0 ||
(err = CALL(p, fn_param_name, name)) != 0)
@@ -208,9 +231,12 @@ ical_parse_contentline(IcalParser *p, char *s)
int err;
char c, *name, *sep;
+ if (*s == '\0')
+ return 0;
+
for (name = s; isalnum(*s) || *s == '-'; s++);
if (s == name || (*s != ';' && *s != ':'))
- return ical_error(p, "invalid entry name");
+ return ical_err(p, "invalid property name");
c = *s, *s = '\0';
if (strcasecmp(name, "BEGIN") != 0 && strcasecmp(name, "END") != 0)
if ((err = hook_entry_name(p, name)) != 0 ||
@@ -227,7 +253,7 @@ ical_parse_contentline(IcalParser *p, char *s)
}
if (*s != ':')
- return ical_error(p, "expected ':' delimiter");
+ return ical_err(p, "expected ':' delimiter");
s++;
*sep = '\0';
@@ -247,47 +273,53 @@ ical_parse_contentline(IcalParser *p, char *s)
return 0;
}
+static ssize_t
+ical_getline(char **contentline, char **line, size_t *sz, FILE *fp)
+{
+ size_t num = 0;
+ int c;
+
+ if ((*contentline = realloc(*contentline, 1)) == NULL)
+ return -1;
+ **contentline = '\0';
+
+ do {
+ if (getline(line, sz, fp) <= 0)
+ goto end;
+ num++;
+ strchomp(*line);
+
+ if (strappend(contentline, *line) < 0)
+ return -1;
+ if ((c = fgetc(fp)) == EOF)
+ goto end;
+ } while (c == ' ');
+ ungetc(c, fp);
+ assert(!ferror(fp));
+end:
+ return ferror(fp) ? -1 : num;
+}
+
int
ical_parse(IcalParser *p, FILE *fp)
{
- char *ln = NULL, *contentline = NULL;
+ char *line = NULL, *contentline = NULL;
size_t sz = 0;
- int err, c;
+ ssize_t l;
+ int err;
p->current = p->stack;
+ p->linenum = 0;
+ p->block = ICAL_BLOCK_OTHER;
- while (!feof(fp)) {
- if ((contentline = realloc(contentline, 1)) == NULL)
- return ical_error(p, strerror(errno));
- *contentline = '\0';
-
- do {
- do {
- p->linenum++;
- if (getline(&ln, &sz, fp) <= 0) {
- if (ferror(fp))
- return ical_error(p, strerror(errno));
- goto end;
- }
- strchomp(ln);
- } while (*ln == '\0');
-
- if (strappend(&contentline, ln) < 0)
- return ical_error(p, strerror(errno));
- if ((c = fgetc(fp)) == EOF) {
- if (ferror(fp))
- return ical_error(p, strerror(errno));
- goto done;
- }
- } while (c == ' ');
- ungetc(c, fp);
-done:
- assert(!ferror(fp));
- if ((err = ical_parse_contentline(p, contentline)) != 0)
+ do {
+ if ((l = ical_getline(&contentline, &line, &sz, fp)) < 0) {
+ err = ical_err(p, "readling line");
break;
- }
-end:
+ }
+ p->linenum += l;
+ } while (l > 0 && (err = ical_parse_contentline(p, contentline)) == 0);
free(contentline);
- free(ln);
+ free(line);
return err;
}
(DIR) diff --git a/ical.h b/ical.h
@@ -9,6 +9,15 @@
typedef struct IcalParser IcalParser;
typedef struct IcalStack IcalStack;
+typedef enum {
+ ICAL_BLOCK_VEVENT,
+ ICAL_BLOCK_VTODO,
+ ICAL_BLOCK_VJOURNAL,
+ ICAL_BLOCK_VFREEBUSY,
+ ICAL_BLOCK_VALARM,
+ ICAL_BLOCK_OTHER,
+} IcalBlock;
+
struct IcalStack {
char name[32];
char tzid[32];
@@ -25,17 +34,19 @@ struct IcalParser {
/* if returning non-zero then halt the parser */
int base64;
- char const *errmsg;
+ char *errmsg;
size_t linenum;
char *tzid;
-
+ IcalBlock block;
IcalStack stack[ICAL_STACK_SIZE], *current;
};
-int ical_parse(IcalParser *, FILE *);
-int ical_get_level(IcalParser *);
-int ical_get_time(IcalParser *, char *, time_t *);
-int ical_get_value(IcalParser *, char *, size_t *);
-int ical_error(IcalParser *, char const *);
+extern char *ical_block_name[ICAL_BLOCK_OTHER + 1];
+
+int ical_parse(IcalParser *, FILE *);
+int ical_get_level(IcalParser *);
+int ical_get_time(IcalParser *, char *, time_t *);
+int ical_get_value(IcalParser *, char *, size_t *);
+int ical_err(IcalParser *, char *);
#endif
(DIR) diff --git a/ics2tree.c b/ics2tree.c
@@ -18,6 +18,7 @@ fn_entry_name(IcalParser *p, char *name)
{
print_ruler(ical_get_level(p));
printf("name %s\n", name);
+ fflush(stdout);
return 0;
}
@@ -26,6 +27,7 @@ fn_block_begin(IcalParser *p, char *name)
{
print_ruler(ical_get_level(p) - 1);
printf("begin %s\n", name);
+ fflush(stdout);
return 0;
}
@@ -34,6 +36,7 @@ fn_param_value(IcalParser *p, char *name, char *value)
{
print_ruler(ical_get_level(p) + 1);
printf("param %s=%s\n", name, value);
+ fflush(stdout);
return 0;
}
@@ -45,21 +48,18 @@ fn_entry_value(IcalParser *p, char *name, char *value)
if (ical_get_value(p, value, &len) < 0)
return -1;
-
print_ruler(ical_get_level(p) + 1);
-
if (strcasecmp(name, "DTSTART") == 0 ||
strcasecmp(name, "DTSTAMP") == 0 ||
strcasecmp(name, "DTEND") == 0) {
time_t t;
-
if (ical_get_time(p, value, &t) != 0)
warn("%s: %s", p->errmsg, value);
printf("epoch %lld\n", t);
} else {
printf("value %s\n", value);
}
-
+ fflush(stdout);
return 0;
}
(DIR) diff --git a/ics2tsv.c b/ics2tsv.c
@@ -0,0 +1,99 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include "ical.h"
+#include "util.h"
+
+#define FIELDS_MAX 64
+
+typedef struct Event Event;
+
+struct Event {
+ time_t beg, end;
+ char *fields[FIELDS_MAX];
+};
+
+static char *fields_time[] = {
+ "DTSTART", "DTEND", "DTSTAMP", "DUE", "EXDATE", "RDATE"
+};
+
+static char *fields_default[] = {
+ "ATTENDEE", "CATEGORY", "DESCRIPTION", "LOCATION", "SUMMARY", "URL"
+};
+
+static char **fields = fields_default;
+
+static int
+fn_entry_name(IcalParser *p, char *name)
+{
+ printf("name %s\n", name);
+ return 0;
+}
+
+static int
+fn_block_begin(IcalParser *p, char *name)
+{
+ printf("begin %s\n", name);
+ return 0;
+}
+
+static int
+fn_param_value(IcalParser *p, char *name, char *value)
+{
+ printf("param %s=%s\n", name, value);
+ return 0;
+}
+
+static int
+fn_entry_value(IcalParser *p, char *name, char *value)
+{
+ size_t len;
+ (void)name;
+
+ if (ical_get_value(p, value, &len) < 0)
+ return -1;
+
+ if (strcasecmp(name, "DTSTART") == 0 ||
+ strcasecmp(name, "DTSTAMP") == 0 ||
+ strcasecmp(name, "DTEND") == 0) {
+ time_t t = 0;
+ if (ical_get_time(p, value, &t) != 0)
+ warn("%s: %s", p->errmsg, value);
+ printf("epoch %lld\n", t);
+ } else {
+ printf("value %s\n", value);
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ IcalParser p = {0};
+ arg0 = *argv++;
+
+ p.fn_entry_name = fn_entry_name;
+ p.fn_block_begin = fn_block_begin;
+ p.fn_param_value = fn_param_value;
+ p.fn_entry_value = fn_entry_value;
+
+ if (*argv == NULL) {
+ if (ical_parse(&p, stdin) < 0)
+ err("parsing stdin:%d: %s", p.linenum, p.errmsg);
+ }
+
+ for (; *argv != NULL; argv++, argc--) {
+ FILE *fp;
+
+ debug("converting \"%s\"", *argv);
+ if ((fp = fopen(*argv, "r")) == NULL)
+ err("opening %s", *argv);
+ if (ical_parse(&p, fp) < 0)
+ err("parsing %s:%d: %s", *argv, p.linenum, p.errmsg);
+ fclose(fp);
+ }
+ return 0;
+}
(DIR) diff --git a/tcal.5 b/tcal.5
@@ -36,17 +36,16 @@ end of line.
.Bd -literal
TZ+0200
-2020-06-28 00:00
-2020-06-05 00:00
+2021-06-28 00:00
+2021-06-05 00:00
loc: 950-0994, Chuo Ward, Niigata, Japan
sum: summer holidays
-2020-06-29 13:30
-2020-06-29 15:00
- loc: online, irc.freenode.net, #bitreich-en
+2021-06-29 13:30
+2021-06-29 15:00
+ loc: online, irc.bitreich.org, #bitreich-en
sum: bitreich irc invitation
des: at this moment like all other moment, everyone invited on IRC
-
.Ed
.
.