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 */