Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/workdir/UnpackedTarball/md4c/src/md4c-html.c
Line
Count
Source
1
/*
2
 * MD4C: Markdown parser for C
3
 * (https://github.com/mity/md4c)
4
 *
5
 * Copyright (c) 2016-2026 Martin Mitáš
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a
8
 * copy of this software and associated documentation files (the "Software"),
9
 * to deal in the Software without restriction, including without limitation
10
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11
 * and/or sell copies of the Software, and to permit persons to whom the
12
 * Software is furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
 * IN THE SOFTWARE.
24
 */
25
26
#include <stdio.h>
27
#include <string.h>
28
29
#include "md4c-html.h"
30
#include "entity.h"
31
32
33
#ifdef _WIN32
34
    #define snprintf _snprintf
35
#endif
36
37
/* For falling through case labels in switch statements. */
38
#if defined __clang__ && __clang_major__ >= 12
39
0
    #define MD_FALLTHROUGH()        ;__attribute__((fallthrough))
40
#elif defined __GNUC__ && __GNUC__ >= 7
41
    #define MD_FALLTHROUGH()        ;__attribute__((fallthrough))
42
#else
43
    #define MD_FALLTHROUGH()        ((void)0)
44
#endif
45
46
47
48
typedef struct MD_HTML_tag MD_HTML;
49
struct MD_HTML_tag {
50
    void (*process_output)(const MD_CHAR*, MD_SIZE, void*);
51
    void* userdata;
52
    unsigned flags;
53
    int image_nesting_level;
54
    char escape_map[256];
55
};
56
57
0
#define NEED_HTML_ESC_FLAG   0x1
58
0
#define NEED_URL_ESC_FLAG    0x2
59
60
61
/*****************************************
62
 ***  HTML rendering helper functions  ***
63
 *****************************************/
64
65
0
#define ISDIGIT(ch)     ('0' <= (ch) && (ch) <= '9')
66
0
#define ISLOWER(ch)     ('a' <= (ch) && (ch) <= 'z')
67
0
#define ISUPPER(ch)     ('A' <= (ch) && (ch) <= 'Z')
68
0
#define ISALNUM(ch)     (ISLOWER(ch) || ISUPPER(ch) || ISDIGIT(ch))
69
70
71
static inline void
72
render_verbatim(MD_HTML* r, const MD_CHAR* text, MD_SIZE size)
73
0
{
74
0
    r->process_output(text, size, r->userdata);
75
0
}
76
77
/* Keep this as a macro. Most compiler should then be smart enough to replace
78
 * the strlen() call with a compile-time constant if the string is a C literal. */
79
#define RENDER_VERBATIM(r, verbatim)                                    \
80
0
        render_verbatim((r), (verbatim), (MD_SIZE) (strlen(verbatim)))
