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