snac2

Fork of https://codeberg.org/grunfink/snac2
git clone https://git.inz.fi/snac2
Log | Files | Refs | README | LICENSE

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