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