main xs_html.h
  1/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */
  2
  3#ifndef _XS_HTML_H
  4
  5#define _XS_HTML_H
  6
  7#include "xs.h"
  8
  9typedef struct xs_html xs_html;
 10
 11xs_str *xs_html_encode(const char *str);
 12
 13xs_html *xs_html_attr(const char *key, const char *value);
 14xs_html *xs_html_text(const char *content);
 15xs_html *xs_html_raw(const char *content);
 16
 17xs_html *_xs_html_add(xs_html *tag, xs_html *var[]);
 18#define xs_html_add(tag, ...) _xs_html_add(tag, (xs_html *[]) { __VA_ARGS__, NULL })
 19
 20xs_html *_xs_html_tag(const char *tag, xs_html *var[]);
 21#define xs_html_tag(tag, ...) _xs_html_tag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
 22
 23xs_html *_xs_html_sctag(const char *tag, xs_html *var[]);
 24#define xs_html_sctag(tag, ...) _xs_html_sctag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
 25
 26xs_html *_xs_html_container(xs_html *var[]);
 27#define xs_html_container(...) _xs_html_container((xs_html *[]) { __VA_ARGS__, NULL })
 28
 29xs_html *_xs_html_xtag(const char *tag, xs_html *var[]);
 30#define xs_html_xtag(tag, ...) _xs_html_xtag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
 31
 32void xs_html_render_f(xs_html *h, FILE *f);
 33xs_str *xs_html_render_s(xs_html *tag, const char *prefix);
 34#define xs_html_render(tag) xs_html_render_s(tag, NULL)
 35
 36
 37#ifdef XS_IMPLEMENTATION
 38
 39typedef enum {
 40    XS_HTML_TAG,
 41    XS_HTML_SCTAG,
 42    XS_HTML_CONTAINER,
 43    XS_HTML_ATTR,
 44    XS_HTML_TEXT
 45} xs_html_type;
 46
 47struct xs_html {
 48    xs_html_type type;
 49    xs_str *content;
 50    xs_html *attrs;
 51    xs_html *tags;
 52    xs_html *next;
 53};
 54
 55xs_str *xs_html_encode(const char *str)
 56/* encodes str using HTML entities */
 57{
 58    xs_str *s = xs_str_new(NULL);
 59    int o = 0;
 60    const char *e = str + strlen(str);
 61
 62    for (;;) {
 63        char *ec = "<>\"'&";   /* characters to escape */
 64        const char *q = e;
 65        int z;
 66
 67        /* find the nearest happening of a char */
 68        while (*ec) {
 69            char *m = memchr(str, *ec++, q - str);
 70            if (m)
 71                q = m;
 72        }
 73
 74        /* copy string to here */
 75        z = q - str;
 76        s = xs_insert_m(s, o, str, z);
 77        o += z;
 78
 79        /* if q points to the end, nothing more to do */
 80        if (q == e)
 81            break;
 82
 83        /* insert the escaped char */
 84        char tmp[8];
 85        z = snprintf(tmp, sizeof(tmp), "&#%d;", *q);
 86        s = xs_insert_m(s, o, tmp, z);
 87        o += z;
 88
 89        str = q + 1;
 90    }
 91
 92    return s;
 93}
 94
 95
 96#define XS_HTML_NEW() memset(xs_realloc(NULL, sizeof(xs_html)), '\0', sizeof(xs_html))
 97
 98xs_html *xs_html_attr(const char *key, const char *value)
 99/* creates an HTML block with an attribute */
