/**
* @brief HTML-ish markup parser.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2018 K. Lange
*/
#include
#include
struct markup_state {
int state;
void * user;
markup_callback_tag_open callback_tag_open;
markup_callback_tag_close callback_tag_close;
markup_callback_data callback_data;
/* Private stuff */
struct markup_tag tag;
size_t len;
char data[64];
char * attr;
};
struct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data) {
struct markup_state * out = malloc(sizeof(struct markup_state));
out->state = 0;
out->user = user;
out->len = 0;
out->callback_tag_open = open;
out->callback_tag_close = close;
out->callback_data = data;
return out;
}
static void _dump_buffer(struct markup_state * state) {
if (state->len) {
state->data[state->len] = '\0';
state->callback_data(state, state->user, state->data);
state->data[0] = '\0';
state->len = 0;
}
}
static void _finish_name(struct markup_state * state) {
state->data[state->len] = '\0';
state->tag.name = strdup(state->data);
state->tag.options = hashmap_create(5);
state->data[0] = '\0';
state->len = 0;
state->state = 2;
}
static void _finish_close(struct markup_state * state) {
state->data[state->len] = '\0';
state->callback_tag_close(state, state->user, state->data);
state->data[0] = '\0';
state->len = 0;
state->state = 0;
}
static void _finish_tag(struct markup_state * state) {
state->callback_tag_open(state, state->user, &state->tag);
state->state = 0;
}
static void _finish_bare_attr(struct markup_state * state) {
state->data[state->len] = '\0';
hashmap_set(state->tag.options, state->data, strdup(state->data));
state->data[0] = '\0';
state->len = 0;
}
static void _finish_attr(struct markup_state * state) {
state->data[state->len] = '\0';
state->attr = strdup(state->data);
state->data[0] = '\0';
state->len = 0;
state->state = 4;
}
static void _finish_attr_value(struct markup_state * state) {
state->data[state->len] = '\0';
hashmap_set(state->tag.options, state->attr, strdup(state->data));
free(state->attr);
state->data[0] = '\0';
state->len = 0;
state->state = 2;
}
int markup_free_tag(struct markup_tag * tag) {
free(tag->name);
list_t * keys = hashmap_keys(tag->options);
if (keys->length) {
foreach(node, keys) {
free(hashmap_get(tag->options, node->value));
}
}
list_free(keys);
free(keys);
hashmap_free(tag->options);
return 0;
}
int markup_parse(struct markup_state * state, char c) {
switch (state->state) {
case 0: /* STATE_NORMAL */
if (state->len == 63) {
_dump_buffer(state);
}
switch (c) {
case '<':
_dump_buffer(state);
state->state = 1;
return 0;
default:
state->data[state->len] = c;
state->len++;
return 0;
}
break;
case 1: /* STATE_TAG_OPEN */
switch (c) {
case '/':
if (state->len) {
fprintf(stderr, "syntax error\n");
return 1;
}
state->state = 3; /* STATE_TAG_CLOSE */
return 0;
case '>':
_finish_name(state);
_finish_tag(state);
return 0;
case ' ':
_finish_name(state);
return 0;
default:
state->data[state->len] = c;
state->len++;
return 0;
}
break;
case 2: /* STATE_TAG_ATTRIB */
switch (c) {
case ' ': /* attribute has no value, end it and append it with = self */
_finish_bare_attr(state);
return 0;
case '>':
_finish_bare_attr(state);
_finish_tag(state);
return 0;
case '=': /* attribute has a value, go to next mode */
_finish_attr(state);
return 0;
default:
state->data[state->len] = c;
state->len++;
return 0;
}
return 0;
case 3: /* STATE_TAG_CLOSE */
switch (c) {
case '>':
_finish_close(state);
return 0;
default:
state->data[state->len] = c;
state->len++;
return 0;
}
break;
case 4: /* STATE_ATTR_VALUE */
switch (c) {
case ' ':
_finish_attr_value(state);
return 0;
case '>':
_finish_attr_value(state);
_finish_tag(state);
return 0;
default:
state->data[state->len] = c;
state->len++;
return 0;
}
break;
default:
fprintf(stderr, "parser in unknown state\n");
return 1;
}
return 0;
}
int markup_finish(struct markup_state * state) {
if (state->state != 0) {
fprintf(stderr, "unexpected end of data\n");
return 1;
} else {
_dump_buffer(state);
free(state);
return 0;
}
}