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