/** * @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; } }