2.74
xs_html.h
1/* copyright (c) 2022 - 2025 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
27void xs_html_render_f(xs_html *h, FILE *f);
28xs_str *xs_html_render_s(xs_html *tag, const 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(const char *str)
51/* encodes str using HTML entities */
52{
53 xs_str *s = xs_str_new(NULL);
54 int o = 0;
55 const char *e = str + strlen(str);
56
57 for (;;) {
58 char *ec = "<>\"'&"; /* characters to escape */
59 const 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(const char *key, const 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(const 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_is_string(content) ? xs_html_encode(content) : xs_str_new(NULL);
118
119 return a;
120}
121
122
123xs_html *xs_html_raw(const 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_is_string(content) ? xs_dup(content) : xs_str_new(NULL);
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, const 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(const 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(const 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, const 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 */