100{
101    xs_html *a = XS_HTML_NEW();
102
103    a->type = XS_HTML_ATTR;
104
105    if (value) {
106        xs *ev = xs_html_encode(value);
107        a->content = xs_fmt("%s=\"%s\"", key, ev);
108    }
109    else
110        a->content = xs_dup(key);
111
112    return a;
113}
114
115
116xs_html *xs_html_text(const char *content)
117/* creates an HTML block of text, escaping it previously */
118{
119    xs_html *a = XS_HTML_NEW();
120
121    a->type    = XS_HTML_TEXT;
122    a->content = xs_is_string(content) ? xs_html_encode(content) : xs_str_new(NULL);
123
124    return a;
125}
126
127
128xs_html *xs_html_raw(const char *content)
129/* creates an HTML block without escaping (for pre-formatted HTML, comments, etc) */
130{
131    xs_html *a = XS_HTML_NEW();
132
133    a->type    = XS_HTML_TEXT;
134    a->content = xs_is_string(content) ? xs_dup(content) : xs_str_new(NULL);
135
136    return a;
137}
138
139
140xs_html *_xs_html_add(xs_html *tag, xs_html *var[])
141/* add data (attrs, tags or text) to a tag */
142{
143    while (*var) {
144        xs_html *data = *var++;
145
146        if (data->type == XS_HTML_ATTR) {
147            data->next = tag->attrs;
148            tag->attrs = data;
149        }
150        else {
151            data->next = tag->tags;
152            tag->tags = data;
153        }
154    }
155
156    return tag;
157}
158
159
160static xs_html *_xs_html_tag_t(xs_html_type type, const char *tag, xs_html *var[])
161/* creates a tag with a variable list of attributes and subtags */
162{
163    xs_html *a = XS_HTML_NEW();
164
165    a->type = type;
166
167    /* a tag can be NULL, to be a kind of 'wrapper' */
168    if (tag)
169        a->content = xs_dup(tag);
170
171    _xs_html_add(a, var);
172
173    return a;
174}
175
176
177xs_html *_xs_html_tag(const char *tag, xs_html *var[])
178{
179    return _xs_html_tag_t(XS_HTML_TAG, tag, var);
180}
181
182
183xs_html *_xs_html_sctag(const char *tag, xs_html *var[])
184{
185    return _xs_html_tag_t(XS_HTML_SCTAG, tag, var);
186}
187
188
189xs_html *_xs_html_container(xs_html *var[])
190{
191    return _xs_html_tag_t(XS_HTML_CONTAINER, NULL, var);
192}
193
194
195xs_html *_xs_html_xtag(const char *tag, xs_html *var[])
196{
197    if (xs_is_null(tag))
198        return _xs_html_container(var);
199    else
200    if (xs_endswith(tag, "/")) {
201        xs *tag2 = xs_crop_i(xs_dup(tag), 0, -1);
202        return _xs_html_sctag(tag2, var);
203    }
204
205    return _xs_html_tag(tag, var);
206}
207
208
209void xs_html_render_f(xs_html *h, FILE *f)
210/* renders the tag and its subtags into a file */
211{
212    if (h == NULL)
213        return;
214
215    /* follow the chain */
216    xs_html_render_f(h->next, f);
217
218    switch (h->type) {
219    case XS_HTML_TAG:
220        fprintf(f, "<%s", h->content);
221
222        /* attributes */
223        xs_html_render_f(h->attrs, f);
224
225        fprintf(f, ">");
226
227        /* sub-tags */
228        xs_html_render_f(h->tags, f);
229
230        fprintf(f, "</%s>", h->content);
231        break;
232
233    case XS_HTML_SCTAG:
234        fprintf(f, "<%s", h->content);
235
236        /* attributes */
237        xs_html_render_f(h->attrs, f);
238
239        fprintf(f, "/>");
240        break;
241
242    case XS_HTML_CONTAINER:
243        /* sub-tags */
244        xs_html_render_f(h->tags, f);
245        break;
246
247    case XS_HTML_ATTR:
248        fprintf(f, " ");
249        /* fallthrough */
250
251    case XS_HTML_TEXT:
252        fprintf(f, "%s", h->content);
253        break;
254    }
255
256    xs_free(h->content);
257    xs_free(h);
258}
259
260
261xs_str *xs_html_render_s(xs_html *tag, const char *prefix)
262/* renders to a string */
263{
264    xs_str *s = NULL;
265    size_t sz;
266    FILE *f;
267
268    if ((f = open_memstream(&s, &sz)) != NULL) {
269        if (prefix)
270            fprintf(f, "%s", prefix);
271
272        xs_html_render_f(tag, f);
273        fclose(f);
274    }
275
276    return s;
277}
278
279
280#endif /* XS_IMPLEMENTATION */
281
282#endif /* _XS_HTML_H */