81
82
83
static void
84
render_html_escaped(MD_HTML* r, const MD_CHAR* data, MD_SIZE size)
85
0
{
86
0
    MD_OFFSET beg = 0;
87
0
    MD_OFFSET off = 0;
88
89
    /* Some characters need to be escaped in normal HTML text. */
90
0
    #define NEED_HTML_ESC(ch)   (r->escape_map[(unsigned char)(ch)] & NEED_HTML_ESC_FLAG)
91
92
0
    while(1) {
93
        /* Optimization: Use some loop unrolling. */
94
0
        while(off + 3 < size  &&  !NEED_HTML_ESC(data[off+0])  &&  !NEED_HTML_ESC(data[off+1])
95
0
                              &&  !NEED_HTML_ESC(data[off+2])  &&  !NEED_HTML_ESC(data[off+3]))
96
0
            off += 4;
97
0
        while(off < size  &&  !NEED_HTML_ESC(data[off]))
98
0
            off++;
99
100
0
        if(off > beg)
101
0
            render_verbatim(r, data + beg, off - beg);
102
103
0
        if(off < size) {
104
0
            switch(data[off]) {
105
0
                case '"':   RENDER_VERBATIM(r, "&quot;"); break;
106
0
                case '&':   RENDER_VERBATIM(r, "&amp;"); break;
107
0
                case '\'':  RENDER_VERBATIM(r, "&#x27;"); break;
108
0
                case '<':   RENDER_VERBATIM(r, "&lt;"); break;
109
0
                case '>':   RENDER_VERBATIM(r, "&gt;"); break;
110
0
            }
111
0
            off++;
112
0
        } else {
113
0
            break;
114
0
        }
115
0
        beg = off;
116
0
    }
117
0
}
118
119
static void
120
render_url_escaped(MD_HTML* r, const MD_CHAR* data, MD_SIZE size)
121
0
{
122
0
    static const MD_CHAR hex_chars[] = "0123456789ABCDEF";
123
0
    MD_OFFSET beg = 0;
124
0
    MD_OFFSET off = 0;
125
126
    /* Some characters need to be escaped in URL attributes. */
127
0
    #define NEED_URL_ESC(ch)    (r->escape_map[(unsigned char)(ch)] & NEED_URL_ESC_FLAG)
128
129
0
    while(1) {
130
0
        while(off < size  &&  !NEED_URL_ESC(data[off]))
131
0
            off++;
132
0
        if(off > beg)
133
0
            render_verbatim(r, data + beg, off - beg);
134
135
0
        if(off < size) {
136
0
            char hex[3];
137
138
0
            switch(data[off]) {
139
0
                case '&':   RENDER_VERBATIM(r, "&amp;"); break;
140
0
                default:
141
0
                    hex[0] = '%';
142
0
                    hex[1] = hex_chars[((unsigned)data[off] >> 4) & 0xf];
143
0
                    hex[2] = hex_chars[((unsigned)data[off] >> 0) & 0xf];
144
0
                    render_verbatim(r, hex, 3);
145
0
                    break;
146
0
            }
147
0
            off++;
148
0
        } else {
149
0
            break;
150
0
        }
151
152
0
        beg = off;
153
0
    }
154
0
}
155
156
static unsigned
157
hex_val(char ch)
158
0
{
159
0
    if('0' <= ch && ch <= '9')
160
0
        return ch - '0';
161
0
    if('A' <= ch && ch <= 'Z')
162
0
        return ch - 'A' + 10;
163
0
    else
164
0
        return ch - 'a' + 10;
165
0
}
166
167
static void
168
render_utf8_codepoint(MD_HTML* r, unsigned codepoint,
169
                      void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
170
0
{
171
0
    static const MD_CHAR utf8_replacement_char[] = { (char)0xef, (char)0xbf, (char)0xbd };
172
173
0
    unsigned char utf8[4];
174
0
    size_t n;
175
176
0
    if(codepoint <= 0x7f) {
177
0
        n = 1;
178
0
        utf8[0] = codepoint;
179
0
    } else if(codepoint <= 0x7ff) {
180
0
        n = 2;
181
0
        utf8[0] = 0xc0 | ((codepoint >>  6) & 0x1f);
182
0
        utf8[1] = 0x80 + ((codepoint >>  0) & 0x3f);
183
0
    } else if(codepoint <= 0xffff) {
184
0
        n = 3;
185
0
        utf8[0] = 0xe0 | ((codepoint >> 12) & 0xf);
186
0
        utf8[1] = 0x80 + ((codepoint >>  6) & 0x3f);
187
0
        utf8[2] = 0x80 + ((codepoint >>  0) & 0x3f);
188
0
    } else {
189
0
        n = 4;
190
0
        utf8[0] = 0xf0 | ((codepoint >> 18) & 0x7);
191
0
        utf8[1] = 0x80 + ((codepoint >> 12) & 0x3f);
192
0
        utf8[2] = 0x80 + ((codepoint >>  6) & 0x3f);
193
0
        utf8[3] = 0x80 + ((codepoint >>  0) & 0x3f);
194
0
    }
195
196
0
    if(0 < codepoint  &&  codepoint <= 0x10ffff
197
0
            &&  (codepoint < 0xd800 || codepoint > 0xdfff))
198
0
        fn_append(r, (char*)utf8, (MD_SIZE)n);
199
0
    else
200
0
        fn_append(r, utf8_replacement_char, 3);
201
0
}
202
203
/* Translate entity to its UTF-8 equivalent, or output the verbatim one
204
 * if such entity is unknown (or if the translation is disabled). */
205
static void
206
render_entity(MD_HTML* r, const MD_CHAR* text, MD_SIZE size,
207
              void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
208
0
{
209
0
    if(r->flags & MD_HTML_FLAG_VERBATIM_ENTITIES) {
210
0
        render_verbatim(r, text, size);
211
0
        return;
212
0
    }
213
214
    /* We assume UTF-8 output is what is desired. */
215
0
    if(size > 3 && text[1] == '#') {
216
0
        unsigned codepoint = 0;
217
218
0
        if(text[2] == 'x' || text[2] == 'X') {
219
            /* Hexadecimal entity (e.g. "&#x1234abcd;")). */
220
0
            MD_SIZE i;
221
0
            for(i = 3; i < size-1; i++)
222
0
                codepoint = 16 * codepoint + hex_val(text[i]);
223
0
        } else {
224
            /* Decimal entity (e.g. "&1234;") */
225
0
            MD_SIZE i;
226
0
            for(i = 2; i < size-1; i++)
227
0
                codepoint = 10 * codepoint + (text[i] - '0');
228
0
        }
229
230
0
        render_utf8_codepoint(r, codepoint, fn_append);
231
0
        return;
232
0
    } else {
233
        /* Named entity (e.g. "&nbsp;"). */
234
0
        const ENTITY* ent;
235
236
0
        ent = entity_lookup(text, size);
237
0
        if(ent != NULL) {
238
0
            render_utf8_codepoint(r, ent->codepoints[0], fn_append);
239
0
            if(ent->codepoints[1])
240
0
                render_utf8_codepoint(r, ent->codepoints[1], fn_append);
241
0
            return;
242
0
        }
243
0
    }
244
245
0
    fn_append(r, text, size);
246
0
}
247
248
static void
249
render_attribute(MD_HTML* r, const MD_ATTRIBUTE* attr,
250
                 void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
251
0
{
252
0
    int i;
253
254
0
    for(i = 0; attr->substr_offsets[i] < attr->size; i++) {
255
0
        MD_TEXTTYPE type = attr->substr_types[i];
256
0
        MD_OFFSET off = attr->substr_offsets[i];
257
0
        MD_SIZE size = attr->substr_offsets[i+1] - off;
258
0
        const MD_CHAR* text = attr->text + off;
259
260
0
        switch(type) {
261
0
            case MD_TEXT_NULLCHAR:  render_utf8_codepoint(r, 0x0000, render_verbatim); break;
262
0
            case MD_TEXT_ENTITY:    render_entity(r, text, size, fn_append); break;
263
0
            default:                fn_append(r, text, size); break;
264
0
        }
265
0
    }
266
0
}
267
268
269
static void
270
render_open_ol_block(MD_HTML* r, const MD_BLOCK_OL_DETAIL* det)
271
0
{
272
0
    char buf[64];
273
274
0
    if(det->start == 1) {
275
0
        RENDER_VERBATIM(r, "<ol>\n");
276
0
        return;
277
0
    }
278
279
0
    snprintf(buf, sizeof(buf), "<ol start=\"%u\">\n", det->start);
280
0
    RENDER_VERBATIM(r, buf);
281
0
}
282
283
static void
284
render_open_li_block(MD_HTML* r, const MD_BLOCK_LI_DETAIL* det)
285
0
{
286
0
    if(det->is_task) {
287
0
        RENDER_VERBATIM(r, "<li class=\"task-list-item\">"
288
0
                          "<input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled");
289
0
        if(r->flags & MD_HTML_FLAG_XHTML) RENDER_VERBATIM(r, "=\"true\"");
290
0
        if(det->task_mark == 'x' || det->task_mark == 'X') {
291
0
            RENDER_VERBATIM(r, " checked");
292
0
            if(r->flags & MD_HTML_FLAG_XHTML) RENDER_VERBATIM(r, "=\"true\"");
293
0
        }
294
0
        RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? " />" : ">");
295
0
    } else {
296
0
        RENDER_VERBATIM(r, "<li>");
297
0
    }
298
0
}
299
300
static void
301
render_open_code_block(MD_HTML* r, const MD_BLOCK_CODE_DETAIL* det)
302
0
{
303
0
    RENDER_VERBATIM(r, "<pre><code");
304
305
    /* If known, output the HTML 5 attribute class="language-LANGNAME". */
306
0
    if(det->lang.text != NULL) {
307
0
        RENDER_VERBATIM(r, " class=\"");
308
0
        if(det->lang.size < 9  ||  strncmp(det->lang.text, "language-", 9) != 0) {
309
0
            RENDER_VERBATIM(r, "language-");
310
0
        }
311
0
        render_attribute(r, &det->lang, render_html_escaped);
312
0
        RENDER_VERBATIM(r, "\"");
313
0
    }
314
315
0
    RENDER_VERBATIM(r, ">");
316
0
}
317
318
static void
319
render_open_td_block(MD_HTML* r, const MD_CHAR* cell_type, const MD_BLOCK_TD_DETAIL* det)
320
0
{
321
0
    RENDER_VERBATIM(r, "<");
322
0
    RENDER_VERBATIM(r, cell_type);
323
324
0
    switch(det->align) {
325
0
        case MD_ALIGN_LEFT:     RENDER_VERBATIM(r, " align=\"left\">"); break;
326
0
        case MD_ALIGN_CENTER:   RENDER_VERBATIM(r, " align=\"center\">"); break;
327
0
        case MD_ALIGN_RIGHT:    RENDER_VERBATIM(r, " align=\"right\">"); break;
328
0
        default:                RENDER_VERBATIM(r, ">"); break;
329
0
    }
330
0
}
331
332
static void
333
render_open_admonition_block(MD_HTML* r, const MD_BLOCK_ADMONITION_DETAIL* det)
334
0
{
335
0
    RENDER_VERBATIM(r, "<div class=\"admonition-");
336
0
    render_attribute(r, &det->type, render_html_escaped);
337
0
    RENDER_VERBATIM(r, "\">");
338
0
    RENDER_VERBATIM(r, "<p class=\"admonition-title\">");
339
0
    render_attribute(r, &det->type, render_html_escaped);
340
0
    RENDER_VERBATIM(r, "</p>");
341
0
}
342
343
static void
344
render_open_a_span(MD_HTML* r, const MD_SPAN_A_DETAIL* det)
345
0
{
346
0
    RENDER_VERBATIM(r, "<a href=\"");
347
0
    render_attribute(r, &det->href, render_url_escaped);
348
349
0
    if(det->title.text != NULL) {
350
0
        RENDER_VERBATIM(r, "\" title=\"");
351
0
        render_attribute(r, &det->title, render_html_escaped);
352
0
    }
353
354
0
    RENDER_VERBATIM(r, "\">");
355
0
}
356
357
static void
358
render_open_img_span(MD_HTML* r, const MD_SPAN_IMG_DETAIL* det)
359
0
{
360
0
    RENDER_VERBATIM(r, "<img src=\"");
361
0
    render_attribute(r, &det->src, render_url_escaped);
362
363
0
    RENDER_VERBATIM(r, "\" alt=\"");
364
0
}
365
366
static void
367
render_close_img_span(MD_HTML* r, const MD_SPAN_IMG_DETAIL* det)
368
0
{
369
0
    if(det->title.text != NULL) {
370
0
        RENDER_VERBATIM(r, "\" title=\"");
371
0
        render_attribute(r, &det->title, render_html_escaped);
372
0
    }
373
374
0
    RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\" />" : "\">");
375
0
}
376
377
static void
378
render_open_wikilink_span(MD_HTML* r, const MD_SPAN_WIKILINK_DETAIL* det)
379
0
{
380
0
    RENDER_VERBATIM(r, "<x-wikilink data-target=\"");
381
0
    render_attribute(r, &det->target, render_html_escaped);
382
383
0
    RENDER_VERBATIM(r, "\">");
384
0
}
385
386
387
/**************************************
388
 ***  HTML renderer implementation  ***
389
 **************************************/
390
391
static void
392
render_open_footnote_ref_span(MD_HTML* r, const MD_SPAN_FOOTNOTE_REF_DETAIL* det)
393
0
{
394
0
    char buf[128];
395
396
0
    snprintf(buf, sizeof(buf), "<sup><a href=\"#fn-%u\" id=\"fnref-%u-%u\">%u</a></sup>",
397
0
             det->id, det->id, det->ref_id, det->id);
398
0
    render_verbatim(r, buf, (MD_SIZE) strlen(buf));
399
0
}
400
401
static void
402
render_open_footnote_def_block(MD_HTML* r, const MD_BLOCK_FOOTNOTE_DEF_DETAIL* det)
403
0
{
404
0
    char buf[64];
405
406
0
    snprintf(buf, sizeof(buf), "<li id=\"fn-%u\">\n", det->id);
407
0
    render_verbatim(r, buf, (MD_SIZE) strlen(buf));
408
0
}
409
410
static void
411
render_close_footnote_def_block(MD_HTML* r, const MD_BLOCK_FOOTNOTE_DEF_DETAIL* det)
412
0
{
413
0
    char buf[128];
414
0
    unsigned int ref_index;
415
416
0
    for(ref_index = 1; ref_index <= det->ref_count; ref_index++) {
417
0
        if(ref_index > 1)
418
0
            RENDER_VERBATIM(r, " ");
419
0
        snprintf(buf, sizeof(buf), "<a href=\"#fnref-%u-%u\" class=\"footnote-backref\">&#8617;</a>",
420
0
                 det->id, ref_index);
421
0
        render_verbatim(r, buf, (MD_SIZE) strlen(buf));
422
0
    }
423
424
0
    RENDER_VERBATIM(r, "\n</li>\n");
425
0
}
426
427
static void
428
render_blank_block(MD_HTML* r, const MD_BLOCK_BLANK_DETAIL* det)
429
0
{
430
0
    unsigned i;
431
    /* The first blank line is the ordinary block separation CommonMark already
432
     * implies; render only the surplus (line_count - 1) as <br>. */
433
0
    for(i = 1; i < det->line_count; i++)
434
0
        RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<br/>\n" : "<br>\n");
435
0
}
436
437
static int
438
enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
439
0
{
440
0
    static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
441
0
    MD_HTML* r = (MD_HTML*) userdata;
442
443
0
    switch(type) {
444
0
        case MD_BLOCK_DOC:      /* noop */ break;
445
0
        case MD_BLOCK_QUOTE:    RENDER_VERBATIM(r, "<blockquote>\n"); break;
446
0
        case MD_BLOCK_UL:       RENDER_VERBATIM(r, "<ul>\n"); break;
447
0
        case MD_BLOCK_OL:       render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
448
0
        case MD_BLOCK_LI:       render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
449
0
        case MD_BLOCK_HR:       RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr />\n" : "<hr>\n"); break;
450
0
        case MD_BLOCK_H:        RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
451
0
        case MD_BLOCK_CODE:     render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
452
0
        case MD_BLOCK_HTML:     /* noop */ break;
453
0
        case MD_BLOCK_P:        RENDER_VERBATIM(r, "<p>"); break;
454
0
        case MD_BLOCK_TABLE:    RENDER_VERBATIM(r, "<table>\n"); break;
455
0
        case MD_BLOCK_THEAD:    RENDER_VERBATIM(r, "<thead>\n"); break;
456
0
        case MD_BLOCK_TBODY:    RENDER_VERBATIM(r, "<tbody>\n"); break;
457
0
        case MD_BLOCK_TR:       RENDER_VERBATIM(r, "<tr>\n"); break;
458
0
        case MD_BLOCK_TH:       render_open_td_block(r, "th", (MD_BLOCK_TD_DETAIL*)detail); break;
459
0
        case MD_BLOCK_TD:       render_open_td_block(r, "td", (MD_BLOCK_TD_DETAIL*)detail); break;
460
0
        case MD_BLOCK_FOOTNOTE_DEF_SECTION: RENDER_VERBATIM(r, "<section class=\"footnotes\">\n<ol>\n"); break;
461
0
        case MD_BLOCK_FOOTNOTE_DEF: render_open_footnote_def_block(r, (MD_BLOCK_FOOTNOTE_DEF_DETAIL*)detail); break;
462
0
        case MD_BLOCK_ADMONITION:   render_open_admonition_block(r, (const MD_BLOCK_ADMONITION_DETAIL*) detail); break;
463
0
        case MD_BLOCK_BLANK:    render_blank_block(r, (const MD_BLOCK_BLANK_DETAIL*) detail); break;
464
0
    }
465
466
0
    return 0;
467
0
}
468
469
static int
470
leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
471
0
{
472
0
    static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
473
0
    MD_HTML* r = (MD_HTML*) userdata;
474
475
0
    switch(type) {
476
0
        case MD_BLOCK_DOC:      /* noop */ break;
477
0
        case MD_BLOCK_QUOTE:    RENDER_VERBATIM(r, "</blockquote>\n"); break;
478
0
        case MD_BLOCK_UL:       RENDER_VERBATIM(r, "</ul>\n"); break;
479
0
        case MD_BLOCK_OL:       RENDER_VERBATIM(r, "</ol>\n"); break;
480
0
        case MD_BLOCK_LI:       RENDER_VERBATIM(r, "</li>\n"); break;
481
0
        case MD_BLOCK_HR:       /*noop*/ break;
482
0
        case MD_BLOCK_H:        RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
483
0
        case MD_BLOCK_CODE:     RENDER_VERBATIM(r, "</code></pre>\n"); break;
484
0
        case MD_BLOCK_HTML:     /* noop */ break;
485
0
        case MD_BLOCK_P:        RENDER_VERBATIM(r, "</p>\n"); break;
486
0
        case MD_BLOCK_TABLE:    RENDER_VERBATIM(r, "</table>\n"); break;
487
0
        case MD_BLOCK_THEAD:    RENDER_VERBATIM(r, "</thead>\n"); break;
488
0
        case MD_BLOCK_TBODY:    RENDER_VERBATIM(r, "</tbody>\n"); break;
489
0
        case MD_BLOCK_TR:       RENDER_VERBATIM(r, "</tr>\n"); break;
490
0
        case MD_BLOCK_TH:       RENDER_VERBATIM(r, "</th>\n"); break;
491
0
        case MD_BLOCK_TD:       RENDER_VERBATIM(r, "</td>\n"); break;
492
0
        case MD_BLOCK_FOOTNOTE_DEF_SECTION: RENDER_VERBATIM(r, "</ol>\n</section>\n"); break;
493
0
        case MD_BLOCK_FOOTNOTE_DEF: render_close_footnote_def_block(r, (MD_BLOCK_FOOTNOTE_DEF_DETAIL*)detail); break;
494
0
        case MD_BLOCK_ADMONITION:   RENDER_VERBATIM(r, "</div>\n"); break;
495
0
        case MD_BLOCK_BLANK:    /* noop (fully emitted on enter) */ break;
496
0
    }
497
498
0
    return 0;
499
0
}
500
501
static int
502
enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
503
0
{
504
0
    MD_HTML* r = (MD_HTML*) userdata;
505
0
    int inside_img = (r->image_nesting_level > 0);
506
507
    /* We are inside a Markdown image label. Markdown allows to use any emphasis
508
     * and other rich contents in that context similarly as in any link label.
509
     *
510
     * However, unlike in the case of links (where that contents becomescontents
511
     * of the <a>...</a> tag), in the case of images the contents is supposed to
512
     * fall into the attribute alt: <img alt="...">.
513
     *
514
     * In that context we naturally cannot output nested HTML tags. So lets
515
     * suppress them and only output the plain text (i.e. what falls into text()
516
     * callback).
517
     *
518
     * CommonMark specification declares this a recommended practice for HTML
519
     * output.
520
     */
521
0
    if(type == MD_SPAN_IMG)
522
0
        r->image_nesting_level++;
523
0
    if(inside_img)
524
0
        return 0;
525
526
0
    switch(type) {
527
0
        case MD_SPAN_EM:                RENDER_VERBATIM(r, "<em>"); break;
528
0
        case MD_SPAN_STRONG:            RENDER_VERBATIM(r, "<strong>"); break;
529
0
        case MD_SPAN_U:                 RENDER_VERBATIM(r, "<u>"); break;
530
0
        case MD_SPAN_A:                 render_open_a_span(r, (MD_SPAN_A_DETAIL*) detail); break;
531
0
        case MD_SPAN_IMG:               render_open_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
532
0
        case MD_SPAN_CODE:              RENDER_VERBATIM(r, "<code>"); break;
533
0
        case MD_SPAN_INS:               RENDER_VERBATIM(r, "<ins>"); break;
534
0
        case MD_SPAN_DEL:               RENDER_VERBATIM(r, "<del>"); break;
535
0
        case MD_SPAN_SPOILER:           RENDER_VERBATIM(r, "<x-spoiler>"); break;
536
0
        case MD_SPAN_SUPERSCRIPT:       RENDER_VERBATIM(r, "<sup>"); break;
537
0
        case MD_SPAN_SUBSCRIPT:         RENDER_VERBATIM(r, "<sub>"); break;
538
0
        case MD_SPAN_MARK:              RENDER_VERBATIM(r, "<mark>"); break;
539
0
        case MD_SPAN_LATEXMATH:         RENDER_VERBATIM(r, "<x-equation>"); break;
540
0
        case MD_SPAN_LATEXMATH_DISPLAY: RENDER_VERBATIM(r, "<x-equation type=\"display\">"); break;
541
0
        case MD_SPAN_WIKILINK:          render_open_wikilink_span(r, (MD_SPAN_WIKILINK_DETAIL*) detail); break;
542
0
        case MD_SPAN_FOOTNOTE_REF:      render_open_footnote_ref_span(r, (MD_SPAN_FOOTNOTE_REF_DETAIL*) detail); break;
543
0
    }
544
545
0
    return 0;
546
0
}
547
548
static int
549
leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
550
0
{
551
0
    MD_HTML* r = (MD_HTML*) userdata;
552
553
0
    if(type == MD_SPAN_IMG)
554
0
        r->image_nesting_level--;
555
0
    if(r->image_nesting_level > 0)
556
0
        return 0;
557
558
0
    switch(type) {
559
0
        case MD_SPAN_EM:                RENDER_VERBATIM(r, "</em>"); break;
560
0
        case MD_SPAN_STRONG:            RENDER_VERBATIM(r, "</strong>"); break;
561
0
        case MD_SPAN_U:                 RENDER_VERBATIM(r, "</u>"); break;
562
0
        case MD_SPAN_A:                 RENDER_VERBATIM(r, "</a>"); break;
563
0
        case MD_SPAN_IMG:               render_close_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
564
0
        case MD_SPAN_CODE:              RENDER_VERBATIM(r, "</code>"); break;
565
0
        case MD_SPAN_INS:               RENDER_VERBATIM(r, "</ins>"); break;
566
0
        case MD_SPAN_DEL:               RENDER_VERBATIM(r, "</del>"); break;
567
0
        case MD_SPAN_SPOILER:           RENDER_VERBATIM(r, "</x-spoiler>"); break;
568
0
        case MD_SPAN_SUPERSCRIPT:       RENDER_VERBATIM(r, "</sup>"); break;
569
0
        case MD_SPAN_SUBSCRIPT:         RENDER_VERBATIM(r, "</sub>"); break;
570
0
        case MD_SPAN_MARK:              RENDER_VERBATIM(r, "</mark>"); break;
571
0
        case MD_SPAN_LATEXMATH:         MD_FALLTHROUGH();
572
0
        case MD_SPAN_LATEXMATH_DISPLAY: RENDER_VERBATIM(r, "</x-equation>"); break;
573
0
        case MD_SPAN_WIKILINK:          RENDER_VERBATIM(r, "</x-wikilink>"); break;
574
0
        case MD_SPAN_FOOTNOTE_REF:      /* noop: enter_span already emitted full HTML */ break;
575
0
    }
576
577
0
    return 0;
578
0
}
579
580
static int
581
text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
582
0
{
583
0
    MD_HTML* r = (MD_HTML*) userdata;
584
585
0
    switch(type) {
586
0
        case MD_TEXT_NULLCHAR:  render_utf8_codepoint(r, 0x0000, render_verbatim); break;
587
0
        case MD_TEXT_BR:        RENDER_VERBATIM(r, (r->image_nesting_level == 0
588
0
                                        ? ((r->flags & MD_HTML_FLAG_XHTML) ? "<br />\n" : "<br>\n")
589
0
                                        : " "));
590
0
                                break;
591
0
        case MD_TEXT_SOFTBR:    RENDER_VERBATIM(r, (r->image_nesting_level == 0 ? "\n" : " ")); break;
592
0
        case MD_TEXT_HTML:      /* When inside a Markdown image label, the text falls into
593
                                 * the alt="..." attribute opened by render_open_img_span().
594
                                 * Raw HTML must be escaped there, exactly like normal text,
595
                                 * otherwise it breaks out of the attribute. Compare the
596
                                 * image_nesting_level handling in enter_span_callback(). */
597
0
                                if(r->image_nesting_level == 0)
598
0
                                    render_verbatim(r, text, size);
599
0
                                else
600
0
                                    render_html_escaped(r, text, size);
601
0
                                break;
602
0
        case MD_TEXT_ENTITY:    render_entity(r, text, size, render_html_escaped); break;
603
0
        default:                render_html_escaped(r, text, size); break;
604
0
    }
605
606
0
    return 0;
607
0
}
608
609
static void
610
debug_log_callback(const char* msg, void* userdata)
611
0
{
612
0
    MD_HTML* r = (MD_HTML*) userdata;
613
0
    if(r->flags & MD_HTML_FLAG_DEBUG)
614
0
        fprintf(stderr, "MD4C: %s\n", msg);
615
0
}
616
617
int
618
md_html(const MD_CHAR* input, MD_SIZE input_size,
619
        void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
620
        void* userdata, unsigned parser_flags, unsigned renderer_flags)
621
0
{
622
0
    MD_HTML render = { process_output, userdata, renderer_flags, 0, { 0 } };
623
0
    int i;
624
625
0
    MD_PARSER parser = {
626
0
        0,
627
0
        parser_flags,
628
0
        enter_block_callback,
629
0
        leave_block_callback,
630
0
        enter_span_callback,
631
0
        leave_span_callback,
632
0
        text_callback,
633
0
        debug_log_callback,
634
0
        NULL
635
0
    };
636
637
    /* Build map of characters which need escaping. */
638
0
    for(i = 0; i < 256; i++) {
639
0
        unsigned char ch = (unsigned char) i;
640
641
0
        if(strchr("\"&'<>", ch) != NULL)
642
0
            render.escape_map[i] |= NEED_HTML_ESC_FLAG;
643
644
0
        if(!ISALNUM(ch)  &&  strchr("~-_.+!*(),%#@?=;:/$", ch) == NULL)
645
0
            render.escape_map[i] |= NEED_URL_ESC_FLAG;
646
0
    }
647
648
    /* Consider skipping UTF-8 byte order mark (BOM). */
649
0
    if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM  &&  sizeof(MD_CHAR) == 1) {
650
0
        static const MD_CHAR bom[3] = { (char)0xef, (char)0xbb, (char)0xbf };
651
0
        if(input_size >= sizeof(bom)  &&  memcmp(input, bom, sizeof(bom)) == 0) {
652
0
            input += sizeof(bom);
653
0
            input_size -= sizeof(bom);
654
0
        }
655
0
    }
656
657
0
    return md_parse(input, input_size, &parser, (void*) &render);
658
0
}