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