2021-11-26 06:59:02 +03:00
|
|
|
/**
|
|
|
|
* @brief HTML-ish markup parser.
|
|
|
|
*
|
|
|
|
* @copyright
|
2018-11-12 14:24:42 +03:00
|
|
|
* 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 <stdio.h>
|
|
|
|
#include <toaru/markup.h>
|
|
|
|
|
|
|
|
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) {
|
2021-07-27 03:53:06 +03:00
|
|
|
struct markup_state * out = malloc(sizeof(struct markup_state));
|
2018-11-12 14:24:42 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|