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.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 <limits.h>
27
#include <stdbool.h>
28
#include <stdint.h>
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <stddef.h>
32
#include <string.h>
33
34
#include "md4c.h"
35
36
37
/*****************************
38
 ***  Miscellaneous Stuff  ***
39
 *****************************/
40
41
/* Make the UTF-8 support the default. */
42
#if !defined MD4C_USE_ASCII && !defined MD4C_USE_UTF8 && !defined MD4C_USE_UTF16
43
    #define MD4C_USE_UTF8
44
#endif
45
46
/* Magic for making wide literals with MD4C_USE_UTF16. */
47
#ifdef _T
48
    #undef _T
49
#endif
50
#if defined MD4C_USE_UTF16
51
    #define _T(x)           L##x
52
#else
53
0
    #define _T(x)           x
54
#endif
55
56
/* Misc. macros. */
57
0
#define SIZEOF_ARRAY(a)     (sizeof(a) / sizeof(a[0]))
58
59
#define STRINGIZE_(x)       #x
60
#define STRINGIZE(x)        STRINGIZE_(x)
61
62
0
#define MAX(a,b)            ((a) > (b) ? (a) : (b))
63
0
#define MIN(a,b)            ((a) < (b) ? (a) : (b))
64
65
#define MD_LOG(msg)                                                     \
66
0
    do {                                                                \
67
0
        if(ctx->parser.debug_log != NULL)                               \
68
0
            ctx->parser.debug_log((msg), ctx->userdata);                \
69
0
    } while(0)
70
71
#ifdef DEBUG
72
    #define MD_ASSERT(cond)                                             \
73
            do {                                                        \
74
                if(!(cond)) {                                           \
75
                    MD_LOG(__FILE__ ":" STRINGIZE(__LINE__) ": "        \
76
                           "Assertion '" STRINGIZE(cond) "' failed.");  \
77
                    exit(EXIT_FAILURE);                                 \
78
                }                                                       \
79
            } while(0)
80
81
    #define MD_UNREACHABLE()        MD_ASSERT(1 == 0)
82
#else
83
    #ifdef __GNUC__
84
0
        #define MD_ASSERT(cond)     do { if(!(cond)) __builtin_unreachable(); } while(0)
85
0
        #define MD_UNREACHABLE()    do { __builtin_unreachable(); } while(0)
86
    #elif defined _MSC_VER  &&  _MSC_VER > 120
87
        #define MD_ASSERT(cond)     do { __assume(cond); } while(0)
88
        #define MD_UNREACHABLE()    do { __assume(0); } while(0)
89
    #else
90
        #define MD_ASSERT(cond)     do {} while(0)
91
        #define MD_UNREACHABLE()    do {} while(0)
92
    #endif
93
#endif
94
95
/* For falling through case labels in switch statements. */
96
#if defined __clang__ && __clang_major__ >= 12
97
0
    #define MD_FALLTHROUGH()        ;__attribute__((fallthrough))
98
#elif defined __GNUC__ && __GNUC__ >= 7
99
    #define MD_FALLTHROUGH()        ;__attribute__((fallthrough))
100
#else
101
    #define MD_FALLTHROUGH()        ((void)0)
102
#endif
103
104
/* Suppress "unused parameter" warnings. */
105
0
#define MD_UNUSED(x)                ((void)x)
106
107
108
/******************************
109
 ***  Some internal limits  ***
110
 ******************************/
111
112
/* We limit code span marks to lower than 32 backticks. This solves the
113
 * pathologic case of too many openers, each of different length: Their
114
 * resolving would be then O(n^2). */
115
0
#define CODESPAN_MARK_MAXLEN    32
116
117
118
/************************
119
 ***  Internal Types  ***
120
 ************************/
121
122
/* These are omnipresent so lets save some typing. */
123
0
#define CHAR    MD_CHAR
124
0
#define SZ      MD_SIZE
125
0
#define OFF     MD_OFFSET
126
127
typedef struct MD_MARK_tag MD_MARK;
128
typedef struct MD_BLOCK_tag MD_BLOCK;
129
typedef struct MD_CONTAINER_tag MD_CONTAINER;
130
typedef struct MD_REF_DEF_tag MD_REF_DEF;
131
typedef struct MD_FOOTNOTE_DEF_tag MD_FOOTNOTE_DEF;
132
133
/* Forward declaration; full definition is below with MD_LINE_tag. */
134
typedef struct MD_LINE_tag MD_LINE;
135
136
typedef struct MD_LABEL_HASH_ENTRY_tag MD_LABEL_HASH_ENTRY;
137
struct MD_LABEL_HASH_ENTRY_tag {
138
    const CHAR* label;
139
    SZ label_size;
140
    unsigned hash;
141
};
142
143
typedef struct MD_LABEL_HASH_TABLE_tag MD_LABEL_HASH_TABLE;
144
struct MD_LABEL_HASH_TABLE_tag {
145
    /* Flat array of all records. */
146
    union {
147
        void* defs;
148
        MD_REF_DEF* ref_defs;
149
        MD_FOOTNOTE_DEF* footnote_defs;
150
    };
151
    unsigned def_size;
152
    unsigned n_defs;
153
    unsigned alloc_defs;
154
155
    /* The hashtable itself, where the actual entries are pointers into the
156
     * array defs[] above. */
157
    void** buckets;
158
    unsigned n_buckets;
159
};
160
161
162
/* During analyzes of inline marks, we need to manage stacks of unresolved
163
 * openers of the given type.
164
 * The stack connects the marks via MD_MARK::next;
165
 */
166
typedef struct MD_MARKSTACK_tag MD_MARKSTACK;
167
struct MD_MARKSTACK_tag {
168
    int top;        /* -1 if empty. */
169
};
170
171
/* Context propagated through all the parsing. */
172
typedef struct MD_CTX_tag MD_CTX;
173
struct MD_CTX_tag {
174
    /* Immutable stuff (parameters of md_parse()). */
175
    const CHAR* text;
176
    SZ size;
177
    MD_PARSER parser;
178
    void* userdata;
179
180
    /* When this is true, it allows some optimizations. */
181
    int doc_ends_with_newline;
182
183
    /* Helper temporary growing buffer. */
184
    CHAR* buffer;
185
    unsigned alloc_buffer;
186
187
    /* Reference definitions. */
188
    MD_LABEL_HASH_TABLE ref_def_hashtable;
189
    SZ max_ref_def_output;
190
191
    /* Footnote definitions. */
192
    MD_LABEL_HASH_TABLE footnote_hashtable;
193
    unsigned next_footnote_index;   /* 1-based counter for sequential numbering */
194
195
    /* Stack of inline/span markers.
196
     * This is only used for parsing a single block contents but by storing it
197
     * here we may reuse the stack for subsequent blocks; i.e. we have fewer
198
     * (re)allocations. */
199
    MD_MARK* marks;
200
    int n_marks;
201
    int alloc_marks;
202
203
#if defined MD4C_USE_UTF16
204
    char mark_char_map[128];
205
#else
206
    char mark_char_map[256];
207
#endif
208
209
    /* For resolving of inline spans. */
210
    MD_MARKSTACK opener_stacks[20];
211
0
#define ASTERISK_OPENERS_oo_mod3_0      (ctx->opener_stacks[0])     /* Opener-only */
212
#define ASTERISK_OPENERS_oo_mod3_1      (ctx->opener_stacks[1])
213
#define ASTERISK_OPENERS_oo_mod3_2      (ctx->opener_stacks[2])
214
#define ASTERISK_OPENERS_oc_mod3_0      (ctx->opener_stacks[3])     /* Both opener and closer candidate */
215
#define ASTERISK_OPENERS_oc_mod3_1      (ctx->opener_stacks[4])
216
#define ASTERISK_OPENERS_oc_mod3_2      (ctx->opener_stacks[5])
217
0
#define UNDERSCORE_OPENERS_oo_mod3_0    (ctx->opener_stacks[6])     /* Opener-only */
218
#define UNDERSCORE_OPENERS_oo_mod3_1    (ctx->opener_stacks[7])
219
#define UNDERSCORE_OPENERS_oo_mod3_2    (ctx->opener_stacks[8])
220
#define UNDERSCORE_OPENERS_oc_mod3_0    (ctx->opener_stacks[9])     /* Both opener and closer candidate */
221
#define UNDERSCORE_OPENERS_oc_mod3_1    (ctx->opener_stacks[10])
222
#define UNDERSCORE_OPENERS_oc_mod3_2    (ctx->opener_stacks[11])
223
0
#define TILDE_OPENERS_1                 (ctx->opener_stacks[12])
224
0
#define TILDE_OPENERS_2                 (ctx->opener_stacks[13])
225
0
#define BRACKET_OPENERS                 (ctx->opener_stacks[14])
226
0
#define DOLLAR_OPENERS                  (ctx->opener_stacks[15])
227
0
#define PIPE_OPENERS                    (ctx->opener_stacks[16])
228
0
#define CARET_OPENERS                   (ctx->opener_stacks[17])
229
0
#define EQUAL_OPENERS                   (ctx->opener_stacks[18])
230
0
#define PLUS_OPENERS                    (ctx->opener_stacks[19])
231
232
    /* Stack of dummies which need to call free() for pointers stored in them.
233
     * These are constructed during inline parsing and freed after all the block
234
     * is processed (i.e. all callbacks referring those strings are called). */
235
    MD_MARKSTACK ptr_stack;
236
237
    /* For resolving table rows. */
238
    int n_table_cell_boundaries;
239
    int table_cell_boundaries_head;
240
    int table_cell_boundaries_tail;
241
242
    /* For resolving links. */
243
    int unresolved_link_head;
244
    int unresolved_link_tail;
245
246
    /* For resolving raw HTML. */
247
    OFF html_comment_horizon;
248
    OFF html_proc_instr_horizon;
249
    OFF html_decl_horizon;
250
    OFF html_cdata_horizon;
251
252
    /* For block analysis.
253
     * Notes:
254
     *   -- It holds MD_BLOCK as well as MD_LINE structures. After each
255
     *      MD_BLOCK, its (multiple) MD_LINE(s) follow.
256
     *   -- For MD_BLOCK_HTML and MD_BLOCK_CODE, MD_VERBATIMLINE(s) are used
257
     *      instead of MD_LINE(s).
258
     */
259
    void* block_bytes;
260
    MD_BLOCK* current_block;
261
    int n_block_bytes;
262
    int alloc_block_bytes;
263
264
    /* Pending count of blank lines not yet reported as MD_BLOCK_BLANK.
265
     * Used only with MD_FLAG_PRESERVEBLANKLINES. */
266
    unsigned n_blank_lines;
267
268
    /* For container block analysis. */
269
    MD_CONTAINER* containers;
270
    int n_containers;
271
    int alloc_containers;
272
273
    /* Minimal indentation to call the block "indented code block". */
274
    unsigned code_indent_offset;
275
276
    /* Contextual info for line analysis. */
277
    SZ code_fence_length;   /* For checking closing fence length. */
278
    int html_block_type;    /* For checking closing raw HTML condition. */
279
    int last_line_has_list_loosening_effect;
280
    int consecutive_blank_lines;
281
};
282
283
enum MD_LINETYPE_tag {
284
    MD_LINE_BLANK,
285
    MD_LINE_HR,
286
    MD_LINE_ATXHEADER,
287
    MD_LINE_SETEXTHEADER,
288
    MD_LINE_SETEXTUNDERLINE,
289
    MD_LINE_INDENTEDCODE,
290
    MD_LINE_FENCEDCODE,
291
    MD_LINE_HTML,
292
    MD_LINE_TEXT,
293
    MD_LINE_TABLE,
294
    MD_LINE_TABLEUNDERLINE
295
};
296
typedef enum MD_LINETYPE_tag MD_LINETYPE;
297
298
typedef struct MD_LINE_ANALYSIS_tag MD_LINE_ANALYSIS;
299
struct MD_LINE_ANALYSIS_tag {
300
    MD_LINETYPE type;
301
    unsigned data;
302
    int enforce_new_block;
303
    OFF beg;
304
    OFF end;
305
    unsigned indent;        /* Indentation level. */
306
};
307
308
struct MD_LINE_tag {
309
    OFF beg;
310
    OFF end;
311
};
312
313
typedef struct MD_VERBATIMLINE_tag MD_VERBATIMLINE;
314
struct MD_VERBATIMLINE_tag {
315
    OFF beg;
316
    OFF end;
317
    OFF indent;
318
};
319
320
321
/*****************
322
 ***  Helpers  ***
323
 *****************/
324
325
/* Character accessors. */
326
0
#define CH(off)                 (ctx->text[(off)])
327
0
#define STR(off)                (ctx->text + (off))
328
329
/* Character classification.
330
 * Note we assume ASCII compatibility of code points < 128 here. */
331
0
#define ISIN_(ch, ch_min, ch_max)       ((ch_min) <= (unsigned)(ch) && (unsigned)(ch) <= (ch_max))
332
0
#define ISANYOF_(ch, palette)           ((ch) != _T('\0')  &&  md_strchr((palette), (ch)) != NULL)
333
0
#define ISANYOF2_(ch, ch1, ch2)         ((ch) == (ch1) || (ch) == (ch2))
334
0
#define ISANYOF3_(ch, ch1, ch2, ch3)    ((ch) == (ch1) || (ch) == (ch2) || (ch) == (ch3))
335
0
#define ISASCII_(ch)                    ((unsigned)(ch) <= 127)
336
0
#define ISBLANK_(ch)                    (ISANYOF2_((ch), _T(' '), _T('\t')))
337
0
#define ISNEWLINE_(ch)                  (ISANYOF2_((ch), _T('\r'), _T('\n')))
338
0
#define ISWHITESPACE_(ch)               (ISBLANK_(ch) || ISANYOF2_((ch), _T('\v'), _T('\f')))
339
0
#define ISCNTRL_(ch)                    ((unsigned)(ch) <= 31 || (unsigned)(ch) == 127)
340
0
#define ISPUNCT_(ch)                    (ISIN_(ch, 33, 47) || ISIN_(ch, 58, 64) || ISIN_(ch, 91, 96) || ISIN_(ch, 123, 126))
341
0
#define ISUPPER_(ch)                    (ISIN_(ch, _T('A'), _T('Z')))
342
0
#define ISLOWER_(ch)                    (ISIN_(ch, _T('a'), _T('z')))
343
0
#define ISALPHA_(ch)                    (ISUPPER_(ch) || ISLOWER_(ch))
344
0
#define ISDIGIT_(ch)                    (ISIN_(ch, _T('0'), _T('9')))
345
0
#define ISXDIGIT_(ch)                   (ISDIGIT_(ch) || ISIN_(ch, _T('A'), _T('F')) || ISIN_(ch, _T('a'), _T('f')))
346
0
#define ISALNUM_(ch)                    (ISALPHA_(ch) || ISDIGIT_(ch))
347
348
0
#define ISANYOF(off, palette)           ISANYOF_(CH(off), (palette))
349
0
#define ISANYOF2(off, ch1, ch2)         ISANYOF2_(CH(off), (ch1), (ch2))
350
0
#define ISANYOF3(off, ch1, ch2, ch3)    ISANYOF3_(CH(off), (ch1), (ch2), (ch3))
351
0
#define ISASCII(off)                    ISASCII_(CH(off))
352
0
#define ISBLANK(off)                    ISBLANK_(CH(off))
353
0
#define ISNEWLINE(off)                  ISNEWLINE_(CH(off))
354
0
#define ISWHITESPACE(off)               ISWHITESPACE_(CH(off))
355
0
#define ISCNTRL(off)                    ISCNTRL_(CH(off))
356
0
#define ISPUNCT(off)                    ISPUNCT_(CH(off))
357
0
#define ISUPPER(off)                    ISUPPER_(CH(off))
358
#define ISLOWER(off)                    ISLOWER_(CH(off))
359
0
#define ISALPHA(off)                    ISALPHA_(CH(off))
360
0
#define ISDIGIT(off)                    ISDIGIT_(CH(off))
361
#define ISXDIGIT(off)                   ISXDIGIT_(CH(off))
362
0
#define ISALNUM(off)                    ISALNUM_(CH(off))
363
364
365
#if defined MD4C_USE_UTF16
366
    #define md_strchr wcschr
367
    #define md_strlen wcslen
368
#else
369
0
    #define md_strchr strchr
370
0
    #define md_strlen strlen
371
#endif
372
373
374
/* Case insensitive check of string equality. */
375
static inline int
376
md_ascii_case_eq(const CHAR* s1, const CHAR* s2, SZ n)
377
0
{
378
0
    OFF i;
379
0
    for(i = 0; i < n; i++) {
380
0
        CHAR ch1 = s1[i];
381
0
        CHAR ch2 = s2[i];
382
383
0
        if(ISLOWER_(ch1))
384
0
            ch1 += ('A'-'a');
385
0
        if(ISLOWER_(ch2))
386
0
            ch2 += ('A'-'a');
387
0
        if(ch1 != ch2)
388
0
            return false;
389
0
    }
390
0
    return true;
391
0
}
392
393
static inline int
394
md_ascii_eq(const CHAR* s1, const CHAR* s2, SZ n)
395
0
{
396
0
    return memcmp(s1, s2, n * sizeof(CHAR)) == 0;
397
0
}
398
399
static int
400
md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ size)
401
0
{
402
0
    OFF off = 0;
403
0
    int ret = 0;
404
405
0
    while(1) {
406
0
        while(off < size  &&  str[off] != _T('\0'))
407
0
            off++;
408
409
0
        if(off > 0) {
410
0
            ret = ctx->parser.text(type, str, off, ctx->userdata);
411
0
            if(ret != 0)
412
0
                return ret;
413
414
0
            str += off;
415
0
            size -= off;
416
0
            off = 0;
417
0
        }
418
419
0
        if(off >= size)
420
0
            return 0;
421
422
0
        ret = ctx->parser.text(MD_TEXT_NULLCHAR, _T(""), 1, ctx->userdata);
423
0
        if(ret != 0)
424
0
            return ret;
425
0
        off++;
426
0
    }
427
0
}
428
429
430
#define MD_CHECK(func)                                                      \
431
0
    do {                                                                    \
432
0
        ret = (func);                                                       \
433
0
        if(ret != 0)                                                        \
434
0
            goto abort;                                                     \
435
0
    } while(0)
436
437
438
#define MD_TEMP_BUFFER(sz)                                                  \
439
0
    do {                                                                    \
440
0
        if(sz > ctx->alloc_buffer) {                                        \
441
0
            CHAR* new_buffer;                                               \
442
0
            SZ new_size = ((sz) + (sz) / 2 + 128) & ~127;                   \
443
0
                                                                            \
444
0
            new_buffer = realloc(ctx->buffer, new_size);                    \
445
0
            if(new_buffer == NULL) {                                        \
446
0
                MD_LOG("realloc() failed.");                                \
447
0
                ret = -1;                                                   \
448
0
                goto abort;                                                 \
449
0
            }                                                               \
450
0
                                                                            \
451
0
            ctx->buffer = new_buffer;                                       \
452
0
            ctx->alloc_buffer = new_size;                                   \
453
0
        }                                                                   \
454
0
    } while(0)
455
456
457
#define MD_ENTER_BLOCK(type, arg)                                           \
458
0
    do {                                                                    \
459
0
        ret = ctx->parser.enter_block((type), (arg), ctx->userdata);        \
460
0
        if(ret != 0) {                                                      \
461
0
            MD_LOG("Aborted from enter_block() callback.");                 \
462
0
            goto abort;                                                     \
463
0
        }                                                                   \
464
0
    } while(0)
465
466
#define MD_LEAVE_BLOCK(type, arg)                                           \
467
0
    do {                                                                    \
468
0
        ret = ctx->parser.leave_block((type), (arg), ctx->userdata);        \
469
0
        if(ret != 0) {                                                      \
470
0
            MD_LOG("Aborted from leave_block() callback.");                 \
471
0
            goto abort;                                                     \
472
0
        }                                                                   \
473
0
    } while(0)
474
475
#define MD_ENTER_SPAN(type, arg)                                            \
476
0
    do {                                                                    \
477
0
        ret = ctx->parser.enter_span((type), (arg), ctx->userdata);         \
478
0
        if(ret != 0) {                                                      \
479
0
            MD_LOG("Aborted from enter_span() callback.");                  \
480
0
            goto abort;                                                     \
481
0
        }                                                                   \
482
0
    } while(0)
483
484
#define MD_LEAVE_SPAN(type, arg)                                            \
485
0
    do {                                                                    \
486
0
        ret = ctx->parser.leave_span((type), (arg), ctx->userdata);         \
487
0
        if(ret != 0) {                                                      \
488
0
            MD_LOG("Aborted from leave_span() callback.");                  \
489
0
            goto abort;                                                     \
490
0
        }                                                                   \
491
0
    } while(0)
492
493
#define MD_TEXT(type, str, size)                                            \
494
0
    do {                                                                    \
495
0
        if(size > 0) {                                                      \
496
0
            ret = ctx->parser.text((type), (str), (size), ctx->userdata);   \
497
0
            if(ret != 0) {                                                  \
498
0
                MD_LOG("Aborted from text() callback.");                    \
499
0
                goto abort;                                                 \
500
0
            }                                                               \
501
0
        }                                                                   \
502
0
    } while(0)
503
504
#define MD_TEXT_INSECURE(type, str, size)                                   \
505
0
    do {                                                                    \
506
0
        if(size > 0) {                                                      \
507
0
            ret = md_text_with_null_replacement(ctx, type, str, size);      \
508
0
            if(ret != 0) {                                                  \
509
0
                MD_LOG("Aborted from text() callback.");                    \
510
0
                goto abort;                                                 \
511
0
            }                                                               \
512
0
        }                                                                   \
513
0
    } while(0)
514
515
516
/* If the offset falls into a gap between line, we return the following
517
 * line. */
518
static const MD_LINE*
519
md_lookup_line(OFF off, const MD_LINE* lines, MD_SIZE n_lines, MD_SIZE* p_line_index)
520
0
{
521
0
    MD_SIZE lo, hi;
522
0
    MD_SIZE pivot;
523
0
    const MD_LINE* line;
524
525
0
    lo = 0;
526
0
    hi = n_lines - 1;
527
0
    while(lo <= hi) {
528
0
        pivot = (lo + hi) / 2;
529
0
        line = &lines[pivot];
530
531
0
        if(off < line->beg) {
532
0
            if(hi == 0  ||  lines[hi-1].end < off) {
533
0
                if(p_line_index != NULL)
534
0
                    *p_line_index = pivot;
535
0
                return line;
536
0
            }
537
0
            hi = pivot - 1;
538
0
        } else if(off > line->end) {
539
0
            lo = pivot + 1;
540
0
        } else {
541
0
            if(p_line_index != NULL)
542
0
                *p_line_index = pivot;
543
0
            return line;
544
0
        }
545
0
    }
546
547
0
    return NULL;
548
0
}
549
550
551
/*************************
552
 ***  Unicode Support  ***
553
 *************************/
554
555
typedef struct MD_UNICODE_FOLD_INFO_tag MD_UNICODE_FOLD_INFO;
556
struct MD_UNICODE_FOLD_INFO_tag {
557
    unsigned codepoints[3];
558
    unsigned n_codepoints;
559
};
560
561
562
#if defined MD4C_USE_UTF16 || defined MD4C_USE_UTF8
563
    /* Binary search over sorted "map" of codepoints. Consecutive sequences
564
     * of codepoints may be encoded in the map by just using the
565
     * (MIN_CODEPOINT | 0x40000000) and (MAX_CODEPOINT | 0x80000000).
566
     *
567
     * Returns index of the found record in the map (in the case of ranges,
568
     * the minimal value is used); or -1 on failure. */
569
    static int
570
    md_unicode_bsearch__(unsigned codepoint, const unsigned* map, size_t map_size)
571
0
    {
572
0
        int beg, end;
573
0
        int pivot_beg, pivot_end;
574
575
0
        beg = 0;
576
0
        end = (int) map_size-1;
577
0
        while(beg <= end) {
578
            /* Pivot may be a range, not just a single value. */
579
0
            pivot_beg = pivot_end = (beg + end) / 2;
580
0
            if(map[pivot_end] & 0x40000000)
581
0
                pivot_end++;
582
0
            if(map[pivot_beg] & 0x80000000)
583
0
                pivot_beg--;
584
585
0
            if(codepoint < (map[pivot_beg] & 0x00ffffff))
586
0
                end = pivot_beg - 1;
587
0
            else if(codepoint > (map[pivot_end] & 0x00ffffff))
588
0
                beg = pivot_end + 1;
589
0
            else
590
0
                return pivot_beg;
591
0
        }
592
593
0
        return -1;
594
0
    }
595
596
    static int
597
    md_is_unicode_whitespace__(unsigned codepoint)
598
0
    {
599
0
#define R(cp_min, cp_max)   ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
600
0
#define S(cp)               (cp)
601
        /* Unicode "Zs" category.
602
         * (generated by scripts/build_whitespace_map.py) */
603
0
        static const unsigned WHITESPACE_MAP[] = {
604
0
            S(0x0020), S(0x00a0), S(0x1680), R(0x2000,0x200a), S(0x202f), S(0x205f), S(0x3000)
605
0
        };
606
0
#undef R
607
0
#undef S
608
609
        /* The ASCII ones are the most frequently used ones, also CommonMark
610
         * specification requests few more in this range. */
611
0
        if(codepoint <= 0x7f)
612
0
            return ISWHITESPACE_(codepoint);
613
614
0
        return (md_unicode_bsearch__(codepoint, WHITESPACE_MAP, SIZEOF_ARRAY(WHITESPACE_MAP)) >= 0);
615
0
    }
616
617
    static int
618
    md_is_unicode_punct__(unsigned codepoint)
619
0
    {
620
0
#define R(cp_min, cp_max)   ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
621
0
#define S(cp)               (cp)
622
        /* Unicode general "P" and "S" categories.
623
         * (generated by scripts/build_punct_map.py) */
624
0
        static const unsigned PUNCT_MAP[] = {
625
0
            R(0x0021,0x002f), R(0x003a,0x0040), R(0x005b,0x0060), R(0x007b,0x007e), R(0x00a1,0x00a9),
626
0
            R(0x00ab,0x00ac), R(0x00ae,0x00b1), S(0x00b4), R(0x00b6,0x00b8), S(0x00bb), S(0x00bf), S(0x00d7),
627
0
            S(0x00f7), R(0x02c2,0x02c5), R(0x02d2,0x02df), R(0x02e5,0x02eb), S(0x02ed), R(0x02ef,0x02ff), S(0x0375),
628
0
            S(0x037e), R(0x0384,0x0385), S(0x0387), S(0x03f6), S(0x0482), R(0x055a,0x055f), R(0x0589,0x058a),
629
0
            R(0x058d,0x058f), S(0x05be), S(0x05c0), S(0x05c3), S(0x05c6), R(0x05f3,0x05f4), R(0x0606,0x060f),
630
0
            S(0x061b), R(0x061d,0x061f), R(0x066a,0x066d), S(0x06d4), S(0x06de), S(0x06e9), R(0x06fd,0x06fe),
631
0
            R(0x0700,0x070d), R(0x07f6,0x07f9), R(0x07fe,0x07ff), R(0x0830,0x083e), S(0x085e), S(0x0888),
632
0
            R(0x0964,0x0965), S(0x0970), R(0x09f2,0x09f3), R(0x09fa,0x09fb), S(0x09fd), S(0x0a76), R(0x0af0,0x0af1),
633
0
            S(0x0b70), R(0x0bf3,0x0bfa), S(0x0c77), S(0x0c7f), S(0x0c84), S(0x0d4f), S(0x0d79), S(0x0df4), S(0x0e3f),
634
0
            S(0x0e4f), R(0x0e5a,0x0e5b), R(0x0f01,0x0f17), R(0x0f1a,0x0f1f), S(0x0f34), S(0x0f36), S(0x0f38),
635
0
            R(0x0f3a,0x0f3d), S(0x0f85), R(0x0fbe,0x0fc5), R(0x0fc7,0x0fcc), R(0x0fce,0x0fda), R(0x104a,0x104f),
636
0
            R(0x109e,0x109f), S(0x10fb), R(0x1360,0x1368), R(0x1390,0x1399), S(0x1400), R(0x166d,0x166e),
637
0
            R(0x169b,0x169c), R(0x16eb,0x16ed), R(0x1735,0x1736), R(0x17d4,0x17d6), R(0x17d8,0x17db),
638
0
            R(0x1800,0x180a), S(0x1940), R(0x1944,0x1945), R(0x19de,0x19ff), R(0x1a1e,0x1a1f), R(0x1aa0,0x1aa6),
639
0
            R(0x1aa8,0x1aad), R(0x1b4e,0x1b4f), R(0x1b5a,0x1b6a), R(0x1b74,0x1b7f), R(0x1bfc,0x1bff),
640
0
            R(0x1c3b,0x1c3f), R(0x1c7e,0x1c7f), R(0x1cc0,0x1cc7), S(0x1cd3), S(0x1fbd), R(0x1fbf,0x1fc1),
641
0
            R(0x1fcd,0x1fcf), R(0x1fdd,0x1fdf), R(0x1fed,0x1fef), R(0x1ffd,0x1ffe), R(0x2010,0x2027),
642
0
            R(0x2030,0x205e), R(0x207a,0x207e), R(0x208a,0x208e), R(0x20a0,0x20c4), R(0x2100,0x2101),
643
0
            R(0x2103,0x2106), R(0x2108,0x2109), S(0x2114), R(0x2116,0x2118), R(0x211e,0x2123), S(0x2125), S(0x2127),
644
0
            S(0x2129), S(0x212e), R(0x213a,0x213b), R(0x2140,0x2144), R(0x214a,0x214d), S(0x214f), R(0x218a,0x218b),
645
0
            R(0x2190,0x2429), R(0x2440,0x244a), R(0x249c,0x24e9), R(0x2500,0x2775), R(0x2794,0x2b73),
646
0
            R(0x2b76,0x2bff), R(0x2ce5,0x2cea), R(0x2cf9,0x2cfc), R(0x2cfe,0x2cff), S(0x2d70), R(0x2e00,0x2e2e),
647
0
            R(0x2e30,0x2e5d), R(0x2e60,0x2e63), R(0x2e80,0x2e99), R(0x2e9b,0x2ef3), R(0x2f00,0x2fd5),
648
0
            R(0x2ff0,0x2fff), R(0x3001,0x3004), R(0x3008,0x3020), S(0x3030), R(0x3036,0x3037), R(0x303d,0x303f),
649
0
            R(0x309b,0x309c), S(0x30a0), S(0x30fb), R(0x3190,0x3191), R(0x3196,0x319f), R(0x31c0,0x31e5), S(0x31ef),
650
0
            R(0x3200,0x321e), R(0x322a,0x3247), S(0x3250), R(0x3260,0x327f), R(0x328a,0x32b0), R(0x32c0,0x33ff),
651
0
            R(0x4dc0,0x4dff), R(0xa490,0xa4c6), R(0xa4fe,0xa4ff), R(0xa60d,0xa60f), S(0xa673), S(0xa67e),
652
0
            R(0xa6f2,0xa6f7), R(0xa700,0xa716), R(0xa720,0xa721), R(0xa789,0xa78a), R(0xa828,0xa82b),
653
0
            R(0xa836,0xa839), R(0xa874,0xa877), R(0xa8ce,0xa8cf), R(0xa8f8,0xa8fa), S(0xa8fc), R(0xa92e,0xa92f),
654
0
            S(0xa95f), R(0xa9c1,0xa9cd), R(0xa9de,0xa9df), R(0xaa5c,0xaa5f), R(0xaa77,0xaa79), R(0xaade,0xaadf),
655
0
            R(0xaaf0,0xaaf1), S(0xab5b), R(0xab6a,0xab6b), S(0xabeb), S(0xfb29), R(0xfbb2,0xfbd2), R(0xfd3e,0xfd4f),
656
0
            R(0xfd90,0xfd91), R(0xfdc8,0xfdcf), R(0xfdfc,0xfdff), R(0xfe10,0xfe19), R(0xfe30,0xfe52),
657
0
            R(0xfe54,0xfe66), R(0xfe68,0xfe6b), R(0xff01,0xff0f), R(0xff1a,0xff20), R(0xff3b,0xff40),
658
0
            R(0xff5b,0xff65), R(0xffe0,0xffe6), R(0xffe8,0xffee), R(0xfffc,0xfffd), R(0x10100,0x10102),
659
0
            R(0x10137,0x1013f), R(0x10179,0x10189), R(0x1018c,0x1018e), R(0x10190,0x1019c), S(0x101a0),
660
0
            R(0x101d0,0x101fc), S(0x1039f), S(0x103d0), S(0x1056f), S(0x10857), R(0x10877,0x10878), S(0x1091f),
661
0
            S(0x1093f), R(0x10a50,0x10a58), S(0x10a7f), S(0x10ac8), R(0x10af0,0x10af6), R(0x10b39,0x10b3f),
662
0
            R(0x10b99,0x10b9c), S(0x10d6e), R(0x10d8e,0x10d8f), S(0x10ead), R(0x10ec9,0x10eca), R(0x10ed0,0x10ed8),
663
0
            R(0x10f55,0x10f59), R(0x10f86,0x10f89), R(0x11047,0x1104d), R(0x110bb,0x110bc), R(0x110be,0x110c1),
664
0
            R(0x11140,0x11143), R(0x11174,0x11175), R(0x111c5,0x111c8), S(0x111cd), S(0x111db), R(0x111dd,0x111df),
665
0
            R(0x11238,0x1123d), S(0x112a9), R(0x113d4,0x113d5), R(0x113d7,0x113d8), R(0x1144b,0x1144f),
666
0
            R(0x1145a,0x1145b), S(0x1145d), S(0x114c6), R(0x115c1,0x115d7), R(0x11641,0x11643), R(0x11660,0x1166c),
667
0
            S(0x116b9), R(0x1173c,0x1173f), S(0x1183b), R(0x11944,0x11946), S(0x119e2), R(0x11a3f,0x11a46),
668
0
            R(0x11a9a,0x11a9c), R(0x11a9e,0x11aa2), R(0x11b00,0x11b09), S(0x11be1), R(0x11c41,0x11c45),
669
0
            R(0x11c70,0x11c71), R(0x11ef7,0x11ef8), R(0x11f43,0x11f4f), R(0x11fd5,0x11ff1), S(0x11fff),
670
0
            R(0x12470,0x12474), R(0x12ff1,0x12ff2), R(0x16a6e,0x16a6f), S(0x16af5), R(0x16b37,0x16b3f),
671
0
            R(0x16b44,0x16b45), R(0x16d6d,0x16d6f), R(0x16e97,0x16e9a), S(0x16fe2), S(0x1bc9c), S(0x1bc9f),
672
0
            R(0x1cc00,0x1ccef), R(0x1ccfa,0x1ccfc), R(0x1cd00,0x1ceb3), R(0x1ceba,0x1ced0), R(0x1ced2,0x1ced4),
673
0
            R(0x1cedd,0x1cefd), R(0x1cf50,0x1cfc3), R(0x1d000,0x1d0f5), R(0x1d100,0x1d126), R(0x1d129,0x1d164),
674
0
            R(0x1d16a,0x1d16c), R(0x1d183,0x1d184), R(0x1d18c,0x1d1a9), R(0x1d1ae,0x1d241), S(0x1d245),
675
0
            R(0x1d253,0x1d25a), R(0x1d25d,0x1d25e), R(0x1d260,0x1d27f), R(0x1d300,0x1d356), S(0x1d6c1), S(0x1d6db),
676
0
            S(0x1d6fb), S(0x1d715), S(0x1d735), S(0x1d74f), S(0x1d76f), S(0x1d789), S(0x1d7a9), S(0x1d7c3),
677
0
            R(0x1d800,0x1d9ff), R(0x1da37,0x1da3a), R(0x1da6d,0x1da74), R(0x1da76,0x1da83), R(0x1da85,0x1da8b),
678
0
            R(0x1db00,0x1db1c), S(0x1e14f), S(0x1e2ff), S(0x1e5ff), R(0x1e95e,0x1e95f), S(0x1ecac), S(0x1ecb0),
679
0
            S(0x1ed2e), R(0x1eef0,0x1eef1), R(0x1f000,0x1f02b), R(0x1f030,0x1f093), R(0x1f0a0,0x1f0ae),
680
0
            R(0x1f0b1,0x1f0bf), R(0x1f0c1,0x1f0cf), R(0x1f0d1,0x1f0f5), R(0x1f10d,0x1f1ae), R(0x1f1e6,0x1f202),
681
0
            R(0x1f210,0x1f23b), R(0x1f240,0x1f248), R(0x1f250,0x1f251), R(0x1f260,0x1f265), R(0x1f300,0x1f6d9),
682
0
            R(0x1f6dc,0x1f6ec), R(0x1f6f0,0x1f6fc), R(0x1f700,0x1f7db), R(0x1f7e0,0x1f7eb), R(0x1f7f0,0x1f80b),
683
0
            R(0x1f810,0x1f847), R(0x1f850,0x1f859), R(0x1f860,0x1f887), R(0x1f890,0x1f8ad), R(0x1f8b0,0x1f8bb),
684
0
            R(0x1f8c0,0x1f8c1), R(0x1f8d0,0x1f8d8), R(0x1f900,0x1fa57), R(0x1fa60,0x1fa6d), R(0x1fa70,0x1fa7c),
685
0
            R(0x1fa80,0x1fac6), S(0x1fac8), R(0x1facc,0x1fadd), R(0x1fadf,0x1faeb), R(0x1faef,0x1fafa),
686
0
            R(0x1fb00,0x1fb92), R(0x1fb94,0x1fbef), S(0x1fbfa)
687
0
        };
688
0
#undef R
689
0
#undef S
690
691
        /* The ASCII ones are the most frequently used ones, also CommonMark
692
         * specification requests few more in this range. */
693
0
        if(codepoint <= 0x7f)
694
0
            return ISPUNCT_(codepoint);
695
696
0
        return (md_unicode_bsearch__(codepoint, PUNCT_MAP, SIZEOF_ARRAY(PUNCT_MAP)) >= 0);
697
0
    }
698
699
    static void
700
    md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info)
701
0
    {
702
0
#define R(cp_min, cp_max)   ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
703
0
#define S(cp)               (cp)
704
        /* Unicode "Pc", "Pd", "Pe", "Pf", "Pi", "Po", "Ps" categories.
705
         * (generated by scripts/build_folding_map.py) */
706
0
        static const unsigned FOLD_MAP_1[] = {
707
0
            R(0x0041,0x005a), S(0x00b5), R(0x00c0,0x00d6), R(0x00d8,0x00de), R(0x0100,0x012e), R(0x0132,0x0136),
708
0
            R(0x0139,0x0147), R(0x014a,0x0176), S(0x0178), R(0x0179,0x017d), S(0x017f), S(0x0181), S(0x0182),
709
0
            S(0x0184), S(0x0186), S(0x0187), S(0x0189), S(0x018a), S(0x018b), S(0x018e), S(0x018f), S(0x0190),
710
0
            S(0x0191), S(0x0193), S(0x0194), S(0x0196), S(0x0197), S(0x0198), S(0x019c), S(0x019d), S(0x019f),
711
0
            R(0x01a0,0x01a4), S(0x01a6), S(0x01a7), S(0x01a9), S(0x01ac), S(0x01ae), S(0x01af), S(0x01b1), S(0x01b2),
712
0
            S(0x01b3), S(0x01b5), S(0x01b7), S(0x01b8), S(0x01bc), S(0x01c4), S(0x01c5), S(0x01c7), S(0x01c8),
713
0
            S(0x01ca), R(0x01cb,0x01db), R(0x01de,0x01ee), S(0x01f1), S(0x01f2), S(0x01f4), S(0x01f6), S(0x01f7),
714
0
            R(0x01f8,0x021e), S(0x0220), R(0x0222,0x0232), S(0x023a), S(0x023b), S(0x023d), S(0x023e), S(0x0241),
715
0
            S(0x0243), S(0x0244), S(0x0245), R(0x0246,0x024e), S(0x0345), S(0x0370), S(0x0372), S(0x0376), S(0x037f),
716
0
            S(0x0386), R(0x0388,0x038a), S(0x038c), S(0x038e), S(0x038f), R(0x0391,0x03a1), R(0x03a3,0x03ab),
717
0
            S(0x03c2), S(0x03cf), S(0x03d0), S(0x03d1), S(0x03d5), S(0x03d6), R(0x03d8,0x03ee), S(0x03f0), S(0x03f1),
718
0
            S(0x03f4), S(0x03f5), S(0x03f7), S(0x03f9), S(0x03fa), R(0x03fd,0x03ff), R(0x0400,0x040f),
719
0
            R(0x0410,0x042f), R(0x0460,0x0480), R(0x048a,0x04be), S(0x04c0), R(0x04c1,0x04cd), R(0x04d0,0x052e),
720
0
            R(0x0531,0x0556), R(0x10a0,0x10c5), S(0x10c7), S(0x10cd), R(0x13f8,0x13fd), S(0x1c80), S(0x1c81),
721
0
            S(0x1c82), S(0x1c83), S(0x1c84), S(0x1c85), S(0x1c86), S(0x1c87), S(0x1c88), S(0x1c89), R(0x1c90,0x1cba),
722
0
            R(0x1cbd,0x1cbf), R(0x1e00,0x1e94), S(0x1e9b), R(0x1ea0,0x1efe), R(0x1f08,0x1f0f), R(0x1f18,0x1f1d),
723
0
            R(0x1f28,0x1f2f), R(0x1f38,0x1f3f), R(0x1f48,0x1f4d), S(0x1f59), S(0x1f5b), S(0x1f5d), S(0x1f5f),
724
0
            R(0x1f68,0x1f6f), S(0x1fb8), S(0x1fb9), S(0x1fba), S(0x1fbb), S(0x1fbe), R(0x1fc8,0x1fcb), S(0x1fd8),
725
0
            S(0x1fd9), S(0x1fda), S(0x1fdb), S(0x1fe8), S(0x1fe9), S(0x1fea), S(0x1feb), S(0x1fec), S(0x1ff8),
726
0
            S(0x1ff9), S(0x1ffa), S(0x1ffb), S(0x2126), S(0x212a), S(0x212b), S(0x2132), R(0x2160,0x216f), S(0x2183),
727
0
            R(0x24b6,0x24cf), R(0x2c00,0x2c2f), S(0x2c60), S(0x2c62), S(0x2c63), S(0x2c64), R(0x2c67,0x2c6b),
728
0
            S(0x2c6d), S(0x2c6e), S(0x2c6f), S(0x2c70), S(0x2c72), S(0x2c75), S(0x2c7e), S(0x2c7f), R(0x2c80,0x2ce2),
729
0
            S(0x2ceb), S(0x2ced), S(0x2cf2), R(0xa640,0xa66c), R(0xa680,0xa69a), R(0xa722,0xa72e), R(0xa732,0xa76e),
730
0
            S(0xa779), S(0xa77b), S(0xa77d), R(0xa77e,0xa786), S(0xa78b), S(0xa78d), S(0xa790), S(0xa792),
731
0
            R(0xa796,0xa7a8), S(0xa7aa), S(0xa7ab), S(0xa7ac), S(0xa7ad), S(0xa7ae), S(0xa7b0), S(0xa7b1), S(0xa7b2),
732
0
            S(0xa7b3), R(0xa7b4,0xa7c2), S(0xa7c4), S(0xa7c5), S(0xa7c6), S(0xa7c7), S(0xa7c9), S(0xa7cb),
733
0
            R(0xa7cc,0xa7da), S(0xa7dc), S(0xa7dd), S(0xa7e2), S(0xa7f5), S(0xab6c), S(0xab6d), R(0xab70,0xabbf),
734
0
            R(0xff21,0xff3a), R(0x10400,0x10427), R(0x104b0,0x104d3), R(0x10570,0x1057a), R(0x1057c,0x1058a),
735
0
            R(0x1058c,0x10592), S(0x10594), S(0x10595), R(0x10c80,0x10cb2), R(0x10d50,0x10d65), R(0x118a0,0x118bf),
736
0
            R(0x16e40,0x16e5f), R(0x16ea0,0x16eb8), S(0x1df40), S(0x1df48), S(0x1df4a), S(0x1df4d), S(0x1df51),
737
0
            R(0x1df68,0x1df6e), R(0x1df72,0x1df7e), R(0x1e900,0x1e921)
738
0
        };
739
0
        static const unsigned FOLD_MAP_1_DATA[] = {
740
0
            0x0061, 0x007a, 0x03bc, 0x00e0, 0x00f6, 0x00f8, 0x00fe, 0x0101, 0x012f, 0x0133, 0x0137, 0x013a, 0x0148,
741
0
            0x014b, 0x0177, 0x00ff, 0x017a, 0x017e, 0x0073, 0x0253, 0x0183, 0x0185, 0x0254, 0x0188, 0x0256, 0x0257,
742
0
            0x018c, 0x01dd, 0x0259, 0x025b, 0x0192, 0x0260, 0x0263, 0x0269, 0x0268, 0x0199, 0x026f, 0x0272, 0x0275,
743
0
            0x01a1, 0x01a5, 0x0280, 0x01a8, 0x0283, 0x01ad, 0x0288, 0x01b0, 0x028a, 0x028b, 0x01b4, 0x01b6, 0x0292,
744
0
            0x01b9, 0x01bd, 0x01c6, 0x01c6, 0x01c9, 0x01c9, 0x01cc, 0x01cc, 0x01dc, 0x01df, 0x01ef, 0x01f3, 0x01f3,
745
0
            0x01f5, 0x0195, 0x01bf, 0x01f9, 0x021f, 0x019e, 0x0223, 0x0233, 0x2c65, 0x023c, 0x019a, 0x2c66, 0x0242,
746
0
            0x0180, 0x0289, 0x028c, 0x0247, 0x024f, 0x03b9, 0x0371, 0x0373, 0x0377, 0x03f3, 0x03ac, 0x03ad, 0x03af,
747
0
            0x03cc, 0x03cd, 0x03ce, 0x03b1, 0x03c1, 0x03c3, 0x03cb, 0x03c3, 0x03d7, 0x03b2, 0x03b8, 0x03c6, 0x03c0,
748
0
            0x03d9, 0x03ef, 0x03ba, 0x03c1, 0x03b8, 0x03b5, 0x03f8, 0x03f2, 0x03fb, 0x037b, 0x037d, 0x0450, 0x045f,
749
0
            0x0430, 0x044f, 0x0461, 0x0481, 0x048b, 0x04bf, 0x04cf, 0x04c2, 0x04ce, 0x04d1, 0x052f, 0x0561, 0x0586,
750
0
            0x2d00, 0x2d25, 0x2d27, 0x2d2d, 0x13f0, 0x13f5, 0x0432, 0x0434, 0x043e, 0x0441, 0x0442, 0x0442, 0x044a,
751
0
            0x0463, 0xa64b, 0x1c8a, 0x10d0, 0x10fa, 0x10fd, 0x10ff, 0x1e01, 0x1e95, 0x1e61, 0x1ea1, 0x1eff, 0x1f00,
752
0
            0x1f07, 0x1f10, 0x1f15, 0x1f20, 0x1f27, 0x1f30, 0x1f37, 0x1f40, 0x1f45, 0x1f51, 0x1f53, 0x1f55, 0x1f57,
753
0
            0x1f60, 0x1f67, 0x1fb0, 0x1fb1, 0x1f70, 0x1f71, 0x03b9, 0x1f72, 0x1f75, 0x1fd0, 0x1fd1, 0x1f76, 0x1f77,
754
0
            0x1fe0, 0x1fe1, 0x1f7a, 0x1f7b, 0x1fe5, 0x1f78, 0x1f79, 0x1f7c, 0x1f7d, 0x03c9, 0x006b, 0x00e5, 0x214e,
755
0
            0x2170, 0x217f, 0x2184, 0x24d0, 0x24e9, 0x2c30, 0x2c5f, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c68, 0x2c6c,
756
0
            0x0251, 0x0271, 0x0250, 0x0252, 0x2c73, 0x2c76, 0x023f, 0x0240, 0x2c81, 0x2ce3, 0x2cec, 0x2cee, 0x2cf3,
757
0
            0xa641, 0xa66d, 0xa681, 0xa69b, 0xa723, 0xa72f, 0xa733, 0xa76f, 0xa77a, 0xa77c, 0x1d79, 0xa77f, 0xa787,
758
0
            0xa78c, 0x0265, 0xa791, 0xa793, 0xa797, 0xa7a9, 0x0266, 0x025c, 0x0261, 0x026c, 0x026a, 0x029e, 0x0287,
759
0
            0x029d, 0xab53, 0xa7b5, 0xa7c3, 0xa794, 0x0282, 0x1d8e, 0xa7c8, 0xa7ca, 0x0264, 0xa7cd, 0xa7db, 0x019b,
760
0
            0x0277, 0x027c, 0xa7f6, 0xab4b, 0xab4c, 0x13a0, 0x13ef, 0xff41, 0xff5a, 0x10428, 0x1044f, 0x104d8,
761
0
            0x104fb, 0x10597, 0x105a1, 0x105a3, 0x105b1, 0x105b3, 0x105b9, 0x105bb, 0x105bc, 0x10cc0, 0x10cf2,
762
0
            0x10d70, 0x10d85, 0x118c0, 0x118df, 0x16e60, 0x16e7f, 0x16ebb, 0x16ed3, 0x1df41, 0x1df49, 0x1df4b,
763
0
            0x1df4e, 0x1df52, 0x1df69, 0x1df6f, 0x1df73, 0x1df7f, 0x1e922, 0x1e943
764
0
        };
765
0
        static const unsigned FOLD_MAP_2[] = {
766
0
            S(0x00df), S(0x0130), S(0x0149), S(0x01f0), S(0x0587), S(0x1e96), S(0x1e97), S(0x1e98), S(0x1e99),
767
0
            S(0x1e9a), S(0x1e9e), S(0x1f50), R(0x1f80,0x1f87), R(0x1f88,0x1f8f), R(0x1f90,0x1f97), R(0x1f98,0x1f9f),
768
0
            R(0x1fa0,0x1fa7), R(0x1fa8,0x1faf), S(0x1fb2), S(0x1fb3), S(0x1fb4), S(0x1fb6), S(0x1fbc), S(0x1fc2),
769
0
            S(0x1fc3), S(0x1fc4), S(0x1fc6), S(0x1fcc), S(0x1fd6), S(0x1fe4), S(0x1fe6), S(0x1ff2), S(0x1ff3),
770
0
            S(0x1ff4), S(0x1ff6), S(0x1ffc), S(0xfb00), S(0xfb01), S(0xfb02), S(0xfb05), S(0xfb06), S(0xfb13),
771
0
            S(0xfb14), S(0xfb15), S(0xfb16), S(0xfb17), S(0x1df95)
772
0
        };
773
0
        static const unsigned FOLD_MAP_2_DATA[] = {
774
0
            0x0073,0x0073, 0x0069,0x0307, 0x02bc,0x006e, 0x006a,0x030c, 0x0565,0x0582, 0x0068,0x0331, 0x0074,0x0308,
775
0
            0x0077,0x030a, 0x0079,0x030a, 0x0061,0x02be, 0x0073,0x0073, 0x03c5,0x0313, 0x1f00,0x03b9, 0x1f07,0x03b9,
776
0
            0x1f00,0x03b9, 0x1f07,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f60,0x03b9,
777
0
            0x1f67,0x03b9, 0x1f60,0x03b9, 0x1f67,0x03b9, 0x1f70,0x03b9, 0x03b1,0x03b9, 0x03ac,0x03b9, 0x03b1,0x0342,
778
0
            0x03b1,0x03b9, 0x1f74,0x03b9, 0x03b7,0x03b9, 0x03ae,0x03b9, 0x03b7,0x0342, 0x03b7,0x03b9, 0x03b9,0x0342,
779
0
            0x03c1,0x0313, 0x03c5,0x0342, 0x1f7c,0x03b9, 0x03c9,0x03b9, 0x03ce,0x03b9, 0x03c9,0x0342, 0x03c9,0x03b9,
780
0
            0x0066,0x0066, 0x0066,0x0069, 0x0066,0x006c, 0x0073,0x0074, 0x0073,0x0074, 0x0574,0x0576, 0x0574,0x0565,
781
0
            0x0574,0x056b, 0x057e,0x0576, 0x0574,0x056d, 0x0073,0x0073
782
0
        };
783
0
        static const unsigned FOLD_MAP_3[] = {
784
0
            S(0x0390), S(0x03b0), S(0x1f52), S(0x1f54), S(0x1f56), S(0x1fb7), S(0x1fc7), S(0x1fd2), S(0x1fd3),
785
0
            S(0x1fd7), S(0x1fe2), S(0x1fe3), S(0x1fe7), S(0x1ff7), S(0xfb03), S(0xfb04)
786
0
        };
787
0
        static const unsigned FOLD_MAP_3_DATA[] = {
788
0
            0x03b9,0x0308,0x0301, 0x03c5,0x0308,0x0301, 0x03c5,0x0313,0x0300, 0x03c5,0x0313,0x0301,
789
0
            0x03c5,0x0313,0x0342, 0x03b1,0x0342,0x03b9, 0x03b7,0x0342,0x03b9, 0x03b9,0x0308,0x0300,
790
0
            0x03b9,0x0308,0x0301, 0x03b9,0x0308,0x0342, 0x03c5,0x0308,0x0300, 0x03c5,0x0308,0x0301,
791
0
            0x03c5,0x0308,0x0342, 0x03c9,0x0342,0x03b9, 0x0066,0x0066,0x0069, 0x0066,0x0066,0x006c
792
0
        };
793
0
#undef R
794
0
#undef S
795
0
        static const struct {
796
0
            const unsigned* map;
797
0
            const unsigned* data;
798
0
            size_t map_size;
799
0
            unsigned n_codepoints;
800
0
        } FOLD_MAP_LIST[] = {
801
0
            { FOLD_MAP_1, FOLD_MAP_1_DATA, SIZEOF_ARRAY(FOLD_MAP_1), 1 },
802
0
            { FOLD_MAP_2, FOLD_MAP_2_DATA, SIZEOF_ARRAY(FOLD_MAP_2), 2 },
803
0
            { FOLD_MAP_3, FOLD_MAP_3_DATA, SIZEOF_ARRAY(FOLD_MAP_3), 3 }
804
0
        };
805
806
0
        int i;
807
808
        /* Fast path for ASCII characters. */
809
0
        if(codepoint <= 0x7f) {
810
0
            info->codepoints[0] = codepoint;
811
0
            if(ISUPPER_(codepoint))
812
0
                info->codepoints[0] += 'a' - 'A';
813
0
            info->n_codepoints = 1;
814
0
            return;
815
0
        }
816
817
        /* Try to locate the codepoint in any of the maps. */
818
0
        for(i = 0; i < (int) SIZEOF_ARRAY(FOLD_MAP_LIST); i++) {
819
0
            int index;
820
821
0
            index = md_unicode_bsearch__(codepoint, FOLD_MAP_LIST[i].map, FOLD_MAP_LIST[i].map_size);
822
0
            if(index >= 0) {
823
                /* Found the mapping. */
824
0
                unsigned n_codepoints = FOLD_MAP_LIST[i].n_codepoints;
825
0
                const unsigned* map = FOLD_MAP_LIST[i].map;
826
0
                const unsigned* codepoints = FOLD_MAP_LIST[i].data + (index * n_codepoints);
827
828
0
                memcpy(info->codepoints, codepoints, sizeof(unsigned) * n_codepoints);
829
0
                info->n_codepoints = n_codepoints;
830
831
0
                if(FOLD_MAP_LIST[i].map[index] != codepoint) {
832
                    /* The found mapping maps whole range of codepoints,
833
                     * i.e. we have to offset info->codepoints[0] accordingly. */
834
0
                    if((map[index] & 0x00ffffff)+1 == codepoints[0]) {
835
                        /* Alternating type of the range. */
836
0
                        info->codepoints[0] = codepoint + ((codepoint & 0x1) == (map[index] & 0x1) ? 1 : 0);
837
0
                    } else {
838
                        /* Range to range kind of mapping. */
839
0
                        info->codepoints[0] += (codepoint - (map[index] & 0x00ffffff));
840
0
                    }
841
0
                }
842
843
0
                return;
844
0
            }
845
0
        }
846
847
        /* No mapping found. Map the codepoint to itself. */
848
0
        info->codepoints[0] = codepoint;
849
0
        info->n_codepoints = 1;
850
0
    }
851
#endif
852
853
854
#if defined MD4C_USE_UTF16
855
    #define IS_UTF16_SURROGATE_HI(word)     (((WORD)(word) & 0xfc00) == 0xd800)
856
    #define IS_UTF16_SURROGATE_LO(word)     (((WORD)(word) & 0xfc00) == 0xdc00)
857
    #define UTF16_DECODE_SURROGATE(hi, lo)  (0x10000 + ((((unsigned)(hi) & 0x3ff) << 10) | (((unsigned)(lo) & 0x3ff) << 0)))
858
859
    static unsigned
860
    md_decode_utf16le__(const CHAR* str, SZ str_size, SZ* p_size)
861
    {
862
        if(IS_UTF16_SURROGATE_HI(str[0])) {
863
            if(1 < str_size && IS_UTF16_SURROGATE_LO(str[1])) {
864
                if(p_size != NULL)
865
                    *p_size = 2;
866
                return UTF16_DECODE_SURROGATE(str[0], str[1]);
867
            }
868
        }
869
870
        if(p_size != NULL)
871
            *p_size = 1;
872
        return str[0];
873
    }
874
875
    static unsigned
876
    md_decode_utf16le_before__(MD_CTX* ctx, OFF off)
877
    {
878
        if(off > 2 && IS_UTF16_SURROGATE_HI(CH(off-2)) && IS_UTF16_SURROGATE_LO(CH(off-1)))
879
            return UTF16_DECODE_SURROGATE(CH(off-2), CH(off-1));
880
881
        return CH(off-1);
882
    }
883
884
    /* No whitespace uses surrogates, so no decoding needed here. */
885
    #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint)
886
    #define ISUNICODEWHITESPACE(off)        md_is_unicode_whitespace__(CH(off))
887
    #define ISUNICODEWHITESPACEBEFORE(off)  md_is_unicode_whitespace__(CH((off)-1))
888
889
    #define ISUNICODEPUNCT(off)             md_is_unicode_punct__(md_decode_utf16le__(STR(off), ctx->size - (off), NULL))
890
    #define ISUNICODEPUNCTBEFORE(off)       md_is_unicode_punct__(md_decode_utf16le_before__(ctx, off))
891
892
    static inline int
893
    md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size)
894
    {
895
        return md_decode_utf16le__(str+off, str_size-off, p_char_size);
896
    }
897
#elif defined MD4C_USE_UTF8
898
0
    #define IS_UTF8_LEAD1(byte)     ((unsigned char)(byte) <= 0x7f)
899
0
    #define IS_UTF8_LEAD2(byte)     (((unsigned char)(byte) & 0xe0) == 0xc0)
900
0
    #define IS_UTF8_LEAD3(byte)     (((unsigned char)(byte) & 0xf0) == 0xe0)
901
0
    #define IS_UTF8_LEAD4(byte)     (((unsigned char)(byte) & 0xf8) == 0xf0)
902
0
    #define IS_UTF8_TAIL(byte)      (((unsigned char)(byte) & 0xc0) == 0x80)
903
904
    static unsigned
905
    md_decode_utf8__(const CHAR* str, SZ str_size, SZ* p_size)
906
0
    {
907
0
        if(!IS_UTF8_LEAD1(str[0])) {
908
0
            if(IS_UTF8_LEAD2(str[0])) {
909
0
                if(1 < str_size && IS_UTF8_TAIL(str[1])) {
910
0
                    if(p_size != NULL)
911
0
                        *p_size = 2;
912
913
0
                    return (((unsigned int)str[0] & 0x1f) << 6) |
914
0
                           (((unsigned int)str[1] & 0x3f) << 0);
915
0
                }
916
0
            } else if(IS_UTF8_LEAD3(str[0])) {
917
0
                if(2 < str_size && IS_UTF8_TAIL(str[1]) && IS_UTF8_TAIL(str[2])) {
918
0
                    if(p_size != NULL)
919
0
                        *p_size = 3;
920
921
0
                    return (((unsigned int)str[0] & 0x0f) << 12) |
922
0
                           (((unsigned int)str[1] & 0x3f) << 6) |
923
0
                           (((unsigned int)str[2] & 0x3f) << 0);
924
0
                }
925
0
            } else if(IS_UTF8_LEAD4(str[0])) {
926
0
                if(3 < str_size && IS_UTF8_TAIL(str[1]) && IS_UTF8_TAIL(str[2]) && IS_UTF8_TAIL(str[3])) {
927
0
                    if(p_size != NULL)
928
0
                        *p_size = 4;
929
930
0
                    return (((unsigned int)str[0] & 0x07) << 18) |
931
0
                           (((unsigned int)str[1] & 0x3f) << 12) |
932
0
                           (((unsigned int)str[2] & 0x3f) << 6) |
933
0
                           (((unsigned int)str[3] & 0x3f) << 0);
934
0
                }
935
0
            }
936
0
        }
937
938
0
        if(p_size != NULL)
939
0
            *p_size = 1;
940
0
        return (unsigned) str[0];
941
0
    }
942
943
    static unsigned
944
    md_decode_utf8_before__(MD_CTX* ctx, OFF off)
945
0
    {
946
0
        if(!IS_UTF8_LEAD1(CH(off-1))) {
947
0
            if(off > 1 && IS_UTF8_LEAD2(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
948
0
                return (((unsigned int)CH(off-2) & 0x1f) << 6) |
949
0
                       (((unsigned int)CH(off-1) & 0x3f) << 0);
950
951
0
            if(off > 2 && IS_UTF8_LEAD3(CH(off-3)) && IS_UTF8_TAIL(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
952
0
                return (((unsigned int)CH(off-3) & 0x0f) << 12) |
953
0
                       (((unsigned int)CH(off-2) & 0x3f) << 6) |
954
0
                       (((unsigned int)CH(off-1) & 0x3f) << 0);
955
956
0
            if(off > 3 && IS_UTF8_LEAD4(CH(off-4)) && IS_UTF8_TAIL(CH(off-3)) && IS_UTF8_TAIL(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
957
0
                return (((unsigned int)CH(off-4) & 0x07) << 18) |
958
0
                       (((unsigned int)CH(off-3) & 0x3f) << 12) |
959
0
                       (((unsigned int)CH(off-2) & 0x3f) << 6) |
960
0
                       (((unsigned int)CH(off-1) & 0x3f) << 0);
961
0
        }
962
963
0
        return (unsigned) CH(off-1);
964
0
    }
965
966
0
    #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint)
967
0
    #define ISUNICODEWHITESPACE(off)        md_is_unicode_whitespace__(md_decode_utf8__(STR(off), ctx->size - (off), NULL))
968
0
    #define ISUNICODEWHITESPACEBEFORE(off)  md_is_unicode_whitespace__(md_decode_utf8_before__(ctx, off))
969
970
0
    #define ISUNICODEPUNCT(off)             md_is_unicode_punct__(md_decode_utf8__(STR(off), ctx->size - (off), NULL))
971
0
    #define ISUNICODEPUNCTBEFORE(off)       md_is_unicode_punct__(md_decode_utf8_before__(ctx, off))
972
973
    static inline unsigned
974
    md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size)
975
0
    {
976
0
        return md_decode_utf8__(str+off, str_size-off, p_char_size);
977
0
    }
978
#else
979
    #define ISUNICODEWHITESPACE_(codepoint) ISWHITESPACE_(codepoint)
980
    #define ISUNICODEWHITESPACE(off)        ISWHITESPACE(off)
981
    #define ISUNICODEWHITESPACEBEFORE(off)  ISWHITESPACE((off)-1)
982
983
    #define ISUNICODEPUNCT(off)             ISPUNCT(off)
984
    #define ISUNICODEPUNCTBEFORE(off)       ISPUNCT((off)-1)
985
986
    static inline void
987
    md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info)
988
    {
989
        info->codepoints[0] = codepoint;
990
        if(ISUPPER_(codepoint))
991
            info->codepoints[0] += 'a' - 'A';
992
        info->n_codepoints = 1;
993
    }
994
995
    static inline unsigned
996
    md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_size)
997
    {
998
        MD_UNUSED(str_size);
999
        *p_size = 1;
1000
        return (unsigned) str[off];
1001
    }
1002
#endif
1003
1004
1005
/*************************************
1006
 ***  Helper string manipulations  ***
1007
 *************************************/
1008
1009
/* Fill buffer with copy of the string between 'beg' and 'end' but replace any
1010
 * line breaks with given replacement character.
1011
 *
1012
 * NOTE: Caller is responsible to make sure the buffer is large enough.
1013
 * (Given the output is always shorter than input, (end - beg) is good idea
1014
 * what the caller should allocate.)
1015
 */
1016
static void
1017
md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, MD_SIZE n_lines,
1018
               CHAR line_break_replacement_char, CHAR* buffer, SZ* p_size)
1019
0
{
1020
0
    CHAR* ptr = buffer;
1021
0
    int line_index = 0;
1022
0
    OFF off = beg;
1023
1024
0
    MD_UNUSED(n_lines);
1025
1026
0
    while(1) {
1027
0
        const MD_LINE* line = &lines[line_index];
1028
0
        OFF line_end = line->end;
1029
0
        if(end < line_end)
1030
0
            line_end = end;
1031
1032
0
        while(off < line_end) {
1033
0
            *ptr = CH(off);
1034
0
            ptr++;
1035
0
            off++;
1036
0
        }
1037
1038
0
        if(off >= end) {
1039
0
            *p_size = (MD_SIZE)(ptr - buffer);
1040
0
            return;
1041
0
        }
1042
1043
0
        *ptr = line_break_replacement_char;
1044
0
        ptr++;
1045
1046
0
        line_index++;
1047
0
        off = lines[line_index].beg;
1048
0
    }
1049
0
}
1050
1051
/* Wrapper of md_merge_lines() which allocates new buffer for the output string.
1052
 */
1053
static int
1054
md_merge_lines_alloc(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, MD_SIZE n_lines,
1055
                    CHAR line_break_replacement_char, CHAR** p_str, SZ* p_size)
1056
0
{
1057
0
    CHAR* buffer;
1058
1059
0
    buffer = (CHAR*) malloc(sizeof(CHAR) * (end - beg));
1060
0
    if(buffer == NULL) {
1061
0
        MD_LOG("malloc() failed.");
1062
0
        return -1;
1063
0
    }
1064
1065
0
    md_merge_lines(ctx, beg, end, lines, n_lines,
1066
0
                line_break_replacement_char, buffer, p_size);
1067
1068
0
    *p_str = buffer;
1069
0
    return 0;
1070
0
}
1071
1072
static OFF
1073
md_skip_unicode_whitespace(const CHAR* label, OFF off, SZ size)
1074
0
{
1075
0
    SZ char_size;
1076
0
    unsigned codepoint;
1077
1078
0
    while(off < size) {
1079
0
        codepoint = md_decode_unicode(label, off, size, &char_size);
1080
0
        if(!ISUNICODEWHITESPACE_(codepoint)  &&  !ISNEWLINE_(label[off]))
1081
0
            break;
1082
0
        off += char_size;
1083
0
    }
1084
1085
0
    return off;
1086
0
}
1087
1088
1089
/******************************
1090
 ***  Recognizing raw HTML  ***
1091
 ******************************/
1092
1093
/* md_is_html_tag() may be called when processing inlines (inline raw HTML)
1094
 * or when breaking document to blocks (checking for start of HTML block type 7).
1095
 *
1096
 * When breaking document to blocks, we do not yet know line boundaries, but
1097
 * in that case the whole tag has to live on a single line. We distinguish this
1098
 * by n_lines == 0.
1099
 */
1100
static int
1101
md_is_html_tag(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1102
0
{
1103
0
    int attr_state;
1104
0
    OFF off = beg;
1105
0
    OFF line_end = (n_lines > 0) ? lines[0].end : ctx->size;
1106
0
    MD_SIZE line_index = 0;
1107
1108
0
    MD_ASSERT(CH(beg) == _T('<'));
1109
1110
0
    if(off + 1 >= line_end)
1111
0
        return false;
1112
0
    off++;
1113
1114
    /* For parsing attributes, we need a little state automaton below.
1115
     * State -1: no attributes are allowed.
1116
     * State 0: attribute could follow after some whitespace.
1117
     * State 1: after a whitespace (attribute name may follow).
1118
     * State 2: after attribute name ('=' MAY follow).
1119
     * State 3: after '=' (value specification MUST follow).
1120
     * State 41: in middle of unquoted attribute value.
1121
     * State 42: in middle of single-quoted attribute value.
1122
     * State 43: in middle of double-quoted attribute value.
1123
     */
1124
0
    attr_state = 0;
1125
1126
0
    if(CH(off) == _T('/')) {
1127
        /* Closer tag "</ ... >". No attributes may be present. */
1128
0
        attr_state = -1;
1129
0
        off++;
1130
0
    }
1131
1132
    /* Tag name */
1133
0
    if(off >= line_end  ||  !ISALPHA(off))
1134
0
        return false;
1135
0
    off++;
1136
0
    while(off < line_end  &&  (ISALNUM(off)  ||  CH(off) == _T('-')))
1137
0
        off++;
1138
1139
    /* (Optional) attributes (if not closer), (optional) '/' (if not closer)
1140
     * and final '>'. */
1141
0
    while(1) {
1142
0
        while(off < line_end  &&  !ISNEWLINE(off)) {
1143
0
            if(attr_state > 40) {
1144
0
                if(attr_state == 41 && (ISBLANK(off) || ISANYOF(off, _T("\"'=<>`")))) {
1145
0
                    attr_state = 0;
1146
0
                    off--;  /* Put the char back for re-inspection in the new state. */
1147
0
                } else if(attr_state == 42 && CH(off) == _T('\'')) {
1148
0
                    attr_state = 0;
1149
0
                } else if(attr_state == 43 && CH(off) == _T('"')) {
1150
0
                    attr_state = 0;
1151
0
                }
1152
0
                off++;
1153
0
            } else if(ISWHITESPACE(off)) {
1154
0
                if(attr_state == 0)
1155
0
                    attr_state = 1;
1156
0
                off++;
1157
0
            } else if(attr_state <= 2 && CH(off) == _T('>')) {
1158
                /* End. */
1159
0
                goto done;
1160
0
            } else if(attr_state <= 2 && CH(off) == _T('/') && off+1 < line_end && CH(off+1) == _T('>')) {
1161
                /* End with digraph '/>' */
1162
0
                off++;
1163
0
                goto done;
1164
0
            } else if((attr_state == 1 || attr_state == 2) && (ISALPHA(off) || CH(off) == _T('_') || CH(off) == _T(':'))) {
1165
0
                off++;
1166
                /* Attribute name */
1167
0
                while(off < line_end && (ISALNUM(off) || ISANYOF(off, _T("_.:-"))))
1168
0
                    off++;
1169
0
                attr_state = 2;
1170
0
            } else if(attr_state == 2 && CH(off) == _T('=')) {
1171
                /* Attribute assignment sign */
1172
0
                off++;
1173
0
                attr_state = 3;
1174
0
            } else if(attr_state == 3) {
1175
                /* Expecting start of attribute value. */
1176
0
                if(CH(off) == _T('"'))
1177
0
                    attr_state = 43;
1178
0
                else if(CH(off) == _T('\''))
1179
0
                    attr_state = 42;
1180
0
                else if(!ISANYOF(off, _T("\"'=<>`"))  &&  !ISNEWLINE(off))
1181
0
                    attr_state = 41;
1182
0
                else
1183
0
                    return false;
1184
0
                off++;
1185
0
            } else {
1186
                /* Anything unexpected. */
1187
0
                return false;
1188
0
            }
1189
0
        }
1190
1191
        /* We have to be on a single line. See definition of start condition
1192
         * of HTML block, type 7. */
1193
0
        if(n_lines == 0)
1194
0
            return false;
1195
1196
0
        line_index++;
1197
0
        if(line_index >= n_lines)
1198
0
            return false;
1199
1200
0
        off = lines[line_index].beg;
1201
0
        line_end = lines[line_index].end;
1202
1203
0
        if(attr_state == 0  ||  attr_state == 41)
1204
0
            attr_state = 1;
1205
1206
0
        if(off >= max_end)
1207
0
            return false;
1208
0
    }
1209
1210
0
done:
1211
0
    if(off >= max_end)
1212
0
        return false;
1213
1214
0
    *p_end = off+1;
1215
0
    return true;
1216
0
}
1217
1218
static int
1219
md_scan_for_html_closer(MD_CTX* ctx, const MD_CHAR* str, MD_SIZE len,
1220
                        const MD_LINE* lines, MD_SIZE n_lines,
1221
                        OFF beg, OFF max_end, OFF* p_end,
1222
                        OFF* p_scan_horizon)
1223
0
{
1224
0
    OFF off = beg;
1225
0
    MD_SIZE line_index = 0;
1226
1227
0
    if(off < *p_scan_horizon  &&  *p_scan_horizon >= max_end - len) {
1228
        /* We have already scanned the range up to the max_end so we know
1229
         * there is nothing to see. */
1230
0
        return false;
1231
0
    }
1232
1233
0
    while(true) {
1234
0
        while(off + len <= lines[line_index].end  &&  off + len <= max_end) {
1235
0
            if(md_ascii_eq(STR(off), str, len)) {
1236
                /* Success. */
1237
0
                *p_end = off + len;
1238
0
                return true;
1239
0
            }
1240
0
            off++;
1241
0
        }
1242
1243
0
        line_index++;
1244
0
        if(off >= max_end  ||  line_index >= n_lines) {
1245
            /* Failure. */
1246
0
            *p_scan_horizon = off;
1247
0
            return false;
1248
0
        }
1249
1250
0
        off = lines[line_index].beg;
1251
0
    }
1252
0
}
1253
1254
static int
1255
md_is_html_comment(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1256
0
{
1257
0
    OFF off = beg;
1258
1259
0
    MD_ASSERT(CH(beg) == _T('<'));
1260
1261
0
    if(off + 4 >= lines[0].end)
1262
0
        return false;
1263
0
    if(CH(off+1) != _T('!')  ||  CH(off+2) != _T('-')  ||  CH(off+3) != _T('-'))
1264
0
        return false;
1265
1266
    /* Skip only "<!" so that we accept also "<!-->" or "<!--->" */
1267
0
    off += 2;
1268
1269
    /* Scan for ordinary comment closer "-->". */
1270
0
    return md_scan_for_html_closer(ctx, _T("-->"), 3,
1271
0
                lines, n_lines, off, max_end, p_end, &ctx->html_comment_horizon);
1272
0
}
1273
1274
static int
1275
md_is_html_processing_instruction(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1276
0
{
1277
0
    OFF off = beg;
1278
1279
0
    if(off + 2 >= lines[0].end)
1280
0
        return false;
1281
0
    if(CH(off+1) != _T('?'))
1282
0
        return false;
1283
0
    off += 2;
1284
1285
0
    return md_scan_for_html_closer(ctx, _T("?>"), 2,
1286
0
                lines, n_lines, off, max_end, p_end, &ctx->html_proc_instr_horizon);
1287
0
}
1288
1289
static int
1290
md_is_html_declaration(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1291
0
{
1292
0
    OFF off = beg;
1293
1294
0
    if(off + 2 >= lines[0].end)
1295
0
        return false;
1296
0
    if(CH(off+1) != _T('!'))
1297
0
        return false;
1298
0
    off += 2;
1299
1300
    /* Declaration name. */
1301
0
    if(off >= lines[0].end  ||  !ISALPHA(off))
1302
0
        return false;
1303
0
    off++;
1304
0
    while(off < lines[0].end  &&  ISALPHA(off))
1305
0
        off++;
1306
1307
0
    return md_scan_for_html_closer(ctx, _T(">"), 1,
1308
0
                lines, n_lines, off, max_end, p_end, &ctx->html_decl_horizon);
1309
0
}
1310
1311
static int
1312
md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1313
0
{
1314
0
    static const CHAR open_str[] = _T("<![CDATA[");
1315
0
    static const SZ open_size = SIZEOF_ARRAY(open_str) - 1;
1316
1317
0
    OFF off = beg;
1318
1319
0
    if(off + open_size >= lines[0].end)
1320
0
        return false;
1321
0
    if(memcmp(STR(off), open_str, open_size * sizeof(CHAR)) != 0)
1322
0
        return false;
1323
0
    off += open_size;
1324
1325
0
    return md_scan_for_html_closer(ctx, _T("]]>"), 3,
1326
0
                lines, n_lines, off, max_end, p_end, &ctx->html_cdata_horizon);
1327
0
}
1328
1329
static int
1330
md_is_html_any(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg, OFF max_end, OFF* p_end)
1331
0
{
1332
0
    MD_ASSERT(CH(beg) == _T('<'));
1333
0
    return (md_is_html_tag(ctx, lines, n_lines, beg, max_end, p_end)  ||
1334
0
            md_is_html_comment(ctx, lines, n_lines, beg, max_end, p_end)  ||
1335
0
            md_is_html_processing_instruction(ctx, lines, n_lines, beg, max_end, p_end)  ||
1336
0
            md_is_html_declaration(ctx, lines, n_lines, beg, max_end, p_end)  ||
1337
0
            md_is_html_cdata(ctx, lines, n_lines, beg, max_end, p_end));
1338
0
}
1339
1340
1341
/****************************
1342
 ***  Recognizing Entity  ***
1343
 ****************************/
1344
1345
static int
1346
md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1347
0
{
1348
0
    OFF off = beg;
1349
0
    MD_UNUSED(ctx);
1350
1351
0
    while(off < max_end  &&  ISXDIGIT_(text[off])  &&  off - beg <= 8)
1352
0
        off++;
1353
1354
0
    if(1 <= off - beg  &&  off - beg <= 6) {
1355
0
        *p_end = off;
1356
0
        return true;
1357
0
    } else {
1358
0
        return false;
1359
0
    }
1360
0
}
1361
1362
static int
1363
md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1364
0
{
1365
0
    OFF off = beg;
1366
0
    MD_UNUSED(ctx);
1367
1368
0
    while(off < max_end  &&  ISDIGIT_(text[off])  &&  off - beg <= 8)
1369
0
        off++;
1370
1371
0
    if(1 <= off - beg  &&  off - beg <= 7) {
1372
0
        *p_end = off;
1373
0
        return true;
1374
0
    } else {
1375
0
        return false;
1376
0
    }
1377
0
}
1378
1379
static int
1380
md_is_named_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1381
0
{
1382
0
    OFF off = beg;
1383
0
    MD_UNUSED(ctx);
1384
1385
0
    if(off < max_end  &&  ISALPHA_(text[off]))
1386
0
        off++;
1387
0
    else
1388
0
        return false;
1389
1390
0
    while(off < max_end  &&  ISALNUM_(text[off])  &&  off - beg <= 48)
1391
0
        off++;
1392
1393
0
    if(2 <= off - beg  &&  off - beg <= 48) {
1394
0
        *p_end = off;
1395
0
        return true;
1396
0
    } else {
1397
0
        return false;
1398
0
    }
1399
0
}
1400
1401
static int
1402
md_is_entity_str(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
1403
0
{
1404
0
    int is_contents;
1405
0
    OFF off = beg;
1406
1407
0
    MD_ASSERT(text[off] == _T('&'));
1408
0
    off++;
1409
1410
0
    if(off+2 < max_end  &&  text[off] == _T('#')  &&  (text[off+1] == _T('x') || text[off+1] == _T('X')))
1411
0
        is_contents = md_is_hex_entity_contents(ctx, text, off+2, max_end, &off);
1412
0
    else if(off+1 < max_end  &&  text[off] == _T('#'))
1413
0
        is_contents = md_is_dec_entity_contents(ctx, text, off+1, max_end, &off);
1414
0
    else
1415
0
        is_contents = md_is_named_entity_contents(ctx, text, off, max_end, &off);
1416
1417
0
    if(is_contents  &&  off < max_end  &&  text[off] == _T(';')) {
1418
0
        *p_end = off+1;
1419
0
        return true;
1420
0
    } else {
1421
0
        return false;
1422
0
    }
1423
0
}
1424
1425
static inline int
1426
md_is_entity(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
1427
0
{
1428
0
    return md_is_entity_str(ctx, ctx->text, beg, max_end, p_end);
1429
0
}
1430
1431
1432
/******************************
1433
 ***  Attribute Management  ***
1434
 ******************************/
1435
1436
typedef struct MD_ATTRIBUTE_BUILD_tag MD_ATTRIBUTE_BUILD;
1437
struct MD_ATTRIBUTE_BUILD_tag {
1438
    CHAR* text;
1439
    MD_TEXTTYPE* substr_types;
1440
    OFF* substr_offsets;
1441
    SZ substr_count;
1442
    SZ substr_alloc;
1443
    MD_TEXTTYPE trivial_types[1];
1444
    OFF trivial_offsets[2];
1445
};
1446
1447
1448
0
#define MD_BUILD_ATTR_NO_ESCAPES    0x0001
1449
1450
static int
1451
md_build_attr_append_substr(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build,
1452
                            MD_TEXTTYPE type, OFF off)
1453
0
{
1454
0
    if(build->substr_count >= build->substr_alloc) {
1455
0
        MD_TEXTTYPE* new_substr_types;
1456
0
        OFF* new_substr_offsets;
1457
0
        SZ new_alloc = (build->substr_alloc > 0
1458
0
                ? build->substr_alloc + build->substr_alloc / 2
1459
0
                : 8);
1460
1461
0
        new_substr_types = (MD_TEXTTYPE*) realloc(build->substr_types,
1462
0
                                    new_alloc * sizeof(MD_TEXTTYPE));
1463
0
        if(new_substr_types == NULL) {
1464
0
            MD_LOG("realloc() failed.");
1465
0
            return -1;
1466
0
        }
1467
0
        build->substr_types = new_substr_types;
1468
1469
        /* Note +1 to reserve space for final offset (== raw_size). */
1470
0
        new_substr_offsets = (OFF*) realloc(build->substr_offsets,
1471
0
                                    (new_alloc+1) * sizeof(OFF));
1472
0
        if(new_substr_offsets == NULL) {
1473
0
            MD_LOG("realloc() failed.");
1474
0
            return -1;
1475
0
        }
1476
0
        build->substr_offsets = new_substr_offsets;
1477
0
        build->substr_alloc = new_alloc;
1478
0
    }
1479
1480
0
    build->substr_types[build->substr_count] = type;
1481
0
    build->substr_offsets[build->substr_count] = off;
1482
0
    build->substr_count++;
1483
0
    return 0;
1484
0
}
1485
1486
static void
1487
md_free_attribute(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build)
1488
0
{
1489
0
    MD_UNUSED(ctx);
1490
1491
    /* A trivial build aliases caller-owned storage and must not be freed.
1492
     * Every other build owns all three pointers from the moment
1493
     * md_build_attribute() leaves the trivial branch, even if a growth
1494
     * realloc later failed while substr_alloc was still 0. Clearing them
1495
     * keeps this idempotent, which matters because md_build_attribute()
1496
     * frees on its own abort path before the caller frees again. */
1497
0
    if(build->substr_types != build->trivial_types) {
1498
0
        free(build->text);
1499
0
        free(build->substr_types);
1500
0
        free(build->substr_offsets);
1501
0
        build->text = NULL;
1502
0
        build->substr_types = NULL;
1503
0
        build->substr_offsets = NULL;
1504
0
        build->substr_alloc = 0;
1505
0
        build->substr_count = 0;
1506
0
    }
1507
0
}
1508
1509
static int
1510
md_build_attribute(MD_CTX* ctx, const CHAR* raw_text, SZ raw_size,
1511
                   unsigned flags, MD_ATTRIBUTE* attr, MD_ATTRIBUTE_BUILD* build)
1512
0
{
1513
0
    OFF raw_off, off;
1514
0
    int is_trivial;
1515
0
    int ret = 0;
1516
1517
0
    memset(build, 0, sizeof(MD_ATTRIBUTE_BUILD));
1518
1519
    /* If there is no backslash and no ampersand, build trivial attribute
1520
     * without any malloc(). */
1521
0
    is_trivial = true;
1522
0
    for(raw_off = 0; raw_off < raw_size; raw_off++) {
1523
0
        if(ISANYOF3_(raw_text[raw_off], _T('\\'), _T('&'), _T('\0'))) {
1524
0
            is_trivial = false;
1525
0
            break;
1526
0
        }
1527
0
    }
1528
1529
0
    if(is_trivial) {
1530
0
        build->text = (CHAR*) (raw_size ? raw_text : NULL);
1531
0
        build->substr_types = build->trivial_types;
1532
0
        build->substr_offsets = build->trivial_offsets;
1533
0
        build->substr_count = 1;
1534
0
        build->substr_alloc = 0;
1535
0
        build->trivial_types[0] = MD_TEXT_NORMAL;
1536
0
        build->trivial_offsets[0] = 0;
1537
0
        build->trivial_offsets[1] = raw_size;
1538
0
        off = raw_size;
1539
0
    } else {
1540
0
        build->text = (CHAR*) malloc(raw_size * sizeof(CHAR));
1541
0
        if(build->text == NULL) {
1542
0
            MD_LOG("malloc() failed.");
1543
0
            goto abort;
1544
0
        }
1545
1546
0
        raw_off = 0;
1547
0
        off = 0;
1548
1549
0
        while(raw_off < raw_size) {
1550
0
            if(raw_text[raw_off] == _T('\0')) {
1551
0
                MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_NULLCHAR, off));
1552
0
                memcpy(build->text + off, raw_text + raw_off, sizeof(CHAR));
1553
0
                off++;
1554
0
                raw_off++;
1555
0
                continue;
1556
0
            }
1557
1558
0
            if(raw_text[raw_off] == _T('&')) {
1559
0
                OFF ent_end;
1560
1561
0
                if(md_is_entity_str(ctx, raw_text, raw_off, raw_size, &ent_end)) {
1562
0
                    MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_ENTITY, off));
1563
0
                    memcpy(build->text + off, raw_text + raw_off, (ent_end - raw_off) * sizeof(CHAR));
1564
0
                    off += ent_end - raw_off;
1565
0
                    raw_off = ent_end;
1566
0
                    continue;
1567
0
                }
1568
0
            }
1569
1570
0
            if(build->substr_count == 0  ||  build->substr_types[build->substr_count-1] != MD_TEXT_NORMAL)
1571
0
                MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_NORMAL, off));
1572
1573
0
            if(!(flags & MD_BUILD_ATTR_NO_ESCAPES)  &&
1574
0
               raw_text[raw_off] == _T('\\')  &&  raw_off+1 < raw_size  &&
1575
0
               (ISPUNCT_(raw_text[raw_off+1]) || ISNEWLINE_(raw_text[raw_off+1])))
1576
0
                raw_off++;
1577
1578
0
            build->text[off++] = raw_text[raw_off++];
1579
0
        }
1580
0
        build->substr_offsets[build->substr_count] = off;
1581
0
    }
1582
1583
0
    attr->text = build->text;
1584
0
    attr->size = off;
1585
0
    attr->substr_offsets = build->substr_offsets;
1586
0
    attr->substr_types = build->substr_types;
1587
0
    return 0;
1588
1589
0
abort:
1590
0
    md_free_attribute(ctx, build);
1591
0
    return -1;
1592
0
}
1593
1594
1595
/**************************************************
1596
 ***  Hashtable (with a text label as its key)  ***
1597
 **************************************************/
1598
1599
0
#define MD_FNV1A_BASE       2166136261U
1600
0
#define MD_FNV1A_PRIME      16777619U
1601
1602
static inline unsigned
1603
md_fnv1a(unsigned base, const void* data, size_t n)
1604
0
{
1605
0
    const unsigned char* buf = (const unsigned char*) data;
1606
0
    unsigned hash = base;
1607
0
    size_t i;
1608
1609
0
    for(i = 0; i < n; i++) {
1610
0
        hash ^= buf[i];
1611
0
        hash *= MD_FNV1A_PRIME;
1612
0
    }
1613
1614
0
    return hash;
1615
0
}
1616
1617
/* Label equivalence is quite complicated with regards to whitespace and case
1618
 * folding. This complicates computing a hash of it as well as direct comparison
1619
 * of two labels. */
1620
1621
static unsigned
1622
md_label_hash(const CHAR* label, SZ size)
1623
0
{
1624
0
    unsigned hash = MD_FNV1A_BASE;
1625
0
    OFF off;
1626
0
    unsigned codepoint;
1627
0
    int is_whitespace = false;
1628
1629
0
    off = md_skip_unicode_whitespace(label, 0, size);
1630
0
    while(off < size) {
1631
0
        SZ char_size;
1632
1633
0
        codepoint = md_decode_unicode(label, off, size, &char_size);
1634
0
        is_whitespace = ISUNICODEWHITESPACE_(codepoint) || ISNEWLINE_(label[off]);
1635
1636
0
        if(is_whitespace) {
1637
0
            codepoint = ' ';
1638
0
            hash = md_fnv1a(hash, &codepoint, sizeof(unsigned));
1639
0
            off = md_skip_unicode_whitespace(label, off, size);
1640
0
        } else {
1641
0
            MD_UNICODE_FOLD_INFO fold_info;
1642
1643
0
            md_get_unicode_fold_info(codepoint, &fold_info);
1644
0
            hash = md_fnv1a(hash, fold_info.codepoints, fold_info.n_codepoints * sizeof(unsigned));
1645
0
            off += char_size;
1646
0
        }
1647
0
    }
1648
1649
0
    return hash;
1650
0
}
1651
1652
static OFF
1653
md_label_cmp_load_fold_info(const CHAR* label, OFF off, SZ size,
1654
                            MD_UNICODE_FOLD_INFO* fold_info)
1655
0
{
1656
0
    unsigned codepoint;
1657
0
    SZ char_size;
1658
1659
0
    if(off >= size) {
1660
        /* Treat end of a link label as a whitespace. */
1661
0
        goto whitespace;
1662
0
    }
1663
1664
0
    codepoint = md_decode_unicode(label, off, size, &char_size);
1665
0
    off += char_size;
1666
0
    if(ISUNICODEWHITESPACE_(codepoint)) {
1667
        /* Treat all whitespace as equivalent */
1668
0
        goto whitespace;
1669
0
    }
1670
1671
    /* Get real folding info. */
1672
0
    md_get_unicode_fold_info(codepoint, fold_info);
1673
0
    return off;
1674
1675
0
whitespace:
1676
0
    fold_info->codepoints[0] = _T(' ');
1677
0
    fold_info->n_codepoints = 1;
1678
0
    return md_skip_unicode_whitespace(label, off, size);
1679
0
}
1680
1681
static int
1682
md_label_cmp(const CHAR* a_label, SZ a_size, const CHAR* b_label, SZ b_size)
1683
0
{
1684
0
    OFF a_off;
1685
0
    OFF b_off;
1686
0
    MD_UNICODE_FOLD_INFO a_fi = { { 0 }, 0 };
1687
0
    MD_UNICODE_FOLD_INFO b_fi = { { 0 }, 0 };
1688
0
    OFF a_fi_off = 0;
1689
0
    OFF b_fi_off = 0;
1690
0
    int cmp;
1691
1692
0
    a_off = md_skip_unicode_whitespace(a_label, 0, a_size);
1693
0
    b_off = md_skip_unicode_whitespace(b_label, 0, b_size);
1694
0
    while(a_off < a_size || a_fi_off < a_fi.n_codepoints ||
1695
0
          b_off < b_size || b_fi_off < b_fi.n_codepoints)
1696
0
    {
1697
        /* If needed, load fold info for next char. */
1698
0
        if(a_fi_off >= a_fi.n_codepoints) {
1699
0
            a_fi_off = 0;
1700
0
            a_off = md_label_cmp_load_fold_info(a_label, a_off, a_size, &a_fi);
1701
0
        }
1702
0
        if(b_fi_off >= b_fi.n_codepoints) {
1703
0
            b_fi_off = 0;
1704
0
            b_off = md_label_cmp_load_fold_info(b_label, b_off, b_size, &b_fi);
1705
0
        }
1706
1707
0
        cmp = b_fi.codepoints[b_fi_off] - a_fi.codepoints[a_fi_off];
1708
0
        if(cmp != 0)
1709
0
            return cmp;
1710
1711
0
        a_fi_off++;
1712
0
        b_fi_off++;
1713
0
    }
1714
1715
0
    return 0;
1716
0
}
1717
1718
typedef struct MD_LABEL_HASH_LIST_tag MD_LABEL_HASH_LIST;
1719
struct MD_LABEL_HASH_LIST_tag {
1720
    unsigned n_entries;
1721
    unsigned alloc_entries;
1722
    MD_LABEL_HASH_ENTRY* entries[];
1723
};
1724
1725
static int
1726
md_label_hash_entry_cmp(const void* a, const void* b)
1727
0
{
1728
0
    const MD_LABEL_HASH_ENTRY* a_entry = *(const MD_LABEL_HASH_ENTRY**)a;
1729
0
    const MD_LABEL_HASH_ENTRY* b_entry = *(const MD_LABEL_HASH_ENTRY**)b;
1730
1731
0
    if(a_entry->hash < b_entry->hash)
1732
0
        return -1;
1733
0
    else if(a_entry->hash > b_entry->hash)
1734
0
        return +1;
1735
0
    else
1736
0
        return md_label_cmp(a_entry->label, a_entry->label_size,
1737
0
                    b_entry->label, b_entry->label_size);
1738
0
}
1739
1740
static int
1741
md_label_hash_entry_cmp_for_sort(const void* a, const void* b)
1742
0
{
1743
0
    int cmp;
1744
1745
0
    cmp = md_label_hash_entry_cmp(a, b);
1746
1747
    /* Ensure stability of the sorting. */
1748
0
    if(cmp == 0) {
1749
0
        const MD_LABEL_HASH_ENTRY* a_entry = *(const MD_LABEL_HASH_ENTRY**)a;
1750
0
        const MD_LABEL_HASH_ENTRY* b_entry = *(const MD_LABEL_HASH_ENTRY**)b;
1751
1752
0
        if(a_entry < b_entry)
1753
0
            cmp = -1;
1754
0
        else if(a_entry > b_entry)
1755
0
            cmp = +1;
1756
0
    }
1757
1758
0
    return cmp;
1759
0
}
1760
1761
static int
1762
md_is_complex_label_bucket(MD_LABEL_HASH_TABLE* table, void* bucket)
1763
0
{
1764
0
    if(bucket == NULL)
1765
0
        return false;
1766
1767
0
    return (MD_LABEL_HASH_ENTRY*) bucket < (MD_LABEL_HASH_ENTRY*) table->defs  ||
1768
0
           (MD_LABEL_HASH_ENTRY*) bucket >= (MD_LABEL_HASH_ENTRY*)((char*)table->defs + table->n_defs * table->def_size);
1769
0
}
1770
1771
static int
1772
md_build_label_hashtable(MD_CTX* ctx, MD_LABEL_HASH_TABLE* table)
1773
0
{
1774
0
    unsigned i, j;
1775
1776
0
    if(table->n_defs == 0)
1777
0
        return 0;
1778
1779
0
    table->n_buckets = (table->n_defs * 5) / 4;
1780
0
    table->buckets = malloc(table->n_buckets * sizeof(void*));
1781
0
    if(table->buckets == NULL) {
1782
0
        MD_LOG("malloc() failed.");
1783
0
        goto abort;
1784
0
    }
1785
0
    memset(table->buckets, 0, table->n_buckets * sizeof(void*));
1786
1787
    /* Each member of table->buckets[] can be:
1788
     *  -- NULL,
1789
     *  -- pointer to the MD_LABEL_HASH_ENTRY, or
1790
     *  -- pointer to a MD_LABEL_HASH_LIST, which holds multiple pointers to
1791
     *     such entries.
1792
     */
1793
0
    for(i = 0; i < table->n_defs; i++) {
1794
0
        MD_LABEL_HASH_ENTRY* entry = (MD_LABEL_HASH_ENTRY*) ((char*) table->defs + i * table->def_size);
1795
0
        void** p_bucket = &table->buckets[entry->hash % table->n_buckets];
1796
0
        MD_LABEL_HASH_LIST* list;
1797
1798
0
        if(*p_bucket == NULL) {
1799
            /* The bucket is empty. Make it just point to the entry. */
1800
0
            *p_bucket = entry;
1801
0
            continue;
1802
0
        }
1803
1804
0
        if(!md_is_complex_label_bucket(table, *p_bucket))
1805
0
        {
1806
            /* The bucket already contains one entry. Lets see whether it is the
1807
             * same label (duplicate) or different one (hash conflict). */
1808
0
            MD_LABEL_HASH_ENTRY* old_entry = (MD_LABEL_HASH_ENTRY*) *p_bucket;
1809
1810
0
            if(md_label_cmp(entry->label, entry->label_size,
1811
0
                        old_entry->label, old_entry->label_size) == 0) {
1812
                /* Duplicate label: We may ignore this definition. */
1813
0
                continue;
1814
0
            }
1815
1816
            /* Make the bucket complex, i.e. able to hold more entries. */
1817
0
            list = (MD_LABEL_HASH_LIST*) malloc(sizeof(MD_LABEL_HASH_LIST) +
1818
0
                        2 * sizeof(MD_LABEL_HASH_ENTRY*));
1819
0
            if(list == NULL) {
1820
0
                MD_LOG("malloc() failed.");
1821
0
                goto abort;
1822
0
            }
1823
0
            list->entries[0] = old_entry;
1824
0
            list->entries[1] = entry;
1825
0
            list->n_entries = 2;
1826
0
            list->alloc_entries = 2;
1827
0
            *p_bucket = list;
1828
0
            continue;
1829
0
        }
1830
1831
        /* Append the entry to the complex bucket list.
1832
         *
1833
         * Note in this case we ignore potential duplicates to avoid expensive
1834
         * iterating over the complex bucket. Below, we revisit all the complex
1835
         * buckets and handle it more cheaply after the complex bucket contents
1836
         * is sorted. */
1837
0
        list = (MD_LABEL_HASH_LIST*) *p_bucket;
1838
0
        if(list->n_entries >= list->alloc_entries) {
1839
0
            size_t alloc_entries = list->alloc_entries + list->alloc_entries / 2;
1840
0
            MD_LABEL_HASH_LIST* list_tmp = (MD_LABEL_HASH_LIST*) realloc(list,
1841
0
                        sizeof(MD_LABEL_HASH_LIST) + alloc_entries * sizeof(MD_LABEL_HASH_ENTRY*));
1842
0
            if(list_tmp == NULL) {
1843
0
                MD_LOG("realloc() failed.");
1844
0
                goto abort;
1845
0
            }
1846
0
            list = list_tmp;
1847
0
            list->alloc_entries = (unsigned) alloc_entries;
1848
0
            table->buckets[entry->hash % table->n_buckets] = list;
1849
0
        }
1850
1851
0
        list->entries[list->n_entries] = entry;
1852
0
        list->n_entries++;
1853
0
    }
1854
1855
    /* Sort the complex buckets so we can use bsearch() with them. */
1856
0
    for(i = 0; i < table->n_buckets; i++) {
1857
0
        void* bucket = table->buckets[i];
1858
0
        MD_LABEL_HASH_LIST* list;
1859
1860
0
        if(bucket == NULL)
1861
0
            continue;
1862
1863
0
        if(!md_is_complex_label_bucket(table, bucket))
1864
0
            continue;
1865
1866
0
        list = (MD_LABEL_HASH_LIST*) bucket;
1867
0
        qsort(list->entries, list->n_entries, sizeof(MD_LABEL_HASH_ENTRY*),
1868
0
                    md_label_hash_entry_cmp_for_sort);
1869
1870
        /* Disable all duplicates in the complex bucket by forcing all such
1871
         * records to point to the 1st such entry. I.e. no matter which
1872
         * record is found during the lookup, it will always point to the right
1873
         * definition. */
1874
0
        for(j = 1; j < list->n_entries; j++) {
1875
0
            if(md_label_hash_entry_cmp(&list->entries[j-1], &list->entries[j]) == 0)
1876
0
                list->entries[j] = list->entries[j-1];
1877
0
        }
1878
0
    }
1879
1880
0
    return 0;
1881
1882
0
abort:
1883
0
    return -1;
1884
0
}
1885
1886
static void
1887
md_free_label_hashtable(MD_CTX* ctx, MD_LABEL_HASH_TABLE* table)
1888
0
{
1889
0
    MD_UNUSED(ctx);
1890
1891
0
    if(table->buckets != NULL) {
1892
0
        unsigned i;
1893
1894
0
        for(i = 0; i < table->n_buckets; i++) {
1895
0
            void* bucket = table->buckets[i];
1896
0
            if(md_is_complex_label_bucket(table, bucket))
1897
0
                free(bucket);
1898
0
        }
1899
1900
0
        free(table->buckets);
1901
0
    }
1902
0
}
1903
1904
static const MD_LABEL_HASH_ENTRY*
1905
md_lookup_label_hashtable(MD_CTX* ctx, MD_LABEL_HASH_TABLE* table, const CHAR* label, SZ label_size)
1906
0
{
1907
0
    unsigned hash;
1908
0
    void* bucket;
1909
1910
0
    MD_UNUSED(ctx);
1911
1912
0
    if(table->n_buckets == 0)
1913
0
        return NULL;
1914
1915
0
    hash = md_label_hash(label, label_size);
1916
0
    bucket = table->buckets[hash % table->n_buckets];
1917
1918
0
    if(bucket == NULL) {
1919
0
        return NULL;
1920
0
    } else if(!md_is_complex_label_bucket(table, bucket)) {
1921
0
        MD_LABEL_HASH_ENTRY* entry = (MD_LABEL_HASH_ENTRY*) bucket;
1922
1923
0
        if(entry->hash == hash  &&
1924
0
           md_label_cmp(entry->label, entry->label_size, label, label_size) == 0)
1925
0
            return entry;
1926
0
        else
1927
0
            return NULL;
1928
0
    } else {
1929
0
        MD_LABEL_HASH_LIST* list = (MD_LABEL_HASH_LIST*) bucket;
1930
0
        MD_LABEL_HASH_ENTRY key_buf;
1931
0
        const MD_LABEL_HASH_ENTRY* key = &key_buf;
1932
0
        MD_LABEL_HASH_ENTRY** ret;
1933
1934
0
        key_buf.label = (CHAR*) label;
1935
0
        key_buf.label_size = label_size;
1936
0
        key_buf.hash = hash;
1937
1938
0
        ret = (MD_LABEL_HASH_ENTRY**) bsearch(&key, list->entries,
1939
0
                    list->n_entries, sizeof(MD_LABEL_HASH_ENTRY*), md_label_hash_entry_cmp);
1940
0
        if(ret != NULL)
1941
0
            return *ret;
1942
0
        else
1943
0
            return NULL;
1944
0
    }
1945
0
}
1946
1947
static void*
1948
md_add_label_def(MD_CTX* ctx, MD_LABEL_HASH_TABLE* table, const CHAR* label, SZ label_size)
1949
0
{
1950
0
    MD_LABEL_HASH_ENTRY* entry;
1951
1952
0
    if(table->n_defs >= table->alloc_defs) {
1953
0
        size_t new_alloc_defs;
1954
0
        void* new_defs;
1955
1956
0
        new_alloc_defs = (table->alloc_defs > 0
1957
0
                ? table->alloc_defs + table->alloc_defs / 2
1958
0
                : 8);
1959
0
        new_defs = realloc(table->defs, new_alloc_defs * table->def_size);
1960
0
        if(new_defs == NULL) {
1961
0
            MD_LOG("realloc() failed.");
1962
0
            return NULL;
1963
0
        }
1964
1965
0
        table->defs = new_defs;
1966
0
        table->alloc_defs = (unsigned) new_alloc_defs;
1967
0
    }
1968
1969
0
    entry = (MD_LABEL_HASH_ENTRY*)((char*)table->defs + table->n_defs * table->def_size);
1970
0
    memset(entry, 0, table->def_size);
1971
0
    entry->label = label;
1972
0
    entry->label_size = label_size;
1973
0
    entry->hash = md_label_hash(label, label_size);
1974
1975
0
    table->n_defs++;
1976
1977
0
    return entry;
1978
0
}
1979
1980
1981
/************************************
1982
 ***  Link Reference Definitions  ***
1983
 ************************************/
1984
1985
struct MD_REF_DEF_tag {
1986
    MD_LABEL_HASH_ENTRY entry;
1987
    CHAR* title;
1988
    SZ title_size;
1989
    OFF dest_beg;
1990
    OFF dest_end;
1991
    unsigned char label_needs_free : 1;
1992
    unsigned char title_needs_free : 1;
1993
};
1994
1995
static int
1996
md_build_ref_def_hashtable(MD_CTX* ctx)
1997
0
{
1998
0
    return md_build_label_hashtable(ctx, &ctx->ref_def_hashtable);
1999
0
}
2000
2001
static const MD_REF_DEF*
2002
md_lookup_ref_def(MD_CTX* ctx, const CHAR* label, SZ label_size)
2003
0
{
2004
0
    return (const MD_REF_DEF*) md_lookup_label_hashtable(ctx,
2005
0
                &ctx->ref_def_hashtable, label, label_size);
2006
0
}
2007
2008
2009
/*******************************
2010
 ***  Footnote Definitions   ***
2011
 *******************************/
2012
2013
struct MD_FOOTNOTE_DEF_tag {
2014
    MD_LABEL_HASH_ENTRY entry;
2015
    unsigned int index;         /* 0 = unreferenced; 1-based order of first reference */
2016
    unsigned int ref_count;     /* Number of references to this footnote. */
2017
    MD_LINE* content_lines;     /* always heap-allocated; freed by md_free_footnote_defs */
2018
    MD_SIZE n_content_lines;
2019
};
2020
2021
static int
2022
md_is_footnote_label(MD_CTX* ctx, OFF beg, OFF* p_end)
2023
0
{
2024
0
    OFF end = beg;
2025
2026
    /* No whitespace, no nested square brackets and length below 75 characters.
2027
     * See https://github.com/mity/md4c/issues/380 */
2028
0
    while(end < ctx->size  &&  end - beg <= 75  &&
2029
0
            !ISWHITESPACE(end)  &&  !ISANYOF2(end, _T('['), _T(']')))
2030
0
        end++;
2031
2032
0
    if(end - beg > 0  &&  end < ctx->size  &&  CH(end) == _T(']')) {
2033
0
        if(p_end != NULL)
2034
0
            *p_end = end;
2035
0
        return true;
2036
0
    } else {
2037
0
        return false;
2038
0
    }
2039
0
}
2040
2041
/* Returns 0 if not a footnote definition.
2042
 * Returns N > 0 (number of lines consumed) if it is one and the definition
2043
 * was stored successfully.
2044
 * Returns -1 on memory allocation error.
2045
 */
2046
static int
2047
md_is_footnote_definition(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
2048
0
{
2049
0
    OFF off;
2050
0
    OFF label_beg, label_end;
2051
0
    MD_LINE* content_lines = NULL;
2052
0
    MD_FOOTNOTE_DEF* def;
2053
0
    MD_SIZE n;
2054
0
    MD_SIZE n_content_lines;
2055
0
    int ret = 0;
2056
2057
    /* Caller guarantees: n_lines >= 1 and lines[0] starts with [^. */
2058
0
    MD_ASSERT(n_lines >= 1);
2059
0
    off = lines[0].beg;
2060
0
    MD_ASSERT(CH(off) == _T('[')  &&  CH(off+1) == _T('^'));
2061
0
    off += 2;
2062
2063
0
    label_beg = off;
2064
0
    if(!md_is_footnote_label(ctx, label_beg, &off))
2065
0
        return false;
2066
0
    label_end = off;
2067
2068
    /* Closing bracket. */
2069
0
    if(off >= lines[0].end  ||  CH(off) != _T(']'))
2070
0
        return false;
2071
0
    off++;
2072
2073
    /* Colon. */
2074
0
    if(off >= lines[0].end  ||  CH(off) != _T(':'))
2075
0
        return false;
2076
0
    off++;
2077
2078
    /* Skip optional whitespace after colon on the first line. */
2079
0
    while(off < lines[0].end  &&  ISWHITESPACE(off))
2080
0
        off++;
2081
2082
    /* Count continuation lines. GitHub-style footnotes allow the rest of the
2083
     * paragraph block to form the footnote body, including unindented lines.
2084
     *
2085
     * Blank lines cannot appear inside a paragraph block (they always trigger
2086
     * a block boundary), so we do not need to handle them here. Stop before a
2087
     * following line which itself starts a new footnote definition. */
2088
0
    n = 1;
2089
0
    while(n < n_lines) {
2090
0
        OFF def_off = lines[n].beg;
2091
2092
0
        if(def_off + 3 < lines[n].end  &&  CH(def_off) == _T('[')  &&  CH(def_off+1) == _T('^')) {
2093
0
            OFF tmp = def_off + 2;
2094
2095
0
            while(tmp < lines[n].end  &&  CH(tmp) != _T(']')  &&
2096
0
                  !ISWHITESPACE(tmp)  &&  CH(tmp) != _T('['))
2097
0
                tmp++;
2098
2099
0
            if(tmp > def_off + 2  &&  tmp + 1 < lines[n].end  &&
2100
0
               CH(tmp) == _T(']')  &&  CH(tmp+1) == _T(':'))
2101
0
                break;
2102
0
        }
2103
2104
0
        n++;
2105
0
    }
2106
2107
    /* Build content_lines array.
2108
     * Line 0 content starts after the "[^label]: " prefix.
2109
     * Lines 1..n-1 are stored verbatim (md4c strips indentation before
2110
     * handing us MD_LINE, so no further adjustment is needed). */
2111
0
    n_content_lines = (off >= lines[0].end  &&  n > 1) ? n - 1 : n;
2112
0
    content_lines = (MD_LINE*) malloc(n_content_lines * sizeof(MD_LINE));
2113
0
    if(content_lines == NULL) {
2114
0
        MD_LOG("malloc() failed.");
2115
0
        ret = -1;
2116
0
        goto abort;
2117
0
    }
2118
2119
0
    if(n_content_lines < n) {
2120
0
        memcpy(content_lines, lines + 1, n_content_lines * sizeof(MD_LINE));
2121
0
    } else {
2122
0
        content_lines[0].beg = off;
2123
0
        content_lines[0].end = lines[0].end;
2124
0
        if(n > 1)
2125
0
            memcpy(content_lines + 1, lines + 1, (n - 1) * sizeof(MD_LINE));
2126
0
    }
2127
2128
0
    def = (MD_FOOTNOTE_DEF*) md_add_label_def(ctx, &ctx->footnote_hashtable,
2129
0
                (CHAR*) STR(label_beg), label_end - label_beg);
2130
0
    if(def == NULL) {
2131
0
        ret = -1;
2132
0
        goto abort;
2133
0
    }
2134
0
    def->content_lines = content_lines;
2135
0
    def->n_content_lines = n_content_lines;
2136
2137
0
    return (int) n;
2138
2139
0
abort:
2140
0
    free(content_lines);
2141
0
    return ret;
2142
0
}
2143
2144
static MD_FOOTNOTE_DEF*
2145
md_lookup_footnote_def(MD_CTX* ctx, const CHAR* label, SZ label_size)
2146
0
{
2147
0
    return (MD_FOOTNOTE_DEF*) md_lookup_label_hashtable(ctx,
2148
0
                &ctx->footnote_hashtable, label, label_size);
2149
0
}
2150
2151
static int
2152
md_build_footnote_def_hashtable(MD_CTX* ctx)
2153
0
{
2154
0
    return md_build_label_hashtable(ctx, &ctx->footnote_hashtable);
2155
0
}
2156
2157
static void
2158
md_free_footnote_defs(MD_CTX* ctx)
2159
0
{
2160
0
    unsigned i;
2161
2162
0
    md_free_label_hashtable(ctx, &ctx->footnote_hashtable);
2163
2164
0
    for(i = 0; i < ctx->footnote_hashtable.n_defs; i++)
2165
0
        free(ctx->footnote_hashtable.footnote_defs[i].content_lines);
2166
0
    free(ctx->footnote_hashtable.footnote_defs);
2167
0
}
2168
2169
2170
/***************************
2171
 ***  Recognizing Links  ***
2172
 ***************************/
2173
2174
/* Note this code is partially shared between processing inlines and blocks
2175
 * as reference definitions and links share some helper parser functions.
2176
 */
2177
2178
typedef struct MD_LINK_ATTR_tag MD_LINK_ATTR;
2179
struct MD_LINK_ATTR_tag {
2180
    OFF dest_beg;
2181
    OFF dest_end;
2182
2183
    CHAR* title;
2184
    SZ title_size;
2185
    int title_needs_free;
2186
};
2187
2188
2189
static int
2190
md_is_link_label(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg,
2191
                 OFF* p_end, MD_SIZE* p_beg_line_index, MD_SIZE* p_end_line_index,
2192
                 OFF* p_contents_beg, OFF* p_contents_end)
2193
0
{
2194
0
    OFF off = beg;
2195
0
    OFF contents_beg = 0;
2196
0
    OFF contents_end = 0;
2197
0
    MD_SIZE line_index = 0;
2198
0
    int len = 0;
2199
2200
0
    *p_beg_line_index = 0;
2201
2202
0
    if(CH(off) != _T('['))
2203
0
        return false;
2204
0
    off++;
2205
2206
0
    while(1) {
2207
0
        OFF line_end = lines[line_index].end;
2208
2209
0
        while(off < line_end) {
2210
0
            if(CH(off) == _T('\\')  &&  off+1 < ctx->size  &&  (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
2211
0
                if(contents_end == 0) {
2212
0
                    contents_beg = off;
2213
0
                    *p_beg_line_index = line_index;
2214
0
                }
2215
0
                contents_end = off + 2;
2216
0
                off += 2;
2217
0
            } else if(CH(off) == _T('[')) {
2218
0
                return false;
2219
0
            } else if(CH(off) == _T(']')) {
2220
0
                if(contents_beg < contents_end) {
2221
                    /* Success. */
2222
0
                    *p_contents_beg = contents_beg;
2223
0
                    *p_contents_end = contents_end;
2224
0
                    *p_end = off+1;
2225
0
                    *p_end_line_index = line_index;
2226
0
                    return true;
2227
0
                } else {
2228
                    /* Link label must have some non-whitespace contents. */
2229
0
                    return false;
2230
0
                }
2231
0
            } else {
2232
0
                unsigned codepoint;
2233
0
                SZ char_size;
2234
2235
0
                codepoint = md_decode_unicode(ctx->text, off, ctx->size, &char_size);
2236
0
                if(!ISUNICODEWHITESPACE_(codepoint)) {
2237
0
                    if(contents_end == 0) {
2238
0
                        contents_beg = off;
2239
0
                        *p_beg_line_index = line_index;
2240
0
                    }
2241
0
                    contents_end = off + char_size;
2242
0
                }
2243
2244
0
                off += char_size;
2245
0
            }
2246
2247
0
            len++;
2248
0
            if(len > 999)
2249
0
                return false;
2250
0
        }
2251
2252
0
        line_index++;
2253
0
        len++;
2254
0
        if(line_index < n_lines)
2255
0
            off = lines[line_index].beg;
2256
0
        else
2257
0
            break;
2258
0
    }
2259
2260
0
    return false;
2261
0
}
2262
2263
static int
2264
md_is_link_destination_A(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
2265
                         OFF* p_contents_beg, OFF* p_contents_end)
2266
0
{
2267
0
    OFF off = beg;
2268
2269
0
    if(off >= max_end  ||  CH(off) != _T('<'))
2270
0
        return false;
2271
0
    off++;
2272
2273
0
    while(off < max_end) {
2274
0
        if(CH(off) == _T('\\')  &&  off+1 < max_end  &&  ISPUNCT(off+1)) {
2275
0
            off += 2;
2276
0
            continue;
2277
0
        }
2278
2279
0
        if(ISNEWLINE(off)  ||  CH(off) == _T('<'))
2280
0
            return false;
2281
2282
0
        if(CH(off) == _T('>')) {
2283
            /* Success. */
2284
0
            *p_contents_beg = beg+1;
2285
0
            *p_contents_end = off;
2286
0
            *p_end = off+1;
2287
0
            return true;
2288
0
        }
2289
2290
0
        off++;
2291
0
    }
2292
2293
0
    return false;
2294
0
}
2295
2296
static int
2297
md_is_link_destination_B(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
2298
                         OFF* p_contents_beg, OFF* p_contents_end)
2299
0
{
2300
0
    OFF off = beg;
2301
0
    int parenthesis_level = 0;
2302
2303
0
    while(off < max_end) {
2304
0
        if(CH(off) == _T('\\')  &&  off+1 < max_end  &&  ISPUNCT(off+1)) {
2305
0
            off += 2;
2306
0
            continue;
2307
0
        }
2308
2309
0
        if(ISWHITESPACE(off) || ISCNTRL(off))
2310
0
            break;
2311
2312
        /* Link destination may include balanced pairs of unescaped '(' ')'.
2313
         * Note we limit the maximal nesting level by 32 to protect us from
2314
         * https://github.com/jgm/cmark/issues/214 */
2315
0
        if(CH(off) == _T('(')) {
2316
0
            parenthesis_level++;
2317
0
            if(parenthesis_level > 32)
2318
0
                return false;
2319
0
        } else if(CH(off) == _T(')')) {
2320
0
            if(parenthesis_level == 0)
2321
0
                break;
2322
0
            parenthesis_level--;
2323
0
        }
2324
2325
0
        off++;
2326
0
    }
2327
2328
0
    if(parenthesis_level != 0  ||  off == beg)
2329
0
        return false;
2330
2331
    /* Success. */
2332
0
    *p_contents_beg = beg;
2333
0
    *p_contents_end = off;
2334
0
    *p_end = off;
2335
0
    return true;
2336
0
}
2337
2338
static inline int
2339
md_is_link_destination(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
2340
                       OFF* p_contents_beg, OFF* p_contents_end)
2341
0
{
2342
0
    if(CH(beg) == _T('<'))
2343
0
        return md_is_link_destination_A(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end);
2344
0
    else
2345
0
        return md_is_link_destination_B(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end);
2346
0
}
2347
2348
static int
2349
md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg,
2350
                 OFF* p_end, MD_SIZE* p_beg_line_index, MD_SIZE* p_end_line_index,
2351
                 OFF* p_contents_beg, OFF* p_contents_end)
2352
0
{
2353
0
    OFF off = beg;
2354
0
    CHAR closer_char;
2355
0
    MD_SIZE line_index = 0;
2356
2357
    /* White space with up to one line break. */
2358
0
    while(off < lines[line_index].end  &&  ISWHITESPACE(off))
2359
0
        off++;
2360
0
    if(off >= lines[line_index].end) {
2361
0
        line_index++;
2362
0
        if(line_index >= n_lines)
2363
0
            return false;
2364
0
        off = lines[line_index].beg;
2365
0
    }
2366
0
    if(off == beg)
2367
0
        return false;
2368
2369
0
    *p_beg_line_index = line_index;
2370
2371
    /* First char determines how to detect end of it. */
2372
0
    switch(CH(off)) {
2373
0
        case _T('"'):   closer_char = _T('"'); break;
2374
0
        case _T('\''):  closer_char = _T('\''); break;
2375
0
        case _T('('):   closer_char = _T(')'); break;
2376
0
        default:        return false;
2377
0
    }
2378
0
    off++;
2379
2380
0
    *p_contents_beg = off;
2381
2382
0
    while(line_index < n_lines) {
2383
0
        OFF line_end = lines[line_index].end;
2384
2385
0
        while(off < line_end) {
2386
0
            if(CH(off) == _T('\\')  &&  off+1 < ctx->size  &&  (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
2387
0
                off++;
2388
0
            } else if(CH(off) == closer_char) {
2389
                /* Success. */
2390
0
                *p_contents_end = off;
2391
0
                *p_end = off+1;
2392
0
                *p_end_line_index = line_index;
2393
0
                return true;
2394
0
            } else if(closer_char == _T(')')  &&  CH(off) == _T('(')) {
2395
                /* ()-style title cannot contain (unescaped '(')) */
2396
0
                return false;
2397
0
            }
2398
2399
0
            off++;
2400
0
        }
2401
2402
0
        line_index++;
2403
0
    }
2404
2405
0
    return false;
2406
0
}
2407
2408
/* Returns 0 if it is not a reference definition.
2409
 *
2410
 * Returns N > 0 if it is a reference definition. N then corresponds to the
2411
 * number of lines forming it). In this case the definition is stored for
2412
 * resolving any links referring to it.
2413
 *
2414
 * Returns -1 in case of an error (out of memory).
2415
 */
2416
static int
2417
md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
2418
0
{
2419
0
    OFF label_contents_beg;
2420
0
    OFF label_contents_end;
2421
0
    MD_SIZE label_contents_line_index;
2422
0
    int label_is_multiline = false;
2423
0
    OFF dest_contents_beg;
2424
0
    OFF dest_contents_end;
2425
0
    OFF title_contents_beg;
2426
0
    OFF title_contents_end;
2427
0
    MD_SIZE title_contents_line_index;
2428
0
    int title_is_multiline = false;
2429
0
    OFF off;
2430
0
    MD_SIZE line_index = 0;
2431
0
    MD_SIZE tmp_line_index;
2432
0
    MD_REF_DEF* def = NULL;
2433
0
    int ret = 0;
2434
2435
    /* Link label. */
2436
0
    if(!md_is_link_label(ctx, lines, n_lines, lines[0].beg,
2437
0
                &off, &label_contents_line_index, &line_index,
2438
0
                &label_contents_beg, &label_contents_end))
2439
0
        return false;
2440
0
    label_is_multiline = (label_contents_line_index != line_index);
2441
2442
    /* Colon. */
2443
0
    if(off >= lines[line_index].end  ||  CH(off) != _T(':'))
2444
0
        return false;
2445
0
    off++;
2446
2447
    /* Optional white space with up to one line break. */
2448
0
    while(off < lines[line_index].end  &&  ISWHITESPACE(off))
2449
0
        off++;
2450
0
    if(off >= lines[line_index].end) {
2451
0
        line_index++;
2452
0
        if(line_index >= n_lines)
2453
0
            return false;
2454
0
        off = lines[line_index].beg;
2455
0
    }
2456
2457
    /* Link destination. */
2458
0
    if(!md_is_link_destination(ctx, off, lines[line_index].end,
2459
0
                &off, &dest_contents_beg, &dest_contents_end))
2460
0
        return false;
2461
2462
    /* (Optional) title. Note we interpret it as an title only if nothing
2463
     * more follows on its last line. */
2464
0
    if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off,
2465
0
                &off, &title_contents_line_index, &tmp_line_index,
2466
0
                &title_contents_beg, &title_contents_end)
2467
0
        &&  off >= lines[line_index + tmp_line_index].end)
2468
0
    {
2469
0
        title_is_multiline = (tmp_line_index != title_contents_line_index);
2470
0
        title_contents_line_index += line_index;
2471
0
        line_index += tmp_line_index;
2472
0
    } else {
2473
        /* Not a title. */
2474
0
        title_is_multiline = false;
2475
0
        title_contents_beg = off;
2476
0
        title_contents_end = off;
2477
0
        title_contents_line_index = 0;
2478
0
    }
2479
2480
    /* Nothing more can follow on the last line. */
2481
0
    if(off < lines[line_index].end)
2482
0
        return false;
2483
2484
0
    if(label_is_multiline) {
2485
0
        CHAR* label;
2486
0
        SZ label_size;
2487
2488
0
        MD_CHECK(md_merge_lines_alloc(ctx, label_contents_beg, label_contents_end,
2489
0
                    lines + label_contents_line_index, n_lines - label_contents_line_index,
2490
0
                    _T(' '), &label, &label_size));
2491
0
        def = (MD_REF_DEF*) md_add_label_def(ctx, &ctx->ref_def_hashtable, label, label_size);
2492
0
        if(def == NULL) {
2493
0
            ret = -1;
2494
0
            free(label);
2495
0
            goto abort;
2496
0
        }
2497
0
        def->label_needs_free = true;
2498
0
    } else {
2499
0
        def = (MD_REF_DEF*) md_add_label_def(ctx, &ctx->ref_def_hashtable,
2500
0
                    STR(label_contents_beg), label_contents_end - label_contents_beg);
2501
0
        if(def == NULL) {
2502
0
            ret = -1;
2503
0
            goto abort;
2504
0
        }
2505
0
    }
2506
2507
0
    if(title_is_multiline) {
2508
0
        MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
2509
0
                    lines + title_contents_line_index, n_lines - title_contents_line_index,
2510
0
                    _T('\n'), &def->title, &def->title_size));
2511
0
        def->title_needs_free = true;
2512
0
    } else {
2513
0
        def->title = (CHAR*) STR(title_contents_beg);
2514
0
        def->title_size = title_contents_end - title_contents_beg;
2515
0
    }
2516
2517
0
    def->dest_beg = dest_contents_beg;
2518
0
    def->dest_end = dest_contents_end;
2519
2520
    /* Success. */
2521
0
    return line_index + 1;
2522
2523
0
abort:
2524
    /* Failure. A non-NULL def is already committed to ctx->ref_def_hashtable,
2525
     * which owns its label and title and frees them in md_free_ref_defs().
2526
     * The def == NULL path frees its local label itself, above. */
2527
0
    return ret;
2528
0
}
2529
2530
static int
2531
md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
2532
                     OFF beg, OFF end, MD_LINK_ATTR* attr)
2533
0
{
2534
0
    const MD_REF_DEF* def;
2535
0
    const MD_LINE* beg_line;
2536
0
    int is_multiline;
2537
0
    CHAR* label;
2538
0
    SZ label_size;
2539
0
    int ret = false;
2540
2541
0
    MD_ASSERT(CH(beg) == _T('[') || CH(beg) == _T('!'));
2542
0
    MD_ASSERT(CH(end-1) == _T(']'));
2543
2544
0
    if(ctx->max_ref_def_output == 0)
2545
0
        return false;
2546
2547
0
    beg += (CH(beg) == _T('!') ? 2 : 1);
2548
0
    end--;
2549
2550
    /* Find lines corresponding to the beg and end positions. */
2551
0
    beg_line = md_lookup_line(beg, lines, n_lines, NULL);
2552
0
    is_multiline = (end > beg_line->end);
2553
2554
0
    if(is_multiline) {
2555
0
        MD_CHECK(md_merge_lines_alloc(ctx, beg, end, beg_line,
2556
0
                 (int)(n_lines - (beg_line - lines)), _T(' '), &label, &label_size));
2557
0
    } else {
2558
0
        label = (CHAR*) STR(beg);
2559
0
        label_size = end - beg;
2560
0
    }
2561
2562
0
    def = md_lookup_ref_def(ctx, label, label_size);
2563
0
    if(def != NULL) {
2564
0
        attr->dest_beg = def->dest_beg;
2565
0
        attr->dest_end = def->dest_end;
2566
0
        attr->title = def->title;
2567
0
        attr->title_size = def->title_size;
2568
0
        attr->title_needs_free = false;
2569
0
    }
2570
2571
0
    if(is_multiline)
2572
0
        free(label);
2573
2574
0
    if(def != NULL) {
2575
        /* See https://github.com/mity/md4c/issues/238 */
2576
0
        MD_SIZE output_size_estimation = def->entry.label_size + def->title_size + def->dest_end - def->dest_beg;
2577
0
        if(output_size_estimation < ctx->max_ref_def_output) {
2578
0
            ctx->max_ref_def_output -= output_size_estimation;
2579
0
            ret = true;
2580
0
        } else {
2581
0
            MD_LOG("Too many link reference definition instantiations.");
2582
0
            ctx->max_ref_def_output = 0;
2583
0
        }
2584
0
    }
2585
2586
0
abort:
2587
0
    return ret;
2588
0
}
2589
2590
static int
2591
md_is_inline_link_spec(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
2592
                       OFF beg, OFF* p_end, MD_LINK_ATTR* attr)
2593
0
{
2594
0
    MD_SIZE line_index = 0;
2595
0
    MD_SIZE tmp_line_index;
2596
0
    OFF title_contents_beg;
2597
0
    OFF title_contents_end;
2598
0
    MD_SIZE title_contents_line_index;
2599
0
    int title_is_multiline;
2600
0
    OFF off = beg;
2601
0
    int ret = false;
2602
2603
0
    md_lookup_line(off, lines, n_lines, &line_index);
2604
2605
0
    MD_ASSERT(CH(off) == _T('('));
2606
0
    off++;
2607
2608
    /* Optional white space with up to one line break. */
2609
0
    while(off < lines[line_index].end  &&  ISWHITESPACE(off))
2610
0
        off++;
2611
0
    if(off >= lines[line_index].end  &&  (off >= ctx->size  ||  ISNEWLINE(off))) {
2612
0
        line_index++;
2613
0
        if(line_index >= n_lines)
2614
0
            return false;
2615
0
        off = lines[line_index].beg;
2616
0
    }
2617
2618
    /* Link destination may be omitted, but only when not also having a title. */
2619
0
    if(off < ctx->size  &&  CH(off) == _T(')')) {
2620
0
        attr->dest_beg = off;
2621
0
        attr->dest_end = off;
2622
0
        attr->title = NULL;
2623
0
        attr->title_size = 0;
2624
0
        attr->title_needs_free = false;
2625
0
        off++;
2626
0
        *p_end = off;
2627
0
        return true;
2628
0
    }
2629
2630
    /* Link destination. */
2631
0
    if(!md_is_link_destination(ctx, off, lines[line_index].end,
2632
0
                        &off, &attr->dest_beg, &attr->dest_end))
2633
0
        return false;
2634
2635
    /* (Optional) title. */
2636
0
    if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off,
2637
0
                &off, &title_contents_line_index, &tmp_line_index,
2638
0
                &title_contents_beg, &title_contents_end))
2639
0
    {
2640
0
        title_is_multiline = (tmp_line_index != title_contents_line_index);
2641
0
        title_contents_line_index += line_index;
2642
0
        line_index += tmp_line_index;
2643
0
    } else {
2644
        /* Not a title. */
2645
0
        title_is_multiline = false;
2646
0
        title_contents_beg = off;
2647
0
        title_contents_end = off;
2648
0
        title_contents_line_index = 0;
2649
0
    }
2650
2651
    /* Optional whitespace followed with final ')'. */
2652
0
    while(off < lines[line_index].end  &&  ISWHITESPACE(off))
2653
0
        off++;
2654
0
    if(off >= lines[line_index].end) {
2655
0
        line_index++;
2656
0
        if(line_index >= n_lines)
2657
0
            return false;
2658
0
        off = lines[line_index].beg;
2659
0
    }
2660
0
    if(CH(off) != _T(')'))
2661
0
        goto abort;
2662
0
    off++;
2663
2664
0
    if(title_contents_beg >= title_contents_end) {
2665
0
        attr->title = NULL;
2666
0
        attr->title_size = 0;
2667
0
        attr->title_needs_free = false;
2668
0
    } else if(!title_is_multiline) {
2669
0
        attr->title = (CHAR*) STR(title_contents_beg);
2670
0
        attr->title_size = title_contents_end - title_contents_beg;
2671
0
        attr->title_needs_free = false;
2672
0
    } else {
2673
0
        MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
2674
0
                    lines + title_contents_line_index, n_lines - title_contents_line_index,
2675
0
                    _T('\n'), &attr->title, &attr->title_size));
2676
0
        attr->title_needs_free = true;
2677
0
    }
2678
2679
0
    *p_end = off;
2680
0
    ret = true;
2681
2682
0
abort:
2683
0
    return ret;
2684
0
}
2685
2686
static void
2687
md_free_ref_defs(MD_CTX* ctx)
2688
0
{
2689
0
    unsigned i;
2690
2691
0
    md_free_label_hashtable(ctx, &ctx->ref_def_hashtable);
2692
2693
0
    for(i = 0; i < ctx->ref_def_hashtable.n_defs; i++) {
2694
0
        MD_REF_DEF* def = &ctx->ref_def_hashtable.ref_defs[i];
2695
2696
0
        if(def->label_needs_free)
2697
0
            free((void*) def->entry.label);
2698
0
        if(def->title_needs_free)
2699
0
            free(def->title);
2700
0
    }
2701
2702
0
    free(ctx->ref_def_hashtable.ref_defs);
2703
0
}
2704
2705
2706
/******************************************
2707
 ***  Processing Inlines (a.k.a Spans)  ***
2708
 ******************************************/
2709
2710
/* We process inlines in few phases:
2711
 *
2712
 * (1) We go through the block text and collect all significant characters
2713
 *     which may start/end a span or some other significant position into
2714
 *     ctx->marks[]. Core of this is what md_collect_marks() does.
2715
 *
2716
 *     We also do some very brief preliminary context-less analysis, whether
2717
 *     it might be opener or closer (e.g. of an emphasis span).
2718
 *
2719
 *     This speeds the other steps as we do not need to re-iterate over all
2720
 *     characters anymore.
2721
 *
2722
 * (2) We analyze each potential mark types, in order by their precedence.
2723
 *
2724
 *     In each md_analyze_XXX() function, we re-iterate list of the marks,
2725
 *     skipping already resolved regions (in preceding precedences) and try to
2726
 *     resolve them.
2727
 *
2728
 * (2.1) For trivial marks, which are single (e.g. HTML entity), we just mark
2729
 *       them as resolved.
2730
 *
2731
 * (2.2) For range-type marks, we analyze whether the mark could be closer
2732
 *       and, if yes, whether there is some preceding opener it could satisfy.
2733
 *
2734
 *       If not we check whether it could be really an opener and if yes, we
2735
 *       remember it so subsequent closers may resolve it.
2736
 *
2737
 * (3) Finally, when all marks were analyzed, we render the block contents
2738
 *     by calling MD_RENDERER::text() callback, interrupting by ::enter_span()
2739
 *     or ::close_span() whenever we reach a resolved mark.
2740
 */
2741
2742
2743
/* The mark structure.
2744
 *
2745
 * '\\': Maybe escape sequence.
2746
 * '\0': NULL char.
2747
 *  '*': Maybe (strong) emphasis start/end.
2748
 *  '_': Maybe (strong) emphasis start/end.
2749
 *  '~': Maybe strikethrough start/end (needs MD_FLAG_STRIKETHROUGH).
2750
 *  '+': Maybe insert start/end (needs MD_FLAG_INSERT)
2751
 *  '`': Maybe code span start/end.
2752
 *  '&': Maybe start of entity.
2753
 *  ';': Maybe end of entity.
2754
 *  '<': Maybe start of raw HTML or autolink.
2755
 *  '>': Maybe end of raw HTML or autolink.
2756
 *  '=': Maybe highlight start/end (needs MD_FLAG_HIGHLIGHT).
2757
 *  '[': Maybe start of link label or link text.
2758
 *  '!': Equivalent of '[' for image.
2759
 *  ']': Maybe end of link label or link text.
2760
 *  '@': Maybe permissive e-mail auto-link (needs MD_FLAG_PERMISSIVEEMAILAUTOLINKS).
2761
 *  ':': Maybe permissive URL auto-link (needs MD_FLAG_PERMISSIVEURLAUTOLINKS).
2762
 *  '.': Maybe permissive WWW auto-link (needs MD_FLAG_PERMISSIVEWWWAUTOLINKS).
2763
 *  'D': Dummy mark, it reserves a space for splitting a previous mark
2764
 *       (e.g. emphasis) or to make more space for storing some special data
2765
 *       related to the preceding mark (e.g. link).
2766
 *
2767
 * Note that not all instances of these chars in the text imply creation of the
2768
 * structure. Only those which have (or may have, after we see more context)
2769
 * the special meaning.
2770
 *
2771
 * (Keep this struct as small as possible to fit as much of them into CPU
2772
 * cache line.)
2773
 */
2774
struct MD_MARK_tag {
2775
    union {
2776
        struct {
2777
            OFF beg;
2778
            OFF end;
2779
        };
2780
        void* pointer; /* Dummy marks can sometimes store a pointer */
2781
    };
2782
2783
    /* For unresolved openers, 'next' may be used to form a stack of
2784
     * unresolved open openers.
2785
     *
2786
     * When resolved with MD_MARK_OPENER/CLOSER flag, next/prev is index of the
2787
     * respective closer/opener.
2788
     */
2789
    int prev;
2790
    int next;
2791
    CHAR ch;
2792
    unsigned char flags;
2793
};
2794
2795
/* Mark flags (these apply to ALL mark types). */
2796
0
#define MD_MARK_POTENTIAL_OPENER            0x01  /* Maybe opener. */
2797
0
#define MD_MARK_POTENTIAL_CLOSER            0x02  /* Maybe closer. */
2798
0
#define MD_MARK_OPENER                      0x04  /* Definitely opener. */
2799
0
#define MD_MARK_CLOSER                      0x08  /* Definitely closer. */
2800
0
#define MD_MARK_RESOLVED                    0x10  /* Resolved in any definite way. */
2801
2802
/* Mark flags specific for just some mark type(s) (so the bits can be reused). */
2803
0
#define MD_MARK_EMPH_OC                     0x20  /* Opener/closer mixed candidate. Helper for the "rule of 3". */
2804
0
#define MD_MARK_EMPH_MOD3_0                 0x40
2805
0
#define MD_MARK_EMPH_MOD3_1                 0x80
2806
0
#define MD_MARK_EMPH_MOD3_2                 (0x40 | 0x80)
2807
0
#define MD_MARK_EMPH_MOD3_MASK              (0x40 | 0x80)
2808
0
#define MD_MARK_AUTOLINK                    0x20  /* Distinguisher for '<', '>'. */
2809
0
#define MD_MARK_AUTOLINK_MISSING_MAILTO     0x40  /* For '@' */
2810
0
#define MD_MARK_VALIDPERMISSIVEAUTOLINK     0x20  /* For '@', ':', '.'. */
2811
0
#define MD_MARK_BRACKET_CANBEIMAGE          0x20  /* For '[', if can be expanded to the left to eat '!'. */
2812
0
#define MD_MARK_BRACKET_HASNESTED           0x40  /* For '[' to rule out invalid link labels early. */
2813
0
#define MD_MARK_BRACKET_FOOTNOTEREF         0x80  /* For '[', To distinguish footnotes. */
2814
2815
static MD_MARKSTACK*
2816
md_emph_stack(MD_CTX* ctx, MD_CHAR ch, unsigned flags)
2817
0
{
2818
0
    MD_MARKSTACK* stack;
2819
2820
0
    switch(ch) {
2821
0
        case '*':   stack = &ASTERISK_OPENERS_oo_mod3_0; break;
2822
0
        case '_':   stack = &UNDERSCORE_OPENERS_oo_mod3_0; break;
2823
0
        default:    MD_UNREACHABLE();
2824
0
    }
2825
2826
0
    if(flags & MD_MARK_EMPH_OC)
2827
0
        stack += 3;
2828
2829
0
    switch(flags & MD_MARK_EMPH_MOD3_MASK) {
2830
0
        case MD_MARK_EMPH_MOD3_0:   stack += 0; break;
2831
0
        case MD_MARK_EMPH_MOD3_1:   stack += 1; break;
2832
0
        case MD_MARK_EMPH_MOD3_2:   stack += 2; break;
2833
0
        default:                    MD_UNREACHABLE();
2834
0
    }
2835
2836
0
    return stack;
2837
0
}
2838
2839
static MD_MARKSTACK*
2840
md_opener_stack(MD_CTX* ctx, int mark_index)
2841
0
{
2842
0
    MD_MARK* mark = &ctx->marks[mark_index];
2843
2844
0
    switch(mark->ch) {
2845
0
        case _T('*'):
2846
0
        case _T('_'):   return md_emph_stack(ctx, mark->ch, mark->flags);
2847
2848
0
        case _T('~'):   return (mark->end - mark->beg == 1) ? &TILDE_OPENERS_1 : &TILDE_OPENERS_2;
2849
2850
0
        case _T('!'):
2851
0
        case _T('['):   return &BRACKET_OPENERS;
2852
2853
0
        default:        MD_UNREACHABLE();
2854
0
    }
2855
0
}
2856
2857
static MD_MARK*
2858
md_add_mark(MD_CTX* ctx)
2859
0
{
2860
0
    if(ctx->n_marks >= ctx->alloc_marks) {
2861
0
        MD_MARK* new_marks;
2862
2863
0
        ctx->alloc_marks = (ctx->alloc_marks > 0
2864
0
                ? ctx->alloc_marks + ctx->alloc_marks / 2
2865
0
                : 64);
2866
0
        new_marks = realloc(ctx->marks, ctx->alloc_marks * sizeof(MD_MARK));
2867
0
        if(new_marks == NULL) {
2868
0
            MD_LOG("realloc() failed.");
2869
0
            return NULL;
2870
0
        }
2871
2872
0
        ctx->marks = new_marks;
2873
0
    }
2874
2875
0
    return &ctx->marks[ctx->n_marks++];
2876
0
}
2877
2878
#define ADD_MARK_()                                                     \
2879
0
        do {                                                            \
2880
0
            mark = md_add_mark(ctx);                                    \
2881
0
            if(mark == NULL) {                                          \
2882
0
                ret = -1;                                               \
2883
0
                goto abort;                                             \
2884
0
            }                                                           \
2885
0
        } while(0)
2886
2887
#define ADD_MARK(ch_, beg_, end_, flags_)                               \
2888
0
        do {                                                            \
2889
0
            ADD_MARK_();                                                \
2890
0
            mark->beg = (beg_);                                         \
2891
0
            mark->end = (end_);                                         \
2892
0
            mark->prev = -1;                                            \
2893
0
            mark->next = -1;                                            \
2894
0
            mark->ch = (char)(ch_);                                     \
2895
0
            mark->flags = (flags_);                                     \
2896
0
        } while(0)
2897
2898
2899
static inline void
2900
md_mark_stack_push(MD_CTX* ctx, MD_MARKSTACK* stack, int mark_index)
2901
0
{
2902
0
    ctx->marks[mark_index].next = stack->top;
2903
0
    stack->top = mark_index;
2904
0
}
2905
2906
static inline int
2907
md_mark_stack_pop(MD_CTX* ctx, MD_MARKSTACK* stack)
2908
0
{
2909
0
    int top = stack->top;
2910
0
    if(top >= 0)
2911
0
        stack->top = ctx->marks[top].next;
2912
0
    return top;
2913
0
}
2914
2915
/* Sometimes, we need to store a pointer into the mark. It can only happen
2916
 * for dummy marks. */
2917
static inline void
2918
md_mark_store_ptr(MD_CTX* ctx, int mark_index, void* ptr)
2919
0
{
2920
0
    MD_ASSERT(ctx->marks[mark_index].ch == 'D');
2921
0
    ctx->marks[mark_index].pointer = ptr;
2922
0
}
2923
2924
static inline void*
2925
md_mark_get_ptr(MD_CTX* ctx, int mark_index)
2926
0
{
2927
0
    MD_ASSERT(ctx->marks[mark_index].ch == 'D');
2928
0
    return ctx->marks[mark_index].pointer;
2929
0
}
2930
2931
static inline void
2932
md_resolve_range(MD_CTX* ctx, int opener_index, int closer_index)
2933
0
{
2934
0
    MD_MARK* opener = &ctx->marks[opener_index];
2935
0
    MD_MARK* closer = &ctx->marks[closer_index];
2936
2937
    /* Interconnect opener and closer and mark both as resolved. */
2938
0
    opener->next = closer_index;
2939
0
    closer->prev = opener_index;
2940
2941
0
    opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
2942
0
    closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
2943
0
}
2944
2945
/* Pop all opener records in the activated chains after the given mark.
2946
 * This makes sure, we have no crossing ranges. */
2947
static void
2948
md_pop_openers(MD_CTX* ctx, int opener_index)
2949
0
{
2950
0
    int i;
2951
2952
0
    for(i = 0; i < (int) SIZEOF_ARRAY(ctx->opener_stacks); i++) {
2953
0
        MD_MARKSTACK* stack = &ctx->opener_stacks[i];
2954
0
        while(stack->top >= opener_index)
2955
0
            md_mark_stack_pop(ctx, stack);
2956
0
    }
2957
0
}
2958
2959
static void
2960
md_disable_marks(MD_CTX* ctx, int mark_index0, int mark_index1)
2961
0
{
2962
0
    int i;
2963
2964
0
    for(i = mark_index0; i < mark_index1; i++) {
2965
0
        MD_MARK* mark = &ctx->marks[i];
2966
2967
0
        mark->ch = 'D';
2968
0
        mark->flags = 0;
2969
0
    }
2970
0
}
2971
2972
static void
2973
md_build_mark_char_map(MD_CTX* ctx)
2974
0
{
2975
0
    memset(ctx->mark_char_map, 0, sizeof(ctx->mark_char_map));
2976
2977
0
    ctx->mark_char_map['\\'] = 1;
2978
0
    ctx->mark_char_map['*'] = 1;
2979
0
    ctx->mark_char_map['_'] = 1;
2980
0
    ctx->mark_char_map['`'] = 1;
2981
0
    ctx->mark_char_map['&'] = 1;
2982
0
    ctx->mark_char_map[';'] = 1;
2983
0
    ctx->mark_char_map['<'] = 1;
2984
0
    ctx->mark_char_map['>'] = 1;
2985
0
    ctx->mark_char_map['['] = 1;
2986
0
    ctx->mark_char_map['!'] = 1;
2987
0
    ctx->mark_char_map[']'] = 1;
2988
0
    ctx->mark_char_map['\0'] = 1;
2989
2990
0
    if(ctx->parser.flags & (MD_FLAG_STRIKETHROUGH | MD_FLAG_SUBSCRIPTS))
2991
0
        ctx->mark_char_map['~'] = 1;
2992
2993
0
    if(ctx->parser.flags & MD_FLAG_SUPERSCRIPTS)
2994
0
        ctx->mark_char_map['^'] = 1;
2995
2996
0
    if(ctx->parser.flags & MD_FLAG_LATEXMATHSPANS)
2997
0
        ctx->mark_char_map['$'] = 1;
2998
2999
0
    if(ctx->parser.flags & MD_FLAG_HIGHLIGHT)
3000
0
        ctx->mark_char_map['='] = 1;
3001
3002
0
    if(ctx->parser.flags & MD_FLAG_INSERT)
3003
0
        ctx->mark_char_map['+'] = 1;
3004
3005
0
    if(ctx->parser.flags & MD_FLAG_PERMISSIVEEMAILAUTOLINKS)
3006
0
        ctx->mark_char_map['@'] = 1;
3007
3008
0
    if(ctx->parser.flags & MD_FLAG_PERMISSIVEURLAUTOLINKS)
3009
0
        ctx->mark_char_map[':'] = 1;
3010
3011
0
    if(ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS)
3012
0
        ctx->mark_char_map['.'] = 1;
3013
3014
0
    if((ctx->parser.flags & MD_FLAG_TABLES) || (ctx->parser.flags & MD_FLAG_WIKILINKS) ||
3015
0
       (ctx->parser.flags & MD_FLAG_SPOILERS))
3016
0
        ctx->mark_char_map['|'] = 1;
3017
3018
0
    if(ctx->parser.flags & MD_FLAG_COLLAPSEWHITESPACE) {
3019
0
        int i;
3020
3021
0
        for(i = 0; i < (int) sizeof(ctx->mark_char_map); i++) {
3022
0
            if(ISWHITESPACE_(i))
3023
0
                ctx->mark_char_map[i] = 1;
3024
0
        }
3025
0
    }
3026
0
}
3027
3028
static int
3029
md_is_code_span(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, OFF beg,
3030
                MD_MARK* opener, MD_MARK* closer,
3031
                OFF last_potential_closers[CODESPAN_MARK_MAXLEN],
3032
                int* p_reached_paragraph_end)
3033
0
{
3034
0
    OFF opener_beg = beg;
3035
0
    OFF opener_end;
3036
0
    OFF closer_beg;
3037
0
    OFF closer_end;
3038
0
    SZ mark_len;
3039
0
    OFF line_end;
3040
0
    int has_space_after_opener = false;
3041
0
    int has_eol_after_opener = false;
3042
0
    int has_space_before_closer = false;
3043
0
    int has_eol_before_closer = false;
3044
0
    int has_only_space = true;
3045
0
    MD_SIZE line_index = 0;
3046
3047
0
    line_end = lines[0].end;
3048
0
    opener_end = opener_beg;
3049
0
    while(opener_end < line_end  &&  CH(opener_end) == _T('`'))
3050
0
        opener_end++;
3051
0
    has_space_after_opener = (opener_end < line_end && CH(opener_end) == _T(' '));
3052
0
    has_eol_after_opener = (opener_end == line_end);
3053
3054
    /* The caller needs to know end of the opening mark even if we fail. */
3055
0
    opener->end = opener_end;
3056
3057
0
    mark_len = opener_end - opener_beg;
3058
0
    if(mark_len > CODESPAN_MARK_MAXLEN)
3059
0
        return false;
3060
3061
    /* Check whether we already know there is no closer of this length.
3062
     * If so, re-scan does no sense. This fixes issue #59. */
3063
0
    if(last_potential_closers[mark_len-1] >= lines[n_lines-1].end  ||
3064
0
       (*p_reached_paragraph_end  &&  last_potential_closers[mark_len-1] < opener_end))
3065
0
        return false;
3066
3067
0
    closer_beg = opener_end;
3068
0
    closer_end = opener_end;
3069
3070
    /* Find closer mark. */
3071
0
    while(true) {
3072
0
        while(closer_beg < line_end  &&  CH(closer_beg) != _T('`')) {
3073
0
            if(CH(closer_beg) != _T(' '))
3074
0
                has_only_space = false;
3075
0
            closer_beg++;
3076
0
        }
3077
0
        closer_end = closer_beg;
3078
0
        while(closer_end < line_end  &&  CH(closer_end) == _T('`'))
3079
0
            closer_end++;
3080
3081
0
        if(closer_end - closer_beg == mark_len) {
3082
            /* Success. */
3083
0
            has_space_before_closer = (closer_beg > lines[line_index].beg && CH(closer_beg-1) == _T(' '));
3084
0
            has_eol_before_closer = (closer_beg == lines[line_index].beg);
3085
0
            break;
3086
0
        }
3087
3088
0
        if(closer_end - closer_beg > 0) {
3089
            /* We have found a back-tick which is not part of the closer. */
3090
0
            has_only_space = false;
3091
3092
            /* But if we eventually fail, remember it as a potential closer
3093
             * of its own length for future attempts. This mitigates needs for
3094
             * rescans. */
3095
0
            if(closer_end - closer_beg < CODESPAN_MARK_MAXLEN) {
3096
0
                if(closer_beg > last_potential_closers[closer_end - closer_beg - 1])
3097
0
                    last_potential_closers[closer_end - closer_beg - 1] = closer_beg;
3098
0
            }
3099
0
        }
3100
3101
0
        if(closer_end >= line_end) {
3102
0
            line_index++;
3103
0
            if(line_index >= n_lines) {
3104
                /* Reached end of the paragraph and still nothing. */
3105
0
                *p_reached_paragraph_end = true;
3106
0
                return false;
3107
0
            }
3108
            /* Try on the next line. */
3109
0
            line_end = lines[line_index].end;
3110
0
            closer_beg = lines[line_index].beg;
3111
0
        } else {
3112
0
            closer_beg = closer_end;
3113
0
        }
3114
0
    }
3115
3116
    /* If there is a space or a new line both after and before the opener
3117
     * (and if the code span is not made of spaces only), consume one initial
3118
     * and one trailing space as part of the marks. */
3119
0
    if(!has_only_space  &&
3120
0
       (has_space_after_opener || has_eol_after_opener)  &&
3121
0
       (has_space_before_closer || has_eol_before_closer))
3122
0
    {
3123
0
        if(has_space_after_opener)
3124
0
            opener_end++;
3125
0
        else
3126
0
            opener_end = lines[1].beg;
3127
3128
0
        if(has_space_before_closer)
3129
0
            closer_beg--;
3130
0
        else {
3131
            /* Go back to the end of prev line */
3132
0
            closer_beg = lines[line_index-1].end;
3133
            /* But restore any trailing whitespace */
3134
0
            while(closer_beg < ctx->size  &&  ISBLANK(closer_beg))
3135
0
                closer_beg++;
3136
0
        }
3137
0
    }
3138
3139
0
    opener->ch = _T('`');
3140
0
    opener->beg = opener_beg;
3141
0
    opener->end = opener_end;
3142
0
    opener->flags = MD_MARK_POTENTIAL_OPENER;
3143
0
    closer->ch = _T('`');
3144
0
    closer->beg = closer_beg;
3145
0
    closer->end = closer_end;
3146
0
    closer->flags = MD_MARK_POTENTIAL_CLOSER;
3147
0
    return true;
3148
0
}
3149
3150
static int
3151
md_is_autolink_uri(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
3152
0
{
3153
0
    OFF off = beg+1;
3154
3155
0
    MD_ASSERT(CH(beg) == _T('<'));
3156
3157
    /* Check for scheme. */
3158
0
    if(off >= max_end  ||  !ISALNUM(off))
3159
0
        return false;
3160
0
    off++;
3161
0
    while(1) {
3162
0
        if(off >= max_end)
3163
0
            return false;
3164
0
        if(off - beg > 32)
3165
0
            return false;
3166
0
        if(CH(off) == _T(':')  &&  off - beg >= 3)
3167
0
            break;
3168
0
        if(!ISALNUM(off) && CH(off) != _T('+') && CH(off) != _T('-') && CH(off) != _T('.'))
3169
0
            return false;
3170
0
        off++;
3171
0
    }
3172
3173
    /* Check the path after the scheme. */
3174
0
    while(off < max_end  &&  CH(off) != _T('>')) {
3175
0
        if(ISWHITESPACE(off) || ISCNTRL(off) || CH(off) == _T('<'))
3176
0
            return false;
3177
0
        off++;
3178
0
    }
3179
3180
0
    if(off >= max_end)
3181
0
        return false;
3182
3183
0
    MD_ASSERT(CH(off) == _T('>'));
3184
0
    *p_end = off+1;
3185
0
    return true;
3186
0
}
3187
3188
static int
3189
md_is_autolink_email(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
3190
0
{
3191
0
    OFF off = beg + 1;
3192
0
    int label_len;
3193
3194
0
    MD_ASSERT(CH(beg) == _T('<'));
3195
3196
    /* The code should correspond to this regexp:
3197
            /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+
3198
            @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
3199
            (?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
3200
     */
3201
3202
    /* Username (before '@'). */
3203
0
    while(off < max_end  &&  (ISALNUM(off) || ISANYOF(off, _T(".!#$%&'*+/=?^_`{|}~-"))))
3204
0
        off++;
3205
0
    if(off <= beg+1)
3206
0
        return false;
3207
3208
    /* '@' */
3209
0
    if(off >= max_end  ||  CH(off) != _T('@'))
3210
0
        return false;
3211
0
    off++;
3212
3213
    /* Labels delimited with '.'; each label is sequence of 1 - 63 alnum
3214
     * characters or '-', but '-' is not allowed as first or last char. */
3215
0
    label_len = 0;
3216
0
    while(off < max_end) {
3217
0
        if(ISALNUM(off))
3218
0
            label_len++;
3219
0
        else if(CH(off) == _T('-')  &&  label_len > 0)
3220
0
            label_len++;
3221
0
        else if(CH(off) == _T('.')  &&  label_len > 0  &&  CH(off-1) != _T('-'))
3222
0
            label_len = 0;
3223
0
        else
3224
0
            break;
3225
3226
0
        if(label_len > 63)
3227
0
            return false;
3228
3229
0
        off++;
3230
0
    }
3231
3232
0
    if(label_len <= 0  || off >= max_end  ||  CH(off) != _T('>') ||  CH(off-1) == _T('-'))
3233
0
        return false;
3234
3235
0
    *p_end = off+1;
3236
0
    return true;
3237
0
}
3238
3239
static int
3240
md_is_autolink(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, int* p_missing_mailto)
3241
0
{
3242
0
    if(md_is_autolink_uri(ctx, beg, max_end, p_end)) {
3243
0
        *p_missing_mailto = false;
3244
0
        return true;
3245
0
    }
3246
3247
0
    if(md_is_autolink_email(ctx, beg, max_end, p_end)) {
3248
0
        *p_missing_mailto = true;
3249
0
        return true;
3250
0
    }
3251
3252
0
    return false;
3253
0
}
3254
3255
static int
3256
md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, int table_mode)
3257
0
{
3258
0
    MD_SIZE line_index;
3259
0
    int ret = 0;
3260
0
    MD_MARK* mark;
3261
0
    OFF codespan_last_potential_closers[CODESPAN_MARK_MAXLEN] = { 0 };
3262
0
    int codespan_scanned_till_paragraph_end = false;
3263
3264
0
    for(line_index = 0; line_index < n_lines; line_index++) {
3265
0
        const MD_LINE* line = &lines[line_index];
3266
0
        OFF off = line->beg;
3267
3268
0
        while(true) {
3269
0
            CHAR ch;
3270
3271
#ifdef MD4C_USE_UTF16
3272
    /* For UTF-16, mark_char_map[] covers only ASCII. */
3273
    #define IS_MARK_CHAR(off)   ((CH(off) < SIZEOF_ARRAY(ctx->mark_char_map))  &&  \
3274
                                (ctx->mark_char_map[(unsigned char) CH(off)]))
3275
#else
3276
    /* For 8-bit encodings, mark_char_map[] covers all 256 elements. */
3277
0
    #define IS_MARK_CHAR(off)   (ctx->mark_char_map[(unsigned char) CH(off)])
3278
0
#endif
3279
3280
            /* Optimization: Use some loop unrolling. */
3281
0
            while(off + 3 < line->end  &&  !IS_MARK_CHAR(off+0)  &&  !IS_MARK_CHAR(off+1)
3282
0
                                       &&  !IS_MARK_CHAR(off+2)  &&  !IS_MARK_CHAR(off+3))
3283
0
                off += 4;
3284
0
            while(off < line->end  &&  !IS_MARK_CHAR(off+0))
3285
0
                off++;
3286
3287
0
            if(off >= line->end)
3288
0
                break;
3289
3290
0
            ch = CH(off);
3291
3292
            /* A backslash escape.
3293
             * It can go beyond line->end as it may involve escaped new
3294
             * line to form a hard break. */
3295
0
            if(ch == _T('\\')  &&  off+1 < ctx->size  &&  (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
3296
                /* Hard-break cannot be on the last line of the block. */
3297
0
                if(!ISNEWLINE(off+1)  ||  line_index+1 < n_lines)
3298
0
                    ADD_MARK(ch, off, off+2, MD_MARK_RESOLVED);
3299
0
                off += 2;
3300
0
                continue;
3301
0
            }
3302
3303
            /* A potential (string) emphasis start/end. */
3304
0
            if(ch == _T('*')  ||  ch == _T('_')) {
3305
0
                OFF tmp = off+1;
3306
0
                int left_level;     /* What precedes: 0 = whitespace; 1 = punctuation; 2 = other char. */
3307
0
                int right_level;    /* What follows: 0 = whitespace; 1 = punctuation; 2 = other char. */
3308
3309
0
                while(tmp < line->end  &&  CH(tmp) == ch)
3310
0
                    tmp++;
3311
3312
0
                if(off == line->beg  ||  ISUNICODEWHITESPACEBEFORE(off))
3313
0
                    left_level = 0;
3314
0
                else if(ISUNICODEPUNCTBEFORE(off))
3315
0
                    left_level = 1;
3316
0
                else
3317
0
                    left_level = 2;
3318
3319
0
                if(tmp == line->end  ||  ISUNICODEWHITESPACE(tmp))
3320
0
                    right_level = 0;
3321
0
                else if(ISUNICODEPUNCT(tmp))
3322
0
                    right_level = 1;
3323
0
                else
3324
0
                    right_level = 2;
3325
3326
                /* Intra-word underscore doesn't have special meaning. */
3327
0
                if(ch == _T('_')  &&  left_level == 2  &&  right_level == 2) {
3328
0
                    left_level = 0;
3329
0
                    right_level = 0;
3330
0
                }
3331
3332
0
                if(left_level != 0  ||  right_level != 0) {
3333
0
                    unsigned flags = 0;
3334
3335
0
                    if(left_level > 0  &&  left_level >= right_level)
3336
0
                        flags |= MD_MARK_POTENTIAL_CLOSER;
3337
0
                    if(right_level > 0  &&  right_level >= left_level)
3338
0
                        flags |= MD_MARK_POTENTIAL_OPENER;
3339
0
                    if(flags == (MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER))
3340
0
                        flags |= MD_MARK_EMPH_OC;
3341
3342
                    /* For "the rule of three" we need to remember the original
3343
                     * size of the mark (modulo three), before we potentially
3344
                     * split the mark when being later resolved partially by some
3345
                     * shorter closer. */
3346
0
                    switch((tmp - off) % 3) {
3347
0
                        case 0: flags |= MD_MARK_EMPH_MOD3_0; break;
3348
0
                        case 1: flags |= MD_MARK_EMPH_MOD3_1; break;
3349
0
                        case 2: flags |= MD_MARK_EMPH_MOD3_2; break;
3350
0
                    }
3351
3352
0
                    ADD_MARK(ch, off, tmp, flags);
3353
3354
                    /* During resolving, multiple asterisks may have to be
3355
                     * split into independent span start/ends. Consider e.g.
3356
                     * "**foo* bar*". Therefore we push also some empty dummy
3357
                     * marks to have enough space for that. */
3358
0
                    off++;
3359
0
                    while(off < tmp) {
3360
0
                        ADD_MARK('D', off, off, 0);
3361
0
                        off++;
3362
0
                    }
3363
0
                    continue;
3364
0
                }
3365
3366
0
                off = tmp;
3367
0
                continue;
3368
0
            }
3369
3370
            /* A potential code span start/end. */
3371
0
            if(ch == _T('`')) {
3372
0
                MD_MARK opener;
3373
0
                MD_MARK closer;
3374
0
                int is_code_span;
3375
3376
0
                is_code_span = md_is_code_span(ctx, line, n_lines - line_index, off,
3377
0
                            &opener, &closer, codespan_last_potential_closers,
3378
0
                            &codespan_scanned_till_paragraph_end);
3379
0
                if(is_code_span) {
3380
0
                    ADD_MARK(opener.ch, opener.beg, opener.end, opener.flags);
3381
0
                    ADD_MARK(closer.ch, closer.beg, closer.end, closer.flags);
3382
0
                    md_resolve_range(ctx, ctx->n_marks-2, ctx->n_marks-1);
3383
0
                    off = closer.end;
3384
3385
                    /* Advance the current line accordingly. */
3386
0
                    if(off > line->end)
3387
0
                        line = md_lookup_line(off, lines, n_lines, &line_index);
3388
0
                    continue;
3389
0
                }
3390
3391
0
                off = opener.end;
3392
0
                continue;
3393
0
            }
3394
3395
            /* A potential entity start. */
3396
0
            if(ch == _T('&')) {
3397
0
                ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
3398
0
                off++;
3399
0
                continue;
3400
0
            }
3401
3402
            /* A potential entity end. */
3403
0
            if(ch == _T(';')) {
3404
                /* We surely cannot be entity unless the previous mark is '&'. */
3405
0
                if(ctx->n_marks > 0  &&  ctx->marks[ctx->n_marks-1].ch == _T('&'))
3406
0
                    ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_CLOSER);
3407
3408
0
                off++;
3409
0
                continue;
3410
0
            }
3411
3412
            /* A potential autolink or raw HTML start/end. */
3413
0
            if(ch == _T('<')) {
3414
0
                int is_autolink;
3415
0
                OFF autolink_end;
3416
0
                int missing_mailto;
3417
3418
0
                if(!(ctx->parser.flags & MD_FLAG_NOHTMLSPANS)) {
3419
0
                    int is_html;
3420
0
                    OFF html_end;
3421
3422
                    /* Given the nature of the raw HTML, we have to recognize
3423
                     * it here. Doing so later in md_analyze_lt_gt() could
3424
                     * open can of worms of quadratic complexity. */
3425
0
                    is_html = md_is_html_any(ctx, line, n_lines - line_index, off,
3426
0
                                    lines[n_lines-1].end, &html_end);
3427
0
                    if(is_html) {
3428
0
                        ADD_MARK(_T('<'), off, off, MD_MARK_OPENER | MD_MARK_RESOLVED);
3429
0
                        ADD_MARK(_T('>'), html_end, html_end, MD_MARK_CLOSER | MD_MARK_RESOLVED);
3430
0
                        ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
3431
0
                        ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
3432
0
                        off = html_end;
3433
3434
                        /* Advance the current line accordingly. */
3435
0
                        if(off > line->end)
3436
0
                            line = md_lookup_line(off, lines, n_lines, &line_index);
3437
0
                        continue;
3438
0
                    }
3439
0
                }
3440
3441
0
                is_autolink = md_is_autolink(ctx, off, lines[n_lines-1].end,
3442
0
                                    &autolink_end, &missing_mailto);
3443
0
                if(is_autolink) {
3444
0
                    unsigned flags = MD_MARK_RESOLVED | MD_MARK_AUTOLINK;
3445
0
                    if(missing_mailto)
3446
0
                        flags |= MD_MARK_AUTOLINK_MISSING_MAILTO;
3447
3448
0
                    ADD_MARK(_T('<'), off, off+1, MD_MARK_OPENER | flags);
3449
0
                    ADD_MARK(_T('>'), autolink_end-1, autolink_end, MD_MARK_CLOSER | flags);
3450
0
                    ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
3451
0
                    ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
3452
0
                    off = autolink_end;
3453
0
                    continue;
3454
0
                }
3455
3456
0
                off++;
3457
0
                continue;
3458
0
            }
3459
3460
            /* A potential link, footnote and similar or its part. */
3461
0
            if(ch == _T('[')  ||  (ch == _T('!') && off+1 < line->end && CH(off+1) == _T('['))) {
3462
0
                if(ch == _T('!')) {
3463
0
                    ADD_MARK(ch, off+1, off+2, MD_MARK_POTENTIAL_OPENER | MD_MARK_BRACKET_CANBEIMAGE);
3464
0
                    off += 2;
3465
0
                } else {
3466
0
                    ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
3467
0
                    off += 1;
3468
0
                }
3469
3470
                /* Two dummies to make enough place for data we need if it is
3471
                 * a link. */
3472
0
                ADD_MARK('D', off, off, 0);
3473
0
                ADD_MARK('D', off, off, 0);
3474
0
                continue;
3475
0
            }
3476
0
            if(ch == _T(']')) {
3477
0
                ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_CLOSER);
3478
0
                off++;
3479
0
                continue;
3480
0
            }
3481
3482
            /* A potential permissive e-mail autolink. */
3483
0
            if(ch == _T('@')) {
3484
0
                if(line->beg + 1 <= off  &&  ISALNUM(off-1)  &&
3485
0
                    off + 3 < line->end  &&  ISALNUM(off+1))
3486
0
                {
3487
0
                    ADD_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
3488
                    /* Push a dummy as a reserve for a closer. */
3489
0
                    ADD_MARK('D', line->beg, line->end, 0);
3490
0
                }
3491
3492
0
                off++;
3493
0
                continue;
3494
0
            }
3495
3496
            /* A potential permissive URL autolink. */
3497
0
            if(ch == _T(':')) {
3498
0
                static const struct {
3499
0
                    const CHAR* scheme;
3500
0
                    SZ scheme_size;
3501
0
                    const CHAR* suffix;
3502
0
                    SZ suffix_size;
3503
0
                } scheme_map[] = {
3504
                    /* In the order from the most frequently used, arguably. */
3505
0
                    { _T("http"), 4,    _T("//"), 2 },
3506
0
                    { _T("https"), 5,   _T("//"), 2 },
3507
0
                    { _T("ftp"), 3,     _T("//"), 2 }
3508
0
                };
3509
0
                int scheme_index;
3510
3511
0
                for(scheme_index = 0; scheme_index < (int) SIZEOF_ARRAY(scheme_map); scheme_index++) {
3512
0
                    const CHAR* scheme = scheme_map[scheme_index].scheme;
3513
0
                    const SZ scheme_size = scheme_map[scheme_index].scheme_size;
3514
0
                    const CHAR* suffix = scheme_map[scheme_index].suffix;
3515
0
                    const SZ suffix_size = scheme_map[scheme_index].suffix_size;
3516
3517
0
                    if(line->beg + scheme_size <= off  &&  md_ascii_eq(STR(off-scheme_size), scheme, scheme_size)  &&
3518
0
                        off + 1 + suffix_size < line->end  &&  md_ascii_eq(STR(off+1), suffix, suffix_size))
3519
0
                    {
3520
0
                        ADD_MARK(ch, off-scheme_size, off+1+suffix_size, MD_MARK_POTENTIAL_OPENER);
3521
                        /* Push a dummy as a reserve for a closer. */
3522
0
                        ADD_MARK('D', line->beg, line->end, 0);
3523
0
                        off += 1 + suffix_size;
3524
0
                        break;
3525
0
                    }
3526
0
                }
3527
3528
0
                off++;
3529
0
                continue;
3530
0
            }
3531
3532
            /* A potential permissive WWW autolink. */
3533
0
            if(ch == _T('.')) {
3534
0
                if(line->beg + 3 <= off  &&  md_ascii_eq(STR(off-3), _T("www"), 3)  &&
3535
0
                   (off-3 == line->beg || ISUNICODEWHITESPACEBEFORE(off-3) || ISUNICODEPUNCTBEFORE(off-3)))
3536
0
                {
3537
0
                    ADD_MARK(ch, off-3, off+1, MD_MARK_POTENTIAL_OPENER);
3538
                    /* Push a dummy as a reserve for a closer. */
3539
0
                    ADD_MARK('D', line->beg, line->end, 0);
3540
0
                    off++;
3541
0
                    continue;
3542
0
                }
3543
3544
0
                off++;
3545
0
                continue;
3546
0
            }
3547
3548
            /* We may need pipes for tables (cell delimiter), for wiki-links
3549
             * Note we coalesce spans of pipes into a single mark.
3550
             *
3551
             *  - Tables may use spans of any length.
3552
             *  - Wiki-links use only (unresolved) spans of length 1.
3553
             *  - Spoilers use use only (unresolved) spans of length 2.
3554
             */
3555
0
            if(ch == _T('|')) {
3556
0
                OFF tmp = off + 1;
3557
0
                while(tmp < line->end  &&  CH(tmp) == _T('|'))
3558
0
                    tmp++;
3559
0
                if(table_mode  ||
3560
0
                   (tmp - off == 1 && (ctx->parser.flags & MD_FLAG_WIKILINKS))  ||
3561
0
                   (tmp - off == 2 && (ctx->parser.flags & MD_FLAG_SPOILERS)))
3562
0
                    ADD_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER);
3563
0
                off = tmp;
3564
0
                continue;
3565
0
            }
3566
3567
            /* A potential superscript start/end: ^text^ */
3568
0
            if(ch == _T('^') && (ctx->parser.flags & MD_FLAG_SUPERSCRIPTS)) {
3569
0
                OFF tmp = off + 1;
3570
3571
0
                while(tmp < line->end && CH(tmp) == _T('^'))
3572
0
                    tmp++;
3573
3574
                /* Only a single caret is a superscript delimiter; longer runs are literal. */
3575
0
                if(tmp - off == 1) {
3576
0
                    unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3577
3578
                    /* Cannot open before whitespace; cannot close after whitespace. */
3579
0
                    if(off + 1 >= line->end  ||  ISUNICODEWHITESPACE(off + 1))
3580
0
                        flags &= ~MD_MARK_POTENTIAL_OPENER;
3581
0
                    if(off == line->beg  ||  ISUNICODEWHITESPACEBEFORE(off))
3582
0
                        flags &= ~MD_MARK_POTENTIAL_CLOSER;
3583
0
                    if(flags != 0)
3584
0
                        ADD_MARK(ch, off, off + 1, flags);
3585
0
                }
3586
3587
0
                off = tmp;
3588
0
                continue;
3589
0
            }
3590
3591
            /* A potential highlight start/end: ==text== */
3592
0
            if(ch == _T('=') && (ctx->parser.flags & MD_FLAG_HIGHLIGHT)) {
3593
0
                OFF tmp = off + 1;
3594
3595
0
                while(tmp < line->end && CH(tmp) == _T('='))
3596
0
                    tmp++;
3597
3598
                /* Only exactly two equals signs form a highlight delimiter. */
3599
0
                if(tmp - off == 2) {
3600
0
                    unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3601
3602
                    /* Cannot open before whitespace; cannot close after whitespace. */
3603
0
                    if(tmp >= line->end  ||  ISUNICODEWHITESPACE(tmp))
3604
0
                        flags &= ~MD_MARK_POTENTIAL_OPENER;
3605
0
                    if(off == line->beg  ||  ISUNICODEWHITESPACEBEFORE(off))
3606
0
                        flags &= ~MD_MARK_POTENTIAL_CLOSER;
3607
0
                    if(flags != 0)
3608
0
                        ADD_MARK(ch, off, tmp, flags);
3609
0
                }
3610
3611
0
                off = tmp;
3612
0
                continue;
3613
0
            }
3614
3615
            /* A potential insert start/end: ++text++ */
3616
0
            if(ch == _T('+') && (ctx->parser.flags & MD_FLAG_INSERT)) {
3617
0
                OFF tmp = off + 1;
3618
3619
0
                while(tmp < line->end && CH(tmp) == _T('+'))
3620
0
                    tmp++;
3621
3622
                /* Only exactly two plus signs form a insert delimiter. */
3623
0
                if(tmp - off == 2) {
3624
0
                    unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3625
3626
                    /* Cannot open before whitespace; cannot close after whitespace. */
3627
0
                    if(tmp >= line->end  ||  ISUNICODEWHITESPACE(tmp))
3628
0
                        flags &= ~MD_MARK_POTENTIAL_OPENER;
3629
0
                    if(off == line->beg  ||  ISUNICODEWHITESPACEBEFORE(off))
3630
0
                        flags &= ~MD_MARK_POTENTIAL_CLOSER;
3631
0
                    if(flags != 0)
3632
0
                        ADD_MARK(ch, off, tmp, flags);
3633
0
                }
3634
3635
0
                off = tmp;
3636
0
                continue;
3637
0
            }
3638
3639
            /* A potential strikethrough/subscript start/end. */
3640
0
            if(ch == _T('~')) {
3641
0
                OFF tmp = off + 1;
3642
3643
0
                while(tmp < line->end && CH(tmp) == _T('~'))
3644
0
                    tmp++;
3645
3646
0
                if(tmp - off == 1  &&  (ctx->parser.flags & MD_FLAG_SUBSCRIPTS)) {
3647
                    /* Subscript: can open after any non-whitespace, cannot open
3648
                     * before whitespace; cannot close after whitespace. */
3649
0
                    unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3650
3651
0
                    if(off + 1 >= line->end  ||  ISUNICODEWHITESPACE(off + 1))
3652
0
                        flags &= ~MD_MARK_POTENTIAL_OPENER;
3653
0
                    if(off == line->beg  ||  ISUNICODEWHITESPACEBEFORE(off))
3654
0
                        flags &= ~MD_MARK_POTENTIAL_CLOSER;
3655
0
                    if(flags != 0)
3656
0
                        ADD_MARK(ch, off, off + 1, flags);
3657
0
                } else if(tmp - off <= 2  &&  (ctx->parser.flags & MD_FLAG_STRIKETHROUGH)) {
3658
                    /* Strikethrough: standard GFM left/right-flanking rules. */
3659
0
                    unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3660
3661
0
                    if(off > line->beg  &&  !ISUNICODEWHITESPACEBEFORE(off)  &&  !ISUNICODEPUNCTBEFORE(off))
3662
0
                        flags &= ~MD_MARK_POTENTIAL_OPENER;
3663
0
                    if(tmp < line->end  &&  !ISUNICODEWHITESPACE(tmp)  &&  !ISUNICODEPUNCT(tmp))
3664
0
                        flags &= ~MD_MARK_POTENTIAL_CLOSER;
3665
0
                    if(flags != 0)
3666
0
                        ADD_MARK(ch, off, tmp, flags);
3667
0
                }
3668
3669
0
                off = tmp;
3670
0
                continue;
3671
0
            }
3672
3673
            /* A potential equation start/end. */
3674
0
            if(ch == _T('$')) {
3675
0
                OFF tmp = off + 1;
3676
3677
0
                while(tmp < line->end && CH(tmp) == _T('$'))
3678
0
                    tmp++;
3679
3680
0
                if(tmp - off <= 2) {
3681
0
                    unsigned flags = MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER;
3682
3683
0
                    if(off > line->beg  &&  !ISUNICODEWHITESPACEBEFORE(off)  &&  !ISUNICODEPUNCTBEFORE(off))
3684
0
                        flags &= ~MD_MARK_POTENTIAL_OPENER;
3685
0
                    if(tmp < line->end  &&  !ISUNICODEWHITESPACE(tmp)  &&  !ISUNICODEPUNCT(tmp))
3686
0
                        flags &= ~MD_MARK_POTENTIAL_CLOSER;
3687
0
                    if(flags != 0)
3688
0
                        ADD_MARK(ch, off, tmp, flags);
3689
0
                }
3690
3691
0
                off = tmp;
3692
0
                continue;
3693
0
            }
3694
3695
            /* Turn non-trivial whitespace into single space. */
3696
0
            if(ISWHITESPACE_(ch)) {
3697
0
                OFF tmp = off+1;
3698
3699
0
                while(tmp < line->end  &&  ISWHITESPACE(tmp))
3700
0
                    tmp++;
3701
3702
0
                if(tmp - off > 1  ||  ch != _T(' '))
3703
0
                    ADD_MARK(ch, off, tmp, MD_MARK_RESOLVED);
3704
3705
0
                off = tmp;
3706
0
                continue;
3707
0
            }
3708
3709
            /* NULL character. */
3710
0
            if(ch == _T('\0')) {
3711
0
                ADD_MARK(ch, off, off+1, MD_MARK_RESOLVED);
3712
0
                off++;
3713
0
                continue;
3714
0
            }
3715
3716
0
            off++;
3717
0
        }
3718
0
    }
3719
3720
    /* Add a dummy mark at the end of the mark vector to simplify
3721
     * process_inlines(). */
3722
0
    ADD_MARK(127, ctx->size, ctx->size, MD_MARK_RESOLVED);
3723
3724
0
abort:
3725
0
    return ret;
3726
0
}
3727
3728
static void
3729
md_analyze_bracket(MD_CTX* ctx, int mark_index)
3730
0
{
3731
    /* We cannot really resolve links here as for that we would need
3732
     * more context. E.g. a following pair of brackets (reference link),
3733
     * or enclosing pair of brackets (if the inner is the link, the outer
3734
     * one cannot be.)
3735
     *
3736
     * Therefore we here only construct a list of '[' ']' pairs ordered by
3737
     * position of the closer. This allows us to analyze what is or is not
3738
     * link in the right order, from inside to outside in case of nested
3739
     * brackets.
3740
     *
3741
     * The resolving itself is deferred to md_resolve_brackets().
3742
     */
3743
3744
0
    MD_MARK* mark = &ctx->marks[mark_index];
3745
3746
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER) {
3747
0
        if(BRACKET_OPENERS.top >= 0)
3748
0
            ctx->marks[BRACKET_OPENERS.top].flags |= MD_MARK_BRACKET_HASNESTED;
3749
3750
0
        md_mark_stack_push(ctx, &BRACKET_OPENERS, mark_index);
3751
0
        return;
3752
0
    }
3753
3754
0
    if(BRACKET_OPENERS.top >= 0) {
3755
0
        int opener_index = md_mark_stack_pop(ctx, &BRACKET_OPENERS);
3756
0
        MD_MARK* opener = &ctx->marks[opener_index];
3757
3758
        /* Interconnect the opener and closer. */
3759
0
        opener->next = mark_index;
3760
0
        mark->prev = opener_index;
3761
3762
        /* Add the pair into a list of potential brackets for md_resolve_brackets().
3763
         * Note we misuse opener->prev for this as opener->next points to its
3764
         * closer. */
3765
0
        if(ctx->unresolved_link_tail >= 0)
3766
0
            ctx->marks[ctx->unresolved_link_tail].prev = opener_index;
3767
0
        else
3768
0
            ctx->unresolved_link_head = opener_index;
3769
0
        ctx->unresolved_link_tail = opener_index;
3770
0
        opener->prev = -1;
3771
0
    }
3772
0
}
3773
3774
/* Forward declaration. */
3775
static void md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
3776
                                     int mark_beg, int mark_end);
3777
3778
/* Try to resolve a bracket pair as a wiki link '[[destination]]' or
3779
 * '[[destination|label]]'.
3780
 * Returns true if resolved, false if not a wiki link, -1 on error. */
3781
static int
3782
md_resolve_bracket_wikilink(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
3783
                            int opener_index, int closer_index,
3784
                            MD_MARK* opener, MD_MARK* closer,
3785
                            MD_MARK* next_opener, MD_MARK* next_closer,
3786
                            OFF* last_link_beg, OFF* last_link_end,
3787
                            int* p_opener_index)
3788
0
{
3789
0
    MD_MARK* delim = NULL;
3790
0
    int delim_index;
3791
0
    OFF dest_beg, dest_end;
3792
0
    OFF off;
3793
3794
0
    if(!(ctx->parser.flags & MD_FLAG_WIKILINKS))
3795
0
        return false;
3796
3797
0
    if(opener->ch != _T('[')  ||  opener->end - opener->beg != 1  ||
3798
0
       next_opener == NULL  ||  next_opener->ch != _T('[')  ||  next_opener->end - next_opener->beg != 1  ||
3799
0
       next_closer == NULL  ||  next_closer->ch != _T(']')  ||  next_closer->end - next_closer->beg != 1)
3800
0
        return false;
3801
3802
    /* Check that the next_opener and next_closer are nested properly. */
3803
0
    if(next_opener->beg != opener->beg - 1  ||  next_closer->beg != closer->beg + 1)
3804
0
        return false;
3805
3806
    /* We don't allow destination to be longer than 100 characters.
3807
     * Lets scan to see whether there is '|'. (If not then the whole
3808
     * wiki-link has to be below the 100 characters.) */
3809
0
    delim_index = opener_index + 1;
3810
0
    while(delim_index < closer_index) {
3811
0
        MD_MARK* m = &ctx->marks[delim_index];
3812
0
        if(m->ch == _T('|')  &&  m->end - m->beg == 1  &&  !(m->flags & MD_MARK_RESOLVED)) {
3813
0
            delim = m;
3814
0
            break;
3815
0
        }
3816
0
        if(m->ch != _T('D')) {
3817
0
            if(m->beg - opener->end > 100)
3818
0
                break;
3819
0
            if(m->ch != _T('D')  &&  (m->flags & MD_MARK_OPENER))
3820
0
                delim_index = m->next;
3821
0
        }
3822
0
        delim_index++;
3823
0
    }
3824
3825
0
    dest_beg = opener->end;
3826
0
    dest_end = (delim != NULL) ? delim->beg : closer->beg;
3827
0
    if(dest_end - dest_beg == 0 || dest_end - dest_beg > 100)
3828
0
        return false;
3829
3830
    /* There may not be any new line in the destination. */
3831
0
    for(off = dest_beg; off < dest_end; off++) {
3832
0
        if(ISNEWLINE(off))
3833
0
            return false;
3834
0
    }
3835
3836
0
    md_pop_openers(ctx, opener_index);
3837
3838
0
    if(delim != NULL) {
3839
0
        if(delim->end < closer->beg) {
3840
0
            md_disable_marks(ctx, opener_index+1, delim_index);
3841
0
            delim->flags |= MD_MARK_RESOLVED;
3842
0
            opener->end = delim->beg;
3843
0
        } else {
3844
            /* The pipe is just before the closer: [[foo|]] */
3845
0
            md_disable_marks(ctx, opener_index+1, closer_index);
3846
0
            closer->beg = delim->beg;
3847
0
        }
3848
0
    }
3849
3850
0
    opener->beg = next_opener->beg;
3851
0
    closer->end = next_closer->end;
3852
0
    md_resolve_range(ctx, opener_index, closer_index);
3853
3854
0
    *last_link_beg = opener->beg;
3855
0
    *last_link_end = closer->end;
3856
3857
0
    if(delim != NULL)
3858
0
        md_analyze_link_contents(ctx, lines, n_lines, delim_index+1, closer_index);
3859
3860
0
    *p_opener_index = next_opener->prev;
3861
0
    return true;
3862
0
}
3863
3864
/* Resolve footnote references [^label] in the current block. */
3865
static int
3866
md_resolve_bracket_footnote(MD_CTX* ctx, MD_MARK* opener, MD_MARK* closer,
3867
                            OFF* last_link_beg, OFF* last_link_end,
3868
                            int* p_opener_index)
3869
0
{
3870
0
    MD_MARK* index_mark;
3871
0
    MD_FOOTNOTE_DEF* def;
3872
0
    OFF label_beg, label_end;
3873
3874
0
    if(!(ctx->parser.flags & MD_FLAG_FOOTNOTES))
3875
0
        return false;
3876
0
    if(opener->ch != _T('[')  ||  opener->end >= ctx->size  ||  CH(opener->end) != _T('^'))
3877
0
        return false;
3878
3879
    /* Expand the opener to eat the '^' */
3880
0
    opener->end++;
3881
0
    closer = &ctx->marks[opener->next];
3882
3883
    /* Verify the label satisfies the label rules. */
3884
0
    label_beg = opener->end;
3885
0
    if(!md_is_footnote_label(ctx, label_beg, &label_end)  ||  label_end != closer->beg)
3886
0
        return false;
3887
3888
0
    if(label_beg >= label_end)
3889
0
        return false;   /* empty label */
3890
3891
0
    def = md_lookup_footnote_def(ctx, STR(label_beg), label_end - label_beg);
3892
0
    if(def == NULL)
3893
0
        return false;
3894
3895
    /* Assign index on first reference. */
3896
0
    if(def->index == 0)
3897
0
        def->index = ++ctx->next_footnote_index;
3898
0
    def->ref_count++;
3899
3900
    /* Store the public callback details in the dummy mark after the opener. */
3901
0
    index_mark = opener + 1;
3902
0
    MD_ASSERT(index_mark->ch == _T('D'));
3903
0
    index_mark->beg = def->index;
3904
0
    index_mark->end = def->ref_count;
3905
3906
    /* Mark as resolved. */
3907
0
    opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED | MD_MARK_BRACKET_FOOTNOTEREF;
3908
0
    closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED | MD_MARK_BRACKET_FOOTNOTEREF;
3909
0
    *last_link_beg = opener->beg;
3910
0
    *last_link_end = closer->end;
3911
3912
0
    *p_opener_index = opener->prev;
3913
0
    return true;
3914
0
}
3915
3916
/* Try to resolve a bracket pair as a CommonMark link or image.
3917
 * Returns -1 on error, 0 otherwise. */
3918
static int
3919
md_resolve_bracket_link(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
3920
                        int opener_index, int closer_index,
3921
                        MD_MARK* opener, MD_MARK* closer,
3922
                        MD_MARK* next_opener, MD_MARK* next_closer,
3923
                        int* p_next_index,
3924
                        OFF* last_link_beg, OFF* last_link_end,
3925
                        OFF* last_img_beg, OFF* last_img_end)
3926
0
{
3927
0
    MD_LINK_ATTR attr;
3928
0
    int is_link = false;
3929
3930
0
    if(next_opener != NULL  &&  next_opener->beg == closer->end) {
3931
0
        if(next_closer->beg > closer->end + 1) {
3932
            /* Might be full reference link. */
3933
0
            if(!(next_opener->flags & MD_MARK_BRACKET_HASNESTED))
3934
0
                is_link = md_is_link_reference(ctx, lines, n_lines, next_opener->beg, next_closer->end, &attr);
3935
0
        } else {
3936
            /* Might be shortcut reference link. */
3937
0
            if(!(opener->flags & MD_MARK_BRACKET_HASNESTED))
3938
0
                is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr);
3939
0
        }
3940
3941
0
        if(is_link < 0)
3942
0
            return -1;
3943
3944
0
        if(is_link) {
3945
            /* Eat the 2nd "[...]". */
3946
0
            closer->end = next_closer->end;
3947
3948
            /* Do not analyze the label as a standalone link in the next
3949
             * iteration. */
3950
0
            *p_next_index = ctx->marks[*p_next_index].prev;
3951
0
        }
3952
0
    } else {
3953
0
        if(closer->end < ctx->size  &&  CH(closer->end) == _T('(')) {
3954
            /* Might be inline link. */
3955
0
            OFF inline_link_end = UINT_MAX;
3956
0
            int following_mark_index = closer_index + 1;
3957
3958
0
            is_link = md_is_inline_link_spec(ctx, lines, n_lines, closer->end, &inline_link_end, &attr);
3959
0
            if(is_link < 0)
3960
0
                return -1;
3961
3962
            /* Check the closing ')' is not inside an already resolved range
3963
             * (i.e. a range with a higher priority), e.g. a code span. */
3964
0
            if(is_link) {
3965
0
                while(following_mark_index < ctx->n_marks) {
3966
0
                    MD_MARK* mark = &ctx->marks[following_mark_index];
3967
3968
0
                    if(mark->beg >= inline_link_end)
3969
0
                        break;
3970
0
                    if((mark->flags & (MD_MARK_OPENER | MD_MARK_RESOLVED)) == (MD_MARK_OPENER | MD_MARK_RESOLVED)) {
3971
0
                        if(ctx->marks[mark->next].beg >= inline_link_end) {
3972
                            /* Cancel the link status. */
3973
0
                            if(attr.title_needs_free)
3974
0
                                free(attr.title);
3975
0
                            is_link = false;
3976
0
                            break;
3977
0
                        }
3978
3979
0
                        following_mark_index = mark->next + 1;
3980
0
                    } else {
3981
0
                        following_mark_index++;
3982
0
                    }
3983
0
                }
3984
0
            }
3985
3986
0
            if(is_link) {
3987
                /* Eat the "(...)" */
3988
0
                closer->end = inline_link_end;
3989
0
                md_disable_marks(ctx, closer_index+1, following_mark_index);
3990
0
            }
3991
0
        }
3992
3993
0
        if(!is_link) {
3994
            /* Might be collapsed reference link. */
3995
0
            if(!(opener->flags & MD_MARK_BRACKET_HASNESTED))
3996
0
                is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr);
3997
0
            if(is_link < 0)
3998
0
                return -1;
3999
0
        }
4000
0
    }
4001
4002
0
    if(is_link) {
4003
        /* Resolve the brackets as a link. */
4004
0
        opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
4005
0
        closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
4006
4007
        /* If it is a link, we store the destination and title in the two
4008
         * dummy marks after the opener. */
4009
0
        MD_ASSERT(ctx->marks[opener_index+1].ch == _T('D'));
4010
0
        ctx->marks[opener_index+1].beg = attr.dest_beg;
4011
0
        ctx->marks[opener_index+1].end = attr.dest_end;
4012
4013
0
        MD_ASSERT(ctx->marks[opener_index+2].ch == _T('D'));
4014
0
        md_mark_store_ptr(ctx, opener_index+2, attr.title);
4015
        /* The title might or might not have been allocated for us. */
4016
0
        if(attr.title_needs_free)
4017
0
            md_mark_stack_push(ctx, &ctx->ptr_stack, opener_index+2);
4018
0
        ctx->marks[opener_index+2].prev = attr.title_size;
4019
4020
0
        if(opener->ch == _T('[')) {
4021
0
            *last_link_beg = opener->beg;
4022
0
            *last_link_end = closer->end;
4023
0
        } else {
4024
0
            *last_img_beg = opener->beg;
4025
0
            *last_img_end = closer->end;
4026
0
        }
4027
4028
0
        md_analyze_link_contents(ctx, lines, n_lines, opener_index+1, closer_index);
4029
4030
        /* If the link text is formed by nothing but permissive autolink,
4031
         * suppress the autolink.
4032
         * See https://github.com/mity/md4c/issues/152 for more info. */
4033
0
        if(ctx->parser.flags & MD_FLAG_PERMISSIVEAUTOLINKS) {
4034
0
            MD_MARK* first_nested;
4035
0
            MD_MARK* last_nested;
4036
4037
0
            first_nested = opener + 1;
4038
0
            while(first_nested->ch == _T('D')  &&  first_nested < closer)
4039
0
                first_nested++;
4040
4041
0
            last_nested = closer - 1;
4042
0
            while(first_nested->ch == _T('D')  &&  last_nested > opener)
4043
0
                last_nested--;
4044
4045
0
            if((first_nested->flags & MD_MARK_RESOLVED)  &&
4046
0
               first_nested->beg == opener->end  &&
4047
0
               ISANYOF_(first_nested->ch, _T("@:."))  &&
4048
0
               first_nested->next == (last_nested - ctx->marks)  &&
4049
0
               last_nested->end == closer->beg)
4050
0
            {
4051
0
                first_nested->ch = _T('D');
4052
0
                first_nested->flags &= ~MD_MARK_RESOLVED;
4053
0
                last_nested->ch = _T('D');
4054
0
                last_nested->flags &= ~MD_MARK_RESOLVED;
4055
0
            }
4056
0
        }
4057
0
    }
4058
4059
0
    return 0;
4060
0
}
4061
4062
/* Resolve bracket pairs as links, wiki links, or (in a 2nd pass) footnotes. */
4063
static int
4064
md_resolve_brackets(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
4065
0
{
4066
0
    int opener_index = ctx->unresolved_link_head;
4067
0
    OFF last_link_beg = 0;
4068
0
    OFF last_link_end = 0;
4069
0
    OFF last_img_beg = 0;
4070
0
    OFF last_img_end = 0;
4071
0
    int ret;
4072
4073
    /* Note we here analyze from inner to outer as the marks are ordered
4074
     * by closer->beg. */
4075
0
    while(opener_index >= 0) {
4076
0
        MD_MARK* opener = &ctx->marks[opener_index];
4077
0
        int closer_index = opener->next;
4078
0
        MD_MARK* closer = &ctx->marks[closer_index];
4079
0
        int next_index = opener->prev;
4080
0
        MD_MARK* next_opener;
4081
0
        MD_MARK* next_closer;
4082
4083
0
        if(opener->ch == _T('D')) {
4084
            /* We could have this disabled in previous iterations, and
4085
             * processing would be just burning CPU cycles. */
4086
0
            opener_index = next_index;
4087
0
            continue;
4088
0
        }
4089
4090
0
        if(next_index >= 0) {
4091
0
            next_opener = &ctx->marks[next_index];
4092
0
            next_closer = &ctx->marks[next_opener->next];
4093
0
        } else {
4094
0
            next_opener = NULL;
4095
0
            next_closer = NULL;
4096
0
        }
4097
4098
        /* We can perhaps be an image? */
4099
0
        if(opener->flags & MD_MARK_BRACKET_CANBEIMAGE) {
4100
0
            opener->ch = _T('!');
4101
0
            opener->beg--;
4102
0
        }
4103
4104
        /* If nested ("[ [ ] ]"), we need to make sure that:
4105
         *   - The outer does not end inside of (...) belonging to the inner.
4106
         *   - The outer cannot be link if the inner is link (but this does not apply to images). */
4107
0
        if((opener->beg < last_link_beg  &&  closer->end < last_link_end)  ||
4108
0
           (opener->beg < last_img_beg  &&  closer->end < last_img_end)  ||
4109
0
           (opener->beg < last_link_end  &&  opener->ch != _T('!')))
4110
0
        {
4111
0
            opener_index = next_index;
4112
0
            continue;
4113
0
        }
4114
4115
0
        ret = md_resolve_bracket_footnote(ctx, opener, closer,
4116
0
                        &last_link_beg, &last_link_end, &opener_index);
4117
0
        if(ret < 0)
4118
0
            return -1;
4119
0
        if(ret > 0)
4120
0
            continue;
4121
4122
0
        ret = md_resolve_bracket_wikilink(ctx, lines, n_lines, opener_index, closer_index,
4123
0
                        opener, closer, next_opener, next_closer,
4124
0
                        &last_link_beg, &last_link_end, &opener_index);
4125
0
        if(ret < 0)
4126
0
            return -1;
4127
0
        if(ret > 0)
4128
0
            continue;
4129
4130
0
        ret = md_resolve_bracket_link(ctx, lines, n_lines, opener_index, closer_index,
4131
0
                        opener, closer, next_opener, next_closer, &next_index,
4132
0
                        &last_link_beg, &last_link_end, &last_img_beg, &last_img_end);
4133
0
        if(ret < 0)
4134
0
            return -1;
4135
4136
0
        opener_index = next_index;
4137
0
    }
4138
4139
0
    return 0;
4140
0
}
4141
4142
/* Analyze whether the mark '&' starts a HTML entity.
4143
 * If so, update its flags as well as flags of corresponding closer ';'. */
4144
static void
4145
md_analyze_entity(MD_CTX* ctx, int mark_index)
4146
0
{
4147
0
    MD_MARK* opener = &ctx->marks[mark_index];
4148
0
    MD_MARK* closer;
4149
0
    OFF off;
4150
4151
    /* Cannot be entity if there is no closer as the next mark.
4152
     * (Any other mark between would mean strange character which cannot be
4153
     * part of the entity.
4154
     *
4155
     * So we can do all the work on '&' and do not call this later for the
4156
     * closing mark ';'.
4157
     */
4158
0
    if(mark_index + 1 >= ctx->n_marks)
4159
0
        return;
4160
0
    closer = &ctx->marks[mark_index+1];
4161
0
    if(closer->ch != ';')
4162
0
        return;
4163
4164
0
    if(md_is_entity(ctx, opener->beg, closer->end, &off)) {
4165
0
        MD_ASSERT(off == closer->end);
4166
4167
0
        md_resolve_range(ctx, mark_index, mark_index+1);
4168
0
        opener->end = closer->end;
4169
0
    }
4170
0
}
4171
4172
static void
4173
md_analyze_table_cell_boundary(MD_CTX* ctx, int mark_index)
4174
0
{
4175
0
    MD_MARK* mark = &ctx->marks[mark_index];
4176
4177
    /* FIXME: With MD_FLAG_SPOILERS, we reserve double "||" for spoiler marks
4178
     * (potentially inside the table). But is it worth it the incompatibility
4179
     * with GFM? Perhaps it would be better to disallow spoilers in a table? */
4180
0
    if((ctx->parser.flags & MD_FLAG_SPOILERS) && mark->end - mark->beg == 2)
4181
0
        return;
4182
4183
0
    mark->flags |= MD_MARK_RESOLVED;
4184
0
    mark->next = -1;
4185
4186
0
    if(ctx->table_cell_boundaries_head < 0)
4187
0
        ctx->table_cell_boundaries_head = mark_index;
4188
0
    else
4189
0
        ctx->marks[ctx->table_cell_boundaries_tail].next = mark_index;
4190
0
    ctx->table_cell_boundaries_tail = mark_index;
4191
0
    ctx->n_table_cell_boundaries += mark->end - mark->beg;
4192
0
}
4193
4194
/* Split a longer mark into two. The new mark takes the given count of
4195
 * characters. May only be called if an adequate number of dummy 'D' marks
4196
 * follows.
4197
 */
4198
static int
4199
md_split_emph_mark(MD_CTX* ctx, int mark_index, SZ n)
4200
0
{
4201
0
    MD_MARK* mark = &ctx->marks[mark_index];
4202
0
    int new_mark_index = mark_index + (mark->end - mark->beg - n);
4203
0
    MD_MARK* dummy = &ctx->marks[new_mark_index];
4204
4205
0
    MD_ASSERT(mark->end - mark->beg > n);
4206
0
    MD_ASSERT(dummy->ch == 'D');
4207
4208
0
    memcpy(dummy, mark, sizeof(MD_MARK));
4209
0
    mark->end -= n;
4210
0
    dummy->beg = mark->end;
4211
4212
0
    return new_mark_index;
4213
0
}
4214
4215
static void
4216
md_analyze_emph(MD_CTX* ctx, int mark_index)
4217
0
{
4218
0
    MD_MARK* mark = &ctx->marks[mark_index];
4219
4220
    /* If we can be a closer, try to resolve with the preceding opener. */
4221
0
    if(mark->flags & MD_MARK_POTENTIAL_CLOSER) {
4222
0
        MD_MARK* opener = NULL;
4223
0
        int opener_index = 0;
4224
0
        MD_MARKSTACK* opener_stacks[6];
4225
0
        int i, n_opener_stacks;
4226
0
        unsigned flags = mark->flags;
4227
4228
0
        n_opener_stacks = 0;
4229
4230
        /* Apply the rule of 3 */
4231
0
        opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_0 | MD_MARK_EMPH_OC);
4232
0
        if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2)
4233
0
            opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_1 | MD_MARK_EMPH_OC);
4234
0
        if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1)
4235
0
            opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_2 | MD_MARK_EMPH_OC);
4236
0
        opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_0);
4237
0
        if(!(flags & MD_MARK_EMPH_OC)  ||  (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2)
4238
0
            opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_1);
4239
0
        if(!(flags & MD_MARK_EMPH_OC)  ||  (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1)
4240
0
            opener_stacks[n_opener_stacks++] = md_emph_stack(ctx, mark->ch, MD_MARK_EMPH_MOD3_2);
4241
4242
        /* Opener is the most recent mark from the allowed stacks. */
4243
0
        for(i = 0; i < n_opener_stacks; i++) {
4244
0
            if(opener_stacks[i]->top >= 0) {
4245
0
                int m_index = opener_stacks[i]->top;
4246
0
                MD_MARK* m = &ctx->marks[m_index];
4247
4248
0
                if(opener == NULL  ||  m->end > opener->end) {
4249
0
                    opener_index = m_index;
4250
0
                    opener = m;
4251
0
                }
4252
0
            }
4253
0
        }
4254
4255
        /* Resolve, if we have found matching opener. */
4256
0
        if(opener != NULL) {
4257
0
            SZ opener_size = opener->end - opener->beg;
4258
0
            SZ closer_size = mark->end - mark->beg;
4259
0
            MD_MARKSTACK* stack = md_opener_stack(ctx, opener_index);
4260
4261
0
            if(opener_size > closer_size) {
4262
0
                opener_index = md_split_emph_mark(ctx, opener_index, closer_size);
4263
0
                md_mark_stack_push(ctx, stack, opener_index);
4264
0
            } else if(opener_size < closer_size) {
4265
0
                md_split_emph_mark(ctx, mark_index, closer_size - opener_size);
4266
0
            }
4267
4268
0
            md_pop_openers(ctx, opener_index);
4269
0
            md_resolve_range(ctx, opener_index, mark_index);
4270
0
            return;
4271
0
        }
4272
0
    }
4273
4274
    /* If we could not resolve as closer, we may be yet be an opener. */
4275
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4276
0
        md_mark_stack_push(ctx, md_emph_stack(ctx, mark->ch, mark->flags), mark_index);
4277
0
}
4278
4279
static void
4280
md_analyze_tilde(MD_CTX* ctx, int mark_index)
4281
0
{
4282
0
    MD_MARK* mark = &ctx->marks[mark_index];
4283
0
    MD_MARKSTACK* stack = md_opener_stack(ctx, mark_index);
4284
4285
    /* We attempt to be Github Flavored Markdown compatible here. GFM accepts
4286
     * only tildes sequences of length 1 and 2, and the length of the opener
4287
     * and closer has to match. */
4288
4289
0
    if((mark->flags & MD_MARK_POTENTIAL_CLOSER)  &&  stack->top >= 0) {
4290
0
        int opener_index = stack->top;
4291
4292
0
        md_pop_openers(ctx, opener_index);
4293
0
        md_resolve_range(ctx, opener_index, mark_index);
4294
0
        return;
4295
0
    }
4296
4297
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4298
0
        md_mark_stack_push(ctx, stack, mark_index);
4299
0
}
4300
4301
static void
4302
md_analyze_caret(MD_CTX* ctx, int mark_index)
4303
0
{
4304
0
    MD_MARK* mark = &ctx->marks[mark_index];
4305
4306
0
    if((mark->flags & MD_MARK_POTENTIAL_CLOSER)  &&  CARET_OPENERS.top >= 0) {
4307
0
        int opener_index = CARET_OPENERS.top;
4308
4309
0
        md_pop_openers(ctx, opener_index);
4310
0
        md_resolve_range(ctx, opener_index, mark_index);
4311
0
        return;
4312
0
    }
4313
4314
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4315
0
        md_mark_stack_push(ctx, &CARET_OPENERS, mark_index);
4316
0
}
4317
4318
static void
4319
md_analyze_dollar(MD_CTX* ctx, int mark_index)
4320
0
{
4321
0
    MD_MARK* mark = &ctx->marks[mark_index];
4322
4323
0
    if((mark->flags & MD_MARK_POTENTIAL_CLOSER)  &&  DOLLAR_OPENERS.top >= 0) {
4324
        /* If the potential closer has a non-matching number of $, discard */
4325
0
        MD_MARK* opener = &ctx->marks[DOLLAR_OPENERS.top];
4326
0
        int opener_index = DOLLAR_OPENERS.top;
4327
0
        MD_MARK* closer = mark;
4328
0
        int closer_index = mark_index;
4329
4330
0
        if(opener->end - opener->beg == closer->end - closer->beg) {
4331
            /* We are the matching closer */
4332
0
            md_pop_openers(ctx, opener_index);
4333
0
            md_disable_marks(ctx, opener_index+1, closer_index);
4334
0
            md_resolve_range(ctx, opener_index, closer_index);
4335
4336
            /* Discard all pending openers: Latex math span do not allow
4337
             * nesting. */
4338
0
            DOLLAR_OPENERS.top = -1;
4339
0
            return;
4340
0
        }
4341
0
    }
4342
4343
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4344
0
        md_mark_stack_push(ctx, &DOLLAR_OPENERS, mark_index);
4345
0
}
4346
4347
static void
4348
md_analyze_spoiler(MD_CTX* ctx, int mark_index)
4349
0
{
4350
0
    MD_MARK* mark = &ctx->marks[mark_index];
4351
4352
    /* Only double "||" are recognized as spoiler marks. */
4353
0
    if((mark->flags & MD_MARK_RESOLVED)  ||  mark->end - mark->beg != 2)
4354
0
        return;
4355
4356
0
    if((mark->flags & MD_MARK_POTENTIAL_CLOSER)  &&  PIPE_OPENERS.top >= 0) {
4357
0
        int opener_index = PIPE_OPENERS.top;
4358
4359
0
        md_pop_openers(ctx, opener_index);
4360
0
        md_resolve_range(ctx, opener_index, mark_index);
4361
0
        return;
4362
0
    }
4363
4364
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4365
0
        md_mark_stack_push(ctx, &PIPE_OPENERS, mark_index);
4366
0
}
4367
4368
static void
4369
md_analyze_highlight(MD_CTX* ctx, int mark_index)
4370
0
{
4371
0
    MD_MARK* mark = &ctx->marks[mark_index];
4372
4373
    /* Only "==" is recognized as a highlight mark. */
4374
0
    if(mark->end - mark->beg != 2)
4375
0
        return;
4376
4377
0
    if((mark->flags & MD_MARK_POTENTIAL_CLOSER)  &&  EQUAL_OPENERS.top >= 0) {
4378
0
        int opener_index = EQUAL_OPENERS.top;
4379
4380
0
        md_pop_openers(ctx, opener_index);
4381
0
        md_resolve_range(ctx, opener_index, mark_index);
4382
0
        return;
4383
0
    }
4384
4385
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4386
0
        md_mark_stack_push(ctx, &EQUAL_OPENERS, mark_index);
4387
0
}
4388
4389
static void
4390
md_analyze_insert(MD_CTX* ctx, int mark_index)
4391
0
{
4392
0
    MD_MARK* mark = &ctx->marks[mark_index];
4393
4394
    /* Only "++" is recognized as a insert mark. */
4395
0
    if(mark->end - mark->beg != 2)
4396
0
        return;
4397
4398
0
    if((mark->flags & MD_MARK_POTENTIAL_CLOSER)  &&  PLUS_OPENERS.top >= 0) {
4399
0
        int opener_index = PLUS_OPENERS.top;
4400
4401
0
        md_pop_openers(ctx, opener_index);
4402
0
        md_resolve_range(ctx, opener_index, mark_index);
4403
0
        return;
4404
0
    }
4405
4406
0
    if(mark->flags & MD_MARK_POTENTIAL_OPENER)
4407
0
        md_mark_stack_push(ctx, &PLUS_OPENERS, mark_index);
4408
0
}
4409
4410
static MD_MARK*
4411
md_scan_left_for_resolved_mark(MD_CTX* ctx, MD_MARK* mark_from, OFF off, MD_MARK** p_cursor)
4412
0
{
4413
0
    MD_MARK* mark;
4414
4415
0
    for(mark = mark_from; mark >= ctx->marks; mark--) {
4416
0
        if(mark->ch == 'D'  ||  mark->beg > off)
4417
0
            continue;
4418
0
        if(mark->beg <= off  &&  off < mark->end  &&  (mark->flags & MD_MARK_RESOLVED)) {
4419
0
            if(p_cursor != NULL)
4420
0
                *p_cursor = mark;
4421
0
            return mark;
4422
0
        }
4423
0
        if(mark->end <= off)
4424
0
            break;
4425
0
    }
4426
4427
0
    if(p_cursor != NULL)
4428
0
        *p_cursor = mark;
4429
0
    return NULL;
4430
0
}
4431
4432
static MD_MARK*
4433
md_scan_right_for_resolved_mark(MD_CTX* ctx, MD_MARK* mark_from, OFF off, MD_MARK** p_cursor)
4434
0
{
4435
0
    MD_MARK* mark;
4436
4437
0
    for(mark = mark_from; mark < ctx->marks + ctx->n_marks; mark++) {
4438
0
        if(mark->ch == 'D'  ||  mark->end <= off)
4439
0
            continue;
4440
0
        if(mark->beg <= off  &&  off < mark->end  &&  (mark->flags & MD_MARK_RESOLVED)) {
4441
0
            if(p_cursor != NULL)
4442
0
                *p_cursor = mark;
4443
0
            return mark;
4444
0
        }
4445
0
        if(mark->beg > off)
4446
0
            break;
4447
0
    }
4448
4449
0
    if(p_cursor != NULL)
4450
0
        *p_cursor = mark;
4451
0
    return NULL;
4452
0
}
4453
4454
static int
4455
md_analyze_permissive_autolink_segment(MD_CTX* ctx, OFF off, OFF end, OFF* p_end,
4456
            int scan_backwards, MD_CHAR component_delim, const MD_CHAR* word_extra,
4457
            const MD_CHAR* word_delims, MD_MARK** p_cursor)
4458
0
{
4459
0
    int n_components = 0;
4460
0
    int n_open_brackets = 0;
4461
0
    int seen_word_delim = true;
4462
0
    int seen_component_delim = true;
4463
0
    OFF component_beg = off;
4464
4465
0
    if(word_extra == NULL)
4466
0
        word_extra = _T("");
4467
0
    if(word_delims == NULL)
4468
0
        word_delims = _T("");
4469
4470
0
    while(off != end) {
4471
0
        if(scan_backwards)
4472
0
            off--;
4473
4474
        /* Only accept extra and delimiter characters if they're not part of a
4475
         * resolved mark. */
4476
0
        if(!ISALNUM(off)  &&  !ISWHITESPACE(off)) {
4477
0
            if((!scan_backwards && md_scan_right_for_resolved_mark(ctx, *p_cursor, off, p_cursor) != NULL)  ||
4478
0
               (scan_backwards && md_scan_left_for_resolved_mark(ctx, *p_cursor, off, p_cursor) != NULL)) {
4479
0
                if(scan_backwards)
4480
0
                    off++;
4481
0
                break;
4482
0
            }
4483
0
        }
4484
4485
        /* The autolink can be _inside_ brackets so we disallow unbalanced bracket pairs in the URL.
4486
         * (Note the brackets are not allowed in e-mail username, so we happily skip this in that case.) */
4487
0
        if(!scan_backwards) {
4488
0
            if(CH(off) == _T('(')) {
4489
0
                n_open_brackets++;
4490
0
            } else if(CH(off) == _T(')')) {
4491
0
                if(n_open_brackets <= 0)
4492
0
                    break;
4493
0
                n_open_brackets--;
4494
0
            }
4495
0
        }
4496
4497
0
        if(ISALNUM(off)  ||  ISANYOF(off, word_extra)) {
4498
0
            seen_word_delim = false;
4499
0
            seen_component_delim = false;
4500
0
        } else {
4501
0
            if(seen_word_delim)
4502
0
                break;
4503
4504
0
            if(ISANYOF(off, word_delims)) {
4505
0
                seen_word_delim = true;
4506
0
            } else if(component_delim != _T('\0')  &&  CH(off) == component_delim) {
4507
0
                if(seen_component_delim)
4508
0
                    break;
4509
0
                seen_component_delim = true;
4510
0
                component_beg = off;
4511
0
                n_components++;
4512
0
            } else {
4513
0
                if(scan_backwards)
4514
0
                    off++;
4515
0
                break;
4516
0
            }
4517
0
        }
4518
4519
0
        if(!scan_backwards)
4520
0
            off++;
4521
0
    }
4522
4523
    /* Rollback falsely consumed delimiter. */
4524
0
    if(seen_word_delim || seen_component_delim)
4525
0
        off = (scan_backwards ? off+1 : off-1);
4526
4527
0
    if(off != component_beg)
4528
0
        n_components++;
4529
4530
0
    if(n_open_brackets != 0)
4531
0
        return -1;
4532
4533
0
    *p_end = off;
4534
0
    return n_components;
4535
0
}
4536
4537
static void
4538
md_analyze_permissive_autolink(MD_CTX* ctx, int mark_index)
4539
0
{
4540
0
    MD_MARK* opener = &ctx->marks[mark_index];
4541
0
    MD_MARK* closer = &ctx->marks[mark_index + 1];  /* The dummy. */
4542
0
    OFF line_beg = closer->beg;     /* md_collect_mark() sets this for us */
4543
0
    OFF line_end = closer->end;     /* ditto */
4544
0
    OFF beg = opener->beg;
4545
0
    OFF end = opener->end;
4546
0
    MD_MARK* left_cursor = opener;
4547
0
    MD_MARK* right_cursor = opener;
4548
4549
0
    MD_ASSERT(closer->ch == 'D');
4550
4551
    /* E-mail requires the user name (before '@', i.e. scanning backwards). */
4552
0
    if(opener->ch == '@') {
4553
0
        MD_ASSERT(CH(opener->beg) == _T('@'));
4554
0
        if(md_analyze_permissive_autolink_segment(ctx, beg, line_beg, &beg, true,
4555
0
                _T('\0'), NULL, _T(".-_+"), &left_cursor) < 1)
4556
0
            return;
4557
4558
        /* Swallow optional "mailto:" or "xmpp:" before it. */
4559
0
        if(beg >= 7  &&  md_ascii_eq(_T("mailto:"), STR(beg-7), 7))
4560
0
            beg -= 7;
4561
0
        else if(beg >= 5  &&  md_ascii_eq(_T("xmpp:"), STR(beg-5), 5))
4562
0
            beg -= 5;
4563
0
        else
4564
0
            opener->flags |= MD_MARK_AUTOLINK_MISSING_MAILTO;
4565
0
    }
4566
4567
    /* Verify there's line boundary, whitespace, allowed punctuation or
4568
     * resolved opener mark just before the suspected autolink. */
4569
0
    if(beg > line_beg  &&  !ISUNICODEWHITESPACEBEFORE(beg)  &&  !ISANYOF(beg-1, _T("({["))) {
4570
0
        MD_MARK* mark;
4571
4572
0
        mark = md_scan_left_for_resolved_mark(ctx, left_cursor, beg-1, &left_cursor);
4573
0
        if(mark == NULL  ||  !(mark->flags & MD_MARK_OPENER))
4574
0
            return;
4575
0
    }
4576
4577
    /* Scan for hostname segment. Hostname is mandatory and requires at least two
4578
     * components delimited with a dot. */
4579
0
    if(md_analyze_permissive_autolink_segment(ctx, end, line_end, &end, false,
4580
0
            _T('.'), NULL, _T("-_"), &right_cursor) < 2)
4581
0
        return;
4582
4583
0
    if(opener->ch != '@') {
4584
        /* Scan for path segment. */
4585
0
        if(end < line_end  &&  CH(end) == _T('/')) {
4586
0
            if(md_analyze_permissive_autolink_segment(ctx, end+1, line_end, &end, false,
4587
0
                        _T('/'), _T(".+-_~%"), NULL, &right_cursor) < 0)
4588
0
                return;
4589
4590
            /* Path can also end with additional '/' if its a directory. */
4591
0
            if(end < line_end  &&  CH(end) == _T('/'))
4592
0
                end++;
4593
0
        }
4594
4595
        /* Scan for query segment. */
4596
0
        if(end < line_end  &&  CH(end) == _T('?')) {
4597
0
            if(md_analyze_permissive_autolink_segment(ctx, end+1, line_end, &end, false,
4598
0
                        _T('&'), _T("._=()"), _T("+-"), &right_cursor) < 0)
4599
0
                return;
4600
0
        }
4601
4602
        /* Scan for fragment segment. */
4603
0
        if(end < line_end  &&  CH(end) == _T('#')) {
4604
0
            if(md_analyze_permissive_autolink_segment(ctx, end+1, line_end, &end, false,
4605
0
                        _T('\0'), NULL, _T(".-+_"), &right_cursor) < 0)
4606
0
                return;
4607
0
        }
4608
0
    }
4609
4610
    /* Verify there's line boundary, whitespace, allowed punctuation or
4611
     * resolved closer mark just after the suspected autolink. */
4612
0
    if(end < line_end  &&  !ISUNICODEWHITESPACE(end)  &&  !ISANYOF(end, _T(")}].!?,;"))) {
4613
0
        MD_MARK* mark;
4614
4615
0
        mark = md_scan_right_for_resolved_mark(ctx, right_cursor, end, &right_cursor);
4616
0
        if(mark == NULL  ||  !(mark->flags & MD_MARK_CLOSER))
4617
0
            return;
4618
0
    }
4619
4620
    /* Success, we are an autolink. */
4621
0
    opener->beg = beg;
4622
0
    opener->end = beg;
4623
0
    closer->beg = end;
4624
0
    closer->end = end;
4625
0
    closer->ch = opener->ch;
4626
0
    md_resolve_range(ctx, mark_index, mark_index + 1);
4627
0
}
4628
4629
#define MD_ANALYZE_NOSKIP_EMPH  0x01
4630
4631
static inline void
4632
md_analyze_marks(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
4633
                 int mark_beg, int mark_end, const CHAR* mark_chars, const CHAR* noskip_mark_chars)
4634
0
{
4635
0
    int i = mark_beg;
4636
0
    OFF last_end = lines[0].beg;
4637
4638
0
    MD_UNUSED(lines);
4639
0
    MD_UNUSED(n_lines);
4640
4641
0
    while(i < mark_end) {
4642
0
        MD_MARK* mark = &ctx->marks[i];
4643
4644
        /* Skip resolved spans. */
4645
0
        if(mark->flags & MD_MARK_RESOLVED) {
4646
0
            if((mark->flags & MD_MARK_OPENER)  &&
4647
0
               (noskip_mark_chars == NULL || !(ISANYOF_(mark->ch, noskip_mark_chars))))
4648
0
            {
4649
0
                MD_ASSERT(i < mark->next);
4650
0
                i = mark->next + 1;
4651
0
            } else {
4652
0
                i++;
4653
0
            }
4654
0
            continue;
4655
0
        }
4656
4657
        /* Skip marks we do not want to deal with. */
4658
0
        if(!ISANYOF_(mark->ch, mark_chars)) {
4659
0
            i++;
4660
0
            continue;
4661
0
        }
4662
4663
        /* The resolving in previous step could have expanded a mark. */
4664
0
        if(mark->beg < last_end) {
4665
0
            i++;
4666
0
            continue;
4667
0
        }
4668
4669
        /* Analyze the mark. */
4670
0
        switch(mark->ch) {
4671
0
            case '[':   MD_FALLTHROUGH();
4672
0
            case '!':   MD_FALLTHROUGH();
4673
0
            case ']':   md_analyze_bracket(ctx, i); break;
4674
0
            case '&':   md_analyze_entity(ctx, i); break;
4675
0
            case '_':   MD_FALLTHROUGH();
4676
0
            case '*':   md_analyze_emph(ctx, i); break;
4677
0
            case '~':   md_analyze_tilde(ctx, i); break;
4678
0
            case '^':   md_analyze_caret(ctx, i); break;
4679
0
            case '$':   md_analyze_dollar(ctx, i); break;
4680
0
            case '.':   MD_FALLTHROUGH();
4681
0
            case ':':   MD_FALLTHROUGH();
4682
0
            case '@':   md_analyze_permissive_autolink(ctx, i); break;
4683
0
            case '|':   md_analyze_spoiler(ctx, i); break;
4684
0
            case '=':   md_analyze_highlight(ctx, i); break;
4685
0
            case '+':   md_analyze_insert(ctx, i); break;
4686
0
        }
4687
4688
0
        if(mark->flags & MD_MARK_RESOLVED) {
4689
0
            if(mark->flags & MD_MARK_OPENER)
4690
0
                last_end = ctx->marks[mark->next].end;
4691
0
            else
4692
0
                last_end = mark->end;
4693
0
        }
4694
4695
0
        i++;
4696
0
    }
4697
0
}
4698
4699
/* Analyze marks (build ctx->marks). */
4700
static int
4701
md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines, int table_mode)
4702
0
{
4703
0
    int i;
4704
0
    int ret;
4705
4706
    /* Reset the previously collected stack of marks. */
4707
0
    ctx->n_marks = 0;
4708
4709
    /* Collect all marks. */
4710
0
    MD_CHECK(md_collect_marks(ctx, lines, n_lines, table_mode));
4711
4712
    /* (1) Bracket spans: links, wiki links, footnotes. */
4713
0
    md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("[]!"), NULL);
4714
0
    MD_CHECK(md_resolve_brackets(ctx, lines, n_lines));
4715
0
    BRACKET_OPENERS.top = -1;
4716
0
    ctx->unresolved_link_head = -1;
4717
0
    ctx->unresolved_link_tail = -1;
4718
4719
0
    if(table_mode) {
4720
        /* (2) Analyze table cell boundaries. */
4721
0
        MD_ASSERT(n_lines == 1);
4722
0
        ctx->n_table_cell_boundaries = 0;
4723
0
        for(i = 0; i < ctx->n_marks; i++) {
4724
0
            if(!(ctx->marks[i].flags & MD_MARK_RESOLVED)  &&  ctx->marks[i].ch == '|')
4725
0
                md_analyze_table_cell_boundary(ctx, i);
4726
0
        }
4727
0
        return ret;
4728
0
    }
4729
4730
    /* (3) Emphasis and strong emphasis; permissive autolinks. */
4731
0
    md_analyze_link_contents(ctx, lines, n_lines, 0, ctx->n_marks);
4732
4733
0
abort:
4734
0
    return ret;
4735
0
}
4736
4737
static void
4738
md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
4739
                         int mark_beg, int mark_end)
4740
0
{
4741
0
    int i;
4742
0
    CHAR emph_mark_types[16];
4743
0
    SZ n_emph_mark_types = 0;
4744
4745
0
    emph_mark_types[n_emph_mark_types++] = _T('&');
4746
0
    emph_mark_types[n_emph_mark_types++] = _T('*');
4747
0
    emph_mark_types[n_emph_mark_types++] = _T('_');
4748
0
    if(ctx->parser.flags & MD_FLAG_LATEXMATHSPANS)
4749
0
        emph_mark_types[n_emph_mark_types++] = _T('$');
4750
0
    if(ctx->parser.flags & MD_FLAG_HIGHLIGHT)
4751
0
        emph_mark_types[n_emph_mark_types++] = _T('=');
4752
0
    if(ctx->parser.flags & MD_FLAG_INSERT)
4753
0
        emph_mark_types[n_emph_mark_types++] = _T('+');
4754
0
    if(ctx->parser.flags & MD_FLAG_SPOILERS)
4755
0
        emph_mark_types[n_emph_mark_types++] = _T('|');
4756
0
    if(ctx->parser.flags & MD_FLAG_SUPERSCRIPTS)
4757
0
        emph_mark_types[n_emph_mark_types++] = _T('^');
4758
0
    if((ctx->parser.flags & MD_FLAG_STRIKETHROUGH) || (ctx->parser.flags & MD_FLAG_SUBSCRIPTS))
4759
0
        emph_mark_types[n_emph_mark_types++] = _T('~');
4760
0
    emph_mark_types[n_emph_mark_types] = _T('\0');
4761
0
    md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, emph_mark_types, NULL);
4762
4763
0
    if(ctx->parser.flags & MD_FLAG_PERMISSIVEAUTOLINKS) {
4764
        /* These have to be processed last, as they may be greedy and expand
4765
         * from their original mark. Also their implementation must be careful
4766
         * not to cross any (previously) resolved marks when doing so. */
4767
0
        CHAR autolink_mark_types[16];
4768
0
        SZ n_autolink_mark_types = 0;
4769
4770
0
        if(ctx->parser.flags & MD_FLAG_PERMISSIVEEMAILAUTOLINKS)
4771
0
            autolink_mark_types[n_autolink_mark_types++] = _T('@');
4772
0
        if(ctx->parser.flags & MD_FLAG_PERMISSIVEURLAUTOLINKS)
4773
0
            autolink_mark_types[n_autolink_mark_types++] = _T(':');
4774
0
        if(ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS)
4775
0
            autolink_mark_types[n_autolink_mark_types++] = _T('.');
4776
0
        autolink_mark_types[n_autolink_mark_types] = _T('\0');
4777
0
        md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, autolink_mark_types, emph_mark_types);
4778
0
    }
4779
4780
0
    for(i = 0; i < (int) SIZEOF_ARRAY(ctx->opener_stacks); i++)
4781
0
        ctx->opener_stacks[i].top = -1;
4782
0
}
4783
4784
static int
4785
md_enter_leave_span_a(MD_CTX* ctx, int enter, MD_SPANTYPE type,
4786
                      const CHAR* dest, SZ dest_size, int is_autolink,
4787
                      const CHAR* title, SZ title_size)
4788
0
{
4789
0
    MD_ATTRIBUTE_BUILD href_build = { 0 };
4790
0
    MD_ATTRIBUTE_BUILD title_build = { 0 };
4791
0
    MD_SPAN_A_DETAIL det;
4792
0
    int ret = 0;
4793
4794
    /* Note we here rely on fact that MD_SPAN_A_DETAIL and
4795
     * MD_SPAN_IMG_DETAIL are binary-compatible. */
4796
0
    memset(&det, 0, sizeof(MD_SPAN_A_DETAIL));
4797
0
    MD_CHECK(md_build_attribute(ctx, dest, dest_size,
4798
0
                    (is_autolink ? MD_BUILD_ATTR_NO_ESCAPES : 0),
4799
0
                    &det.href, &href_build));
4800
0
    MD_CHECK(md_build_attribute(ctx, title, title_size, 0, &det.title, &title_build));
4801
0
    det.is_autolink = is_autolink;
4802
0
    if(enter)
4803
0
        MD_ENTER_SPAN(type, &det);
4804
0
    else
4805
0
        MD_LEAVE_SPAN(type, &det);
4806
4807
0
abort:
4808
0
    md_free_attribute(ctx, &href_build);
4809
0
    md_free_attribute(ctx, &title_build);
4810
0
    return ret;
4811
0
}
4812
4813
static int
4814
md_enter_leave_span_wikilink(MD_CTX* ctx, int enter, const CHAR* target, SZ target_size)
4815
0
{
4816
0
    MD_ATTRIBUTE_BUILD target_build = { 0 };
4817
0
    MD_SPAN_WIKILINK_DETAIL det;
4818
0
    int ret = 0;
4819
4820
0
    memset(&det, 0, sizeof(MD_SPAN_WIKILINK_DETAIL));
4821
0
    MD_CHECK(md_build_attribute(ctx, target, target_size, 0, &det.target, &target_build));
4822
4823
0
    if (enter)
4824
0
        MD_ENTER_SPAN(MD_SPAN_WIKILINK, &det);
4825
0
    else
4826
0
        MD_LEAVE_SPAN(MD_SPAN_WIKILINK, &det);
4827
4828
0
abort:
4829
0
    md_free_attribute(ctx, &target_build);
4830
0
    return ret;
4831
0
}
4832
4833
static int
4834
md_enter_leave_span_footnote_ref(MD_CTX* ctx, unsigned int id,
4835
                                 unsigned int ref_id, const CHAR* label, SZ label_size)
4836
0
{
4837
0
    MD_ATTRIBUTE_BUILD label_build = { 0 };
4838
0
    MD_SPAN_FOOTNOTE_REF_DETAIL det;
4839
0
    int ret = 0;
4840
4841
0
    memset(&det, 0, sizeof(MD_SPAN_FOOTNOTE_REF_DETAIL));
4842
0
    det.id = id;
4843
0
    det.ref_id = ref_id;
4844
0
    MD_CHECK(md_build_attribute(ctx, label, label_size, 0,
4845
0
                                &det.label, &label_build));
4846
4847
0
    MD_ENTER_SPAN(MD_SPAN_FOOTNOTE_REF, &det);
4848
0
    MD_LEAVE_SPAN(MD_SPAN_FOOTNOTE_REF, &det);
4849
4850
0
abort:
4851
0
    md_free_attribute(ctx, &label_build);
4852
0
    return ret;
4853
0
}
4854
4855
4856
/* Render the output, accordingly to the analyzed ctx->marks. */
4857
static int
4858
md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
4859
0
{
4860
0
    MD_TEXTTYPE text_type;
4861
0
    const MD_LINE* line = lines;
4862
0
    MD_MARK* prev_mark = NULL;
4863
0
    MD_MARK* mark;
4864
0
    OFF off = lines[0].beg;
4865
0
    OFF end = lines[n_lines-1].end;
4866
0
    OFF tmp;
4867
0
    int enforce_hardbreak = 0;
4868
0
    int ret = 0;
4869
4870
    /* Find first resolved mark. Note there is always at least one resolved
4871
     * mark,  the dummy last one after the end of the latest line we actually
4872
     * never really reach. This saves us of a lot of special checks and cases
4873
     * in this function. */
4874
0
    mark = ctx->marks;
4875
0
    while(!(mark->flags & MD_MARK_RESOLVED))
4876
0
        mark++;
4877
4878
0
    text_type = MD_TEXT_NORMAL;
4879
4880
0
    while(1) {
4881
        /* Process the text up to the next mark or end-of-line. */
4882
0
        tmp = (line->end < mark->beg ? line->end : mark->beg);
4883
0
        if(tmp > off) {
4884
0
            MD_TEXT(text_type, STR(off), tmp - off);
4885
0
            off = tmp;
4886
0
        }
4887
4888
        /* If reached the mark, process it and move to next one. */
4889
0
        if(off >= mark->beg) {
4890
0
            switch(mark->ch) {
4891
0
                case '\\':      /* Backslash escape. */
4892
0
                    if(ISNEWLINE(mark->beg+1))
4893
0
                        enforce_hardbreak = 1;
4894
0
                    else
4895
0
                        MD_TEXT(text_type, STR(mark->beg+1), 1);
4896
0
                    break;
4897
4898
0
                case ' ':       /* Non-trivial space. */
4899
0
                    MD_TEXT(text_type, _T(" "), 1);
4900
0
                    break;
4901
4902
0
                case '`':       /* Code span. */
4903
0
                    if(mark->flags & MD_MARK_OPENER) {
4904
0
                        MD_ENTER_SPAN(MD_SPAN_CODE, NULL);
4905
0
                        text_type = MD_TEXT_CODE;
4906
0
                    } else {
4907
0
                        MD_LEAVE_SPAN(MD_SPAN_CODE, NULL);
4908
0
                        text_type = MD_TEXT_NORMAL;
4909
0
                    }
4910
0
                    break;
4911
4912
0
                case '_':       /* Underline (or emphasis if we fall through). */
4913
0
                    if(ctx->parser.flags & MD_FLAG_UNDERLINE) {
4914
0
                        if(mark->flags & MD_MARK_OPENER) {
4915
0
                            while(off < mark->end) {
4916
0
                                MD_ENTER_SPAN(MD_SPAN_U, NULL);
4917
0
                                off++;
4918
0
                            }
4919
0
                        } else {
4920
0
                            while(off < mark->end) {
4921
0
                                MD_LEAVE_SPAN(MD_SPAN_U, NULL);
4922
0
                                off++;
4923
0
                            }
4924
0
                        }
4925
0
                        break;
4926
0
                    }
4927
0
                    MD_FALLTHROUGH();
4928
4929
0
                case '*':       /* Emphasis, strong emphasis. */
4930
0
                    if(mark->flags & MD_MARK_OPENER) {
4931
0
                        if((mark->end - off) % 2) {
4932
0
                            MD_ENTER_SPAN(MD_SPAN_EM, NULL);
4933
0
                            off++;
4934
0
                        }
4935
0
                        while(off + 1 < mark->end) {
4936
0
                            MD_ENTER_SPAN(MD_SPAN_STRONG, NULL);
4937
0
                            off += 2;
4938
0
                        }
4939
0
                    } else {
4940
0
                        while(off + 1 < mark->end) {
4941
0
                            MD_LEAVE_SPAN(MD_SPAN_STRONG, NULL);
4942
0
                            off += 2;
4943
0
                        }
4944
0
                        if((mark->end - off) % 2) {
4945
0
                            MD_LEAVE_SPAN(MD_SPAN_EM, NULL);
4946
0
                            off++;
4947
0
                        }
4948
0
                    }
4949
0
                    break;
4950
4951
0
                case '~':
4952
0
                    if(mark->end - mark->beg == 1  &&  (ctx->parser.flags & MD_FLAG_SUBSCRIPTS)) {
4953
0
                        if(mark->flags & MD_MARK_OPENER)
4954
0
                            MD_ENTER_SPAN(MD_SPAN_SUBSCRIPT, NULL);
4955
0
                        else
4956
0
                            MD_LEAVE_SPAN(MD_SPAN_SUBSCRIPT, NULL);
4957
0
                    } else {
4958
0
                        if(mark->flags & MD_MARK_OPENER)
4959
0
                            MD_ENTER_SPAN(MD_SPAN_DEL, NULL);
4960
0
                        else
4961
0
                            MD_LEAVE_SPAN(MD_SPAN_DEL, NULL);
4962
0
                    }
4963
0
                    break;
4964
4965
0
                case '^':
4966
0
                    if(mark->flags & MD_MARK_OPENER)
4967
0
                        MD_ENTER_SPAN(MD_SPAN_SUPERSCRIPT, NULL);
4968
0
                    else
4969
0
                        MD_LEAVE_SPAN(MD_SPAN_SUPERSCRIPT, NULL);
4970
0
                    break;
4971
4972
0
                case '|':
4973
0
                    if(mark->end - mark->beg == 2) {
4974
0
                        if(mark->flags & MD_MARK_OPENER)
4975
0
                            MD_ENTER_SPAN(MD_SPAN_SPOILER, NULL);
4976
0
                        else
4977
0
                            MD_LEAVE_SPAN(MD_SPAN_SPOILER, NULL);
4978
0
                    }
4979
0
                    break;
4980
4981
0
                case '=':
4982
0
                    if(mark->end - mark->beg == 2) {
4983
0
                        if(mark->flags & MD_MARK_OPENER)
4984
0
                            MD_ENTER_SPAN(MD_SPAN_MARK, NULL);
4985
0
                        else
4986
0
                            MD_LEAVE_SPAN(MD_SPAN_MARK, NULL);
4987
0
                    }
4988
0
                    break;
4989
4990
0
                case '+':
4991
0
                    if(mark->end - mark->beg == 2) {
4992
0
                        if(mark->flags & MD_MARK_OPENER)
4993
0
                            MD_ENTER_SPAN(MD_SPAN_INS, NULL);
4994
0
                        else
4995
0
                            MD_LEAVE_SPAN(MD_SPAN_INS, NULL);
4996
0
                    }
4997
0
                    break;
4998
4999
0
                case '$':
5000
0
                    if(mark->flags & MD_MARK_OPENER) {
5001
0
                        MD_ENTER_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL);
5002
0
                        text_type = MD_TEXT_LATEXMATH;
5003
0
                    } else {
5004
0
                        MD_LEAVE_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL);
5005
0
                        text_type = MD_TEXT_NORMAL;
5006
0
                    }
5007
0
                    break;
5008
5009
0
                case '[':       /* Footnote reference, link, wiki link, or image. */
5010
0
                case '!':
5011
0
                case ']':
5012
0
                {
5013
0
                    const MD_MARK* opener = (mark->ch != ']' ? mark : &ctx->marks[mark->prev]);
5014
0
                    const MD_MARK* closer = &ctx->marks[opener->next];
5015
0
                    const MD_MARK* dest_mark;
5016
0
                    const MD_MARK* title_mark;
5017
5018
                    /* Footnote reference: self-contained span, no text emitted.
5019
                     * Only the opener is ever processed here; the closer mark
5020
                     * is guaranteed to be skipped by the off-advance below. */
5021
0
                    if(opener->flags & MD_MARK_BRACKET_FOOTNOTEREF) {
5022
0
                        MD_MARK* index_mark = (MD_MARK*) opener + 1;
5023
0
                        MD_ASSERT(mark->ch != ']');
5024
0
                        MD_ASSERT(index_mark->ch == 'D');
5025
0
                        MD_CHECK(md_enter_leave_span_footnote_ref(ctx,
5026
0
                                      (unsigned int) index_mark->beg,
5027
0
                                      (unsigned int) index_mark->end,
5028
0
                                      STR(opener->end), closer->beg - opener->end));
5029
                        /* Redirect the opener's end past the whole [^label]
5030
                         * so that the post-switch "off = mark->end" skips
5031
                         * the label text and the closing ]. */
5032
0
                        ((MD_MARK*) mark)->end = closer->end;
5033
0
                        break;
5034
0
                    }
5035
5036
0
                    if ((opener->ch == '[' && closer->ch == ']') &&
5037
0
                        opener->end - opener->beg >= 2 &&
5038
0
                        closer->end - closer->beg >= 2)
5039
0
                    {
5040
0
                        int has_label = (opener->end - opener->beg > 2);
5041
0
                        SZ target_sz;
5042
5043
0
                        if(has_label)
5044
0
                            target_sz = opener->end - (opener->beg+2);
5045
0
                        else
5046
0
                            target_sz = closer->beg - opener->end;
5047
5048
0
                        MD_CHECK(md_enter_leave_span_wikilink(ctx, (mark->ch != ']'),
5049
0
                                 has_label ? STR(opener->beg+2) : STR(opener->end),
5050
0
                                 target_sz));
5051
5052
0
                        break;
5053
0
                    }
5054
5055
0
                    dest_mark = opener+1;
5056
0
                    MD_ASSERT(dest_mark->ch == 'D');
5057
0
                    title_mark = opener+2;
5058
0
                    MD_ASSERT(title_mark->ch == 'D');
5059
5060
0
                    MD_CHECK(md_enter_leave_span_a(ctx, (mark->ch != ']'),
5061
0
                                (opener->ch == '!' ? MD_SPAN_IMG : MD_SPAN_A),
5062
0
                                STR(dest_mark->beg), dest_mark->end - dest_mark->beg, false,
5063
0
                                md_mark_get_ptr(ctx, (int)(title_mark - ctx->marks)),
5064
0
                title_mark->prev));
5065
5066
                    /* link/image closer may span multiple lines. */
5067
0
                    if(mark->ch == ']') {
5068
0
                        while(mark->end > line->end)
5069
0
                            line++;
5070
0
                    }
5071
5072
0
                    break;
5073
0
                }
5074
5075
0
                case '<':
5076
0
                case '>':       /* Autolink or raw HTML. */
5077
0
                    if(!(mark->flags & MD_MARK_AUTOLINK)) {
5078
                        /* Raw HTML. */
5079
0
                        if(mark->flags & MD_MARK_OPENER)
5080
0
                            text_type = MD_TEXT_HTML;
5081
0
                        else
5082
0
                            text_type = MD_TEXT_NORMAL;
5083
0
                        break;
5084
0
                    }
5085
                    /* Pass through, if auto-link. */
5086
0
                    MD_FALLTHROUGH();
5087
5088
0
                case '@':       /* Permissive e-mail autolink. */
5089
0
                case ':':       /* Permissive URL autolink. */
5090
0
                case '.':       /* Permissive WWW autolink. */
5091
0
                {
5092
0
                    MD_MARK* opener = ((mark->flags & MD_MARK_OPENER) ? mark : &ctx->marks[mark->prev]);
5093
0
                    MD_MARK* closer = &ctx->marks[opener->next];
5094
0
                    const CHAR* dest = STR(opener->end);
5095
0
                    SZ dest_size = closer->beg - opener->end;
5096
5097
                    /* For permissive auto-links we do not know closer mark
5098
                     * position at the time of md_collect_marks(), therefore
5099
                     * it can be out-of-order in ctx->marks[].
5100
                     *
5101
                     * With this flag, we make sure that we output the closer
5102
                     * only if we processed the opener. */
5103
0
                    if(mark->flags & MD_MARK_OPENER)
5104
0
                        closer->flags |= MD_MARK_VALIDPERMISSIVEAUTOLINK;
5105
5106
0
                    if(opener->ch == '.' ||
5107
0
                        (ISANYOF2_(opener->ch, _T('@'), _T('<')) && (opener->flags & MD_MARK_AUTOLINK_MISSING_MAILTO)))
5108
0
                    {
5109
0
                        dest_size += 7;
5110
0
                        MD_TEMP_BUFFER(dest_size * sizeof(CHAR));
5111
0
                        memcpy(ctx->buffer,
5112
0
                                (opener->ch == '.' ? _T("http://") : _T("mailto:")),
5113
0
                                7 * sizeof(CHAR));
5114
0
                        memcpy(ctx->buffer + 7, dest, (dest_size-7) * sizeof(CHAR));
5115
0
                        dest = ctx->buffer;
5116
0
                    }
5117
5118
0
                    if(closer->flags & MD_MARK_VALIDPERMISSIVEAUTOLINK)
5119
0
                        MD_CHECK(md_enter_leave_span_a(ctx, (mark->flags & MD_MARK_OPENER),
5120
0
                                    MD_SPAN_A, dest, dest_size, true, NULL, 0));
5121
0
                    break;
5122
0
                }
5123
5124
0
                case '&':       /* Entity. */
5125
0
                    MD_TEXT(MD_TEXT_ENTITY, STR(mark->beg), mark->end - mark->beg);
5126
0
                    break;
5127
5128
0
                case '\0':
5129
0
                    MD_TEXT(MD_TEXT_NULLCHAR, _T(""), 1);
5130
0
                    break;
5131
5132
0
                case 127:
5133
0
                    goto abort;
5134
0
            }
5135
5136
0
            off = mark->end;
5137
5138
            /* Move to next resolved mark. */
5139
0
            prev_mark = mark;
5140
0
            mark++;
5141
0
            while(!(mark->flags & MD_MARK_RESOLVED)  ||  mark->beg < off)
5142
0
                mark++;
5143
0
        }
5144
5145
        /* If reached end of line, move to next one. */
5146
0
        if(off >= line->end) {
5147
            /* If it is the last line, we are done. */
5148
0
            if(off >= end)
5149
0
                break;
5150
5151
0
            if(text_type == MD_TEXT_CODE || text_type == MD_TEXT_LATEXMATH) {
5152
0
                MD_ASSERT(prev_mark != NULL);
5153
0
                MD_ASSERT(ISANYOF2_(prev_mark->ch, '`', '$')  &&  (prev_mark->flags & MD_MARK_OPENER));
5154
0
                MD_ASSERT(ISANYOF2_(mark->ch, '`', '$')  &&  (mark->flags & MD_MARK_CLOSER));
5155
5156
                /* Inside a code span, trailing line whitespace has to be
5157
                 * outputted. */
5158
0
                tmp = off;
5159
0
                while(off < ctx->size  &&  ISBLANK(off))
5160
0
                    off++;
5161
0
                if(off > tmp)
5162
0
                    MD_TEXT(text_type, STR(tmp), off-tmp);
5163
5164
                /* and new lines are transformed into single spaces. Emit the
5165
                 * space when off rests on an interior newline still preceding
5166
                 * the closer. Testing off == line->end here instead drops the
5167
                 * space when the line ends in whitespace, because the loop
5168
                 * above advances off past line->end over the trailing blanks
5169
                 * (CommonMark code-span examples 335, 337, 640). */
5170
0
                if(off < mark->beg  &&  ISNEWLINE(off))
5171
0
                    MD_TEXT(text_type, _T(" "), 1);
5172
0
            } else if(text_type == MD_TEXT_HTML) {
5173
                /* Inside raw HTML, we output the new line verbatim, including
5174
                 * any trailing spaces. */
5175
0
                tmp = off;
5176
0
                while(tmp < end  &&  ISBLANK(tmp))
5177
0
                    tmp++;
5178
0
                if(tmp > off)
5179
0
                    MD_TEXT(MD_TEXT_HTML, STR(off), tmp - off);
5180
0
                MD_TEXT(MD_TEXT_HTML, _T("\n"), 1);
5181
0
            } else {
5182
                /* Output soft or hard line break. */
5183
0
                MD_TEXTTYPE break_type = MD_TEXT_SOFTBR;
5184
5185
0
                if(text_type == MD_TEXT_NORMAL) {
5186
0
                    if(enforce_hardbreak  ||  (ctx->parser.flags & MD_FLAG_HARD_SOFT_BREAKS)) {
5187
0
                        break_type = MD_TEXT_BR;
5188
0
                    } else {
5189
0
                        while(off < ctx->size  &&  ISBLANK(off))
5190
0
                            off++;
5191
0
                        if(off >= line->end + 2  &&  CH(off-2) == _T(' ')  &&  CH(off-1) == _T(' ')  &&  ISNEWLINE(off))
5192
0
                            break_type = MD_TEXT_BR;
5193
0
                    }
5194
0
                }
5195
5196
0
                MD_TEXT(break_type, _T("\n"), 1);
5197
0
            }
5198
5199
            /* Move to the next line. */
5200
0
            line++;
5201
0
            off = MAX(off, line->beg);
5202
5203
0
            enforce_hardbreak = 0;
5204
0
        }
5205
0
    }
5206
5207
0
abort:
5208
0
    return ret;
5209
0
}
5210
5211
5212
/***************************
5213
 ***  Processing Tables  ***
5214
 ***************************/
5215
5216
static void
5217
md_analyze_table_alignment(MD_CTX* ctx, OFF beg, OFF end, MD_ALIGN* align, int n_align)
5218
0
{
5219
0
    static const MD_ALIGN align_map[] = { MD_ALIGN_DEFAULT, MD_ALIGN_LEFT, MD_ALIGN_RIGHT, MD_ALIGN_CENTER };
5220
0
    OFF off = beg;
5221
5222
0
    while(n_align > 0) {
5223
0
        int index = 0;  /* index into align_map[] */
5224
5225
0
        while(off < end  &&  CH(off) != _T('-'))
5226
0
            off++;
5227
0
        if(off > beg  &&  CH(off-1) == _T(':'))
5228
0
            index |= 1;
5229
0
        while(off < end  &&  CH(off) == _T('-'))
5230
0
            off++;
5231
0
        if(off < end  &&  CH(off) == _T(':'))
5232
0
            index |= 2;
5233
5234
0
        *align = align_map[index];
5235
0
        align++;
5236
0
        n_align--;
5237
0
    }
5238
5239
0
}
5240
5241
/* Forward declaration. */
5242
static int md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines);
5243
5244
static int
5245
md_process_table_cell(MD_CTX* ctx, MD_BLOCKTYPE cell_type, MD_ALIGN align, OFF beg, OFF end)
5246
0
{
5247
0
    MD_LINE line;
5248
0
    MD_BLOCK_TD_DETAIL det;
5249
0
    int ret = 0;
5250
5251
0
    while(beg < end  &&  ISWHITESPACE(beg))
5252
0
        beg++;
5253
0
    while(end > beg  &&  ISWHITESPACE(end-1))
5254
0
        end--;
5255
5256
0
    det.align = align;
5257
0
    line.beg = beg;
5258
0
    line.end = end;
5259
5260
0
    MD_ENTER_BLOCK(cell_type, &det);
5261
0
    MD_CHECK(md_process_normal_block_contents(ctx, &line, 1));
5262
0
    MD_LEAVE_BLOCK(cell_type, &det);
5263
5264
0
abort:
5265
0
    return ret;
5266
0
}
5267
5268
static int
5269
md_process_table_row(MD_CTX* ctx, MD_BLOCKTYPE cell_type, OFF beg, OFF end,
5270
                     const MD_ALIGN* align, int col_count)
5271
0
{
5272
0
    MD_LINE line;
5273
0
    OFF* cell_begs = NULL;
5274
0
    int i, j, k, n;
5275
0
    int ret = 0;
5276
0
    MD_MARK* mark = NULL;
5277
5278
0
    line.beg = beg;
5279
0
    line.end = end;
5280
5281
    /* Break the line into table cells by identifying pipe characters who
5282
     * form the cell boundary. */
5283
0
    MD_CHECK(md_analyze_inlines(ctx, &line, 1, true));
5284
5285
    /* We have to remember the cell boundaries in local buffer because
5286
     * ctx->marks[] shall be reused during cell contents processing. */
5287
0
    n = ctx->n_table_cell_boundaries + 2;
5288
0
    cell_begs = (OFF*) malloc(n * sizeof(OFF));
5289
0
    if(cell_begs == NULL) {
5290
0
        MD_LOG("malloc() failed.");
5291
0
        ret = -1;
5292
0
        goto abort;
5293
0
    }
5294
0
    j = 0;
5295
5296
    /* First cell of the row may or may not be started with '|'. */
5297
0
    if(ctx->table_cell_boundaries_head < 0  ||
5298
0
       ctx->marks[ctx->table_cell_boundaries_head].beg > beg)
5299
0
        cell_begs[j++] = beg;
5300
0
    for(i = ctx->table_cell_boundaries_head; i >= 0; i = ctx->marks[i].next) {
5301
0
        mark = &ctx->marks[i];
5302
0
        for(k = 0; k < (int)(mark->end - mark->beg); k++)
5303
0
            cell_begs[j++] = mark->beg + k + 1;
5304
0
    }
5305
0
    if(mark == NULL || mark->end < end)
5306
0
        cell_begs[j++] = end+1;
5307
5308
    /* Process cells. */
5309
0
    MD_ENTER_BLOCK(MD_BLOCK_TR, NULL);
5310
0
    for(i = 0; i < j-1 && i < col_count; i++)
5311
0
        MD_CHECK(md_process_table_cell(ctx, cell_type, align[i], cell_begs[i], cell_begs[i+1]-1));
5312
    /* Make sure we report enough table cells even if the current table contains
5313
     * too few of them. */
5314
0
    while(i < col_count)
5315
0
        MD_CHECK(md_process_table_cell(ctx, cell_type, align[i++], 0, 0));
5316
0
    MD_LEAVE_BLOCK(MD_BLOCK_TR, NULL);
5317
5318
0
abort:
5319
0
    free(cell_begs);
5320
5321
0
    ctx->table_cell_boundaries_head = -1;
5322
0
    ctx->table_cell_boundaries_tail = -1;
5323
5324
0
    return ret;
5325
0
}
5326
5327
static int
5328
md_process_table_block_contents(MD_CTX* ctx, int col_count, const MD_LINE* lines, MD_SIZE n_lines)
5329
0
{
5330
0
    MD_ALIGN* align;
5331
0
    MD_SIZE line_index;
5332
0
    int ret = 0;
5333
5334
    /* At least two lines have to be present: The column headers and the line
5335
     * with the underlines. */
5336
0
    MD_ASSERT(n_lines >= 2);
5337
5338
0
    align = malloc(col_count * sizeof(MD_ALIGN));
5339
0
    if(align == NULL) {
5340
0
        MD_LOG("malloc() failed.");
5341
0
        ret = -1;
5342
0
        goto abort;
5343
0
    }
5344
5345
0
    md_analyze_table_alignment(ctx, lines[1].beg, lines[1].end, align, col_count);
5346
5347
0
    MD_ENTER_BLOCK(MD_BLOCK_THEAD, NULL);
5348
0
    MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TH,
5349
0
                        lines[0].beg, lines[0].end, align, col_count));
5350
0
    MD_LEAVE_BLOCK(MD_BLOCK_THEAD, NULL);
5351
5352
0
    if(n_lines > 2) {
5353
0
        MD_ENTER_BLOCK(MD_BLOCK_TBODY, NULL);
5354
0
        for(line_index = 2; line_index < n_lines; line_index++) {
5355
0
            MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TD,
5356
0
                     lines[line_index].beg, lines[line_index].end, align, col_count));
5357
0
        }
5358
0
        MD_LEAVE_BLOCK(MD_BLOCK_TBODY, NULL);
5359
0
    }
5360
5361
0
abort:
5362
0
    free(align);
5363
0
    return ret;
5364
0
}
5365
5366
5367
/**************************
5368
 ***  Processing Block  ***
5369
 **************************/
5370
5371
0
#define MD_BLOCK_CONTAINER_OPENER   0x01
5372
0
#define MD_BLOCK_CONTAINER_CLOSER   0x02
5373
0
#define MD_BLOCK_CONTAINER          (MD_BLOCK_CONTAINER_OPENER | MD_BLOCK_CONTAINER_CLOSER)
5374
0
#define MD_BLOCK_LOOSE_LIST         0x04
5375
0
#define MD_BLOCK_SETEXT_HEADER      0x08
5376
5377
struct MD_BLOCK_tag {
5378
    MD_BLOCKTYPE type  :  8;
5379
    unsigned flags     :  8;
5380
5381
    /* MD_BLOCK_H:      Header level (1 - 6)
5382
     * MD_BLOCK_CODE:   Non-zero if fenced, zero if indented.
5383
     * MD_BLOCK_LI:     Task mark character (0 if not task list item, 'x', 'X' or ' ').
5384
     * MD_BLOCK_TABLE:  Column count (as determined by the table underline).
5385
     * MD_BLOCK_ADMONITION: Admonition type.
5386
     */
5387
    unsigned data      : 16;
5388
5389
    /* Leaf blocks:     Count of lines (MD_LINE or MD_VERBATIMLINE) on the block.
5390
     * MD_BLOCK_LI:     Task mark offset in the input doc.
5391
     * MD_BLOCK_OL:     Start item number.
5392
     */
5393
    MD_SIZE n_lines;
5394
};
5395
5396
struct MD_CONTAINER_tag {
5397
    CHAR ch;
5398
    unsigned is_loose           : 1;
5399
    unsigned is_task            : 1;
5400
    unsigned is_admonition      : 1;
5401
    unsigned admonition_type    : 3;
5402
    unsigned start;
5403
    unsigned mark_indent;
5404
    unsigned contents_indent;
5405
    OFF block_byte_off;
5406
    OFF task_mark_off;
5407
};
5408
5409
5410
static int
5411
md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines)
5412
0
{
5413
0
    int i;
5414
0
    int ret;
5415
5416
0
    MD_CHECK(md_analyze_inlines(ctx, lines, n_lines, false));
5417
0
    MD_CHECK(md_process_inlines(ctx, lines, n_lines));
5418
5419
0
abort:
5420
    /* Free any temporary memory blocks stored within some dummy marks. */
5421
0
    for(i = ctx->ptr_stack.top; i >= 0; i = ctx->marks[i].next)
5422
0
        free(md_mark_get_ptr(ctx, i));
5423
0
    ctx->ptr_stack.top = -1;
5424
5425
0
    return ret;
5426
0
}
5427
5428
static int
5429
md_process_verbatim_block_contents(MD_CTX* ctx, MD_TEXTTYPE text_type, const MD_VERBATIMLINE* lines, MD_SIZE n_lines)
5430
0
{
5431
0
    static const CHAR indent_chunk_str[] = _T("                ");
5432
0
    static const SZ indent_chunk_size = SIZEOF_ARRAY(indent_chunk_str) - 1;
5433
5434
0
    MD_SIZE line_index;
5435
0
    int ret = 0;
5436
5437
0
    for(line_index = 0; line_index < n_lines; line_index++) {
5438
0
        const MD_VERBATIMLINE* line = &lines[line_index];
5439
0
        int indent = line->indent;
5440
5441
0
        MD_ASSERT(indent >= 0);
5442
5443
        /* Output code indentation. */
5444
0
        while(indent > (int) indent_chunk_size) {
5445
0
            MD_TEXT(text_type, indent_chunk_str, indent_chunk_size);
5446
0
            indent -= indent_chunk_size;
5447
0
        }
5448
0
        if(indent > 0)
5449
0
            MD_TEXT(text_type, indent_chunk_str, indent);
5450
5451
        /* Output the code line itself. */
5452
0
        MD_TEXT_INSECURE(text_type, STR(line->beg), line->end - line->beg);
5453
5454
        /* Enforce end-of-line. */
5455
0
        MD_TEXT(text_type, _T("\n"), 1);
5456
0
    }
5457
5458
0
abort:
5459
0
    return ret;
5460
0
}
5461
5462
static int
5463
md_process_code_block_contents(MD_CTX* ctx, int is_fenced, const MD_VERBATIMLINE* lines, MD_SIZE n_lines)
5464
0
{
5465
0
    if(is_fenced) {
5466
        /* Skip the first line in case of fenced code: It is the fence.
5467
         * (Only the starting fence is present due to logic in md_analyze_line().) */
5468
0
        lines++;
5469
0
        n_lines--;
5470
0
    } else {
5471
        /* Ignore blank lines at start/end of indented code block. */
5472
0
        while(n_lines > 0  &&  lines[0].beg == lines[0].end) {
5473
0
            lines++;
5474
0
            n_lines--;
5475
0
        }
5476
0
        while(n_lines > 0  &&  lines[n_lines-1].beg == lines[n_lines-1].end) {
5477
0
            n_lines--;
5478
0
        }
5479
0
    }
5480
5481
0
    if(n_lines == 0)
5482
0
        return 0;
5483
5484
0
    return md_process_verbatim_block_contents(ctx, MD_TEXT_CODE, lines, n_lines);
5485
0
}
5486
5487
static int
5488
md_setup_fenced_code_detail(MD_CTX* ctx, const MD_BLOCK* block, MD_BLOCK_CODE_DETAIL* det,
5489
                            MD_ATTRIBUTE_BUILD* info_build, MD_ATTRIBUTE_BUILD* lang_build)
5490
0
{
5491
0
    const MD_VERBATIMLINE* fence_line = (const MD_VERBATIMLINE*)(block + 1);
5492
0
    OFF beg = fence_line->beg;
5493
0
    OFF end = fence_line->end;
5494
0
    OFF lang_end;
5495
0
    CHAR fence_ch = CH(fence_line->beg);
5496
0
    int ret = 0;
5497
5498
    /* Skip the fence itself. */
5499
0
    while(beg < ctx->size  &&  CH(beg) == fence_ch)
5500
0
        beg++;
5501
    /* Trim initial spaces. */
5502
0
    while(beg < ctx->size  &&  CH(beg) == _T(' '))
5503
0
        beg++;
5504
5505
    /* Trim trailing spaces. */
5506
0
    while(end > beg  &&  CH(end-1) == _T(' '))
5507
0
        end--;
5508
5509
    /* Build info string attribute. */
5510
0
    MD_CHECK(md_build_attribute(ctx, STR(beg), end - beg, 0, &det->info, info_build));
5511
5512
    /* Build info string attribute. */
5513
0
    lang_end = beg;
5514
0
    while(lang_end < end  &&  !ISWHITESPACE(lang_end))
5515
0
        lang_end++;
5516
0
    MD_CHECK(md_build_attribute(ctx, STR(beg), lang_end - beg, 0, &det->lang, lang_build));
5517
5518
0
    det->fence_char = fence_ch;
5519
5520
0
abort:
5521
0
    return ret;
5522
0
}
5523
5524
static int
5525
md_process_leaf_block(MD_CTX* ctx, MD_BLOCK* block)
5526
0
{
5527
0
    union {
5528
0
        MD_BLOCK_H_DETAIL header;
5529
0
        MD_BLOCK_CODE_DETAIL code;
5530
0
        MD_BLOCK_TABLE_DETAIL table;
5531
0
        MD_BLOCK_BLANK_DETAIL blank;
5532
0
    } det;
5533
0
    MD_ATTRIBUTE_BUILD info_build = { 0 };
5534
0
    MD_ATTRIBUTE_BUILD lang_build = { 0 };
5535
0
    int is_in_tight_list;
5536
0
    int clean_fence_code_detail = false;
5537
0
    int ret = 0;
5538
5539
    /* For large tables check the table density: If it's too low, lets suppress
5540
     * its interpretation as a table, as a safety measure against quadratic
5541
     * output size explosion. See https://github.com/mity/md4c/issues/345 */
5542
0
    if(block->type == MD_BLOCK_TABLE) {
5543
0
        unsigned n_cols = block->data;
5544
0
        unsigned n_rows = block->n_lines;
5545
5546
0
        if(n_cols > 32  &&  n_rows > 4096 / n_cols) {
5547
0
            const MD_LINE* lines = (const MD_LINE*)(block + 1);
5548
0
            SZ table_input_size = n_rows;
5549
0
            unsigned i;
5550
5551
0
            for(i = 0; i < n_rows; i++)
5552
0
                table_input_size += lines[i].end - lines[i].beg;
5553
5554
0
            if(table_input_size / n_cols < n_rows / 4) {
5555
                /* Number of characters encoding the table in the input is
5556
                 * lower then 25% of all cells to be generated? */
5557
0
                MD_LOG("Suppressing too sparse table "
5558
0
                       "(see https://github.com/mity/md4c/issues/345)");
5559
0
                block->type = MD_BLOCK_P;
5560
0
            }
5561
0
        }
5562
0
    }
5563
5564
0
    memset(&det, 0, sizeof(det));
5565
5566
0
    if(ctx->n_containers == 0)
5567
0
        is_in_tight_list = false;
5568
0
    else
5569
0
        is_in_tight_list = !ctx->containers[ctx->n_containers-1].is_loose;
5570
5571
0
    switch(block->type) {
5572
0
        case MD_BLOCK_H:
5573
0
            det.header.level = block->data;
5574
0
            break;
5575
5576
0
        case MD_BLOCK_CODE:
5577
            /* For fenced code block, we may need to set the info string. */
5578
0
            if(block->data != 0) {
5579
0
                memset(&det.code, 0, sizeof(MD_BLOCK_CODE_DETAIL));
5580
0
                clean_fence_code_detail = true;
5581
0
                MD_CHECK(md_setup_fenced_code_detail(ctx, block, &det.code, &info_build, &lang_build));
5582
0
            }
5583
0
            break;
5584
5585
0
        case MD_BLOCK_TABLE:
5586
0
            det.table.col_count = block->data;
5587
0
            det.table.head_row_count = 1;
5588
0
            det.table.body_row_count = block->n_lines - 2;
5589
0
            break;
5590
5591
0
        case MD_BLOCK_BLANK:
5592
0
            det.blank.line_count = block->data;
5593
0
            break;
5594
5595
0
        default:
5596
            /* Noop. */
5597
0
            break;
5598
0
    }
5599
5600
0
    if(!is_in_tight_list  ||  block->type != MD_BLOCK_P)
5601
0
        MD_ENTER_BLOCK(block->type, (void*) &det);
5602
5603
    /* Process the block contents accordingly to is type. */
5604
0
    switch(block->type) {
5605
0
        case MD_BLOCK_HR:
5606
0
        case MD_BLOCK_BLANK:
5607
            /* noop (no contents) */
5608
0
            break;
5609
5610
0
        case MD_BLOCK_CODE:
5611
0
            MD_CHECK(md_process_code_block_contents(ctx, (block->data != 0),
5612
0
                            (const MD_VERBATIMLINE*)(block + 1), block->n_lines));
5613
0
            break;
5614
5615
0
        case MD_BLOCK_HTML:
5616
0
            MD_CHECK(md_process_verbatim_block_contents(ctx, MD_TEXT_HTML,
5617
0
                            (const MD_VERBATIMLINE*)(block + 1), block->n_lines));
5618
0
            break;
5619
5620
0
        case MD_BLOCK_TABLE:
5621
0
            MD_CHECK(md_process_table_block_contents(ctx, block->data,
5622
0
                            (const MD_LINE*)(block + 1), block->n_lines));
5623
0
            break;
5624
5625
0
        default:
5626
0
            MD_CHECK(md_process_normal_block_contents(ctx,
5627
0
                            (const MD_LINE*)(block + 1), block->n_lines));
5628
0
            break;
5629
0
    }
5630
5631
0
    if(!is_in_tight_list  ||  block->type != MD_BLOCK_P)
5632
0
        MD_LEAVE_BLOCK(block->type, (void*) &det);
5633
5634
0
abort:
5635
0
    if(clean_fence_code_detail) {
5636
0
        md_free_attribute(ctx, &info_build);
5637
0
        md_free_attribute(ctx, &lang_build);
5638
0
    }
5639
0
    return ret;
5640
0
}
5641
5642
static const MD_CHAR* MD_ADMONITION_TAGS[] = { _T("note"), _T("tip"), _T("important"), _T("warning"), _T("caution") };
5643
5644
static int
5645
md_process_all_blocks(MD_CTX* ctx)
5646
0
{
5647
0
    MD_TEXTTYPE adm_substr_types[1] = { MD_TEXT_NORMAL };
5648
0
    MD_OFFSET adm_substr_offsets[2];
5649
0
    int byte_off = 0;
5650
0
    int ret = 0;
5651
5652
    /* ctx->containers now is not needed for detection of lists and list items
5653
     * so we reuse it for tracking what lists are loose or tight. We rely
5654
     * on the fact the vector is large enough to hold the deepest nesting
5655
     * level of lists. */
5656
0
    ctx->n_containers = 0;
5657
5658
0
    while(byte_off < ctx->n_block_bytes) {
5659
0
        MD_BLOCK* block = (MD_BLOCK*)((char*)ctx->block_bytes + byte_off);
5660
0
        union {
5661
0
            MD_BLOCK_UL_DETAIL ul;
5662
0
            MD_BLOCK_OL_DETAIL ol;
5663
0
            MD_BLOCK_LI_DETAIL li;
5664
0
            MD_BLOCK_ADMONITION_DETAIL adm;
5665
0
        } det;
5666
5667
0
        switch(block->type) {
5668
0
            case MD_BLOCK_UL:
5669
0
                det.ul.is_tight = (block->flags & MD_BLOCK_LOOSE_LIST) ? false : true;
5670
0
                det.ul.mark = (CHAR) block->data;
5671
0
                break;
5672
5673
0
            case MD_BLOCK_OL:
5674
0
                det.ol.start = block->n_lines;
5675
0
                det.ol.is_tight = (block->flags & MD_BLOCK_LOOSE_LIST) ? false : true;
5676
0
                det.ol.mark_delimiter = (CHAR) block->data;
5677
0
                break;
5678
5679
0
            case MD_BLOCK_LI:
5680
0
                det.li.is_task = (block->data != 0);
5681
0
                det.li.task_mark = (CHAR) block->data;
5682
0
                det.li.task_mark_offset = (OFF) block->n_lines;
5683
0
                break;
5684
5685
0
            case MD_BLOCK_ADMONITION:
5686
0
                adm_substr_offsets[0] = 0;
5687
0
                adm_substr_offsets[1] = (unsigned) md_strlen(MD_ADMONITION_TAGS[block->data]);
5688
5689
0
                det.adm.type.text = MD_ADMONITION_TAGS[block->data];
5690
0
                det.adm.type.size = adm_substr_offsets[1];
5691
0
                det.adm.type.substr_types = adm_substr_types;
5692
0
                det.adm.type.substr_offsets = adm_substr_offsets;
5693
0
                break;
5694
5695
0
            default:
5696
                /* noop */
5697
0
                break;
5698
0
        }
5699
5700
0
        if(block->flags & MD_BLOCK_CONTAINER) {
5701
0
            if(block->flags & MD_BLOCK_CONTAINER_CLOSER) {
5702
0
                MD_LEAVE_BLOCK(block->type, &det);
5703
5704
0
                if(block->type == MD_BLOCK_UL || block->type == MD_BLOCK_OL ||
5705
0
                   block->type == MD_BLOCK_QUOTE || block->type == MD_BLOCK_ADMONITION)
5706
0
                    ctx->n_containers--;
5707
0
            }
5708
5709
0
            if(block->flags & MD_BLOCK_CONTAINER_OPENER) {
5710
0
                MD_ENTER_BLOCK(block->type, &det);
5711
5712
0
                if(block->type == MD_BLOCK_UL || block->type == MD_BLOCK_OL) {
5713
0
                    ctx->containers[ctx->n_containers].is_loose = (block->flags & MD_BLOCK_LOOSE_LIST) ? true : false;
5714
0
                    ctx->n_containers++;
5715
0
                } else if(block->type == MD_BLOCK_QUOTE  ||  block->type == MD_BLOCK_ADMONITION) {
5716
                    /* This causes that any text in a block quote, even if
5717
                     * nested inside a tight list item, is wrapped with
5718
                     * <p>...</p>. */
5719
0
                    ctx->containers[ctx->n_containers].is_loose = true;
5720
0
                    ctx->n_containers++;
5721
0
                }
5722
0
            }
5723
0
        } else {
5724
0
            MD_CHECK(md_process_leaf_block(ctx, block));
5725
5726
0
            if(block->type == MD_BLOCK_CODE || block->type == MD_BLOCK_HTML)
5727
0
                byte_off += block->n_lines * sizeof(MD_VERBATIMLINE);
5728
0
            else
5729
0
                byte_off += block->n_lines * sizeof(MD_LINE);
5730
0
        }
5731
5732
0
        byte_off += sizeof(MD_BLOCK);
5733
0
    }
5734
5735
0
    ctx->n_block_bytes = 0;
5736
5737
0
abort:
5738
0
    return ret;
5739
0
}
5740
5741
5742
/************************************
5743
 ***  Grouping Lines into Blocks  ***
5744
 ************************************/
5745
5746
static void*
5747
md_push_block_bytes(MD_CTX* ctx, int n_bytes)
5748
0
{
5749
0
    void* ptr;
5750
5751
0
    if(ctx->n_block_bytes + n_bytes > ctx->alloc_block_bytes) {
5752
0
        void* new_block_bytes;
5753
5754
0
        ctx->alloc_block_bytes = (ctx->alloc_block_bytes > 0
5755
0
                ? ctx->alloc_block_bytes + ctx->alloc_block_bytes / 2
5756
0
                : 512);
5757
0
        new_block_bytes = realloc(ctx->block_bytes, ctx->alloc_block_bytes);
5758
0
        if(new_block_bytes == NULL) {
5759
0
            MD_LOG("realloc() failed.");
5760
0
            return NULL;
5761
0
        }
5762
5763
        /* Fix the ->current_block after the reallocation. */
5764
0
        if(ctx->current_block != NULL) {
5765
0
            OFF off_current_block = (OFF) ((char*) ctx->current_block - (char*) ctx->block_bytes);
5766
0
            ctx->current_block = (MD_BLOCK*) ((char*) new_block_bytes + off_current_block);
5767
0
        }
5768
5769
0
        ctx->block_bytes = new_block_bytes;
5770
0
    }
5771
5772
0
    ptr = (char*)ctx->block_bytes + ctx->n_block_bytes;
5773
0
    ctx->n_block_bytes += n_bytes;
5774
0
    return ptr;
5775
0
}
5776
5777
static int
5778
md_start_new_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* line)
5779
0
{
5780
0
    MD_BLOCK* block;
5781
5782
0
    MD_ASSERT(ctx->current_block == NULL);
5783
5784
0
    block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
5785
0
    if(block == NULL)
5786
0
        return -1;
5787
5788
0
    switch(line->type) {
5789
0
        case MD_LINE_HR:
5790
0
            block->type = MD_BLOCK_HR;
5791
0
            break;
5792
5793
0
        case MD_LINE_ATXHEADER:
5794
0
        case MD_LINE_SETEXTHEADER:
5795
0
            block->type = MD_BLOCK_H;
5796
0
            break;
5797
5798
0
        case MD_LINE_FENCEDCODE:
5799
0
        case MD_LINE_INDENTEDCODE:
5800
0
            block->type = MD_BLOCK_CODE;
5801
0
            break;
5802
5803
0
        case MD_LINE_TEXT:
5804
0
            block->type = MD_BLOCK_P;
5805
0
            break;
5806
5807
0
        case MD_LINE_HTML:
5808
0
            block->type = MD_BLOCK_HTML;
5809
0
            break;
5810
5811
0
        case MD_LINE_BLANK:
5812
0
        case MD_LINE_SETEXTUNDERLINE:
5813
0
        case MD_LINE_TABLEUNDERLINE:
5814
0
        default:
5815
0
            MD_UNREACHABLE();
5816
0
            break;
5817
0
    }
5818
5819
0
    block->flags = 0;
5820
0
    block->data = line->data;
5821
0
    block->n_lines = 0;
5822
5823
0
    ctx->current_block = block;
5824
0
    return 0;
5825
0
}
5826
5827
/* Forward declarations */
5828
static int md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end, OFF* p_killer);
5829
static void* md_push_block_bytes(MD_CTX* ctx, int n_bytes);
5830
5831
/* Eat from start of current (textual) block any reference definitions and/or
5832
 * footnote definitions, and remember them.
5833
 *
5834
 * (Such definitions can only be at start of it as they cannot break a
5835
 * paragraph.)
5836
 */
5837
static int
5838
md_consume_link_reference_definitions(MD_CTX* ctx)
5839
0
{
5840
0
    MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1);
5841
0
    MD_SIZE n_lines = ctx->current_block->n_lines;
5842
0
    MD_SIZE n = 0;
5843
0
    bool inject_hr = false;
5844
0
    OFF ignored;
5845
5846
0
    while(n < n_lines) {
5847
0
        int n_consumed = 0;
5848
5849
        /* When footnotes are enabled, try footnote definition first for lines
5850
         * starting with [^, so they are not accidentally consumed as link ref
5851
         * definitions (which would also match [^label]: url). */
5852
0
        if((ctx->parser.flags & MD_FLAG_FOOTNOTES)  &&
5853
0
           lines[n].beg + 1 < ctx->size  &&
5854
0
           CH(lines[n].beg) == _T('[')  &&  CH(lines[n].beg + 1) == _T('^'))
5855
0
        {
5856
0
            n_consumed = md_is_footnote_definition(ctx, lines + n, n_lines - n);
5857
0
            if(n_consumed < 0)
5858
0
                return -1;
5859
0
        }
5860
5861
0
        if(n_consumed == 0) {
5862
0
            n_consumed = md_is_link_reference_definition(ctx, lines + n, n_lines - n);
5863
0
            if(n_consumed < 0)
5864
0
                return -1;
5865
0
        }
5866
5867
0
        if(n_consumed == 0)
5868
0
            break;
5869
5870
0
        n += n_consumed;
5871
0
    }
5872
5873
    /* If no link ref. def. was detected, leave the block intact. */
5874
0
    if(n == 0)
5875
0
        return 0;
5876
5877
    /* We may need to turn the first line after the link ref. def(s) into HR.
5878
     * (https://github.com/mity/md4c/issues/414) */
5879
0
    if(n < n_lines  &&  md_is_hr_line(ctx, lines[n].beg, &ignored, &ignored)) {
5880
0
        inject_hr = true;
5881
0
        n++;    /* Remove one more line below. */
5882
0
    }
5883
5884
0
    if(n == n_lines) {
5885
        /* Undo the whole block. */
5886
0
        ctx->n_block_bytes -= n_lines * sizeof(MD_LINE);
5887
0
        ctx->n_block_bytes -= sizeof(MD_BLOCK);
5888
0
        ctx->current_block = NULL;
5889
0
    } else {
5890
        /* Remove some initial lines from the block. */
5891
0
        memmove(lines, lines + n, (n_lines - n) * sizeof(MD_LINE));
5892
0
        ctx->current_block->n_lines -= n;
5893
0
        ctx->n_block_bytes -= n * sizeof(MD_LINE);
5894
0
    }
5895
5896
0
    if(inject_hr) {
5897
0
        MD_BLOCK* hr_block;
5898
5899
0
        hr_block = md_push_block_bytes(ctx, sizeof(MD_BLOCK));
5900
0
        if(hr_block == NULL)
5901
0
            return -1;
5902
5903
0
        if(ctx->current_block != NULL) {
5904
0
            memmove(ctx->current_block + 1, ctx->current_block,
5905
0
                    (sizeof(MD_BLOCK) + ctx->current_block->n_lines * sizeof(MD_LINE)));
5906
0
            hr_block = ctx->current_block;
5907
0
            ctx->current_block++;
5908
0
        }
5909
5910
0
        memset(hr_block, 0, sizeof(MD_BLOCK));
5911
0
        hr_block->type = MD_BLOCK_HR;
5912
0
    }
5913
5914
0
    return 0;
5915
0
}
5916
5917
static int
5918
md_end_current_block(MD_CTX* ctx)
5919
0
{
5920
0
    int ret = 0;
5921
5922
0
    if(ctx->current_block == NULL)
5923
0
        return ret;
5924
5925
    /* Check whether there is a reference definition. (We do this here instead
5926
     * of in md_analyze_line() because reference definition can take multiple
5927
     * lines.) */
5928
0
    if(ctx->current_block->type == MD_BLOCK_P  ||
5929
0
       (ctx->current_block->type == MD_BLOCK_H  &&  (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)))
5930
0
    {
5931
0
        MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1);
5932
0
        if(lines[0].beg < ctx->size  &&  CH(lines[0].beg) == _T('[')) {
5933
0
            MD_CHECK(md_consume_link_reference_definitions(ctx));
5934
0
            if(ctx->current_block == NULL)
5935
0
                return ret;
5936
0
        }
5937
0
    }
5938
5939
0
    if(ctx->current_block->type == MD_BLOCK_H  &&  (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)) {
5940
0
        MD_SIZE n_lines = ctx->current_block->n_lines;
5941
5942
0
        if(n_lines > 1) {
5943
            /* Get rid of the underline. */
5944
0
            ctx->current_block->n_lines--;
5945
0
            ctx->n_block_bytes -= sizeof(MD_LINE);
5946
0
        } else {
5947
            /* Only the underline has left after eating the ref. defs.
5948
             * Keep the line as beginning of a new ordinary paragraph. */
5949
0
            ctx->current_block->type = MD_BLOCK_P;
5950
0
            return 0;
5951
0
        }
5952
0
    }
5953
5954
    /* Mark we are not building any block anymore. */
5955
0
    ctx->current_block = NULL;
5956
5957
0
abort:
5958
0
    return ret;
5959
0
}
5960
5961
static int
5962
md_add_line_into_current_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* analysis)
5963
0
{
5964
0
    MD_ASSERT(ctx->current_block != NULL);
5965
5966
0
    if(ctx->current_block->type == MD_BLOCK_CODE || ctx->current_block->type == MD_BLOCK_HTML) {
5967
0
        MD_VERBATIMLINE* line;
5968
5969
0
        line = (MD_VERBATIMLINE*) md_push_block_bytes(ctx, sizeof(MD_VERBATIMLINE));
5970
0
        if(line == NULL)
5971
0
            return -1;
5972
5973
0
        line->indent = analysis->indent;
5974
0
        line->beg = analysis->beg;
5975
0
        line->end = analysis->end;
5976
0
    } else {
5977
0
        MD_LINE* line;
5978
5979
0
        line = (MD_LINE*) md_push_block_bytes(ctx, sizeof(MD_LINE));
5980
0
        if(line == NULL)
5981
0
            return -1;
5982
5983
0
        line->beg = analysis->beg;
5984
0
        line->end = analysis->end;
5985
0
    }
5986
0
    ctx->current_block->n_lines++;
5987
5988
0
    return 0;
5989
0
}
5990
5991
/* Part of the MD_FLAG_PRESERVEBLANKLINES implementation. */
5992
static int
5993
md_flush_blank_lines(MD_CTX* ctx)
5994
0
{
5995
0
    MD_BLOCK* block;
5996
5997
0
    if(ctx->n_blank_lines == 0)
5998
0
        return 0;
5999
6000
0
    block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
6001
0
    if(block == NULL)
6002
0
        return -1;
6003
6004
0
    block->type = MD_BLOCK_BLANK;
6005
0
    block->flags = 0;
6006
0
    block->data = ctx->n_blank_lines;
6007
0
    block->n_lines = 0;
6008
6009
0
    ctx->n_blank_lines = 0;
6010
0
    return 0;
6011
0
}
6012
6013
static int
6014
md_push_container_bytes(MD_CTX* ctx, MD_BLOCKTYPE type, unsigned start,
6015
                        unsigned data, unsigned flags)
6016
0
{
6017
0
    MD_BLOCK* block;
6018
0
    int ret = 0;
6019
6020
0
    MD_CHECK(md_flush_blank_lines(ctx));
6021
0
    MD_CHECK(md_end_current_block(ctx));
6022
6023
0
    block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
6024
0
    if(block == NULL)
6025
0
        return -1;
6026
6027
0
    block->type = type;
6028
0
    block->flags = flags;
6029
0
    block->data = data;
6030
0
    block->n_lines = start;
6031
6032
0
abort:
6033
0
    return ret;
6034
0
}
6035
6036
6037
6038
/***********************
6039
 ***  Line Analysis  ***
6040
 ***********************/
6041
6042
static int
6043
md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end, OFF* p_killer)
6044
0
{
6045
0
    OFF off = beg + 1;
6046
0
    int n = 1;
6047
6048
0
    if(!ISANYOF(beg, _T("-_*")))
6049
0
        return false;
6050
6051
0
    while(off < ctx->size  &&  (CH(off) == CH(beg) || CH(off) == _T(' ') || CH(off) == _T('\t'))) {
6052
0
        if(CH(off) == CH(beg))
6053
0
            n++;
6054
0
        off++;
6055
0
    }
6056
6057
0
    if(n < 3) {
6058
0
        *p_killer = off;
6059
0
        return false;
6060
0
    }
6061
6062
    /* Nothing else can be present on the line. */
6063
0
    if(off < ctx->size  &&  !ISNEWLINE(off)) {
6064
0
        *p_killer = off;
6065
0
        return false;
6066
0
    }
6067
6068
0
    *p_end = off;
6069
0
    return true;
6070
0
}
6071
6072
static int
6073
md_is_atxheader_line(MD_CTX* ctx, OFF beg, OFF* p_beg, OFF* p_end, unsigned* p_level)
6074
0
{
6075
0
    int n;
6076
0
    OFF off = beg + 1;
6077
6078
0
    while(off < ctx->size  &&  CH(off) == _T('#')  &&  off - beg < 7)
6079
0
        off++;
6080
0
    n = off - beg;
6081
6082
0
    if(n > 6)
6083
0
        return false;
6084
0
    *p_level = n;
6085
6086
0
    if(!(ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS)  &&  off < ctx->size  &&
6087
0
       !ISBLANK(off)  &&  !ISNEWLINE(off))
6088
0
        return false;
6089
6090
0
    while(off < ctx->size  &&  ISBLANK(off))
6091
0
        off++;
6092
0
    *p_beg = off;
6093
0
    *p_end = off;
6094
0
    return true;
6095
0
}
6096
6097
static int
6098
md_is_setext_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_level)
6099
0
{
6100
0
    OFF off = beg + 1;
6101
6102
0
    while(off < ctx->size  &&  CH(off) == CH(beg))
6103
0
        off++;
6104
6105
    /* Optionally, space(s) or tabs can follow. */
6106
0
    while(off < ctx->size  &&  ISBLANK(off))
6107
0
        off++;
6108
6109
    /* But nothing more is allowed on the line. */
6110
0
    if(off < ctx->size  &&  !ISNEWLINE(off))
6111
0
        return false;
6112
6113
0
    *p_level = (CH(beg) == _T('=') ? 1 : 2);
6114
0
    *p_end = off;
6115
0
    return true;
6116
0
}
6117
6118
static int
6119
md_is_table_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_col_count)
6120
0
{
6121
0
    OFF off = beg;
6122
0
    int found_pipe = false;
6123
0
    unsigned col_count = 0;
6124
6125
0
    if(off < ctx->size  &&  CH(off) == _T('|')) {
6126
0
        found_pipe = true;
6127
0
        off++;
6128
0
        while(off < ctx->size  &&  ISWHITESPACE(off))
6129
0
            off++;
6130
0
    }
6131
6132
0
    while(1) {
6133
0
        int delimited = false;
6134
6135
        /* Cell underline ("-----", ":----", "----:" or ":----:") */
6136
0
        if(off < ctx->size  &&  CH(off) == _T(':'))
6137
0
            off++;
6138
0
        if(off >= ctx->size  ||  CH(off) != _T('-'))
6139
0
            return false;
6140
0
        while(off < ctx->size  &&  CH(off) == _T('-'))
6141
0
            off++;
6142
0
        if(off < ctx->size  &&  CH(off) == _T(':'))
6143
0
            off++;
6144
6145
0
        col_count++;
6146
6147
        /* Pipe delimiter (optional at the end of line). */
6148
0
        while(off < ctx->size  &&  ISWHITESPACE(off))
6149
0
            off++;
6150
0
        if(off < ctx->size  &&  CH(off) == _T('|')) {
6151
0
            delimited = true;
6152
0
            found_pipe =  true;
6153
0
            off++;
6154
0
            while(off < ctx->size  &&  ISWHITESPACE(off))
6155
0
                off++;
6156
0
        }
6157
6158
        /* Success, if we reach end of line. */
6159
0
        if(off >= ctx->size  ||  ISNEWLINE(off))
6160
0
            break;
6161
6162
0
        if(!delimited)
6163
0
            return false;
6164
0
    }
6165
6166
0
    if(!found_pipe)
6167
0
        return false;
6168
6169
0
    *p_end = off;
6170
0
    *p_col_count = col_count;
6171
0
    return true;
6172
0
}
6173
6174
static int
6175
md_is_opening_code_fence(MD_CTX* ctx, OFF beg, OFF* p_end)
6176
0
{
6177
0
    OFF off = beg;
6178
6179
0
    while(off < ctx->size && CH(off) == CH(beg))
6180
0
        off++;
6181
6182
    /* Fence must have at least three characters. */
6183
0
    if(off - beg < 3)
6184
0
        return false;
6185
6186
0
    ctx->code_fence_length = off - beg;
6187
6188
    /* Optionally, space(s) can follow. */
6189
0
    while(off < ctx->size  &&  CH(off) == _T(' '))
6190
0
        off++;
6191
6192
    /* Optionally, an info string can follow. */
6193
0
    while(off < ctx->size  &&  !ISNEWLINE(off)) {
6194
        /* Backtick-based fence must not contain '`' in the info string. */
6195
0
        if(CH(beg) == _T('`')  &&  CH(off) == _T('`'))
6196
0
            return false;
6197
0
        off++;
6198
0
    }
6199
6200
0
    *p_end = off;
6201
0
    return true;
6202
0
}
6203
6204
static int
6205
md_is_closing_code_fence(MD_CTX* ctx, CHAR ch, OFF beg, OFF* p_end)
6206
0
{
6207
0
    OFF off = beg;
6208
0
    int ret = false;
6209
6210
    /* Closing fence must have at least the same length and use same char as
6211
     * opening one. */
6212
0
    while(off < ctx->size  &&  CH(off) == ch)
6213
0
        off++;
6214
0
    if(off - beg < ctx->code_fence_length)
6215
0
        goto out;
6216
6217
    /* Optionally, space(s) can follow */
6218
0
    while(off < ctx->size  &&  ISANYOF2(off, _T(' '), _T('\t')))
6219
0
        off++;
6220
6221
    /* But nothing more is allowed on the line. */
6222
0
    if(off < ctx->size  &&  !ISNEWLINE(off))
6223
0
        goto out;
6224
6225
0
    ret = true;
6226
6227
0
out:
6228
    /* Note we set *p_end even on failure: If we are not closing fence, caller
6229
     * would eat the line anyway without any parsing. */
6230
0
    *p_end = off;
6231
0
    return ret;
6232
0
}
6233
6234
6235
/* Helper data for md_is_html_block_start_condition() and
6236
 * md_is_html_block_end_condition() */
6237
typedef struct TAG_tag TAG;
6238
struct TAG_tag {
6239
    const CHAR* name;
6240
    unsigned len    : 8;
6241
};
6242
6243
#ifdef X
6244
    #undef X
6245
#endif
6246
#define X(name)     { _T(name), (sizeof(_T(name))-1) / sizeof(CHAR) }
6247
#define Xend        { NULL, 0 }
6248
6249
static const TAG t1[] = { X("pre"), X("script"), X("style"), X("textarea"), Xend };
6250
6251
static const TAG a6[] = { X("address"), X("article"), X("aside"), Xend };
6252
static const TAG b6[] = { X("base"), X("basefont"), X("blockquote"), X("body"), Xend };
6253
static const TAG c6[] = { X("caption"), X("center"), X("col"), X("colgroup"), Xend };
6254
static const TAG d6[] = { X("dd"), X("details"), X("dialog"), X("dir"),
6255
                          X("div"), X("dl"), X("dt"), Xend };
6256
static const TAG f6[] = { X("fieldset"), X("figcaption"), X("figure"), X("footer"),
6257
                          X("form"), X("frame"), X("frameset"), Xend };
6258
static const TAG h6[] = { X("h1"), X("h2"), X("h3"), X("h4"), X("h5"), X("h6"),
6259
                          X("head"), X("header"), X("hr"), X("html"), Xend };
6260
static const TAG i6[] = { X("iframe"), Xend };
6261
static const TAG l6[] = { X("legend"), X("li"), X("link"), Xend };
6262
static const TAG m6[] = { X("main"), X("menu"), X("menuitem"), Xend };
6263
static const TAG n6[] = { X("nav"), X("noframes"), Xend };
6264
static const TAG o6[] = { X("ol"), X("optgroup"), X("option"), Xend };
6265
static const TAG p6[] = { X("p"), X("param"), Xend };
6266
static const TAG s6[] = { X("search"), X("section"), X("summary"), Xend };
6267
static const TAG t6[] = { X("table"), X("tbody"), X("td"), X("tfoot"), X("th"),
6268
                          X("thead"), X("title"), X("tr"), X("track"), Xend };
6269
static const TAG u6[] = { X("ul"), Xend };
6270
static const TAG xx[] = { Xend };
6271
6272
#undef X
6273
#undef Xend
6274
6275
/* Returns type of the raw HTML block, or false if it is not HTML block.
6276
 * (Refer to CommonMark specification for details about the types.)
6277
 */
6278
static int
6279
md_is_html_block_start_condition(MD_CTX* ctx, OFF beg)
6280
0
{
6281
    /* Type 6 is started by a long list of allowed tags. We use two-level
6282
     * tree to speed-up the search. */
6283
0
    static const TAG* map6[26] = {
6284
0
        a6, b6, c6, d6, xx, f6, xx, h6, i6, xx, xx, l6, m6,
6285
0
        n6, o6, p6, xx, xx, s6, t6, u6, xx, xx, xx, xx, xx
6286
0
    };
6287
0
    OFF off = beg + 1;
6288
0
    int i;
6289
6290
    /* Check for type 1: <script, <pre, or <style */
6291
0
    for(i = 0; t1[i].name != NULL; i++) {
6292
0
        if(off + t1[i].len <= ctx->size) {
6293
0
            if(md_ascii_case_eq(STR(off), t1[i].name, t1[i].len))
6294
0
                return 1;
6295
0
        }
6296
0
    }
6297
6298
    /* Check for type 2: <!-- */
6299
0
    if(off + 3 < ctx->size  &&  CH(off) == _T('!')  &&  CH(off+1) == _T('-')  &&  CH(off+2) == _T('-'))
6300
0
        return 2;
6301
6302
    /* Check for type 3: <? */
6303
0
    if(off < ctx->size  &&  CH(off) == _T('?'))
6304
0
        return 3;
6305
6306
    /* Check for type 4 or 5: <! */
6307
0
    if(off < ctx->size  &&  CH(off) == _T('!')) {
6308
        /* Check for type 4: <! followed by uppercase letter. */
6309
0
        if(off + 1 < ctx->size  &&  ISASCII(off+1))
6310
0
            return 4;
6311
6312
        /* Check for type 5: <![CDATA[ */
6313
0
        if(off + 8 < ctx->size) {
6314
0
            if(md_ascii_eq(STR(off), _T("![CDATA["), 8))
6315
0
                return 5;
6316
0
        }
6317
0
    }
6318
6319
    /* Check for type 6: Many possible starting tags listed above. */
6320
0
    if(off + 1 < ctx->size  &&  (ISALPHA(off) || (CH(off) == _T('/') && ISALPHA(off+1)))) {
6321
0
        int slot;
6322
0
        const TAG* tags;
6323
6324
0
        if(CH(off) == _T('/'))
6325
0
            off++;
6326
6327
0
        slot = (ISUPPER(off) ? CH(off) - 'A' : CH(off) - 'a');
6328
0
        tags = map6[slot];
6329
6330
0
        for(i = 0; tags[i].name != NULL; i++) {
6331
0
            if(off + tags[i].len <= ctx->size) {
6332
0
                if(md_ascii_case_eq(STR(off), tags[i].name, tags[i].len)) {
6333
0
                    OFF tmp = off + tags[i].len;
6334
0
                    if(tmp >= ctx->size)
6335
0
                        return 6;
6336
0
                    if(ISBLANK(tmp) || ISNEWLINE(tmp) || CH(tmp) == _T('>'))
6337
0
                        return 6;
6338
0
                    if(tmp+1 < ctx->size && CH(tmp) == _T('/') && CH(tmp+1) == _T('>'))
6339
0
                        return 6;
6340
0
                    break;
6341
0
                }
6342
0
            }
6343
0
        }
6344
0
    }
6345
6346
    /* Check for type 7: any COMPLETE other opening or closing tag. */
6347
0
    if(off + 1 < ctx->size) {
6348
0
        OFF end;
6349
6350
0
        if(md_is_html_tag(ctx, NULL, 0, beg, ctx->size, &end)) {
6351
            /* Only optional whitespace and new line may follow. */
6352
0
            while(end < ctx->size  &&  ISWHITESPACE(end))
6353
0
                end++;
6354
0
            if(end >= ctx->size  ||  ISNEWLINE(end))
6355
0
                return 7;
6356
0
        }
6357
0
    }
6358
6359
0
    return false;
6360
0
}
6361
6362
/* Case sensitive check whether there is a substring 'what' between 'beg'
6363
 * and end of line. */
6364
static int
6365
md_line_contains(MD_CTX* ctx, OFF beg, const CHAR* what, SZ what_len, OFF* p_end)
6366
0
{
6367
0
    OFF i;
6368
0
    for(i = beg; i + what_len < ctx->size; i++) {
6369
0
        if(ISNEWLINE(i))
6370
0
            break;
6371
0
        if(memcmp(STR(i), what, what_len * sizeof(CHAR)) == 0) {
6372
0
            *p_end = i + what_len;
6373
0
            return true;
6374
0
        }
6375
0
    }
6376
6377
0
    *p_end = i;
6378
0
    return false;
6379
0
}
6380
6381
/* Returns type of HTML block end condition or false if not an end condition.
6382
 *
6383
 * Note it fills p_end even when it is not end condition as the caller
6384
 * does not need to analyze contents of a raw HTML block.
6385
 */
6386
static int
6387
md_is_html_block_end_condition(MD_CTX* ctx, OFF beg, OFF* p_end)
6388
0
{
6389
0
    switch(ctx->html_block_type) {
6390
0
        case 1:
6391
0
        {
6392
0
            OFF off = beg;
6393
0
            int i;
6394
6395
0
            while(off+1 < ctx->size  &&  !ISNEWLINE(off)) {
6396
0
                if(CH(off) == _T('<')  &&  CH(off+1) == _T('/')) {
6397
0
                    for(i = 0; t1[i].name != NULL; i++) {
6398
0
                        if(off + 2 + t1[i].len < ctx->size) {
6399
0
                            if(md_ascii_case_eq(STR(off+2), t1[i].name, t1[i].len)  &&
6400
0
                               CH(off+2+t1[i].len) == _T('>'))
6401
0
                            {
6402
0
                                *p_end = off+2+t1[i].len+1;
6403
0
                                return true;
6404
0
                            }
6405
0
                        }
6406
0
                    }
6407
0
                }
6408
0
                off++;
6409
0
            }
6410
0
            *p_end = off;
6411
0
            return false;
6412
0
        }
6413
6414
0
        case 2:
6415
0
            return (md_line_contains(ctx, beg, _T("-->"), 3, p_end) ? 2 : false);
6416
6417
0
        case 3:
6418
0
            return (md_line_contains(ctx, beg, _T("?>"), 2, p_end) ? 3 : false);
6419
6420
0
        case 4:
6421
0
            return (md_line_contains(ctx, beg, _T(">"), 1, p_end) ? 4 : false);
6422
6423
0
        case 5:
6424
0
            return (md_line_contains(ctx, beg, _T("]]>"), 3, p_end) ? 5 : false);
6425
6426
0
        case 6:     MD_FALLTHROUGH();
6427
0
        case 7:
6428
0
            if(beg >= ctx->size  ||  ISNEWLINE(beg)) {
6429
                /* Blank line ends types 6 and 7. */
6430
0
                *p_end = beg;
6431
0
                return ctx->html_block_type;
6432
0
            }
6433
0
            return false;
6434
6435
0
        default:
6436
0
            MD_UNREACHABLE();
6437
0
    }
6438
0
    return false;
6439
0
}
6440
6441
6442
static int
6443
md_is_container_compatible(const MD_CONTAINER* pivot, const MD_CONTAINER* container)
6444
0
{
6445
    /* Block quote has no "items" like lists. */
6446
0
    if(container->ch == _T('>'))
6447
0
        return false;
6448
6449
0
    if(container->ch != pivot->ch)
6450
0
        return false;
6451
0
    if(container->mark_indent > pivot->contents_indent)
6452
0
        return false;
6453
6454
0
    return true;
6455
0
}
6456
6457
static int
6458
md_push_container(MD_CTX* ctx, const MD_CONTAINER* container)
6459
0
{
6460
0
    if(ctx->n_containers >= ctx->alloc_containers) {
6461
0
        MD_CONTAINER* new_containers;
6462
6463
0
        ctx->alloc_containers = (ctx->alloc_containers > 0
6464
0
                ? ctx->alloc_containers + ctx->alloc_containers / 2
6465
0
                : 16);
6466
0
        new_containers = realloc(ctx->containers, ctx->alloc_containers * sizeof(MD_CONTAINER));
6467
0
        if(new_containers == NULL) {
6468
0
            MD_LOG("realloc() failed.");
6469
0
            return -1;
6470
0
        }
6471
6472
0
        ctx->containers = new_containers;
6473
0
    }
6474
6475
0
    memcpy(&ctx->containers[ctx->n_containers++], container, sizeof(MD_CONTAINER));
6476
0
    return 0;
6477
0
}
6478
6479
static int
6480
md_enter_child_containers(MD_CTX* ctx, int n_children)
6481
0
{
6482
0
    int i;
6483
0
    int ret = 0;
6484
6485
0
    for(i = ctx->n_containers - n_children; i < ctx->n_containers; i++) {
6486
0
        MD_CONTAINER* c = &ctx->containers[i];
6487
0
        int is_ordered_list = false;
6488
6489
0
        switch(c->ch) {
6490
0
            case _T(')'):
6491
0
            case _T('.'):
6492
0
                is_ordered_list = true;
6493
0
                MD_FALLTHROUGH();
6494
6495
0
            case _T('-'):
6496
0
            case _T('+'):
6497
0
            case _T('*'):
6498
                /* Remember offset in ctx->block_bytes so we can revisit the
6499
                 * block if we detect it is a loose list. */
6500
0
                MD_CHECK(md_end_current_block(ctx));
6501
0
                c->block_byte_off = ctx->n_block_bytes;
6502
6503
0
                MD_CHECK(md_push_container_bytes(ctx,
6504
0
                                (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL),
6505
0
                                c->start, c->ch, MD_BLOCK_CONTAINER_OPENER));
6506
0
                MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
6507
0
                                c->task_mark_off,
6508
0
                                (c->is_task ? CH(c->task_mark_off) : 0),
6509
0
                                MD_BLOCK_CONTAINER_OPENER));
6510
0
                break;
6511
6512
0
            case _T('>'):
6513
0
                MD_CHECK(md_push_container_bytes(ctx,
6514
0
                                (c->is_admonition ? MD_BLOCK_ADMONITION : MD_BLOCK_QUOTE),
6515
0
                                0, c->admonition_type, MD_BLOCK_CONTAINER_OPENER));
6516
0
                break;
6517
6518
0
            default:
6519
0
                MD_UNREACHABLE();
6520
0
                break;
6521
0
        }
6522
0
    }
6523
6524
0
abort:
6525
0
    return ret;
6526
0
}
6527
6528
static int
6529
md_leave_child_containers(MD_CTX* ctx, int n_keep)
6530
0
{
6531
0
    int ret = 0;
6532
6533
0
    while(ctx->n_containers > n_keep) {
6534
0
        MD_CONTAINER* c = &ctx->containers[ctx->n_containers-1];
6535
0
        int is_ordered_list = false;
6536
6537
0
        switch(c->ch) {
6538
0
            case _T(')'):
6539
0
            case _T('.'):
6540
0
                is_ordered_list = true;
6541
0
                MD_FALLTHROUGH();
6542
6543
0
            case _T('-'):
6544
0
            case _T('+'):
6545
0
            case _T('*'):
6546
0
                MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
6547
0
                                c->task_mark_off, (c->is_task ? CH(c->task_mark_off) : 0),
6548
0
                                MD_BLOCK_CONTAINER_CLOSER));
6549
0
                MD_CHECK(md_push_container_bytes(ctx,
6550
0
                                (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL), 0,
6551
0
                                c->ch, MD_BLOCK_CONTAINER_CLOSER));
6552
0
                break;
6553
6554
0
            case _T('>'):
6555
0
                MD_CHECK(md_push_container_bytes(ctx,
6556
0
                                (c->is_admonition ? MD_BLOCK_ADMONITION : MD_BLOCK_QUOTE),
6557
0
                                0, c->admonition_type, MD_BLOCK_CONTAINER_CLOSER));
6558
0
                break;
6559
6560
0
            default:
6561
0
                MD_UNREACHABLE();
6562
0
                break;
6563
0
        }
6564
6565
0
        ctx->n_containers--;
6566
0
    }
6567
6568
0
abort:
6569
0
    return ret;
6570
0
}
6571
6572
static int
6573
md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTAINER* p_container)
6574
0
{
6575
0
    OFF off = beg;
6576
0
    OFF max_end;
6577
6578
0
    if(off >= ctx->size  ||  indent >= ctx->code_indent_offset)
6579
0
        return false;
6580
6581
    /* Check for block quote mark. */
6582
0
    if(CH(off) == _T('>')) {
6583
0
        off++;
6584
0
        p_container->ch = _T('>');
6585
0
        p_container->is_loose = false;
6586
0
        p_container->is_task = false;
6587
0
        p_container->mark_indent = indent;
6588
0
        p_container->contents_indent = indent + 1;
6589
0
        *p_end = off;
6590
0
        return true;
6591
0
    }
6592
6593
    /* Check for list item bullet mark. */
6594
0
    if(ISANYOF(off, _T("-+*"))  &&  (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1))) {
6595
0
        p_container->ch = CH(off);
6596
0
        p_container->is_loose = false;
6597
0
        p_container->is_task = false;
6598
0
        p_container->mark_indent = indent;
6599
0
        p_container->contents_indent = indent + 1;
6600
0
        *p_end = off+1;
6601
0
        return true;
6602
0
    }
6603
6604
    /* Check for ordered list item marks. */
6605
0
    max_end = off + 9;
6606
0
    if(max_end > ctx->size)
6607
0
        max_end = ctx->size;
6608
0
    p_container->start = 0;
6609
0
    while(off < max_end  &&  ISDIGIT(off)) {
6610
0
        p_container->start = p_container->start * 10 + CH(off) - _T('0');
6611
0
        off++;
6612
0
    }
6613
0
    if(off > beg  &&
6614
0
       off < ctx->size  &&
6615
0
       (CH(off) == _T('.') || CH(off) == _T(')'))  &&
6616
0
       (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1)))
6617
0
    {
6618
0
        p_container->ch = CH(off);
6619
0
        p_container->is_loose = false;
6620
0
        p_container->is_task = false;
6621
0
        p_container->mark_indent = indent;
6622
0
        p_container->contents_indent = indent + off - beg + 1;
6623
0
        *p_end = off+1;
6624
0
        return true;
6625
0
    }
6626
6627
0
    return false;
6628
0
}
6629
6630
static unsigned
6631
md_line_indentation(MD_CTX* ctx, unsigned total_indent, OFF beg, OFF* p_end)
6632
0
{
6633
0
    OFF off = beg;
6634
0
    unsigned indent = total_indent;
6635
6636
0
    while(off < ctx->size  &&  ISBLANK(off)) {
6637
0
        if(CH(off) == _T('\t'))
6638
0
            indent = (indent + 4) & ~3;
6639
0
        else
6640
0
            indent++;
6641
0
        off++;
6642
0
    }
6643
6644
0
    *p_end = off;
6645
0
    return indent - total_indent;
6646
0
}
6647
6648
static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0, 0, 0, 0, 0 };
6649
6650
/* Analyze type of the line and find some its properties. This serves as a
6651
 * main input for determining type and boundaries of a block. */
6652
static int
6653
md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
6654
                const MD_LINE_ANALYSIS* pivot_line, MD_LINE_ANALYSIS* line)
6655
0
{
6656
0
    unsigned total_indent = 0;
6657
0
    int n_parents = 0;
6658
0
    int n_brothers = 0;
6659
0
    int n_children = 0;
6660
0
    MD_CONTAINER container = { 0 };
6661
0
    int prev_line_has_list_loosening_effect = ctx->last_line_has_list_loosening_effect;
6662
0
    OFF off = beg;
6663
0
    OFF hr_killer = 0;
6664
0
    int ret = 0;
6665
6666
0
    line->indent = md_line_indentation(ctx, total_indent, off, &off);
6667
0
    total_indent += line->indent;
6668
0
    line->beg = off;
6669
0
    line->enforce_new_block = false;
6670
6671
    /* Given the indentation and block quote marks '>', determine how many of
6672
     * the current containers are our parents. */
6673
0
    while(n_parents < ctx->n_containers) {
6674
0
        MD_CONTAINER* c = &ctx->containers[n_parents];
6675
6676
0
        if(c->ch == _T('>')  &&  line->indent < ctx->code_indent_offset  &&
6677
0
            off < ctx->size  &&  CH(off) == _T('>'))
6678
0
        {
6679
            /* Block quote mark. */
6680
0
            off++;
6681
0
            total_indent++;
6682
0
            line->indent = md_line_indentation(ctx, total_indent, off, &off);
6683
0
            total_indent += line->indent;
6684
6685
            /* The optional 1st space after '>' is part of the block quote mark. */
6686
0
            if(line->indent > 0)
6687
0
                line->indent--;
6688
6689
0
            line->beg = off;
6690
6691
0
        } else if(c->ch != _T('>')  &&  line->indent >= c->contents_indent) {
6692
            /* List. */
6693
0
            line->indent -= c->contents_indent;
6694
0
        } else {
6695
0
            break;
6696
0
        }
6697
6698
0
        n_parents++;
6699
0
    }
6700
6701
0
    if(off >= ctx->size  ||  ISNEWLINE(off)) {
6702
        /* Blank line does not need any real indentation to be nested inside
6703
         * a list. */
6704
0
        if(n_brothers + n_children == 0) {
6705
0
            while(n_parents < ctx->n_containers  &&  ctx->containers[n_parents].ch != _T('>'))
6706
0
                n_parents++;
6707
0
        }
6708
0
    }
6709
6710
0
    while(true) {
6711
        /* Check whether we are fenced code continuation. */
6712
0
        if(pivot_line->type == MD_LINE_FENCEDCODE) {
6713
0
            line->beg = off;
6714
6715
            /* We are another MD_LINE_FENCEDCODE unless we are closing fence
6716
             * which we transform into MD_LINE_BLANK. */
6717
0
            if(line->indent < ctx->code_indent_offset) {
6718
0
                if(md_is_closing_code_fence(ctx, CH(pivot_line->beg), off, &off)) {
6719
0
                    line->type = MD_LINE_BLANK;
6720
0
                    ctx->last_line_has_list_loosening_effect = false;
6721
0
                    break;
6722
0
                }
6723
0
            }
6724
6725
            /* Change indentation accordingly to the initial code fence. */
6726
0
            if(n_parents == ctx->n_containers) {
6727
0
                if(line->indent > pivot_line->indent)
6728
0
                    line->indent -= pivot_line->indent;
6729
0
                else
6730
0
                    line->indent = 0;
6731
6732
0
                line->type = MD_LINE_FENCEDCODE;
6733
0
                break;
6734
0
            }
6735
0
        }
6736
6737
        /* Check whether we are HTML block continuation. */
6738
0
        if(pivot_line->type == MD_LINE_HTML  &&  ctx->html_block_type > 0) {
6739
0
            if(n_parents < ctx->n_containers) {
6740
                /* HTML block is implicitly ended if the enclosing container
6741
                 * block ends. */
6742
0
                ctx->html_block_type = 0;
6743
0
            } else {
6744
0
                int html_block_type;
6745
6746
0
                html_block_type = md_is_html_block_end_condition(ctx, off, &off);
6747
0
                if(html_block_type > 0) {
6748
0
                    MD_ASSERT(html_block_type == ctx->html_block_type);
6749
6750
                    /* Make sure this is the last line of the block. */
6751
0
                    ctx->html_block_type = 0;
6752
6753
                    /* Some end conditions serve as blank lines at the same time. */
6754
0
                    if(html_block_type == 6 || html_block_type == 7) {
6755
0
                        line->type = MD_LINE_BLANK;
6756
0
                        line->indent = 0;
6757
0
                        break;
6758
0
                    }
6759
0
                }
6760
6761
0
                line->type = MD_LINE_HTML;
6762
0
                n_parents = ctx->n_containers;
6763
0
                break;
6764
0
            }
6765
0
        }
6766
6767
        /* Check for blank line. */
6768
0
        if(off >= ctx->size  ||  ISNEWLINE(off)) {
6769
0
            if(pivot_line->type == MD_LINE_INDENTEDCODE  &&  n_parents == ctx->n_containers) {
6770
0
                line->type = MD_LINE_INDENTEDCODE;
6771
0
                if(line->indent > ctx->code_indent_offset)
6772
0
                    line->indent -= ctx->code_indent_offset;
6773
0
                else
6774
0
                    line->indent = 0;
6775
0
                ctx->last_line_has_list_loosening_effect = false;
6776
0
            } else {
6777
0
                line->type = MD_LINE_BLANK;
6778
0
                ctx->consecutive_blank_lines++;
6779
0
                ctx->last_line_has_list_loosening_effect = (n_parents > 0  &&
6780
0
                        n_brothers + n_children == 0  &&
6781
0
                        ctx->containers[n_parents-1].ch != _T('>'));
6782
0
            }
6783
0
            break;
6784
0
        } else {
6785
            /* CommonMark requires a list item cannot begin with two (or more)
6786
             * blank lines so we may need to forcefully end the list.
6787
             * (See https://github.com/mity/md4c/issues/6) */
6788
0
            if(ctx->consecutive_blank_lines >= 2) {
6789
0
                if(n_parents > 0  &&  n_parents == ctx->n_containers  &&
6790
0
                   ctx->containers[n_parents-1].ch != _T('>')  &&
6791
0
                   n_brothers + n_children == 0  &&  ctx->current_block == NULL  &&
6792
0
                   ctx->n_block_bytes > (int) sizeof(MD_BLOCK))
6793
0
                {
6794
0
                    MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK));
6795
0
                    if(top_block->type == MD_BLOCK_LI) {
6796
0
                        n_parents--;
6797
6798
0
                        line->indent = total_indent;
6799
0
                        if(n_parents > 0)
6800
0
                            line->indent -= MIN(line->indent, ctx->containers[n_parents-1].contents_indent);
6801
0
                    }
6802
0
                }
6803
0
            }
6804
0
            ctx->consecutive_blank_lines = 0;
6805
6806
0
            ctx->last_line_has_list_loosening_effect = false;
6807
0
        }
6808
6809
        /* Check whether we are Setext underline. */
6810
0
        if(line->indent < ctx->code_indent_offset  &&  pivot_line->type == MD_LINE_TEXT
6811
0
            &&  off < ctx->size  &&  ISANYOF2(off, _T('='), _T('-'))
6812
0
            &&  (n_parents == ctx->n_containers))
6813
0
        {
6814
0
            unsigned level;
6815
6816
0
            if(md_is_setext_underline(ctx, off, &off, &level)) {
6817
0
                line->type = MD_LINE_SETEXTUNDERLINE;
6818
0
                line->data = level;
6819
0
                break;
6820
0
            }
6821
0
        }
6822
6823
        /* Check for thematic break line. */
6824
0
        if(line->indent < ctx->code_indent_offset
6825
0
            &&  off < ctx->size  &&  off >= hr_killer)
6826
0
        {
6827
0
            if(md_is_hr_line(ctx, off, &off, &hr_killer)) {
6828
0
                line->type = MD_LINE_HR;
6829
0
                break;
6830
0
            }
6831
0
        }
6832
6833
        /* Check for "brother" container. I.e. whether we are another list item
6834
         * in already started list. */
6835
0
        if(n_parents < ctx->n_containers  &&  n_brothers + n_children == 0) {
6836
0
            OFF tmp;
6837
6838
0
            if(md_is_container_mark(ctx, line->indent, off, &tmp, &container)  &&
6839
0
               md_is_container_compatible(&ctx->containers[n_parents], &container))
6840
0
            {
6841
0
                pivot_line = &md_dummy_blank_line;
6842
6843
0
                off = tmp;
6844
6845
0
                total_indent += container.contents_indent - container.mark_indent;
6846
0
                line->indent = md_line_indentation(ctx, total_indent, off, &off);
6847
0
                total_indent += line->indent;
6848
0
                line->beg = off;
6849
6850
                /* Some of the following whitespace actually still belongs to the mark. */
6851
0
                if(off >= ctx->size || ISNEWLINE(off)) {
6852
0
                    container.contents_indent++;
6853
0
                } else if(line->indent <= ctx->code_indent_offset) {
6854
0
                    container.contents_indent += line->indent;
6855
0
                    line->indent = 0;
6856
0
                } else {
6857
0
                    container.contents_indent += 1;
6858
0
                    line->indent--;
6859
0
                }
6860
6861
0
                ctx->containers[n_parents].mark_indent = container.mark_indent;
6862
0
                ctx->containers[n_parents].contents_indent = container.contents_indent;
6863
6864
0
                n_brothers++;
6865
0
                continue;
6866
0
            }
6867
0
        }
6868
6869
        /* Check for indented code.
6870
         * Note indented code block cannot interrupt a paragraph. */
6871
0
        if(line->indent >= ctx->code_indent_offset  &&  (pivot_line->type != MD_LINE_TEXT)) {
6872
0
            line->type = MD_LINE_INDENTEDCODE;
6873
0
            line->indent -= ctx->code_indent_offset;
6874
0
            line->data = 0;
6875
0
            break;
6876
0
        }
6877
6878
        /* Check for start of a new container block. */
6879
0
        if(line->indent < ctx->code_indent_offset  &&
6880
0
           md_is_container_mark(ctx, line->indent, off, &off, &container))
6881
0
        {
6882
0
            if(pivot_line->type == MD_LINE_TEXT  &&  n_parents == ctx->n_containers  &&
6883
0
                        (off >= ctx->size || ISNEWLINE(off))  &&  container.ch != _T('>'))
6884
0
            {
6885
                /* Noop. List mark followed by a blank line cannot interrupt a paragraph. */
6886
0
            } else if(pivot_line->type == MD_LINE_TEXT  &&  n_parents == ctx->n_containers  &&
6887
0
                        ISANYOF2_(container.ch, _T('.'), _T(')'))  &&  container.start != 1)
6888
0
            {
6889
                /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */
6890
0
            } else {
6891
0
                total_indent += container.contents_indent - container.mark_indent;
6892
0
                line->indent = md_line_indentation(ctx, total_indent, off, &off);
6893
0
                total_indent += line->indent;
6894
6895
0
                line->beg = off;
6896
0
                line->data = container.ch;
6897
6898
                /* Some of the following whitespace actually still belongs to the mark. */
6899
0
                if(off >= ctx->size || ISNEWLINE(off)) {
6900
0
                    container.contents_indent++;
6901
0
                } else if(line->indent <= ctx->code_indent_offset) {
6902
0
                    container.contents_indent += line->indent;
6903
0
                    line->indent = 0;
6904
0
                } else {
6905
0
                    container.contents_indent += 1;
6906
0
                    line->indent--;
6907
0
                }
6908
6909
0
                if(n_brothers + n_children == 0)
6910
0
                    pivot_line = &md_dummy_blank_line;
6911
6912
0
                if(n_children == 0)
6913
0
                    MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers));
6914
6915
0
                n_children++;
6916
0
                MD_CHECK(md_push_container(ctx, &container));
6917
0
                continue;
6918
0
            }
6919
0
        }
6920
6921
        /* Check whether we are table continuation. */
6922
0
        if(pivot_line->type == MD_LINE_TABLE  &&  n_parents == ctx->n_containers) {
6923
0
            line->type = MD_LINE_TABLE;
6924
0
            break;
6925
0
        }
6926
6927
        /* Check for ATX header. */
6928
0
        if(line->indent < ctx->code_indent_offset  &&
6929
0
                off < ctx->size  &&  CH(off) == _T('#'))
6930
0
        {
6931
0
            unsigned level;
6932
6933
0
            if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) {
6934
0
                line->type = MD_LINE_ATXHEADER;
6935
0
                line->data = level;
6936
0
                break;
6937
0
            }
6938
0
        }
6939
6940
        /* Check whether we are starting code fence. */
6941
0
        if(line->indent < ctx->code_indent_offset  &&
6942
0
                off < ctx->size  &&  ISANYOF2(off, _T('`'), _T('~')))
6943
0
        {
6944
0
            if(md_is_opening_code_fence(ctx, off, &off)) {
6945
0
                line->type = MD_LINE_FENCEDCODE;
6946
0
                line->data = 1;
6947
0
                line->enforce_new_block = true;
6948
0
                break;
6949
0
            }
6950
0
        }
6951
6952
        /* Check for start of raw HTML block. */
6953
0
        if(off < ctx->size  &&  CH(off) == _T('<')
6954
0
            &&  !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
6955
0
        {
6956
0
            ctx->html_block_type = md_is_html_block_start_condition(ctx, off);
6957
6958
            /* HTML block type 7 cannot interrupt paragraph. */
6959
0
            if(ctx->html_block_type == 7  &&  pivot_line->type == MD_LINE_TEXT)
6960
0
                ctx->html_block_type = 0;
6961
6962
0
            if(ctx->html_block_type > 0) {
6963
                /* The line itself also may immediately close the block. */
6964
0
                if(md_is_html_block_end_condition(ctx, off, &off) == ctx->html_block_type) {
6965
                    /* Make sure this is the last line of the block. */
6966
0
                    ctx->html_block_type = 0;
6967
0
                }
6968
6969
0
                line->enforce_new_block = true;
6970
0
                line->type = MD_LINE_HTML;
6971
0
                break;
6972
0
            }
6973
0
        }
6974
6975
        /* Check for table underline. */
6976
0
        if((ctx->parser.flags & MD_FLAG_TABLES)  &&  pivot_line->type == MD_LINE_TEXT
6977
0
            &&  off < ctx->size  &&  ISANYOF3(off, _T('|'), _T('-'), _T(':'))
6978
0
            &&  n_parents == ctx->n_containers)
6979
0
        {
6980
0
            unsigned col_count;
6981
6982
0
            if(ctx->current_block != NULL  &&  ctx->current_block->n_lines == 1  &&
6983
0
                md_is_table_underline(ctx, off, &off, &col_count))
6984
0
            {
6985
0
                line->data = col_count;
6986
0
                line->type = MD_LINE_TABLEUNDERLINE;
6987
0
                break;
6988
0
            }
6989
0
        }
6990
6991
        /* By default, we are normal text line. */
6992
0
        line->type = MD_LINE_TEXT;
6993
0
        if(pivot_line->type == MD_LINE_TEXT  &&  n_brothers + n_children == 0) {
6994
            /* Lazy continuation. */
6995
0
            n_parents = ctx->n_containers;
6996
0
        }
6997
6998
        /* Check for task mark. */
6999
0
        if((ctx->parser.flags & MD_FLAG_TASKLISTS)  &&  n_brothers + n_children > 0  &&
7000
0
           ISANYOF_(ctx->containers[ctx->n_containers-1].ch, _T("-+*.)")))
7001
0
        {
7002
0
            OFF tmp = off;
7003
7004
0
            while(tmp < ctx->size  &&  tmp < off + 3  &&  ISBLANK(tmp))
7005
0
                tmp++;
7006
0
            if(tmp + 2 < ctx->size  &&  CH(tmp) == _T('[')  &&
7007
0
               ISANYOF(tmp+1, _T("xX "))  &&  CH(tmp+2) == _T(']')  &&
7008
0
               (tmp + 3 == ctx->size  ||  ISBLANK(tmp+3)  ||  ISNEWLINE(tmp+3)))
7009
0
            {
7010
0
                MD_CONTAINER* task_container = (n_children > 0 ? &ctx->containers[ctx->n_containers-1] : &container);
7011
0
                task_container->is_task = true;
7012
0
                task_container->task_mark_off = tmp + 1;
7013
0
                off = tmp + 3;
7014
0
                while(off < ctx->size  &&  ISWHITESPACE(off))
7015
0
                    off++;
7016
0
                line->beg = off;
7017
0
            }
7018
0
        }
7019
7020
0
        break;
7021
0
    }
7022
7023
    /* Scan for end of the line. */
7024
    /* Optimization: Use some loop unrolling. */
7025
0
    while(off + 3 < ctx->size  &&  !ISNEWLINE(off+0)  &&  !ISNEWLINE(off+1)
7026
0
                               &&  !ISNEWLINE(off+2)  &&  !ISNEWLINE(off+3))
7027
0
        off += 4;
7028
0
    while(off < ctx->size  &&  !ISNEWLINE(off))
7029
0
        off++;
7030
7031
    /* Set end of the line. */
7032
0
    line->end = off;
7033
7034
    /* But for ATX header, we should exclude the optional trailing mark. */
7035
0
    if(line->type == MD_LINE_ATXHEADER) {
7036
0
        OFF tmp = line->end;
7037
0
        while(tmp > line->beg && ISBLANK(tmp-1))
7038
0
            tmp--;
7039
0
        while(tmp > line->beg && CH(tmp-1) == _T('#'))
7040
0
            tmp--;
7041
0
        if(tmp == line->beg || ISBLANK(tmp-1) || (ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS))
7042
0
            line->end = tmp;
7043
0
    }
7044
7045
    /* Trim trailing spaces. */
7046
0
    if(line->type != MD_LINE_INDENTEDCODE  &&  line->type != MD_LINE_FENCEDCODE  && line->type != MD_LINE_HTML) {
7047
0
        while(line->end > line->beg && ISBLANK(line->end-1))
7048
0
            line->end--;
7049
0
    }
7050
7051
    /* Eat also the new line. */
7052
0
    if(off < ctx->size && CH(off) == _T('\r'))
7053
0
        off++;
7054
0
    if(off < ctx->size && CH(off) == _T('\n'))
7055
0
        off++;
7056
7057
0
    *p_end = off;
7058
7059
    /* If we belong to a list after seeing a blank line, the list is loose. */
7060
0
    if(prev_line_has_list_loosening_effect  &&  line->type != MD_LINE_BLANK  &&  n_parents + n_brothers > 0) {
7061
0
        MD_CONTAINER* c = &ctx->containers[n_parents + n_brothers - 1];
7062
0
        if(c->ch != _T('>')) {
7063
0
            MD_BLOCK* block = (MD_BLOCK*) (((char*)ctx->block_bytes) + c->block_byte_off);
7064
0
            block->flags |= MD_BLOCK_LOOSE_LIST;
7065
0
        }
7066
0
    }
7067
7068
    /* Leave any containers we are not part of anymore. */
7069
0
    if(n_children == 0  &&  n_parents + n_brothers < ctx->n_containers)
7070
0
        MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers));
7071
7072
    /* Enter any container we found a mark for. */
7073
0
    if(n_brothers > 0) {
7074
0
        MD_ASSERT(n_brothers == 1);
7075
0
        MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
7076
0
                    ctx->containers[n_parents].task_mark_off,
7077
0
                    (ctx->containers[n_parents].is_task ? CH(ctx->containers[n_parents].task_mark_off) : 0),
7078
0
                    MD_BLOCK_CONTAINER_CLOSER));
7079
0
        MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
7080
0
                    container.task_mark_off,
7081
0
                    (container.is_task ? CH(container.task_mark_off) : 0),
7082
0
                    MD_BLOCK_CONTAINER_OPENER));
7083
0
        ctx->containers[n_parents].is_task = container.is_task;
7084
0
        ctx->containers[n_parents].task_mark_off = container.task_mark_off;
7085
0
    }
7086
7087
0
    if(n_children > 0) {
7088
        /* Check for admonition tag. */
7089
0
        if((ctx->parser.flags & MD_FLAG_ADMONITIONS)  &&  n_children > 0  &&
7090
0
           ctx->containers[ctx->n_containers-1].ch == _T('>')  &&  line->type == MD_LINE_TEXT  &&
7091
0
           3 < line->end - line->beg  && line->end - line->beg < 16  &&
7092
0
           CH(line->beg) == _T('[') && CH(line->beg+1) == _T('!') && CH(line->end-1) == _T(']'))
7093
0
        {
7094
0
            unsigned i;
7095
7096
0
            for(i = 0; i < SIZEOF_ARRAY(MD_ADMONITION_TAGS); i++) {
7097
0
                if(line->end - line->beg == md_strlen(MD_ADMONITION_TAGS[i]) + 3  &&
7098
0
                   md_ascii_case_eq(STR(line->beg+2), MD_ADMONITION_TAGS[i], line->end - line->beg - 3))
7099
0
                {
7100
0
                    ctx->containers[ctx->n_containers-1].is_admonition = true;
7101
0
                    ctx->containers[ctx->n_containers-1].admonition_type = i;
7102
0
                    line->type = MD_LINE_BLANK;
7103
0
                    break;
7104
0
                }
7105
0
            }
7106
0
        }
7107
7108
        /* Enter all the child container blocks. */
7109
0
        MD_CHECK(md_enter_child_containers(ctx, n_children));
7110
0
    }
7111
7112
0
abort:
7113
0
    return ret;
7114
0
}
7115
7116
static int
7117
md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, MD_LINE_ANALYSIS* line)
7118
0
{
7119
0
    const MD_LINE_ANALYSIS* pivot_line = *p_pivot_line;
7120
0
    int ret = 0;
7121
7122
    /* Blank line ends current leaf block. */
7123
0
    if(line->type == MD_LINE_BLANK) {
7124
0
        MD_CHECK(md_end_current_block(ctx));
7125
        /* Count only genuinely empty lines: some non-blank lines (e.g. a closing
7126
         * code fence) are internally retyped as MD_LINE_BLANK but still hold
7127
         * their text (beg < end), and must not be counted. */
7128
0
        if((ctx->parser.flags & MD_FLAG_PRESERVEBLANKLINES)  &&  line->beg >= line->end)
7129
0
            ctx->n_blank_lines++;
7130
0
        *p_pivot_line = &md_dummy_blank_line;
7131
0
        return 0;
7132
0
    }
7133
7134
    /* The blank lines preceding this block (if any) form a block separation
7135
     * which we report before the block itself. */
7136
0
    MD_CHECK(md_flush_blank_lines(ctx));
7137
7138
0
    if(line->enforce_new_block)
7139
0
        MD_CHECK(md_end_current_block(ctx));
7140
7141
    /* Some line types form block on their own. */
7142
0
    if(line->type == MD_LINE_HR || line->type == MD_LINE_ATXHEADER) {
7143
0
        MD_CHECK(md_end_current_block(ctx));
7144
7145
        /* Add our single-line block. */
7146
0
        MD_CHECK(md_start_new_block(ctx, line));
7147
0
        MD_CHECK(md_add_line_into_current_block(ctx, line));
7148
0
        MD_CHECK(md_end_current_block(ctx));
7149
0
        *p_pivot_line = &md_dummy_blank_line;
7150
0
        return 0;
7151
0
    }
7152
7153
    /* MD_LINE_SETEXTUNDERLINE changes meaning of the current block and ends it. */
7154
0
    if(line->type == MD_LINE_SETEXTUNDERLINE) {
7155
0
        MD_ASSERT(ctx->current_block != NULL);
7156
0
        ctx->current_block->type = MD_BLOCK_H;
7157
0
        ctx->current_block->data = line->data;
7158
0
        ctx->current_block->flags |= MD_BLOCK_SETEXT_HEADER;
7159
0
        MD_CHECK(md_add_line_into_current_block(ctx, line));
7160
0
        MD_CHECK(md_end_current_block(ctx));
7161
0
        if(ctx->current_block == NULL) {
7162
0
            *p_pivot_line = &md_dummy_blank_line;
7163
0
        } else {
7164
            /* This happens if we have consumed all the body as link ref. defs.
7165
             * and downgraded the underline into start of a new paragraph block. */
7166
0
            line->type = MD_LINE_TEXT;
7167
0
            *p_pivot_line = line;
7168
0
        }
7169
0
        return 0;
7170
0
    }
7171
7172
    /* MD_LINE_TABLEUNDERLINE changes meaning of the current block. */
7173
0
    if(line->type == MD_LINE_TABLEUNDERLINE) {
7174
0
        MD_ASSERT(ctx->current_block != NULL);
7175
0
        MD_ASSERT(ctx->current_block->n_lines == 1);
7176
0
        ctx->current_block->type = MD_BLOCK_TABLE;
7177
0
        ctx->current_block->data = line->data;
7178
0
        MD_ASSERT(pivot_line != &md_dummy_blank_line);
7179
0
        ((MD_LINE_ANALYSIS*)pivot_line)->type = MD_LINE_TABLE;
7180
0
        MD_CHECK(md_add_line_into_current_block(ctx, line));
7181
0
        return 0;
7182
0
    }
7183
7184
    /* The current block also ends if the line has different type. */
7185
0
    if(line->type != pivot_line->type)
7186
0
        MD_CHECK(md_end_current_block(ctx));
7187
7188
    /* The current line may start a new block. */
7189
0
    if(ctx->current_block == NULL) {
7190
0
        MD_CHECK(md_start_new_block(ctx, line));
7191
0
        *p_pivot_line = line;
7192
0
    }
7193
7194
    /* In all other cases the line is just a continuation of the current block. */
7195
0
    MD_CHECK(md_add_line_into_current_block(ctx, line));
7196
7197
0
abort:
7198
0
    return ret;
7199
0
}
7200
7201
static int
7202
md_footnote_def_cmp_index(const void* a, const void* b)
7203
0
{
7204
0
    const MD_FOOTNOTE_DEF* da = (const MD_FOOTNOTE_DEF*) a;
7205
0
    const MD_FOOTNOTE_DEF* db = (const MD_FOOTNOTE_DEF*) b;
7206
7207
    /* Unreferenced defs (index == 0) always sort after referenced ones. */
7208
0
    if(da->index == 0  &&  db->index == 0) return 0;
7209
0
    if(da->index == 0) return +1;
7210
0
    if(db->index == 0) return -1;
7211
0
    if(da->index < db->index) return -1;
7212
0
    if(da->index > db->index) return +1;
7213
0
    return 0;
7214
0
}
7215
7216
static int
7217
md_process_footnote_def(MD_CTX* ctx, MD_FOOTNOTE_DEF* def)
7218
0
{
7219
0
    MD_BLOCK_FOOTNOTE_DEF_DETAIL det;
7220
0
    MD_ATTRIBUTE_BUILD label_build = { 0 };
7221
0
    int ret = 0;
7222
7223
0
    memset(&det, 0, sizeof(MD_BLOCK_FOOTNOTE_DEF_DETAIL));
7224
0
    det.id = def->index;
7225
0
    det.ref_count = def->ref_count;
7226
0
    MD_CHECK(md_build_attribute(ctx, def->entry.label, def->entry.label_size, 0,
7227
0
                                &det.label, &label_build));
7228
7229
0
    MD_ENTER_BLOCK(MD_BLOCK_FOOTNOTE_DEF, &det);
7230
0
    MD_CHECK(md_process_normal_block_contents(ctx, def->content_lines,
7231
0
                                              def->n_content_lines));
7232
0
    MD_LEAVE_BLOCK(MD_BLOCK_FOOTNOTE_DEF, &det);
7233
7234
0
abort:
7235
0
    md_free_attribute(ctx, &label_build);
7236
0
    return ret;
7237
0
}
7238
7239
/* Render footnote definitions that were actually referenced, in reference order.
7240
 * Called from md_process_doc() after md_process_all_blocks(). */
7241
static int
7242
md_process_footnote_defs(MD_CTX* ctx)
7243
0
{
7244
0
    unsigned i;
7245
0
    int ret = 0;
7246
7247
0
    if(ctx->footnote_hashtable.n_defs == 0  ||  ctx->next_footnote_index == 0)
7248
0
        return 0;
7249
7250
    /* Sort defs by index so we emit in reference order. */
7251
0
    qsort(ctx->footnote_hashtable.defs, ctx->footnote_hashtable.n_defs,
7252
0
          sizeof(MD_FOOTNOTE_DEF), md_footnote_def_cmp_index);
7253
7254
0
    MD_ENTER_BLOCK(MD_BLOCK_FOOTNOTE_DEF_SECTION, NULL);
7255
7256
0
    for(i = 0; i < ctx->footnote_hashtable.n_defs; i++) {
7257
0
        MD_FOOTNOTE_DEF* def = &ctx->footnote_hashtable.footnote_defs[i];
7258
0
        if(def->index == 0)
7259
0
            break;
7260
0
        MD_CHECK(md_process_footnote_def(ctx, def));
7261
0
    }
7262
7263
0
    MD_LEAVE_BLOCK(MD_BLOCK_FOOTNOTE_DEF_SECTION, NULL);
7264
7265
0
abort:
7266
0
    return ret;
7267
0
}
7268
7269
static int
7270
md_process_doc(MD_CTX *ctx)
7271
0
{
7272
0
    const MD_LINE_ANALYSIS* pivot_line = &md_dummy_blank_line;
7273
0
    MD_LINE_ANALYSIS line_buf[2];
7274
0
    MD_LINE_ANALYSIS* line = &line_buf[0];
7275
0
    OFF off = 0;
7276
0
    int ret = 0;
7277
7278
0
    MD_ENTER_BLOCK(MD_BLOCK_DOC, NULL);
7279
7280
0
    while(off < ctx->size) {
7281
0
        if(line == pivot_line)
7282
0
            line = (line == &line_buf[0] ? &line_buf[1] : &line_buf[0]);
7283
7284
0
        MD_CHECK(md_analyze_line(ctx, off, &off, pivot_line, line));
7285
0
        MD_CHECK(md_process_line(ctx, &pivot_line, line));
7286
0
    }
7287
7288
0
    MD_CHECK(md_end_current_block(ctx));
7289
7290
0
    MD_CHECK(md_build_ref_def_hashtable(ctx));
7291
0
    if(ctx->parser.flags & MD_FLAG_FOOTNOTES)
7292
0
        MD_CHECK(md_build_footnote_def_hashtable(ctx));
7293
7294
    /* Process all blocks. */
7295
0
    MD_CHECK(md_leave_child_containers(ctx, 0));
7296
    /* Report any blank lines trailing the document. */
7297
0
    MD_CHECK(md_flush_blank_lines(ctx));
7298
0
    MD_CHECK(md_process_all_blocks(ctx));
7299
7300
    /* Emit footnote definitions that were referenced, in reference order. */
7301
0
    if(ctx->parser.flags & MD_FLAG_FOOTNOTES)
7302
0
        MD_CHECK(md_process_footnote_defs(ctx));
7303
7304
0
    MD_LEAVE_BLOCK(MD_BLOCK_DOC, NULL);
7305
7306
0
abort:
7307
7308
#if 0
7309
    /* Output some memory consumption statistics. */
7310
    {
7311
        char buffer[256];
7312
        sprintf(buffer, "Alloced %u bytes for block buffer.",
7313
                    (unsigned)(ctx->alloc_block_bytes));
7314
        MD_LOG(buffer);
7315
7316
        sprintf(buffer, "Alloced %u bytes for containers buffer.",
7317
                    (unsigned)(ctx->alloc_containers * sizeof(MD_CONTAINER)));
7318
        MD_LOG(buffer);
7319
7320
        sprintf(buffer, "Alloced %u bytes for marks buffer.",
7321
                    (unsigned)(ctx->alloc_marks * sizeof(MD_MARK)));
7322
        MD_LOG(buffer);
7323
7324
        sprintf(buffer, "Alloced %u bytes for aux. buffer.",
7325
                    (unsigned)(ctx->alloc_buffer * sizeof(MD_CHAR)));
7326
        MD_LOG(buffer);
7327
    }
7328
#endif
7329
7330
0
    return ret;
7331
0
}
7332
7333
7334
/********************
7335
 ***  Public API  ***
7336
 ********************/
7337
7338
int
7339
md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata)
7340
0
{
7341
0
    MD_CTX ctx;
7342
0
    int i;
7343
0
    int ret;
7344
7345
0
    if(parser->abi_version != 0) {
7346
0
        if(parser->debug_log != NULL)
7347
0
            parser->debug_log("Unsupported abi_version.", userdata);
7348
0
        return -1;
7349
0
    }
7350
7351
    /* Setup context structure. */
7352
0
    memset(&ctx, 0, sizeof(MD_CTX));
7353
0
    ctx.text = text;
7354
0
    ctx.size = size;
7355
0
    memcpy(&ctx.parser, parser, sizeof(MD_PARSER));
7356
0
    ctx.userdata = userdata;
7357
0
    ctx.code_indent_offset = (ctx.parser.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4;
7358
0
    md_build_mark_char_map(&ctx);
7359
0
    ctx.doc_ends_with_newline = (size > 0  &&  ISNEWLINE_(text[size-1]));
7360
0
    ctx.ref_def_hashtable.def_size = sizeof(MD_REF_DEF);
7361
0
    ctx.max_ref_def_output = 16 * MIN(size, (MD_SIZE)(1024 * 1024 / 16));
7362
0
    ctx.footnote_hashtable.def_size = sizeof(MD_FOOTNOTE_DEF);
7363
7364
    /* Reset all mark stacks and lists. */
7365
0
    for(i = 0; i < (int) SIZEOF_ARRAY(ctx.opener_stacks); i++)
7366
0
        ctx.opener_stacks[i].top = -1;
7367
0
    ctx.ptr_stack.top = -1;
7368
0
    ctx.unresolved_link_head = -1;
7369
0
    ctx.unresolved_link_tail = -1;
7370
0
    ctx.table_cell_boundaries_head = -1;
7371
0
    ctx.table_cell_boundaries_tail = -1;
7372
7373
    /* All the work. */
7374
0
    ret = md_process_doc(&ctx);
7375
7376
    /* Clean-up. */
7377
0
    md_free_ref_defs(&ctx);
7378
0
    md_free_footnote_defs(&ctx);
7379
0
    free(ctx.buffer);
7380
0
    free(ctx.marks);
7381
0
    free(ctx.block_bytes);
7382
0
    free(ctx.containers);
7383
7384
0
    return ret;
7385
0
}