|
@@ -0,0 +1,240 @@
|
|
|
+/* copyright (c) 2022 - 2023 grunfink et al. / MIT license */
|
|
|
+
|
|
|
+#ifndef _XS_HTML_H
|
|
|
+
|
|
|
+#define _XS_HTML_H
|
|
|
+
|
|
|
+typedef struct xs_html xs_html;
|
|
|
+
|
|
|
+xs_str *xs_html_encode(char *str);
|
|
|
+
|
|
|
+xs_html *xs_html_attr(char *key, char *value);
|
|
|
+xs_html *xs_html_text(char *content);
|
|
|
+xs_html *xs_html_raw(char *content);
|
|
|
+
|
|
|
+xs_html *xs_html_add(xs_html *tag, xs_html *data);
|
|
|
+
|
|
|
+xs_html *_xs_html_tag(char *tag, xs_html *var[]);
|
|
|
+#define xs_html_tag(tag, ...) _xs_html_tag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
|
|
|
+xs_html *_xs_html_sctag(char *tag, xs_html *var[]);
|
|
|
+#define xs_html_sctag(tag, ...) _xs_html_sctag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
|
|
|
+xs_str *_xs_html_render(xs_html *h, xs_str *s);
|
|
|
+#define xs_html_render(h) _xs_html_render(h, xs_str_new(NULL))
|
|
|
+
|
|
|
+#ifdef XS_IMPLEMENTATION
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ XS_HTML_TAG,
|
|
|
+ XS_HTML_SCTAG,
|
|
|
+ XS_HTML_ATTR,
|
|
|
+ XS_HTML_TEXT
|
|
|
+} xs_html_type;
|
|
|
+
|
|
|
+struct xs_html {
|
|
|
+ xs_html_type type;
|
|
|
+ xs_str *content;
|
|
|
+ xs_html *f_attr;
|
|
|
+ xs_html *l_attr;
|
|
|
+ xs_html *f_tag;
|
|
|
+ xs_html *l_tag;
|
|
|
+ xs_html *next;
|
|
|
+};
|
|
|
+
|
|
|
+xs_str *xs_html_encode(char *str)
|
|
|
+/* encodes str using HTML entities */
|
|
|
+{
|
|
|
+ xs_str *s = xs_str_new(NULL);
|
|
|
+ int o = 0;
|
|
|
+ char *e = str + strlen(str);
|
|
|
+
|
|
|
+ for (;;) {
|
|
|
+ char *ec = "<>\"'&"; /* characters to escape */
|
|
|
+ char *q = e;
|
|
|
+ int z;
|
|
|
+
|
|
|
+ /* find the nearest happening of a char */
|
|
|
+ while (*ec) {
|
|
|
+ char *m = memchr(str, *ec++, q - str);
|
|
|
+ if (m)
|
|
|
+ q = m;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* copy string to here */
|
|
|
+ z = q - str;
|
|
|
+ s = xs_insert_m(s, o, str, z);
|
|
|
+ o += z;
|
|
|
+
|
|
|
+ /* if q points to the end, nothing more to do */
|
|
|
+ if (q == e)
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* insert the escaped char */
|
|
|
+ char tmp[8];
|
|
|
+ snprintf(tmp, sizeof(tmp), "&#%d;", *q);
|
|
|
+
|
|
|
+ z = strlen(tmp);
|
|
|
+ s = xs_insert_m(s, o, tmp, z);
|
|
|
+ o += z;
|
|
|
+
|
|
|
+ str = q + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return s;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#define XS_HTML_NEW() memset(xs_realloc(NULL, sizeof(xs_html)), '\0', sizeof(xs_html))
|
|
|
+
|
|
|
+xs_html *xs_html_attr(char *key, char *value)
|
|
|
+/* creates an HTML block with an attribute */
|
|
|
+{
|
|
|
+ xs_html *a = XS_HTML_NEW();
|
|
|
+
|
|
|
+ a->type = XS_HTML_ATTR;
|
|
|
+
|
|
|
+ if (value) {
|
|
|
+ xs *ev = xs_html_encode(value);
|
|
|
+ a->content = xs_fmt("%s=\"%s\"", key, ev);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ a->content = xs_dup(key);
|
|
|
+
|
|
|
+ return a;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+xs_html *xs_html_text(char *content)
|
|
|
+/* creates an HTML block of text, escaping it previously */
|
|
|
+{
|
|
|
+ xs_html *a = XS_HTML_NEW();
|
|
|
+
|
|
|
+ a->type = XS_HTML_TEXT;
|
|
|
+ a->content = xs_html_encode(content);
|
|
|
+
|
|
|
+ return a;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+xs_html *xs_html_raw(char *content)
|
|
|
+/* creates an HTML block without escaping (for pre-formatted HTML, comments, etc) */
|
|
|
+{
|
|
|
+ xs_html *a = XS_HTML_NEW();
|
|
|
+
|
|
|
+ a->type = XS_HTML_TEXT;
|
|
|
+ a->content = xs_dup(content);
|
|
|
+
|
|
|
+ return a;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+xs_html *xs_html_add(xs_html *tag, xs_html *data)
|
|
|
+/* add data (attrs, tags or text) to a tag */
|
|
|
+{
|
|
|
+ xs_html **first;
|
|
|
+ xs_html **last;
|
|
|
+
|
|
|
+ if (data->type == XS_HTML_ATTR) {
|
|
|
+ first = &tag->f_attr;
|
|
|
+ last = &tag->l_attr;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ first = &tag->f_tag;
|
|
|
+ last = &tag->l_tag;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (*first == NULL)
|
|
|
+ *first = data;
|
|
|
+
|
|
|
+ if (*last != NULL)
|
|
|
+ (*last)->next = data;
|
|
|
+
|
|
|
+ *last = data;
|
|
|
+
|
|
|
+ return tag;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static xs_html *_xs_html_tag_t(xs_html_type type, char *tag, xs_html *var[])
|
|
|
+/* creates a tag with a variable list of attributes and subtags */
|
|
|
+{
|
|
|
+ xs_html *a = XS_HTML_NEW();
|
|
|
+
|
|
|
+ a->type = type;
|
|
|
+ a->content = xs_dup(tag);
|
|
|
+
|
|
|
+ while (*var)
|
|
|
+ xs_html_add(a, *var++);
|
|
|
+
|
|
|
+ return a;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+xs_html *_xs_html_tag(char *tag, xs_html *var[])
|
|
|
+{
|
|
|
+ return _xs_html_tag_t(XS_HTML_TAG, tag, var);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+xs_html *_xs_html_sctag(char *tag, xs_html *var[])
|
|
|
+{
|
|
|
+ return _xs_html_tag_t(XS_HTML_SCTAG, tag, var);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+xs_str *_xs_html_render(xs_html *h, xs_str *s)
|
|
|
+/* renders the tag and its subtags */
|
|
|
+{
|
|
|
+ xs_html *st;
|
|
|
+
|
|
|
+ switch (h->type) {
|
|
|
+ case XS_HTML_TAG:
|
|
|
+ case XS_HTML_SCTAG:
|
|
|
+ s = xs_str_cat(s, "<", h->content);
|
|
|
+
|
|
|
+ /* render the attributes */
|
|
|
+ st = h->f_attr;
|
|
|
+ while (st) {
|
|
|
+ xs_html *nst = st->next;
|
|
|
+ s = _xs_html_render(st, s);
|
|
|
+ st = nst;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (h->type == XS_HTML_SCTAG) {
|
|
|
+ /* self-closing tags should not have subtags */
|
|
|
+ s = xs_str_cat(s, "/>");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ s = xs_str_cat(s, ">");
|
|
|
+
|
|
|
+ /* render the subtags */
|
|
|
+ st = h->f_tag;
|
|
|
+ while (st) {
|
|
|
+ xs_html *nst = st->next;
|
|
|
+ s = _xs_html_render(st, s);
|
|
|
+ st = nst;
|
|
|
+ }
|
|
|
+
|
|
|
+ s = xs_str_cat(s, "</", h->content, ">");
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+
|
|
|
+ case XS_HTML_ATTR:
|
|
|
+ s = xs_str_cat(s, " ", h->content);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case XS_HTML_TEXT:
|
|
|
+ s = xs_str_cat(s, h->content);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ xs_free(h->content);
|
|
|
+ xs_free(h);
|
|
|
+
|
|
|
+ return s;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#endif /* XS_IMPLEMENTATION */
|
|
|
+
|
|
|
+#endif /* _XS_HTML_H */
|