File Coverage

hoedown/src/document.c
Criterion Covered Total %
statement 453 1158 39.1
branch 427 1766 24.1
condition n/a
subroutine n/a
pod n/a
total 880 2924 30.1


line stmt bran cond sub pod time code
1             #include "document.h"
2              
3             #include
4             #include
5             #include
6             #include
7              
8             #include "stack.h"
9              
10             #ifndef _MSC_VER
11             #include
12             #else
13             #define strncasecmp _strnicmp
14             #endif
15              
16             #define REF_TABLE_SIZE 8
17              
18             #define BUFFER_BLOCK 0
19             #define BUFFER_SPAN 1
20              
21             #define HOEDOWN_LI_END 8 /* internal list flag */
22              
23             const char *hoedown_find_block_tag(const char *str, unsigned int len);
24              
25             /***************
26             * LOCAL TYPES *
27             ***************/
28              
29             /* link_ref: reference to a link */
30             struct link_ref {
31             unsigned int id;
32              
33             hoedown_buffer *link;
34             hoedown_buffer *title;
35              
36             struct link_ref *next;
37             };
38              
39             /* footnote_ref: reference to a footnote */
40             struct footnote_ref {
41             unsigned int id;
42              
43             int is_used;
44             unsigned int num;
45              
46             hoedown_buffer *contents;
47             };
48              
49             /* footnote_item: an item in a footnote_list */
50             struct footnote_item {
51             struct footnote_ref *ref;
52             struct footnote_item *next;
53             };
54              
55             /* footnote_list: linked list of footnote_item */
56             struct footnote_list {
57             unsigned int count;
58             struct footnote_item *head;
59             struct footnote_item *tail;
60             };
61              
62             /* char_trigger: function pointer to render active chars */
63             /* returns the number of chars taken care of */
64             /* data is the pointer of the beginning of the span */
65             /* offset is the number of valid chars before data */
66             typedef size_t
67             (*char_trigger)(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
68              
69             static size_t char_emphasis(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
70             static size_t char_quote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
71             static size_t char_linebreak(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
72             static size_t char_codespan(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
73             static size_t char_escape(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
74             static size_t char_entity(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
75             static size_t char_langle_tag(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
76             static size_t char_autolink_url(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
77             static size_t char_autolink_email(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
78             static size_t char_autolink_www(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
79             static size_t char_link(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
80             static size_t char_image(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
81             static size_t char_superscript(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
82             static size_t char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
83              
84             enum markdown_char_t {
85             MD_CHAR_NONE = 0,
86             MD_CHAR_EMPHASIS,
87             MD_CHAR_CODESPAN,
88             MD_CHAR_LINEBREAK,
89             MD_CHAR_LINK,
90             MD_CHAR_IMAGE,
91             MD_CHAR_LANGLE,
92             MD_CHAR_ESCAPE,
93             MD_CHAR_ENTITY,
94             MD_CHAR_AUTOLINK_URL,
95             MD_CHAR_AUTOLINK_EMAIL,
96             MD_CHAR_AUTOLINK_WWW,
97             MD_CHAR_SUPERSCRIPT,
98             MD_CHAR_QUOTE,
99             MD_CHAR_MATH
100             };
101              
102             static char_trigger markdown_char_ptrs[] = {
103             NULL,
104             &char_emphasis,
105             &char_codespan,
106             &char_linebreak,
107             &char_link,
108             &char_image,
109             &char_langle_tag,
110             &char_escape,
111             &char_entity,
112             &char_autolink_url,
113             &char_autolink_email,
114             &char_autolink_www,
115             &char_superscript,
116             &char_quote,
117             &char_math
118             };
119              
120             struct hoedown_document {
121             hoedown_renderer md;
122             hoedown_renderer_data data;
123              
124             struct link_ref *refs[REF_TABLE_SIZE];
125             struct footnote_list footnotes_found;
126             struct footnote_list footnotes_used;
127             uint8_t active_char[256];
128             hoedown_stack work_bufs[2];
129             hoedown_extensions ext_flags;
130             size_t max_nesting;
131             int in_link_body;
132             };
133              
134             /***************************
135             * HELPER FUNCTIONS *
136             ***************************/
137              
138             static hoedown_buffer *
139 51           newbuf(hoedown_document *doc, int type)
140             {
141             static const size_t buf_size[2] = {256, 64};
142             hoedown_buffer *work = NULL;
143 51           hoedown_stack *pool = &doc->work_bufs[type];
144              
145 51 50         if (pool->size < pool->asize &&
    100          
146 51           pool->item[pool->size] != NULL) {
147 39           work = pool->item[pool->size++];
148 39           work->size = 0;
149             } else {
150 12           work = hoedown_buffer_new(buf_size[type]);
151 12           hoedown_stack_push(pool, work);
152             }
153              
154 51           return work;
155             }
156              
157             static void
158             popbuf(hoedown_document *doc, int type)
159             {
160 28           doc->work_bufs[type].size--;
161             }
162              
163             static void
164 12           unscape_text(hoedown_buffer *ob, hoedown_buffer *src)
165             {
166             size_t i = 0, org;
167 6 50         while (i < src->size) {
168             org = i;
169 121 100         while (i < src->size && src->data[i] != '\\')
    50          
170 115           i++;
171              
172 6 50         if (i > org)
173 6           hoedown_buffer_put(ob, src->data + org, i - org);
174              
175 6 50         if (i + 1 >= src->size)
176             break;
177              
178 0           hoedown_buffer_putc(ob, src->data[i + 1]);
179 0           i += 2;
180             }
181 6           }
182              
183             static unsigned int
184 6           hash_link_ref(const uint8_t *link_ref, size_t length)
185             {
186             size_t i;
187             unsigned int hash = 0;
188              
189 12 100         for (i = 0; i < length; ++i)
190 6           hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash;
191              
192 6           return hash;
193             }
194              
195             static struct link_ref *
196 3           add_link_ref(
197             struct link_ref **references,
198             const uint8_t *name, size_t name_size)
199             {
200 3           struct link_ref *ref = hoedown_calloc(1, sizeof(struct link_ref));
201              
202 3           ref->id = hash_link_ref(name, name_size);
203 3           ref->next = references[ref->id % REF_TABLE_SIZE];
204              
205 3           references[ref->id % REF_TABLE_SIZE] = ref;
206 3           return ref;
207             }
208              
209             static struct link_ref *
210 3           find_link_ref(struct link_ref **references, uint8_t *name, size_t length)
211             {
212 3           unsigned int hash = hash_link_ref(name, length);
213             struct link_ref *ref = NULL;
214              
215 3           ref = references[hash % REF_TABLE_SIZE];
216              
217 3 50         while (ref != NULL) {
218 3 50         if (ref->id == hash)
219             return ref;
220              
221 0           ref = ref->next;
222             }
223              
224             return NULL;
225             }
226              
227             static void
228 7           free_link_refs(struct link_ref **references)
229             {
230             size_t i;
231              
232 63 100         for (i = 0; i < REF_TABLE_SIZE; ++i) {
233 56           struct link_ref *r = references[i];
234             struct link_ref *next;
235              
236 59 100         while (r) {
237 3           next = r->next;
238 3           hoedown_buffer_free(r->link);
239 3           hoedown_buffer_free(r->title);
240 3           free(r);
241             r = next;
242             }
243             }
244 7           }
245              
246             static struct footnote_ref *
247 0           create_footnote_ref(struct footnote_list *list, const uint8_t *name, size_t name_size)
248             {
249 0           struct footnote_ref *ref = hoedown_calloc(1, sizeof(struct footnote_ref));
250              
251 0           ref->id = hash_link_ref(name, name_size);
252              
253 0           return ref;
254             }
255              
256             static int
257 0           add_footnote_ref(struct footnote_list *list, struct footnote_ref *ref)
258             {
259 0           struct footnote_item *item = hoedown_calloc(1, sizeof(struct footnote_item));
260 0 0         if (!item)
261             return 0;
262 0           item->ref = ref;
263              
264 0 0         if (list->head == NULL) {
265 0           list->head = list->tail = item;
266             } else {
267 0           list->tail->next = item;
268 0           list->tail = item;
269             }
270 0           list->count++;
271              
272 0           return 1;
273             }
274              
275             static struct footnote_ref *
276             find_footnote_ref(struct footnote_list *list, uint8_t *name, size_t length)
277             {
278 0           unsigned int hash = hash_link_ref(name, length);
279             struct footnote_item *item = NULL;
280              
281             item = list->head;
282              
283 0 0         while (item != NULL) {
284 0 0         if (item->ref->id == hash)
285             return item->ref;
286 0           item = item->next;
287             }
288              
289             return NULL;
290             }
291              
292             static void
293             free_footnote_ref(struct footnote_ref *ref)
294             {
295 0           hoedown_buffer_free(ref->contents);
296 0           free(ref);
297             }
298              
299             static void
300 2           free_footnote_list(struct footnote_list *list, int free_refs)
301             {
302 1           struct footnote_item *item = list->head;
303             struct footnote_item *next;
304              
305 2 50         while (item) {
    50          
306 0           next = item->next;
307 0 0         if (free_refs)
308 0           free_footnote_ref(item->ref);
309 0           free(item);
310             item = next;
311             }
312 1           }
313              
314              
315             /*
316             * Check whether a char is a Markdown spacing char.
317              
318             * Right now we only consider spaces the actual
319             * space and a newline: tabs and carriage returns
320             * are filtered out during the preprocessing phase.
321             *
322             * If we wanted to actually be UTF-8 compliant, we
323             * should instead extract an Unicode codepoint from
324             * this character and check for space properties.
325             */
326             static int
327             _isspace(int c)
328             {
329 76           return c == ' ' || c == '\n';
330             }
331              
332             /* is_empty_all: verify that all the data is spacing */
333             static int
334             is_empty_all(const uint8_t *data, size_t size)
335             {
336             size_t i = 0;
337 0 0         while (i < size && _isspace(data[i])) i++;
    0          
    0          
    0          
338             return i == size;
339             }
340              
341             /*
342             * Replace all spacing characters in data with spaces. As a special
343             * case, this collapses a newline with the previous space, if possible.
344             */
345             static void
346 0           replace_spacing(hoedown_buffer *ob, const uint8_t *data, size_t size)
347             {
348             size_t i = 0, mark;
349 0           hoedown_buffer_grow(ob, size);
350             while (1) {
351             mark = i;
352 0 0         while (i < size && data[i] != '\n') i++;
    0          
353 0           hoedown_buffer_put(ob, data + mark, i - mark);
354              
355 0 0         if (i >= size) break;
356              
357 0 0         if (!(i > 0 && data[i-1] == ' '))
    0          
358 0           hoedown_buffer_putc(ob, ' ');
359 0           i++;
360 0           }
361 0           }
362              
363             /****************************
364             * INLINE PARSING FUNCTIONS *
365             ****************************/
366              
367             /* is_mail_autolink • looks for the address part of a mail autolink and '>' */
368             /* this is less strict than the original markdown e-mail address matching */
369             static size_t
370 0           is_mail_autolink(uint8_t *data, size_t size)
371             {
372             size_t i = 0, nb = 0;
373              
374             /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */
375 0 0         for (i = 0; i < size; ++i) {
376 0 0         if (isalnum(data[i]))
377 0           continue;
378              
379 0           switch (data[i]) {
380             case '@':
381 0           nb++;
382              
383             case '-':
384             case '.':
385             case '_':
386             break;
387              
388             case '>':
389 0 0         return (nb == 1) ? i + 1 : 0;
390              
391             default:
392             return 0;
393             }
394             }
395              
396             return 0;
397             }
398              
399             /* tag_length • returns the length of the given tag, or 0 is it's not valid */
400             static size_t
401 2           tag_length(uint8_t *data, size_t size, hoedown_autolink_type *autolink)
402             {
403             size_t i, j;
404              
405             /* a valid tag can't be shorter than 3 chars */
406 2 50         if (size < 3) return 0;
407              
408 2 50         if (data[0] != '<') return 0;
409              
410             /* HTML comment, laxist form */
411 2 50         if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
    50          
    0          
    0          
412             i = 5;
413              
414 0 0         while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>'))
    0          
    0          
    0          
415 0           i++;
416              
417 0           i++;
418              
419 0 0         if (i <= size)
420             return i;
421             }
422              
423             /* begins with a '<' optionally followed by '/', followed by letter or number */
424 2 100         i = (data[1] == '/') ? 2 : 1;
425              
426 2 50         if (!isalnum(data[i]))
427             return 0;
428              
429             /* scheme test */
430 2           *autolink = HOEDOWN_AUTOLINK_NONE;
431              
432             /* try to find the beginning of an URI */
433 4 50         while (i < size && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-'))
    100          
    50          
    50          
    50          
434 2           i++;
435              
436 2 50         if (i > 1 && data[i] == '@') {
    50          
437 0 0         if ((j = is_mail_autolink(data + i, size - i)) != 0) {
438 0           *autolink = HOEDOWN_AUTOLINK_EMAIL;
439 0           return i + j;
440             }
441             }
442              
443 2 100         if (i > 2 && data[i] == ':') {
    50          
444 0           *autolink = HOEDOWN_AUTOLINK_NORMAL;
445 0           i++;
446             }
447              
448             /* completing autolink test: no spacing or ' or " */
449 2 50         if (i >= size)
450 0           *autolink = HOEDOWN_AUTOLINK_NONE;
451              
452 2 50         else if (*autolink) {
453             j = i;
454              
455 0 0         while (i < size) {
456 0 0         if (data[i] == '\\') i += 2;
457 0 0         else if (data[i] == '>' || data[i] == '\'' ||
    0          
458 0 0         data[i] == '"' || data[i] == ' ' || data[i] == '\n')
    0          
459             break;
460 0           else i++;
461             }
462              
463 0 0         if (i >= size) return 0;
464 0 0         if (i > j && data[i] == '>') return i + 1;
    0          
465             /* one of the forbidden chars has been found */
466 2           *autolink = HOEDOWN_AUTOLINK_NONE;
467             }
468              
469             /* looking for something looking like a tag end */
470 2 50         while (i < size && data[i] != '>') i++;
    50          
471 2 50         if (i >= size) return 0;
472 2           return i + 1;
473             }
474              
475             /* parse_inline • parses inline markdown elements */
476             static void
477 33           parse_inline(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
478             {
479             size_t i = 0, end = 0, consumed = 0;
480 33           hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL };
481 33           uint8_t *active_char = doc->active_char;
482              
483 33 50         if (doc->work_bufs[BUFFER_SPAN].size +
484 66           doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting)
485 0           return;
486              
487 84 100         while (i < size) {
488             /* copying inactive chars into the output */
489 250 100         while (end < size && active_char[data[end]] == 0)
    100          
490 199           end++;
491              
492 51 50         if (doc->md.normal_text) {
493 51           work.data = data + i;
494 51           work.size = end - i;
495 51           doc->md.normal_text(ob, &work, &doc->data);
496             }
497             else
498 0           hoedown_buffer_put(ob, data + i, end - i);
499              
500 51 100         if (end >= size) break;
501             i = end;
502              
503 20           end = markdown_char_ptrs[ (int)active_char[data[end]] ](ob, doc, data + i, i - consumed, size - i);
504 20 100         if (!end) /* no action from the callback */
505 8           end = i + 1;
506             else {
507 20           i += end;
508             end = i;
509             consumed = i;
510             }
511             }
512             }
513              
514             /* is_escaped • returns whether special char at data[loc] is escaped by '\\' */
515             static int
516             is_escaped(uint8_t *data, size_t loc)
517             {
518             size_t i = loc;
519 9 50         while (i >= 1 && data[i - 1] == '\\')
    50          
    0          
    0          
    50          
    50          
520             i--;
521              
522             /* odd numbers of backslashes escapes data[loc] */
523 9           return (loc - i) % 2;
524             }
525              
526             /* find_emph_char • looks for the next emph uint8_t, skipping other constructs */
527             static size_t
528 7           find_emph_char(uint8_t *data, size_t size, uint8_t c)
529             {
530             size_t i = 0;
531              
532 7 50         while (i < size) {
533 52 50         while (i < size && data[i] != c && data[i] != '[' && data[i] != '`')
    100          
    50          
    50          
534 45           i++;
535              
536 7 50         if (i == size)
537             return 0;
538              
539             /* not counting escaped chars */
540 7 50         if (is_escaped(data, i)) {
541 0           i++; continue;
542             }
543              
544 7 50         if (data[i] == c)
545             return i;
546              
547             /* skipping a codespan */
548 0 0         if (data[i] == '`') {
549             size_t span_nb = 0, bt;
550             size_t tmp_i = 0;
551              
552             /* counting the number of opening backticks */
553 0 0         while (i < size && data[i] == '`') {
    0          
554 0           i++; span_nb++;
555             }
556              
557 0 0         if (i >= size) return 0;
558              
559             /* finding the matching closing sequence */
560             bt = 0;
561 0 0         while (i < size && bt < span_nb) {
562 0 0         if (!tmp_i && data[i] == c) tmp_i = i;
    0          
563 0 0         if (data[i] == '`') bt++;
564             else bt = 0;
565 0           i++;
566             }
567              
568             /* not a well-formed codespan; use found matching emph char */
569 0 0         if (bt < span_nb && i >= size) return tmp_i;
570             }
571             /* skipping a link */
572 0 0         else if (data[i] == '[') {
573             size_t tmp_i = 0;
574             uint8_t cc;
575              
576 0           i++;
577 0 0         while (i < size && data[i] != ']') {
    0          
578 0 0         if (!tmp_i && data[i] == c) tmp_i = i;
    0          
579 0           i++;
580             }
581              
582 0           i++;
583 0 0         while (i < size && _isspace(data[i]))
    0          
584 0           i++;
585              
586 0 0         if (i >= size)
587             return tmp_i;
588              
589 0           switch (data[i]) {
590             case '[':
591             cc = ']'; break;
592              
593             case '(':
594 0           cc = ')'; break;
595              
596             default:
597 0 0         if (tmp_i)
598             return tmp_i;
599             else
600 0           continue;
601             }
602              
603 0           i++;
604 0 0         while (i < size && data[i] != cc) {
    0          
605 0 0         if (!tmp_i && data[i] == c) tmp_i = i;
    0          
606 0           i++;
607             }
608              
609 0 0         if (i >= size)
610             return tmp_i;
611              
612 0           i++;
613             }
614             }
615              
616             return 0;
617             }
618              
619             /* parse_emph1 • parsing single emphase */
620             /* closed by a symbol not preceded by spacing and not followed by symbol */
621             static size_t
622 1           parse_emph1(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c)
623             {
624             size_t i = 0, len;
625             hoedown_buffer *work = 0;
626             int r;
627              
628             /* skipping one symbol if coming from emph3 */
629 1 50         if (size > 1 && data[0] == c && data[1] == c) i = 1;
    50          
    0          
630              
631 1 50         while (i < size) {
632 1           len = find_emph_char(data + i, size - i, c);
633 1 50         if (!len) return 0;
634 1           i += len;
635 1 50         if (i >= size) return 0;
636              
637 1 50         if (data[i] == c && !_isspace(data[i - 1])) {
    50          
638              
639 1 50         if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) {
640 0 0         if (i + 1 < size && isalnum(data[i + 1]))
    0          
641 0           continue;
642             }
643              
644 1           work = newbuf(doc, BUFFER_SPAN);
645 1           parse_inline(work, doc, data, i);
646              
647 1 50         if (doc->ext_flags & HOEDOWN_EXT_UNDERLINE && c == '_')
    50          
648 1           r = doc->md.underline(ob, work, &doc->data);
649             else
650 0           r = doc->md.emphasis(ob, work, &doc->data);
651              
652             popbuf(doc, BUFFER_SPAN);
653 1 50         return r ? i + 1 : 0;
654             }
655             }
656              
657             return 0;
658             }
659              
660             /* parse_emph2 • parsing single emphase */
661             static size_t
662 0           parse_emph2(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c)
663             {
664             size_t i = 0, len;
665             hoedown_buffer *work = 0;
666             int r;
667              
668 0 0         while (i < size) {
669 0           len = find_emph_char(data + i, size - i, c);
670 0 0         if (!len) return 0;
671 0           i += len;
672              
673 0 0         if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
    0          
    0          
    0          
    0          
674 0           work = newbuf(doc, BUFFER_SPAN);
675 0           parse_inline(work, doc, data, i);
676              
677 0 0         if (c == '~')
678 0           r = doc->md.strikethrough(ob, work, &doc->data);
679 0 0         else if (c == '=')
680 0           r = doc->md.highlight(ob, work, &doc->data);
681             else
682 0           r = doc->md.double_emphasis(ob, work, &doc->data);
683              
684             popbuf(doc, BUFFER_SPAN);
685 0 0         return r ? i + 2 : 0;
686             }
687             i++;
688             }
689             return 0;
690             }
691              
692             /* parse_emph3 • parsing single emphase */
693             /* finds the first closing tag, and delegates to the other emph */
694             static size_t
695 0           parse_emph3(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c)
696             {
697             size_t i = 0, len;
698             int r;
699              
700 0 0         while (i < size) {
701 0           len = find_emph_char(data + i, size - i, c);
702 0 0         if (!len) return 0;
703 0           i += len;
704              
705             /* skip spacing preceded symbols */
706 0 0         if (data[i] != c || _isspace(data[i - 1]))
    0          
707 0           continue;
708              
709 0 0         if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && doc->md.triple_emphasis) {
    0          
    0          
    0          
710             /* triple symbol found */
711 0           hoedown_buffer *work = newbuf(doc, BUFFER_SPAN);
712              
713 0           parse_inline(work, doc, data, i);
714 0           r = doc->md.triple_emphasis(ob, work, &doc->data);
715             popbuf(doc, BUFFER_SPAN);
716 0 0         return r ? i + 3 : 0;
717              
718 0 0         } else if (i + 1 < size && data[i + 1] == c) {
    0          
719             /* double symbol found, handing over to emph1 */
720 0           len = parse_emph1(ob, doc, data - 2, size + 2, c);
721 0 0         if (!len) return 0;
722 0           else return len - 2;
723              
724             } else {
725             /* single symbol found, handing over to emph2 */
726 0           len = parse_emph2(ob, doc, data - 1, size + 1, c);
727 0 0         if (!len) return 0;
728 0           else return len - 1;
729             }
730             }
731             return 0;
732             }
733              
734             /* parse_math • parses a math span until the given ending delimiter */
735             static size_t
736 0           parse_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size, const char *end, size_t delimsz, int displaymode)
737             {
738 0           hoedown_buffer text = { NULL, 0, 0, 0, NULL, NULL, NULL };
739             size_t i = delimsz;
740              
741 0 0         if (!doc->md.math)
742             return 0;
743              
744             /* find ending delimiter */
745             while (1) {
746 0 0         while (i < size && data[i] != (uint8_t)end[0])
    0          
747 0           i++;
748              
749 0 0         if (i >= size)
750             return 0;
751              
752 0 0         if (!is_escaped(data, i) && !(i + delimsz > size)
    0          
753 0 0         && memcmp(data + i, end, delimsz) == 0)
754             break;
755              
756 0           i++;
757 0           }
758              
759             /* prepare buffers */
760 0           text.data = data + delimsz;
761 0           text.size = i - delimsz;
762              
763             /* if this is a $$ and MATH_EXPLICIT is not active,
764             * guess whether displaymode should be enabled from the context */
765             i += delimsz;
766 0 0         if (delimsz == 2 && !(doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT))
    0          
767 0 0         displaymode = is_empty_all(data - offset, offset) && is_empty_all(data + i, size - i);
    0          
768              
769             /* call callback */
770 0 0         if (doc->md.math(ob, &text, displaymode, &doc->data))
771 0           return i;
772              
773             return 0;
774             }
775              
776             /* char_emphasis • single and double emphasis parsing */
777             static size_t
778 1           char_emphasis(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
779             {
780 1           uint8_t c = data[0];
781             size_t ret;
782              
783 1 50         if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) {
784 0 0         if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>' && data[-1] != '(')
    0          
    0          
    0          
785             return 0;
786             }
787              
788 1 50         if (size > 2 && data[1] != c) {
    50          
789             /* spacing cannot follow an opening emphasis;
790             * strikethrough and highlight only takes two characters '~~' */
791 1 50         if (c == '~' || c == '=' || _isspace(data[1]) || (ret = parse_emph1(ob, doc, data + 1, size - 1, c)) == 0)
    50          
    50          
792             return 0;
793              
794 1           return ret + 1;
795             }
796              
797 0 0         if (size > 3 && data[1] == c && data[2] != c) {
    0          
    0          
798 0 0         if (_isspace(data[2]) || (ret = parse_emph2(ob, doc, data + 2, size - 2, c)) == 0)
    0          
799             return 0;
800              
801 0           return ret + 2;
802             }
803              
804 0 0         if (size > 4 && data[1] == c && data[2] == c && data[3] != c) {
    0          
    0          
    0          
805 0 0         if (c == '~' || c == '=' || _isspace(data[3]) || (ret = parse_emph3(ob, doc, data + 3, size - 3, c)) == 0)
    0          
    0          
806             return 0;
807              
808 0           return ret + 3;
809             }
810              
811             return 0;
812             }
813              
814              
815             /* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */
816             static size_t
817 7           char_linebreak(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
818             {
819 7 100         if (offset < 2 || data[-1] != ' ' || data[-2] != ' ')
    50          
    0          
820             return 0;
821              
822             /* removing the last space from ob and rendering */
823 0 0         while (ob->size && ob->data[ob->size - 1] == ' ')
    0          
824 0           ob->size--;
825              
826 0           return doc->md.linebreak(ob, &doc->data) ? 1 : 0;
827             }
828              
829              
830             /* char_codespan • '`' parsing a code span (assuming codespan != 0) */
831             static size_t
832 1           char_codespan(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
833             {
834 1           hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
835             size_t end, nb = 0, i, f_begin, f_end;
836              
837             /* counting the number of backticks in the delimiter */
838 2 50         while (nb < size && data[nb] == '`')
    100          
839 1           nb++;
840              
841             /* finding the next delimiter */
842             i = 0;
843 8 100         for (end = nb; end < size && i < nb; end++) {
844 7 100         if (data[end] == '`') i++;
845             else i = 0;
846             }
847              
848 1 50         if (i < nb && end >= size)
849             return 0; /* no matching delimiter */
850              
851             /* trimming outside spaces */
852             f_begin = nb;
853 1 50         while (f_begin < end && data[f_begin] == ' ')
    50          
854 0           f_begin++;
855              
856 1           f_end = end - nb;
857 1 50         while (f_end > nb && data[f_end-1] == ' ')
    50          
858             f_end--;
859              
860             /* real code span */
861 1 50         if (f_begin < f_end) {
862 1           work.data = data + f_begin;
863 1           work.size = f_end - f_begin;
864              
865 1 50         if (!doc->md.codespan(ob, &work, &doc->data))
866             end = 0;
867             } else {
868 0 0         if (!doc->md.codespan(ob, 0, &doc->data))
869             end = 0;
870             }
871              
872             return end;
873             }
874              
875             /* char_quote • '"' parsing a quote */
876             static size_t
877 0           char_quote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
878             {
879             size_t end, nq = 0, i, f_begin, f_end;
880              
881             /* counting the number of quotes in the delimiter */
882 0 0         while (nq < size && data[nq] == '"')
    0          
883 0           nq++;
884              
885             /* finding the next delimiter */
886             end = nq;
887             while (1) {
888             i = end;
889 0           end += find_emph_char(data + end, size - end, '"');
890 0 0         if (end == i) return 0; /* no matching delimiter */
891             i = end;
892 0 0         while (end < size && data[end] == '"' && end - i < nq) end++;
    0          
    0          
893 0 0         if (end - i >= nq) break;
894             }
895              
896             /* trimming outside spaces */
897             f_begin = nq;
898 0 0         while (f_begin < end && data[f_begin] == ' ')
    0          
899 0           f_begin++;
900              
901 0           f_end = end - nq;
902 0 0         while (f_end > nq && data[f_end-1] == ' ')
    0          
903             f_end--;
904              
905             /* real quote */
906 0 0         if (f_begin < f_end) {
907 0           hoedown_buffer *work = newbuf(doc, BUFFER_SPAN);
908 0           parse_inline(work, doc, data + f_begin, f_end - f_begin);
909              
910 0 0         if (!doc->md.quote(ob, work, &doc->data))
911             end = 0;
912             popbuf(doc, BUFFER_SPAN);
913             } else {
914 0 0         if (!doc->md.quote(ob, 0, &doc->data))
915             end = 0;
916             }
917              
918             return end;
919             }
920              
921              
922             /* char_escape • '\\' backslash escape */
923             static size_t
924 0           char_escape(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
925             {
926             static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~=\"$";
927 0           hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL };
928             size_t w;
929              
930 0 0         if (size > 1) {
931 0 0         if (data[1] == '\\' && (doc->ext_flags & HOEDOWN_EXT_MATH) &&
    0          
    0          
932 0 0         size > 2 && (data[2] == '(' || data[2] == '[')) {
933 0 0         const char *end = (data[2] == '[') ? "\\\\]" : "\\\\)";
934 0           w = parse_math(ob, doc, data, offset, size, end, 3, data[2] == '[');
935 0 0         if (w) return w;
936             }
937              
938 0 0         if (strchr(escape_chars, data[1]) == NULL)
    0          
    0          
939             return 0;
940              
941 0 0         if (doc->md.normal_text) {
942 0           work.data = data + 1;
943 0           work.size = 1;
944 0           doc->md.normal_text(ob, &work, &doc->data);
945             }
946 0           else hoedown_buffer_putc(ob, data[1]);
947 0 0         } else if (size == 1) {
948 0 0         if (doc->md.normal_text) {
949 0           work.data = data;
950 0           work.size = 1;
951 0           doc->md.normal_text(ob, &work, &doc->data);
952             }
953 0           else hoedown_buffer_putc(ob, data[0]);
954             }
955              
956             return 2;
957             }
958              
959             /* char_entity • '&' escaped when it doesn't belong to an entity */
960             /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
961             static size_t
962 1           char_entity(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
963             {
964             size_t end = 1;
965 1           hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL };
966              
967 1 50         if (end < size && data[end] == '#')
    50          
968             end++;
969              
970 1 50         while (end < size && isalnum(data[end]))
    50          
971 0           end++;
972              
973 1 50         if (end < size && data[end] == ';')
    50          
974 0           end++; /* real entity */
975             else
976             return 0; /* lone '&' */
977              
978 0 0         if (doc->md.entity) {
979 0           work.data = data;
980 0           work.size = end;
981 0           doc->md.entity(ob, &work, &doc->data);
982             }
983 0           else hoedown_buffer_put(ob, data, end);
984              
985             return end;
986             }
987              
988             /* char_langle_tag • '<' when tags or autolinks are allowed */
989             static size_t
990 2           char_langle_tag(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
991             {
992 2           hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
993 2           hoedown_autolink_type altype = HOEDOWN_AUTOLINK_NONE;
994 2           size_t end = tag_length(data, size, &altype);
995             int ret = 0;
996              
997 2           work.data = data;
998 2           work.size = end;
999              
1000 2 50         if (end > 2) {
1001 2 50         if (doc->md.autolink && altype != HOEDOWN_AUTOLINK_NONE) {
    50          
1002 0           hoedown_buffer *u_link = newbuf(doc, BUFFER_SPAN);
1003 0           work.data = data + 1;
1004 0           work.size = end - 2;
1005 0           unscape_text(u_link, &work);
1006 0           ret = doc->md.autolink(ob, u_link, altype, &doc->data);
1007             popbuf(doc, BUFFER_SPAN);
1008             }
1009 2 50         else if (doc->md.raw_html)
1010 2           ret = doc->md.raw_html(ob, &work, &doc->data);
1011             }
1012              
1013 2 50         if (!ret) return 0;
1014 2           else return end;
1015             }
1016              
1017             static size_t
1018 0           char_autolink_www(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
1019             {
1020             hoedown_buffer *link, *link_url, *link_text;
1021             size_t link_len, rewind;
1022              
1023 0 0         if (!doc->md.link || doc->in_link_body)
    0          
1024             return 0;
1025              
1026 0           link = newbuf(doc, BUFFER_SPAN);
1027              
1028 0 0         if ((link_len = hoedown_autolink__www(&rewind, link, data, offset, size, HOEDOWN_AUTOLINK_SHORT_DOMAINS)) > 0) {
1029 0           link_url = newbuf(doc, BUFFER_SPAN);
1030 0           HOEDOWN_BUFPUTSL(link_url, "http://");
1031 0           hoedown_buffer_put(link_url, link->data, link->size);
1032              
1033 0 0         if (ob->size > rewind)
1034 0           ob->size -= rewind;
1035             else
1036 0           ob->size = 0;
1037              
1038 0 0         if (doc->md.normal_text) {
1039 0           link_text = newbuf(doc, BUFFER_SPAN);
1040 0           doc->md.normal_text(link_text, link, &doc->data);
1041 0           doc->md.link(ob, link_text, link_url, NULL, &doc->data);
1042             popbuf(doc, BUFFER_SPAN);
1043             } else {
1044 0           doc->md.link(ob, link, link_url, NULL, &doc->data);
1045             }
1046             popbuf(doc, BUFFER_SPAN);
1047             }
1048              
1049             popbuf(doc, BUFFER_SPAN);
1050 0           return link_len;
1051             }
1052              
1053             static size_t
1054 0           char_autolink_email(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
1055             {
1056             hoedown_buffer *link;
1057             size_t link_len, rewind;
1058              
1059 0 0         if (!doc->md.autolink || doc->in_link_body)
    0          
1060             return 0;
1061              
1062 0           link = newbuf(doc, BUFFER_SPAN);
1063              
1064 0 0         if ((link_len = hoedown_autolink__email(&rewind, link, data, offset, size, 0)) > 0) {
1065 0 0         if (ob->size > rewind)
1066 0           ob->size -= rewind;
1067             else
1068 0           ob->size = 0;
1069              
1070 0           doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_EMAIL, &doc->data);
1071             }
1072              
1073             popbuf(doc, BUFFER_SPAN);
1074 0           return link_len;
1075             }
1076              
1077             static size_t
1078 2           char_autolink_url(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
1079             {
1080             hoedown_buffer *link;
1081             size_t link_len, rewind;
1082              
1083 2 50         if (!doc->md.autolink || doc->in_link_body)
    50          
1084             return 0;
1085              
1086 2           link = newbuf(doc, BUFFER_SPAN);
1087              
1088 2 50         if ((link_len = hoedown_autolink__url(&rewind, link, data, offset, size, 0)) > 0) {
1089 2 100         if (ob->size > rewind)
1090 1           ob->size -= rewind;
1091             else
1092 1           ob->size = 0;
1093              
1094 2           doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_NORMAL, &doc->data);
1095             }
1096              
1097             popbuf(doc, BUFFER_SPAN);
1098 2           return link_len;
1099             }
1100              
1101             static size_t
1102 2           char_image(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) {
1103             size_t ret;
1104              
1105 2 50         if (size < 2 || data[1] != '[') return 0;
    50          
1106              
1107 2           ret = char_link(ob, doc, data + 1, offset + 1, size - 1);
1108 2 50         if (!ret) return 0;
1109 2           return ret + 1;
1110             }
1111              
1112             /* char_link • '[': parsing a link, a footnote or an image */
1113             static size_t
1114 6           char_link(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
1115             {
1116 8 50         int is_img = (offset && data[-1] == '!' && !is_escaped(data - offset, offset - 1));
    100          
    50          
1117 6 50         int is_footnote = (doc->ext_flags & HOEDOWN_EXT_FOOTNOTES && data[1] == '^');
    50          
1118             size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
1119             hoedown_buffer *content = NULL;
1120             hoedown_buffer *link = NULL;
1121             hoedown_buffer *title = NULL;
1122             hoedown_buffer *u_link = NULL;
1123 6           size_t org_work_size = doc->work_bufs[BUFFER_SPAN].size;
1124             int ret = 0, in_title = 0, qtype = 0;
1125              
1126             /* checking whether the correct renderer exists */
1127 6 50         if ((is_footnote && !doc->md.footnote_ref) || (is_img && !doc->md.image)
    0          
    100          
    50          
1128 6 100         || (!is_img && !is_footnote && !doc->md.link))
    50          
1129             goto cleanup;
1130              
1131             /* looking for the matching closing bracket */
1132 6           i += find_emph_char(data + i, size - i, ']');
1133             txt_e = i;
1134              
1135 6 50         if (i < size && data[i] == ']') i++;
    50          
1136             else goto cleanup;
1137              
1138             /* footnote link */
1139 6 50         if (is_footnote) {
1140             hoedown_buffer id = { NULL, 0, 0, 0, NULL, NULL, NULL };
1141             struct footnote_ref *fr;
1142              
1143 0 0         if (txt_e < 3)
1144             goto cleanup;
1145              
1146 0           id.data = data + 2;
1147 0           id.size = txt_e - 2;
1148              
1149             fr = find_footnote_ref(&doc->footnotes_found, id.data, id.size);
1150              
1151             /* mark footnote used */
1152 0 0         if (fr && !fr->is_used) {
    0          
1153 0 0         if(!add_footnote_ref(&doc->footnotes_used, fr))
1154             goto cleanup;
1155 0           fr->is_used = 1;
1156 0           fr->num = doc->footnotes_used.count;
1157              
1158             /* render */
1159 0 0         if (doc->md.footnote_ref)
1160 0           ret = doc->md.footnote_ref(ob, fr->num, &doc->data);
1161             }
1162              
1163             goto cleanup;
1164             }
1165              
1166             /* skip any amount of spacing */
1167             /* (this is much more laxist than original markdown syntax) */
1168 9 50         while (i < size && _isspace(data[i]))
    100          
1169 3           i++;
1170              
1171             /* inline style link */
1172 9 50         if (i < size && data[i] == '(') {
    100          
1173             size_t nb_p;
1174              
1175             /* skipping initial spacing */
1176 3           i++;
1177              
1178 3 50         while (i < size && _isspace(data[i]))
    50          
1179 0           i++;
1180              
1181             link_b = i;
1182              
1183             /* looking for link end: ' " ) */
1184             /* Count the number of open parenthesis */
1185             nb_p = 0;
1186              
1187 56 50         while (i < size) {
1188 56 50         if (data[i] == '\\') i += 2;
1189 56 50         else if (data[i] == '(' && i != 0) {
    0          
1190 0           nb_p++; i++;
1191             }
1192 56 100         else if (data[i] == ')') {
1193 1 50         if (nb_p == 0) break;
1194 0           else nb_p--; i++;
1195 55 50         } else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break;
    100          
    50          
1196 53           else i++;
1197             }
1198              
1199 3 50         if (i >= size) goto cleanup;
1200             link_e = i;
1201              
1202             /* looking for title end if present */
1203 3 100         if (data[i] == '\'' || data[i] == '"') {
1204             qtype = data[i];
1205             in_title = 1;
1206 2           i++;
1207             title_b = i;
1208              
1209 23 50         while (i < size) {
1210 23 50         if (data[i] == '\\') i += 2;
1211 23 100         else if (data[i] == qtype) {in_title = 0; i++;}
1212 21 100         else if ((data[i] == ')') && !in_title) break;
    50          
1213 21           else i++;
1214             }
1215              
1216 2 50         if (i >= size) goto cleanup;
1217              
1218             /* skipping spacing after title */
1219 2           title_e = i - 1;
1220 2 50         while (title_e > title_b && _isspace(data[title_e]))
    50          
1221 0           title_e--;
1222              
1223             /* checking for closing quote presence */
1224 3 50         if (data[title_e] != '\'' && data[title_e] != '"') {
1225             title_b = title_e = 0;
1226             link_e = i;
1227             }
1228             }
1229              
1230             /* remove spacing at the end of the link */
1231 5 50         while (link_e > link_b && _isspace(data[link_e - 1]))
    100          
1232             link_e--;
1233              
1234             /* remove optional angle brackets around the link */
1235 3 50         if (data[link_b] == '<' && data[link_e - 1] == '>') {
    0          
1236 0           link_b++;
1237             link_e--;
1238             }
1239              
1240             /* building escaped link and title */
1241 3 50         if (link_e > link_b) {
1242 3           link = newbuf(doc, BUFFER_SPAN);
1243 3           hoedown_buffer_put(link, data + link_b, link_e - link_b);
1244             }
1245              
1246 3 100         if (title_e > title_b) {
1247 2           title = newbuf(doc, BUFFER_SPAN);
1248 2           hoedown_buffer_put(title, data + title_b, title_e - title_b);
1249             }
1250              
1251 3           i++;
1252             }
1253              
1254             /* reference style link */
1255 6 50         else if (i < size && data[i] == '[') {
    50          
1256 3           hoedown_buffer *id = newbuf(doc, BUFFER_SPAN);
1257             struct link_ref *lr;
1258              
1259             /* looking for the id */
1260 3           i++;
1261             link_b = i;
1262 6 50         while (i < size && data[i] != ']') i++;
    100          
1263 3 50         if (i >= size) goto cleanup;
1264             link_e = i;
1265              
1266             /* finding the link_ref */
1267 3 50         if (link_b == link_e)
1268 0           replace_spacing(id, data + 1, txt_e - 1);
1269             else
1270 3           hoedown_buffer_put(id, data + link_b, link_e - link_b);
1271              
1272 3           lr = find_link_ref(doc->refs, id->data, id->size);
1273 3 50         if (!lr)
1274             goto cleanup;
1275              
1276             /* keeping link and title from link_ref */
1277 3           link = lr->link;
1278 3           title = lr->title;
1279 3           i++;
1280             }
1281              
1282             /* shortcut reference style link */
1283             else {
1284 0           hoedown_buffer *id = newbuf(doc, BUFFER_SPAN);
1285             struct link_ref *lr;
1286              
1287             /* crafting the id */
1288 0           replace_spacing(id, data + 1, txt_e - 1);
1289              
1290             /* finding the link_ref */
1291 0           lr = find_link_ref(doc->refs, id->data, id->size);
1292 0 0         if (!lr)
1293             goto cleanup;
1294              
1295             /* keeping link and title from link_ref */
1296 0           link = lr->link;
1297 0           title = lr->title;
1298              
1299             /* rewinding the spacing */
1300             i = txt_e + 1;
1301             }
1302              
1303             /* building content: img alt is kept, only link content is parsed */
1304 6 50         if (txt_e > 1) {
1305 6           content = newbuf(doc, BUFFER_SPAN);
1306 6 100         if (is_img) {
1307 2           hoedown_buffer_put(content, data + 1, txt_e - 1);
1308             } else {
1309             /* disable autolinking when parsing inline the
1310             * content of a link */
1311 4           doc->in_link_body = 1;
1312 4           parse_inline(content, doc, data + 1, txt_e - 1);
1313 4           doc->in_link_body = 0;
1314             }
1315             }
1316              
1317 6 50         if (link) {
1318 6           u_link = newbuf(doc, BUFFER_SPAN);
1319 6           unscape_text(u_link, link);
1320             }
1321              
1322             /* calling the relevant rendering function */
1323 6 100         if (is_img) {
1324 2           ret = doc->md.image(ob, u_link, title, content, &doc->data);
1325             } else {
1326 4           ret = doc->md.link(ob, content, u_link, title, &doc->data);
1327             }
1328              
1329             /* cleanup */
1330             cleanup:
1331 6           doc->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
1332 6 50         return ret ? i : 0;
1333             }
1334              
1335             static size_t
1336 0           char_superscript(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
1337             {
1338             size_t sup_start, sup_len;
1339             hoedown_buffer *sup;
1340              
1341 0 0         if (!doc->md.superscript)
1342             return 0;
1343              
1344 0 0         if (size < 2)
1345             return 0;
1346              
1347 0 0         if (data[1] == '(') {
1348             sup_start = 2;
1349 0           sup_len = find_emph_char(data + 2, size - 2, ')') + 2;
1350              
1351 0 0         if (sup_len == size)
1352             return 0;
1353             } else {
1354             sup_start = sup_len = 1;
1355              
1356 0 0         while (sup_len < size && !_isspace(data[sup_len]))
    0          
1357 0           sup_len++;
1358             }
1359              
1360 0 0         if (sup_len - sup_start == 0)
1361 0 0         return (sup_start == 2) ? 3 : 0;
1362              
1363 0           sup = newbuf(doc, BUFFER_SPAN);
1364 0           parse_inline(sup, doc, data + sup_start, sup_len - sup_start);
1365 0           doc->md.superscript(ob, sup, &doc->data);
1366             popbuf(doc, BUFFER_SPAN);
1367              
1368 0 0         return (sup_start == 2) ? sup_len + 1 : sup_len;
1369             }
1370              
1371             static size_t
1372 0           char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
1373             {
1374             /* double dollar */
1375 0 0         if (size > 1 && data[1] == '$')
    0          
1376 0           return parse_math(ob, doc, data, offset, size, "$$", 2, 1);
1377              
1378             /* single dollar allowed only with MATH_EXPLICIT flag */
1379 0 0         if (doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT)
1380 0           return parse_math(ob, doc, data, offset, size, "$", 1, 0);
1381              
1382             return 0;
1383             }
1384              
1385             /*********************************
1386             * BLOCK-LEVEL PARSING FUNCTIONS *
1387             *********************************/
1388              
1389             /* is_empty • returns the line length when it is empty, 0 otherwise */
1390             static size_t
1391             is_empty(const uint8_t *data, size_t size)
1392             {
1393             size_t i;
1394              
1395 45 0         for (i = 0; i < size && data[i] != '\n'; i++)
    0          
    0          
    0          
    0          
    0          
    50          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    100          
    0          
    0          
    0          
    0          
1396 15 0         if (data[i] != ' ')
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    0          
    0          
1397             return 0;
1398              
1399 30           return i + 1;
1400             }
1401              
1402             /* is_hrule • returns whether a line is a horizontal rule */
1403             static int
1404 15           is_hrule(uint8_t *data, size_t size)
1405             {
1406             size_t i = 0, n = 0;
1407             uint8_t c;
1408              
1409             /* skipping initial spaces */
1410 15 50         if (size < 3) return 0;
1411 15 50         if (data[0] == ' ') { i++;
1412 0 0         if (data[1] == ' ') { i++;
1413 0 0         if (data[2] == ' ') { i++; } } }
1414              
1415             /* looking at the hrule uint8_t */
1416 15 50         if (i + 2 >= size
1417 15 50         || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
    100          
1418             return 0;
1419             c = data[i];
1420              
1421             /* the whole line must be the char or space */
1422 2 50         while (i < size && data[i] != '\n') {
    50          
1423 2 100         if (data[i] == c) n++;
1424 1 50         else if (data[i] != ' ')
1425             return 0;
1426              
1427 1           i++;
1428             }
1429              
1430 0           return n >= 3;
1431             }
1432              
1433             /* check if a line is a code fence; return the
1434             * end of the code fence. if passed, width of
1435             * the fence rule and character will be returned */
1436             static size_t
1437 0           is_codefence(uint8_t *data, size_t size, size_t *width, uint8_t *chr)
1438             {
1439             size_t i = 0, n = 1;
1440             uint8_t c;
1441              
1442             /* skipping initial spaces */
1443 0 0         if (size < 3)
1444             return 0;
1445              
1446 0 0         if (data[0] == ' ') { i++;
1447 0 0         if (data[1] == ' ') { i++;
1448 0 0         if (data[2] == ' ') { i++; } } }
1449              
1450             /* looking at the hrule uint8_t */
1451 0           c = data[i];
1452 0 0         if (i + 2 >= size || !(c=='~' || c=='`'))
    0          
1453             return 0;
1454              
1455             /* the fence must be that same character */
1456 0 0         while (++i < size && data[i] == c)
    0          
1457 0           ++n;
1458              
1459 0 0         if (n < 3)
1460             return 0;
1461              
1462 0 0         if (width) *width = n;
1463 0 0         if (chr) *chr = c;
1464             return i;
1465             }
1466              
1467             /* expects single line, checks if it's a codefence and extracts language */
1468             static size_t
1469 0           parse_codefence(uint8_t *data, size_t size, hoedown_buffer *lang, size_t *width, uint8_t *chr)
1470             {
1471             size_t i, w, lang_start;
1472              
1473 0           i = w = is_codefence(data, size, width, chr);
1474 0 0         if (i == 0)
1475             return 0;
1476              
1477 0 0         while (i < size && _isspace(data[i]))
    0          
1478 0           i++;
1479              
1480             lang_start = i;
1481              
1482 0 0         while (i < size && !_isspace(data[i]))
    0          
1483 0           i++;
1484              
1485 0           lang->data = data + lang_start;
1486 0           lang->size = i - lang_start;
1487              
1488             /* Avoid parsing a codespan as a fence */
1489 0           i = lang_start + 2;
1490 0 0         while (i < size && !(data[i] == *chr && data[i-1] == *chr && data[i-2] == *chr)) i++;
    0          
    0          
    0          
1491 0 0         if (i < size) return 0;
1492              
1493             return w;
1494             }
1495              
1496             /* is_atxheader • returns whether the line is a hash-prefixed header */
1497             static int
1498 132           is_atxheader(hoedown_document *doc, uint8_t *data, size_t size)
1499             {
1500 66 100         if (data[0] != '#')
1501             return 0;
1502              
1503 24 50         if (doc->ext_flags & HOEDOWN_EXT_SPACE_HEADERS) {
1504             size_t level = 0;
1505              
1506 0 0         while (level < size && level < 6 && data[level] == '#')
    0          
1507 0           level++;
1508              
1509 24 0         if (level < size && data[level] != ' ')
    0          
1510             return 0;
1511             }
1512              
1513             return 1;
1514             }
1515              
1516             /* is_headerline • returns whether the line is a setext-style hdr underline */
1517             static int
1518 11           is_headerline(uint8_t *data, size_t size)
1519             {
1520             size_t i = 0;
1521              
1522             /* test of level 1 header */
1523 11 50         if (data[i] == '=') {
1524 0 0         for (i = 1; i < size && data[i] == '='; i++);
    0          
1525 0 0         while (i < size && data[i] == ' ') i++;
    0          
1526 0 0         return (i >= size || data[i] == '\n') ? 1 : 0; }
    0          
1527              
1528             /* test of level 2 header */
1529 11 50         if (data[i] == '-') {
1530 0 0         for (i = 1; i < size && data[i] == '-'; i++);
    0          
1531 0 0         while (i < size && data[i] == ' ') i++;
    0          
1532 0 0         return (i >= size || data[i] == '\n') ? 2 : 0; }
    0          
1533              
1534             return 0;
1535             }
1536              
1537             static int
1538 0           is_next_headerline(uint8_t *data, size_t size)
1539             {
1540             size_t i = 0;
1541              
1542 0 0         while (i < size && data[i] != '\n')
    0          
1543 0           i++;
1544              
1545 0 0         if (++i >= size)
1546             return 0;
1547              
1548 0           return is_headerline(data + i, size - i);
1549             }
1550              
1551             /* prefix_quote • returns blockquote prefix length */
1552             static size_t
1553 15           prefix_quote(uint8_t *data, size_t size)
1554             {
1555             size_t i = 0;
1556 15 50         if (i < size && data[i] == ' ') i++;
    50          
1557 15 50         if (i < size && data[i] == ' ') i++;
    50          
1558 15 50         if (i < size && data[i] == ' ') i++;
    50          
1559              
1560 15 50         if (i < size && data[i] == '>') {
    50          
1561 0 0         if (i + 1 < size && data[i + 1] == ' ')
    0          
1562 0           return i + 2;
1563              
1564             return i + 1;
1565             }
1566              
1567             return 0;
1568             }
1569              
1570             /* prefix_code • returns prefix length for block code*/
1571             static size_t
1572             prefix_code(uint8_t *data, size_t size)
1573             {
1574 4 50         if (size > 3 && data[0] == ' ' && data[1] == ' '
    50          
    0          
    0          
    0          
    0          
1575 4 0         && data[2] == ' ' && data[3] == ' ') return 4;
    0          
    0          
    0          
1576              
1577             return 0;
1578             }
1579              
1580             /* prefix_oli • returns ordered list item prefix */
1581             static size_t
1582 4           prefix_oli(uint8_t *data, size_t size)
1583             {
1584             size_t i = 0;
1585              
1586 4 50         if (i < size && data[i] == ' ') i++;
    50          
1587 4 50         if (i < size && data[i] == ' ') i++;
    50          
1588 4 50         if (i < size && data[i] == ' ') i++;
    50          
1589              
1590 4 50         if (i >= size || data[i] < '0' || data[i] > '9')
    50          
    50          
1591             return 0;
1592              
1593 0 0         while (i < size && data[i] >= '0' && data[i] <= '9')
    0          
    0          
1594 0           i++;
1595              
1596 0 0         if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ')
    0          
    0          
1597             return 0;
1598              
1599 0 0         if (is_next_headerline(data + i, size - i))
1600             return 0;
1601              
1602 0           return i + 2;
1603             }
1604              
1605             /* prefix_uli • returns ordered list item prefix */
1606             static size_t
1607 4           prefix_uli(uint8_t *data, size_t size)
1608             {
1609             size_t i = 0;
1610              
1611 4 50         if (i < size && data[i] == ' ') i++;
    50          
1612 4 50         if (i < size && data[i] == ' ') i++;
    50          
1613 4 50         if (i < size && data[i] == ' ') i++;
    50          
1614              
1615 4 50         if (i + 1 >= size ||
    50          
1616 0 0         (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
1617 0           data[i + 1] != ' ')
1618             return 0;
1619              
1620 0 0         if (is_next_headerline(data + i, size - i))
1621             return 0;
1622              
1623 0           return i + 2;
1624             }
1625              
1626              
1627             /* parse_block • parsing of one block, returning next uint8_t to parse */
1628             static void parse_block(hoedown_buffer *ob, hoedown_document *doc,
1629             uint8_t *data, size_t size);
1630              
1631              
1632             /* parse_blockquote • handles parsing of a blockquote fragment */
1633             static size_t
1634 0           parse_blockquote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
1635             {
1636             size_t beg, end = 0, pre, work_size = 0;
1637             uint8_t *work_data = 0;
1638             hoedown_buffer *out = 0;
1639              
1640 0           out = newbuf(doc, BUFFER_BLOCK);
1641             beg = 0;
1642 0 0         while (beg < size) {
1643 0 0         for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);
    0          
1644              
1645 0           pre = prefix_quote(data + beg, end - beg);
1646              
1647 0 0         if (pre)
1648 0           beg += pre; /* skipping prefix */
1649              
1650             /* empty line followed by non-quote line */
1651 0 0         else if (is_empty(data + beg, end - beg) &&
    0          
1652 0 0         (end >= size || (prefix_quote(data + end, size - end) == 0 &&
    0          
1653             !is_empty(data + end, size - end))))
1654             break;
1655              
1656 0 0         if (beg < end) { /* copy into the in-place working buffer */
1657             /* hoedown_buffer_put(work, data + beg, end - beg); */
1658 0 0         if (!work_data)
1659 0           work_data = data + beg;
1660 0 0         else if (data + beg != work_data + work_size)
1661 0           memmove(work_data + work_size, data + beg, end - beg);
1662 0           work_size += end - beg;
1663             }
1664             beg = end;
1665             }
1666              
1667 0           parse_block(out, doc, work_data, work_size);
1668 0 0         if (doc->md.blockquote)
1669 0           doc->md.blockquote(ob, out, &doc->data);
1670             popbuf(doc, BUFFER_BLOCK);
1671 0           return end;
1672             }
1673              
1674             static size_t
1675             parse_htmlblock(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, int do_render);
1676              
1677             /* parse_blockquote • handles parsing of a regular paragraph */
1678             static size_t
1679 4           parse_paragraph(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
1680             {
1681             hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
1682             size_t i = 0, end = 0;
1683             int level = 0;
1684              
1685             work.data = data;
1686              
1687 15 100         while (i < size) {
1688 297 100         for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;
    100          
1689              
1690 28 100         if (is_empty(data + i, size - i))
1691             break;
1692              
1693 11 50         if ((level = is_headerline(data + i, size - i)) != 0)
1694             break;
1695              
1696 22           if (is_atxheader(doc, data + i, size - i) ||
1697 22 50         is_hrule(data + i, size - i) ||
1698 11           prefix_quote(data + i, size - i)) {
1699             end = i;
1700             break;
1701             }
1702              
1703             i = end;
1704             }
1705              
1706             work.size = i;
1707 8 50         while (work.size && data[work.size - 1] == '\n')
    100          
1708             work.size--;
1709              
1710 4 50         if (!level) {
1711 4           hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK);
1712 4           parse_inline(tmp, doc, work.data, work.size);
1713 4 50         if (doc->md.paragraph)
1714 4           doc->md.paragraph(ob, tmp, &doc->data);
1715             popbuf(doc, BUFFER_BLOCK);
1716             } else {
1717             hoedown_buffer *header_work;
1718              
1719 0 0         if (work.size) {
1720             size_t beg;
1721             i = work.size;
1722 0           work.size -= 1;
1723              
1724 0 0         while (work.size && data[work.size] != '\n')
    0          
1725 0           work.size -= 1;
1726              
1727 0           beg = work.size + 1;
1728 0 0         while (work.size && data[work.size - 1] == '\n')
    0          
1729             work.size -= 1;
1730              
1731 0 0         if (work.size > 0) {
1732 0           hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK);
1733 0           parse_inline(tmp, doc, work.data, work.size);
1734              
1735 0 0         if (doc->md.paragraph)
1736 0           doc->md.paragraph(ob, tmp, &doc->data);
1737              
1738             popbuf(doc, BUFFER_BLOCK);
1739 0           work.data += beg;
1740 0           work.size = i - beg;
1741             }
1742             else work.size = i;
1743             }
1744              
1745 0           header_work = newbuf(doc, BUFFER_SPAN);
1746 0           parse_inline(header_work, doc, work.data, work.size);
1747              
1748 0 0         if (doc->md.header)
1749 0           doc->md.header(ob, header_work, (int)level, &doc->data);
1750              
1751             popbuf(doc, BUFFER_SPAN);
1752             }
1753              
1754 4           return end;
1755             }
1756              
1757             /* parse_fencedcode • handles parsing of a block-level code fragment */
1758             static size_t
1759 0           parse_fencedcode(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
1760             {
1761 0           hoedown_buffer text = { 0, 0, 0, 0, NULL, NULL, NULL };
1762 0           hoedown_buffer lang = { 0, 0, 0, 0, NULL, NULL, NULL };
1763             size_t i = 0, text_start, line_start;
1764             size_t w, w2;
1765             size_t width, width2;
1766             uint8_t chr, chr2;
1767              
1768             /* parse codefence line */
1769 0 0         while (i < size && data[i] != '\n')
    0          
1770 0           i++;
1771              
1772 0           w = parse_codefence(data, i, &lang, &width, &chr);
1773 0 0         if (!w)
1774             return 0;
1775              
1776             /* search for end */
1777 0           i++;
1778             text_start = i;
1779 0 0         while ((line_start = i) < size) {
1780 0 0         while (i < size && data[i] != '\n')
    0          
1781 0           i++;
1782              
1783 0           w2 = is_codefence(data + line_start, i - line_start, &width2, &chr2);
1784 0 0         if (w == w2 && width == width2 && chr == chr2 &&
    0          
    0          
    0          
1785 0           is_empty(data + (line_start+w), i - (line_start+w)))
1786             break;
1787              
1788 0           i++;
1789             }
1790              
1791 0           text.data = data + text_start;
1792 0           text.size = line_start - text_start;
1793              
1794 0 0         if (doc->md.blockcode)
1795 0 0         doc->md.blockcode(ob, text.size ? &text : NULL, lang.size ? &lang : NULL, &doc->data);
    0          
1796              
1797             return i;
1798             }
1799              
1800             static size_t
1801 0           parse_blockcode(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
1802             {
1803             size_t beg, end, pre;
1804             hoedown_buffer *work = 0;
1805              
1806 0           work = newbuf(doc, BUFFER_BLOCK);
1807              
1808             beg = 0;
1809 0 0         while (beg < size) {
1810 0 0         for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {};
    0          
1811 0           pre = prefix_code(data + beg, end - beg);
1812              
1813 0 0         if (pre)
1814 0           beg += pre; /* skipping prefix */
1815 0 0         else if (!is_empty(data + beg, end - beg))
1816             /* non-empty non-prefixed line breaks the pre */
1817             break;
1818              
1819 0 0         if (beg < end) {
1820             /* verbatim copy to the working buffer,
1821             escaping entities */
1822 0 0         if (is_empty(data + beg, end - beg))
1823 0           hoedown_buffer_putc(work, '\n');
1824 0           else hoedown_buffer_put(work, data + beg, end - beg);
1825             }
1826             beg = end;
1827             }
1828              
1829 0 0         while (work->size && work->data[work->size - 1] == '\n')
    0          
1830 0           work->size -= 1;
1831              
1832 0           hoedown_buffer_putc(work, '\n');
1833              
1834 0 0         if (doc->md.blockcode)
1835 0           doc->md.blockcode(ob, work, NULL, &doc->data);
1836              
1837             popbuf(doc, BUFFER_BLOCK);
1838 0           return beg;
1839             }
1840              
1841             /* parse_listitem • parsing of a single list item */
1842             /* assuming initial prefix is already removed */
1843             static size_t
1844 0           parse_listitem(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, hoedown_list_flags *flags)
1845             {
1846             hoedown_buffer *work = 0, *inter = 0;
1847             size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
1848             int in_empty = 0, has_inside_empty = 0, in_fence = 0;
1849              
1850             /* keeping track of the first indentation prefix */
1851 0 0         while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')
    0          
1852 0           orgpre++;
1853              
1854 0           beg = prefix_uli(data, size);
1855 0 0         if (!beg)
1856 0           beg = prefix_oli(data, size);
1857              
1858 0 0         if (!beg)
1859             return 0;
1860              
1861             /* skipping to the beginning of the following line */
1862             end = beg;
1863 0 0         while (end < size && data[end - 1] != '\n')
    0          
1864 0           end++;
1865              
1866             /* getting working buffers */
1867 0           work = newbuf(doc, BUFFER_SPAN);
1868 0           inter = newbuf(doc, BUFFER_SPAN);
1869              
1870             /* putting the first line into the working buffer */
1871 0           hoedown_buffer_put(work, data + beg, end - beg);
1872             beg = end;
1873              
1874             /* process the following lines */
1875 0 0         while (beg < size) {
1876             size_t has_next_uli = 0, has_next_oli = 0;
1877              
1878 0           end++;
1879              
1880 0 0         while (end < size && data[end - 1] != '\n')
    0          
1881 0           end++;
1882              
1883             /* process an empty line */
1884 0 0         if (is_empty(data + beg, end - beg)) {
1885             in_empty = 1;
1886             beg = end;
1887 0           continue;
1888             }
1889              
1890             /* calculating the indentation */
1891             i = 0;
1892 0 0         while (i < 4 && beg + i < end && data[beg + i] == ' ')
    0          
    0          
1893 0           i++;
1894              
1895             pre = i;
1896              
1897 0 0         if (doc->ext_flags & HOEDOWN_EXT_FENCED_CODE) {
1898 0 0         if (is_codefence(data + beg + i, end - beg - i, NULL, NULL))
1899 0           in_fence = !in_fence;
1900             }
1901              
1902             /* Only check for new list items if we are **not** inside
1903             * a fenced code block */
1904 0 0         if (!in_fence) {
1905 0           has_next_uli = prefix_uli(data + beg + i, end - beg - i);
1906 0           has_next_oli = prefix_oli(data + beg + i, end - beg - i);
1907             }
1908              
1909             /* checking for a new item */
1910 0 0         if ((has_next_uli && !is_hrule(data + beg + i, end - beg - i)) || has_next_oli) {
    0          
    0          
1911 0 0         if (in_empty)
1912             has_inside_empty = 1;
1913              
1914             /* the following item must have the same (or less) indentation */
1915 0 0         if (pre <= orgpre) {
1916             /* if the following item has different list type, we end this list */
1917 0 0         if (in_empty && (
    0          
1918 0 0         ((*flags & HOEDOWN_LIST_ORDERED) && has_next_uli) ||
    0          
1919 0 0         (!(*flags & HOEDOWN_LIST_ORDERED) && has_next_oli)))
1920 0           *flags |= HOEDOWN_LI_END;
1921              
1922             break;
1923             }
1924              
1925 0 0         if (!sublist)
1926 0           sublist = work->size;
1927             }
1928             /* joining only indented stuff after empty lines;
1929             * note that now we only require 1 space of indentation
1930             * to continue a list */
1931 0 0         else if (in_empty && pre == 0) {
1932 0           *flags |= HOEDOWN_LI_END;
1933 0           break;
1934             }
1935              
1936 0 0         if (in_empty) {
1937 0           hoedown_buffer_putc(work, '\n');
1938             has_inside_empty = 1;
1939             in_empty = 0;
1940             }
1941              
1942             /* adding the line without prefix into the working buffer */
1943 0           hoedown_buffer_put(work, data + beg + i, end - beg - i);
1944             beg = end;
1945             }
1946              
1947             /* render of li contents */
1948 0 0         if (has_inside_empty)
1949 0           *flags |= HOEDOWN_LI_BLOCK;
1950              
1951 0 0         if (*flags & HOEDOWN_LI_BLOCK) {
1952             /* intermediate render of block li */
1953 0 0         if (sublist && sublist < work->size) {
    0          
1954 0           parse_block(inter, doc, work->data, sublist);
1955 0           parse_block(inter, doc, work->data + sublist, work->size - sublist);
1956             }
1957             else
1958 0           parse_block(inter, doc, work->data, work->size);
1959             } else {
1960             /* intermediate render of inline li */
1961 0 0         if (sublist && sublist < work->size) {
    0          
1962 0           parse_inline(inter, doc, work->data, sublist);
1963 0           parse_block(inter, doc, work->data + sublist, work->size - sublist);
1964             }
1965             else
1966 0           parse_inline(inter, doc, work->data, work->size);
1967             }
1968              
1969             /* render of li itself */
1970 0 0         if (doc->md.listitem)
1971 0           doc->md.listitem(ob, inter, *flags, &doc->data);
1972              
1973             popbuf(doc, BUFFER_SPAN);
1974             popbuf(doc, BUFFER_SPAN);
1975 0           return beg;
1976             }
1977              
1978              
1979             /* parse_list • parsing ordered or unordered list block */
1980             static size_t
1981 0           parse_list(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, hoedown_list_flags flags)
1982             {
1983             hoedown_buffer *work = 0;
1984             size_t i = 0, j;
1985              
1986 0           work = newbuf(doc, BUFFER_BLOCK);
1987              
1988 0 0         while (i < size) {
1989 0           j = parse_listitem(work, doc, data + i, size - i, &flags);
1990 0           i += j;
1991              
1992 0 0         if (!j || (flags & HOEDOWN_LI_END))
    0          
1993             break;
1994             }
1995              
1996 0 0         if (doc->md.list)
1997 0           doc->md.list(ob, work, flags, &doc->data);
1998             popbuf(doc, BUFFER_BLOCK);
1999 0           return i;
2000             }
2001              
2002             /* parse_atxheader • parsing of atx-style headers */
2003             static size_t
2004 24           parse_atxheader(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
2005             {
2006             size_t level = 0;
2007             size_t i, end, skip;
2008              
2009 69 50         while (level < size && level < 6 && data[level] == '#')
    100          
2010 45           level++;
2011              
2012 48 50         for (i = level; i < size && data[i] == ' '; i++);
    100          
2013              
2014 103 50         for (end = i; end < size && data[end] != '\n'; end++);
    100          
2015             skip = end;
2016              
2017 24 50         while (end && data[end - 1] == '#')
    50          
2018             end--;
2019              
2020 24 50         while (end && data[end - 1] == ' ')
    50          
2021             end--;
2022              
2023 24 50         if (end > i) {
2024 24           hoedown_buffer *work = newbuf(doc, BUFFER_SPAN);
2025              
2026 24           parse_inline(work, doc, data + i, end - i);
2027              
2028 24 50         if (doc->md.header)
2029 24           doc->md.header(ob, work, (int)level, &doc->data);
2030              
2031             popbuf(doc, BUFFER_SPAN);
2032             }
2033              
2034 24           return skip;
2035             }
2036              
2037             /* parse_footnote_def • parse a single footnote definition */
2038             static void
2039 0           parse_footnote_def(hoedown_buffer *ob, hoedown_document *doc, unsigned int num, uint8_t *data, size_t size)
2040             {
2041             hoedown_buffer *work = 0;
2042 0           work = newbuf(doc, BUFFER_SPAN);
2043              
2044 0           parse_block(work, doc, data, size);
2045              
2046 0 0         if (doc->md.footnote_def)
2047 0           doc->md.footnote_def(ob, work, num, &doc->data);
2048             popbuf(doc, BUFFER_SPAN);
2049 0           }
2050              
2051             /* parse_footnote_list • render the contents of the footnotes */
2052             static void
2053 2           parse_footnote_list(hoedown_buffer *ob, hoedown_document *doc, struct footnote_list *footnotes)
2054             {
2055             hoedown_buffer *work = 0;
2056             struct footnote_item *item;
2057             struct footnote_ref *ref;
2058              
2059 1 50         if (footnotes->count == 0)
2060             return;
2061              
2062 0           work = newbuf(doc, BUFFER_BLOCK);
2063              
2064 0           item = footnotes->head;
2065 0 0         while (item) {
2066 0           ref = item->ref;
2067 0           parse_footnote_def(work, doc, ref->num, ref->contents->data, ref->contents->size);
2068 0           item = item->next;
2069             }
2070              
2071 0 0         if (doc->md.footnotes)
2072 0           doc->md.footnotes(ob, work, &doc->data);
2073             popbuf(doc, BUFFER_BLOCK);
2074             }
2075              
2076             /* htmlblock_is_end • check for end of HTML block : ( *)\n */
2077             /* returns tag length on match, 0 otherwise */
2078             /* assumes data starts with "<" */
2079             static size_t
2080 0           htmlblock_is_end(
2081             const char *tag,
2082             size_t tag_len,
2083             hoedown_document *doc,
2084             uint8_t *data,
2085             size_t size)
2086             {
2087 0           size_t i = tag_len + 3, w;
2088              
2089             /* try to match the end tag */
2090             /* note: we're not considering tags like "" which are still valid */
2091 0 0         if (i > size ||
    0          
2092 0 0         data[1] != '/' ||
2093 0 0         strncasecmp((char *)data + 2, tag, tag_len) != 0 ||
2094 0           data[tag_len + 2] != '>')
2095             return 0;
2096              
2097             /* rest of the line must be empty */
2098 0 0         if ((w = is_empty(data + i, size - i)) == 0 && i < size)
    0          
2099             return 0;
2100              
2101 0           return i + w;
2102             }
2103              
2104             /* htmlblock_find_end • try to find HTML block ending tag */
2105             /* returns the length on match, 0 otherwise */
2106             static size_t
2107 0           htmlblock_find_end(
2108             const char *tag,
2109             size_t tag_len,
2110             hoedown_document *doc,
2111             uint8_t *data,
2112             size_t size)
2113             {
2114             size_t i = 0, w;
2115              
2116             while (1) {
2117 0 0         while (i < size && data[i] != '<') i++;
    0          
2118 0 0         if (i >= size) return 0;
2119              
2120 0           w = htmlblock_is_end(tag, tag_len, doc, data + i, size - i);
2121 0 0         if (w) return i + w;
2122 0           i++;
2123             }
2124             }
2125              
2126             /* htmlblock_find_end_strict • try to find end of HTML block in strict mode */
2127             /* (it must be an unindented line, and have a blank line afterwads) */
2128             /* returns the length on match, 0 otherwise */
2129             static size_t
2130 0           htmlblock_find_end_strict(
2131             const char *tag,
2132             size_t tag_len,
2133             hoedown_document *doc,
2134             uint8_t *data,
2135             size_t size)
2136             {
2137             size_t i = 0, mark;
2138              
2139             while (1) {
2140             mark = i;
2141 0 0         while (i < size && data[i] != '\n') i++;
    0          
2142 0 0         if (i < size) i++;
2143 0 0         if (i == mark) return 0;
2144              
2145 0 0         if (data[mark] == ' ' && mark > 0) continue;
    0          
2146 0           mark += htmlblock_find_end(tag, tag_len, doc, data + mark, i - mark);
2147 0 0         if (mark == i && (is_empty(data + i, size - i) || i >= size)) break;
    0          
    0          
2148             }
2149              
2150             return i;
2151             }
2152              
2153             /* parse_htmlblock • parsing of inline HTML block */
2154             static size_t
2155 0           parse_htmlblock(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, int do_render)
2156             {
2157 0           hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
2158             size_t i, j = 0, tag_len, tag_end;
2159             const char *curtag = NULL;
2160              
2161 0           work.data = data;
2162              
2163             /* identification of the opening tag */
2164 0 0         if (size < 2 || data[0] != '<')
    0          
2165             return 0;
2166              
2167             i = 1;
2168 0 0         while (i < size && data[i] != '>' && data[i] != ' ')
    0          
    0          
2169 0           i++;
2170              
2171 0 0         if (i < size)
2172 0           curtag = hoedown_find_block_tag((char *)data + 1, (int)i - 1);
2173              
2174             /* handling of special cases */
2175 0 0         if (!curtag) {
2176              
2177             /* HTML comment, laxist form */
2178 0 0         if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
    0          
    0          
    0          
2179             i = 5;
2180              
2181 0 0         while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>'))
    0          
    0          
    0          
2182 0           i++;
2183              
2184 0           i++;
2185              
2186 0 0         if (i < size)
2187 0           j = is_empty(data + i, size - i);
2188              
2189 0 0         if (j) {
2190 0           work.size = i + j;
2191 0 0         if (do_render && doc->md.blockhtml)
    0          
2192 0           doc->md.blockhtml(ob, &work, &doc->data);
2193 0           return work.size;
2194             }
2195             }
2196              
2197             /* HR, which is the only self-closing block tag considered */
2198 0 0         if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {
    0          
    0          
2199             i = 3;
2200 0 0         while (i < size && data[i] != '>')
    0          
2201 0           i++;
2202              
2203 0 0         if (i + 1 < size) {
2204             i++;
2205 0           j = is_empty(data + i, size - i);
2206 0 0         if (j) {
2207 0           work.size = i + j;
2208 0 0         if (do_render && doc->md.blockhtml)
    0          
2209 0           doc->md.blockhtml(ob, &work, &doc->data);
2210 0           return work.size;
2211             }
2212             }
2213             }
2214              
2215             /* no special case recognised */
2216             return 0;
2217             }
2218              
2219             /* looking for a matching closing tag in strict mode */
2220 0           tag_len = strlen(curtag);
2221 0           tag_end = htmlblock_find_end_strict(curtag, tag_len, doc, data, size);
2222              
2223             /* if not found, trying a second pass looking for indented match */
2224             /* but not if tag is "ins" or "del" (following original Markdown.pl) */
2225 0 0         if (!tag_end && strcmp(curtag, "ins") != 0 && strcmp(curtag, "del") != 0)
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
2226 0           tag_end = htmlblock_find_end(curtag, tag_len, doc, data, size);
2227              
2228 0 0         if (!tag_end)
2229             return 0;
2230              
2231             /* the end of the block has been found */
2232 0           work.size = tag_end;
2233 0 0         if (do_render && doc->md.blockhtml)
    0          
2234 0           doc->md.blockhtml(ob, &work, &doc->data);
2235              
2236             return tag_end;
2237             }
2238              
2239             static void
2240 0           parse_table_row(
2241             hoedown_buffer *ob,
2242             hoedown_document *doc,
2243             uint8_t *data,
2244             size_t size,
2245             size_t columns,
2246             hoedown_table_flags *col_data,
2247             hoedown_table_flags header_flag)
2248             {
2249             size_t i = 0, col, len;
2250             hoedown_buffer *row_work = 0;
2251              
2252 0 0         if (!doc->md.table_cell || !doc->md.table_row)
    0          
2253             return;
2254              
2255 0           row_work = newbuf(doc, BUFFER_SPAN);
2256              
2257 0 0         if (i < size && data[i] == '|')
    0          
2258             i++;
2259              
2260 0 0         for (col = 0; col < columns && i < size; ++col) {
2261             size_t cell_start, cell_end;
2262             hoedown_buffer *cell_work;
2263              
2264 0           cell_work = newbuf(doc, BUFFER_SPAN);
2265              
2266 0 0         while (i < size && _isspace(data[i]))
    0          
2267 0           i++;
2268              
2269             cell_start = i;
2270              
2271 0           len = find_emph_char(data + i, size - i, '|');
2272              
2273             /* Two possibilities for len == 0:
2274             1) No more pipe char found in the current line.
2275             2) The next pipe is right after the current one, i.e. empty cell.
2276             For case 1, we skip to the end of line; for case 2 we just continue.
2277             */
2278 0 0         if (len == 0 && i < size && data[i] != '|')
    0          
2279             len = size - i;
2280 0           i += len;
2281              
2282 0           cell_end = i - 1;
2283              
2284 0 0         while (cell_end > cell_start && _isspace(data[cell_end]))
    0          
2285 0           cell_end--;
2286              
2287 0           parse_inline(cell_work, doc, data + cell_start, 1 + cell_end - cell_start);
2288 0           doc->md.table_cell(row_work, cell_work, col_data[col] | header_flag, &doc->data);
2289              
2290             popbuf(doc, BUFFER_SPAN);
2291 0           i++;
2292             }
2293              
2294 0 0         for (; col < columns; ++col) {
2295 0           hoedown_buffer empty_cell = { 0, 0, 0, 0, NULL, NULL, NULL };
2296 0           doc->md.table_cell(row_work, &empty_cell, col_data[col] | header_flag, &doc->data);
2297             }
2298              
2299 0           doc->md.table_row(ob, row_work, &doc->data);
2300              
2301             popbuf(doc, BUFFER_SPAN);
2302             }
2303              
2304             static size_t
2305 0           parse_table_header(
2306             hoedown_buffer *ob,
2307             hoedown_document *doc,
2308             uint8_t *data,
2309             size_t size,
2310             size_t *columns,
2311             hoedown_table_flags **column_data)
2312             {
2313             int pipes;
2314             size_t i = 0, col, header_end, under_end;
2315              
2316             pipes = 0;
2317 0 0         while (i < size && data[i] != '\n')
    0          
2318 0 0         if (data[i++] == '|')
2319 0           pipes++;
2320              
2321 0 0         if (i == size || pipes == 0)
2322             return 0;
2323              
2324             header_end = i;
2325              
2326 0 0         while (header_end > 0 && _isspace(data[header_end - 1]))
    0          
2327             header_end--;
2328              
2329 0 0         if (data[0] == '|')
2330 0           pipes--;
2331              
2332 0 0         if (header_end && data[header_end - 1] == '|')
    0          
2333 0           pipes--;
2334              
2335 0 0         if (pipes < 0)
2336             return 0;
2337              
2338 0           *columns = pipes + 1;
2339 0           *column_data = hoedown_calloc(*columns, sizeof(hoedown_table_flags));
2340              
2341             /* Parse the header underline */
2342 0           i++;
2343 0 0         if (i < size && data[i] == '|')
    0          
2344 0           i++;
2345              
2346             under_end = i;
2347 0 0         while (under_end < size && data[under_end] != '\n')
    0          
2348 0           under_end++;
2349              
2350 0 0         for (col = 0; col < *columns && i < under_end; ++col) {
    0          
2351             size_t dashes = 0;
2352              
2353 0 0         while (i < under_end && data[i] == ' ')
    0          
2354 0           i++;
2355              
2356 0 0         if (data[i] == ':') {
2357 0           i++; (*column_data)[col] |= HOEDOWN_TABLE_ALIGN_LEFT;
2358             dashes++;
2359             }
2360              
2361 0 0         while (i < under_end && data[i] == '-') {
    0          
2362 0           i++; dashes++;
2363             }
2364              
2365 0 0         if (i < under_end && data[i] == ':') {
    0          
2366 0           i++; (*column_data)[col] |= HOEDOWN_TABLE_ALIGN_RIGHT;
2367 0           dashes++;
2368             }
2369              
2370 0 0         while (i < under_end && data[i] == ' ')
    0          
2371 0           i++;
2372              
2373 0 0         if (i < under_end && data[i] != '|' && data[i] != '+')
    0          
    0          
2374             break;
2375              
2376 0 0         if (dashes < 3)
2377             break;
2378              
2379 0           i++;
2380             }
2381              
2382 0 0         if (col < *columns)
2383             return 0;
2384              
2385 0           parse_table_row(
2386             ob, doc, data,
2387             header_end,
2388             *columns,
2389             *column_data,
2390             HOEDOWN_TABLE_HEADER
2391             );
2392              
2393 0           return under_end + 1;
2394             }
2395              
2396             static size_t
2397 0           parse_table(
2398             hoedown_buffer *ob,
2399             hoedown_document *doc,
2400             uint8_t *data,
2401             size_t size)
2402             {
2403             size_t i;
2404              
2405             hoedown_buffer *work = 0;
2406             hoedown_buffer *header_work = 0;
2407             hoedown_buffer *body_work = 0;
2408              
2409             size_t columns;
2410 0           hoedown_table_flags *col_data = NULL;
2411              
2412 0           work = newbuf(doc, BUFFER_BLOCK);
2413 0           header_work = newbuf(doc, BUFFER_SPAN);
2414 0           body_work = newbuf(doc, BUFFER_BLOCK);
2415              
2416 0           i = parse_table_header(header_work, doc, data, size, &columns, &col_data);
2417 0 0         if (i > 0) {
2418              
2419 0 0         while (i < size) {
2420             size_t row_start;
2421             int pipes = 0;
2422              
2423             row_start = i;
2424              
2425 0 0         while (i < size && data[i] != '\n')
    0          
2426 0 0         if (data[i++] == '|')
2427 0           pipes++;
2428              
2429 0 0         if (pipes == 0 || i == size) {
2430             i = row_start;
2431             break;
2432             }
2433              
2434 0           parse_table_row(
2435             body_work,
2436             doc,
2437             data + row_start,
2438             i - row_start,
2439             columns,
2440             col_data, 0
2441             );
2442              
2443 0           i++;
2444             }
2445              
2446 0 0         if (doc->md.table_header)
2447 0           doc->md.table_header(work, header_work, &doc->data);
2448              
2449 0 0         if (doc->md.table_body)
2450 0           doc->md.table_body(work, body_work, &doc->data);
2451              
2452 0 0         if (doc->md.table)
2453 0           doc->md.table(ob, work, &doc->data);
2454             }
2455              
2456 0           free(col_data);
2457             popbuf(doc, BUFFER_SPAN);
2458             popbuf(doc, BUFFER_BLOCK);
2459             popbuf(doc, BUFFER_BLOCK);
2460 0           return i;
2461             }
2462              
2463             /* parse_block • parsing of one block, returning next uint8_t to parse */
2464             static void
2465 7           parse_block(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
2466             {
2467             size_t beg, end, i;
2468             uint8_t *txt_data;
2469             beg = 0;
2470              
2471 7 50         if (doc->work_bufs[BUFFER_SPAN].size +
2472 14           doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting)
2473             return;
2474              
2475 62 100         while (beg < size) {
2476 55           txt_data = data + beg;
2477 55           end = size - beg;
2478              
2479 55 100         if (is_atxheader(doc, txt_data, end))
2480 24           beg += parse_atxheader(ob, doc, txt_data, end);
2481              
2482 31 50         else if (data[beg] == '<' && doc->md.blockhtml &&
    0          
    0          
2483             (i = parse_htmlblock(ob, doc, txt_data, end, 1)) != 0)
2484 31           beg += i;
2485              
2486 31 100         else if ((i = is_empty(txt_data, end)) != 0)
2487 27           beg += i;
2488              
2489 4 50         else if (is_hrule(txt_data, end)) {
2490 0 0         if (doc->md.hrule)
2491 0           doc->md.hrule(ob, &doc->data);
2492              
2493 0 0         while (beg < size && data[beg] != '\n')
    0          
2494 0           beg++;
2495              
2496 0           beg++;
2497             }
2498              
2499 4 50         else if ((doc->ext_flags & HOEDOWN_EXT_FENCED_CODE) != 0 &&
    0          
2500             (i = parse_fencedcode(ob, doc, txt_data, end)) != 0)
2501 0           beg += i;
2502              
2503 4 50         else if ((doc->ext_flags & HOEDOWN_EXT_TABLES) != 0 &&
    0          
2504             (i = parse_table(ob, doc, txt_data, end)) != 0)
2505 0           beg += i;
2506              
2507 4 50         else if (prefix_quote(txt_data, end))
2508 0           beg += parse_blockquote(ob, doc, txt_data, end);
2509              
2510 8 50         else if (!(doc->ext_flags & HOEDOWN_EXT_DISABLE_INDENTED_CODE) && prefix_code(txt_data, end))
    50          
2511 0           beg += parse_blockcode(ob, doc, txt_data, end);
2512              
2513 4 50         else if (prefix_uli(txt_data, end))
2514 0           beg += parse_list(ob, doc, txt_data, end, 0);
2515              
2516 4 50         else if (prefix_oli(txt_data, end))
2517 0           beg += parse_list(ob, doc, txt_data, end, HOEDOWN_LIST_ORDERED);
2518              
2519             else
2520 55           beg += parse_paragraph(ob, doc, txt_data, end);
2521             }
2522             }
2523              
2524              
2525              
2526             /*********************
2527             * REFERENCE PARSING *
2528             *********************/
2529              
2530             /* is_footnote • returns whether a line is a footnote definition or not */
2531             static int
2532 17           is_footnote(const uint8_t *data, size_t beg, size_t end, size_t *last, struct footnote_list *list)
2533             {
2534             size_t i = 0;
2535             hoedown_buffer *contents = 0;
2536             size_t ind = 0;
2537             int in_empty = 0;
2538             size_t start = 0;
2539              
2540             size_t id_offset, id_end;
2541              
2542             /* up to 3 optional leading spaces */
2543 17 100         if (beg + 3 >= end) return 0;
2544 16 100         if (data[beg] == ' ') { i = 1;
2545 3 50         if (data[beg + 1] == ' ') { i = 2;
2546 0 0         if (data[beg + 2] == ' ') { i = 3;
2547 0 0         if (data[beg + 3] == ' ') return 0; } } }
2548 16           i += beg;
2549              
2550             /* id part: caret followed by anything between brackets */
2551 16 100         if (data[i] != '[') return 0;
2552 4           i++;
2553 4 50         if (i >= end || data[i] != '^') return 0;
    50          
2554 0           i++;
2555             id_offset = i;
2556 0 0         while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
    0          
    0          
    0          
2557 0           i++;
2558 0 0         if (i >= end || data[i] != ']') return 0;
    0          
2559             id_end = i;
2560              
2561             /* spacer: colon (space | tab)* newline? (space | tab)* */
2562 0           i++;
2563 0 0         if (i >= end || data[i] != ':') return 0;
    0          
2564 0           i++;
2565              
2566             /* getting content buffer */
2567 0           contents = hoedown_buffer_new(64);
2568              
2569             start = i;
2570              
2571             /* process lines similar to a list item */
2572 0 0         while (i < end) {
2573 0 0         while (i < end && data[i] != '\n' && data[i] != '\r') i++;
    0          
    0          
2574              
2575             /* process an empty line */
2576 0 0         if (is_empty(data + start, i - start)) {
2577             in_empty = 1;
2578 0 0         if (i < end && (data[i] == '\n' || data[i] == '\r')) {
    0          
2579 0           i++;
2580 0 0         if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
    0          
    0          
2581             }
2582             start = i;
2583 0           continue;
2584             }
2585              
2586             /* calculating the indentation */
2587             ind = 0;
2588 0 0         while (ind < 4 && start + ind < end && data[start + ind] == ' ')
    0          
    0          
2589 0           ind++;
2590              
2591             /* joining only indented stuff after empty lines;
2592             * note that now we only require 1 space of indentation
2593             * to continue, just like lists */
2594 0 0         if (ind == 0) {
2595 0 0         if (start == id_end + 2 && data[start] == '\t') {}
    0          
2596             else break;
2597             }
2598 0 0         else if (in_empty) {
2599 0           hoedown_buffer_putc(contents, '\n');
2600             }
2601              
2602             in_empty = 0;
2603              
2604             /* adding the line into the content buffer */
2605 0           hoedown_buffer_put(contents, data + start + ind, i - start - ind);
2606             /* add carriage return */
2607 0 0         if (i < end) {
2608 0           hoedown_buffer_putc(contents, '\n');
2609 0 0         if (i < end && (data[i] == '\n' || data[i] == '\r')) {
    0          
2610 0           i++;
2611 0 0         if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
    0          
    0          
2612             }
2613             }
2614             start = i;
2615             }
2616              
2617 0 0         if (last)
2618 0           *last = start;
2619              
2620 0 0         if (list) {
2621             struct footnote_ref *ref;
2622 0           ref = create_footnote_ref(list, data + id_offset, id_end - id_offset);
2623 0 0         if (!ref)
2624             return 0;
2625 0 0         if (!add_footnote_ref(list, ref)) {
2626             free_footnote_ref(ref);
2627 0           return 0;
2628             }
2629 0           ref->contents = contents;
2630             }
2631              
2632             return 1;
2633             }
2634              
2635             /* is_ref • returns whether a line is a reference or not */
2636             static int
2637 41           is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs)
2638             {
2639             /* int n; */
2640             size_t i = 0;
2641             size_t id_offset, id_end;
2642             size_t link_offset, link_end;
2643             size_t title_offset, title_end;
2644             size_t line_end;
2645              
2646             /* up to 3 optional leading spaces */
2647 41 100         if (beg + 3 >= end) return 0;
2648 40 100         if (data[beg] == ' ') { i = 1;
2649 3 50         if (data[beg + 1] == ' ') { i = 2;
2650 0 0         if (data[beg + 2] == ' ') { i = 3;
2651 0 0         if (data[beg + 3] == ' ') return 0; } } }
2652 40           i += beg;
2653              
2654             /* id part: anything but a newline between brackets */
2655 40 100         if (data[i] != '[') return 0;
2656 4           i++;
2657             id_offset = i;
2658 12 50         while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
    50          
    50          
    100          
2659 8           i++;
2660 4 50         if (i >= end || data[i] != ']') return 0;
    50          
2661             id_end = i;
2662              
2663             /* spacer: colon (space | tab)* newline? (space | tab)* */
2664 4           i++;
2665 4 50         if (i >= end || data[i] != ':') return 0;
    100          
2666 3           i++;
2667 6 50         while (i < end && data[i] == ' ') i++;
    100          
2668 3 50         if (i < end && (data[i] == '\n' || data[i] == '\r')) {
    50          
2669 0           i++;
2670 3 0         if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }
    0          
    0          
2671 3 50         while (i < end && data[i] == ' ') i++;
    50          
2672 3 50         if (i >= end) return 0;
2673              
2674             /* link: spacing-free sequence, optionally between angle brackets */
2675 3 50         if (data[i] == '<')
2676 0           i++;
2677              
2678             link_offset = i;
2679              
2680 67 50         while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r')
    100          
    50          
    50          
2681 64           i++;
2682              
2683 3 50         if (data[i - 1] == '>') link_end = i - 1;
2684             else link_end = i;
2685              
2686             /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
2687 17 50         while (i < end && data[i] == ' ') i++;
    100          
2688 3 50         if (i < end && data[i] != '\n' && data[i] != '\r'
    50          
    50          
2689 3 50         && data[i] != '\'' && data[i] != '"' && data[i] != '(')
    50          
    0          
2690             return 0;
2691             line_end = 0;
2692             /* computing end-of-line */
2693 3 50         if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i;
    50          
    50          
2694 3 50         if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
    50          
    0          
2695             line_end = i + 1;
2696              
2697             /* optional (space|tab)* spacer after a newline */
2698 3 50         if (line_end) {
2699 0           i = line_end + 1;
2700 0 0         while (i < end && data[i] == ' ') i++; }
    0          
2701              
2702             /* optional title: any non-newline sequence enclosed in '"()
2703             alone on its line */
2704             title_offset = title_end = 0;
2705 3 50         if (i + 1 < end
2706 3 50         && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {
    0          
2707             i++;
2708             title_offset = i;
2709             /* looking for EOL */
2710 34 50         while (i < end && data[i] != '\n' && data[i] != '\r') i++;
    100          
    50          
2711 3 100         if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
    50          
    50          
2712             title_end = i + 1;
2713             else title_end = i;
2714             /* stepping back */
2715 3           i -= 1;
2716 3 50         while (i > title_offset && data[i] == ' ')
    50          
2717 0           i -= 1;
2718 3 50         if (i > title_offset
2719 3 50         && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
    0          
2720             line_end = title_end;
2721             title_end = i; } }
2722              
2723 3 50         if (!line_end || link_end == link_offset)
2724             return 0; /* garbage after the link empty link */
2725              
2726             /* a valid ref has been found, filling-in return structures */
2727 3 50         if (last)
2728 3           *last = line_end;
2729              
2730 3 50         if (refs) {
2731             struct link_ref *ref;
2732              
2733 3           ref = add_link_ref(refs, data + id_offset, id_end - id_offset);
2734 3 50         if (!ref)
2735             return 0;
2736              
2737 3           ref->link = hoedown_buffer_new(link_end - link_offset);
2738 3           hoedown_buffer_put(ref->link, data + link_offset, link_end - link_offset);
2739              
2740 3 50         if (title_end > title_offset) {
2741 3           ref->title = hoedown_buffer_new(title_end - title_offset);
2742 3           hoedown_buffer_put(ref->title, data + title_offset, title_end - title_offset);
2743             }
2744             }
2745              
2746             return 1;
2747             }
2748              
2749 35           static void expand_tabs(hoedown_buffer *ob, const uint8_t *line, size_t size)
2750             {
2751             /* This code makes two assumptions:
2752             * - Input is valid UTF-8. (Any byte with top two bits 10 is skipped,
2753             * whether or not it is a valid UTF-8 continuation byte.)
2754             * - Input contains no combining characters. (Combining characters
2755             * should be skipped but are not.)
2756             */
2757             size_t i = 0, tab = 0;
2758              
2759 35 50         while (i < size) {
2760             size_t org = i;
2761              
2762 466 100         while (i < size && line[i] != '\t') {
    50          
2763             /* ignore UTF-8 continuation bytes */
2764 431 100         if ((line[i] & 0xc0) != 0x80)
2765 425           tab++;
2766 431           i++;
2767             }
2768              
2769 35 50         if (i > org)
2770 35           hoedown_buffer_put(ob, line + org, i - org);
2771              
2772 35 50         if (i >= size)
2773             break;
2774              
2775             do {
2776 0           hoedown_buffer_putc(ob, ' '); tab++;
2777 0 0         } while (tab % 4);
2778              
2779 0           i++;
2780             }
2781 35           }
2782              
2783             /**********************
2784             * EXPORTED FUNCTIONS *
2785             **********************/
2786              
2787             hoedown_document *
2788 7           hoedown_document_new(
2789             const hoedown_renderer *renderer,
2790             hoedown_extensions extensions,
2791             size_t max_nesting)
2792             {
2793             hoedown_document *doc = NULL;
2794              
2795 7 50         assert(max_nesting > 0 && renderer);
2796              
2797 7           doc = hoedown_malloc(sizeof(hoedown_document));
2798 7           memcpy(&doc->md, renderer, sizeof(hoedown_renderer));
2799              
2800 7           doc->data.opaque = renderer->opaque;
2801              
2802 7           hoedown_stack_init(&doc->work_bufs[BUFFER_BLOCK], 4);
2803 7           hoedown_stack_init(&doc->work_bufs[BUFFER_SPAN], 8);
2804              
2805 7           memset(doc->active_char, 0x0, 256);
2806              
2807 7 100         if (extensions & HOEDOWN_EXT_UNDERLINE && doc->md.underline) {
    50          
2808 1           doc->active_char['_'] = MD_CHAR_EMPHASIS;
2809             }
2810              
2811 7 100         if (doc->md.emphasis || doc->md.double_emphasis || doc->md.triple_emphasis) {
    50          
    50          
2812 6           doc->active_char['*'] = MD_CHAR_EMPHASIS;
2813 6           doc->active_char['_'] = MD_CHAR_EMPHASIS;
2814 6 50         if (extensions & HOEDOWN_EXT_STRIKETHROUGH)
2815 0           doc->active_char['~'] = MD_CHAR_EMPHASIS;
2816 6 50         if (extensions & HOEDOWN_EXT_HIGHLIGHT)
2817 0           doc->active_char['='] = MD_CHAR_EMPHASIS;
2818             }
2819              
2820 7 50         if (doc->md.codespan)
2821 7           doc->active_char['`'] = MD_CHAR_CODESPAN;
2822              
2823 7 100         if (doc->md.linebreak)
2824 6           doc->active_char['\n'] = MD_CHAR_LINEBREAK;
2825              
2826 7 100         if (doc->md.image || doc->md.link || doc->md.footnotes || doc->md.footnote_ref) {
    50          
    0          
    0          
2827 7           doc->active_char['['] = MD_CHAR_LINK;
2828 7           doc->active_char['!'] = MD_CHAR_IMAGE;
2829             }
2830              
2831 7           doc->active_char['<'] = MD_CHAR_LANGLE;
2832 7           doc->active_char['\\'] = MD_CHAR_ESCAPE;
2833 7           doc->active_char['&'] = MD_CHAR_ENTITY;
2834              
2835 7 100         if (extensions & HOEDOWN_EXT_AUTOLINK) {
2836 2           doc->active_char[':'] = MD_CHAR_AUTOLINK_URL;
2837 2           doc->active_char['@'] = MD_CHAR_AUTOLINK_EMAIL;
2838 2           doc->active_char['w'] = MD_CHAR_AUTOLINK_WWW;
2839             }
2840              
2841 7 50         if (extensions & HOEDOWN_EXT_SUPERSCRIPT)
2842 0           doc->active_char['^'] = MD_CHAR_SUPERSCRIPT;
2843              
2844 7 50         if (extensions & HOEDOWN_EXT_QUOTE)
2845 0           doc->active_char['"'] = MD_CHAR_QUOTE;
2846              
2847 7 50         if (extensions & HOEDOWN_EXT_MATH)
2848 0           doc->active_char['$'] = MD_CHAR_MATH;
2849              
2850             /* Extension data */
2851 7           doc->ext_flags = extensions;
2852 7           doc->max_nesting = max_nesting;
2853 7           doc->in_link_body = 0;
2854              
2855 7           return doc;
2856             }
2857              
2858             void
2859 7           hoedown_document_render(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size)
2860             {
2861             static const uint8_t UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
2862              
2863             hoedown_buffer *text;
2864             size_t beg, end;
2865              
2866             int footnotes_enabled;
2867              
2868 7           text = hoedown_buffer_new(64);
2869              
2870             /* Preallocate enough space for our buffer to avoid expanding while copying */
2871 7           hoedown_buffer_grow(text, size);
2872              
2873             /* reset the references table */
2874 7           memset(&doc->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
2875              
2876 7           footnotes_enabled = doc->ext_flags & HOEDOWN_EXT_FOOTNOTES;
2877              
2878             /* reset the footnotes lists */
2879 7 100         if (footnotes_enabled) {
2880 1           memset(&doc->footnotes_found, 0x0, sizeof(doc->footnotes_found));
2881 1           memset(&doc->footnotes_used, 0x0, sizeof(doc->footnotes_used));
2882             }
2883              
2884             /* first pass: looking for references, copying everything else */
2885             beg = 0;
2886              
2887             /* Skip a possible UTF-8 BOM, even though the Unicode standard
2888             * discourages having these in UTF-8 documents */
2889 7 50         if (size >= 3 && memcmp(data, UTF8_BOM, 3) == 0)
    50          
2890             beg += 3;
2891              
2892 48 100         while (beg < size) /* iterating over lines */
2893 41 100         if (footnotes_enabled && is_footnote(data, beg, size, &end, &doc->footnotes_found))
    50          
2894 0           beg = end;
2895 41 100         else if (is_ref(data, beg, size, &end, doc->refs))
2896 3           beg = end;
2897             else { /* skipping to the next line */
2898 38           end = beg;
2899 469 100         while (end < size && data[end] != '\n' && data[end] != '\r')
    100          
    50          
2900 431           end++;
2901              
2902             /* adding the line body if present */
2903 38 100         if (end > beg)
2904 38           expand_tabs(text, data + beg, end - beg);
2905              
2906 79 100         while (end < size && (data[end] == '\n' || data[end] == '\r')) {
    100          
2907             /* add one \n per newline */
2908 38 50         if (data[end] == '\n' || (end + 1 < size && data[end + 1] != '\n'))
    0          
    0          
2909 38           hoedown_buffer_putc(text, '\n');
2910 38           end++;
2911             }
2912              
2913             beg = end;
2914             }
2915              
2916             /* pre-grow the output buffer to minimize allocations */
2917 7           hoedown_buffer_grow(ob, text->size + (text->size >> 1));
2918              
2919             /* second pass: actual rendering */
2920 7 100         if (doc->md.doc_header)
2921 1           doc->md.doc_header(ob, 0, &doc->data);
2922              
2923 7 50         if (text->size) {
2924             /* adding a final newline if not already present */
2925 7 100         if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r')
2926 3           hoedown_buffer_putc(text, '\n');
2927              
2928 7           parse_block(ob, doc, text->data, text->size);
2929             }
2930              
2931             /* footnotes */
2932 7 100         if (footnotes_enabled)
2933 1           parse_footnote_list(ob, doc, &doc->footnotes_used);
2934              
2935 7 100         if (doc->md.doc_footer)
2936 2           doc->md.doc_footer(ob, 0, &doc->data);
2937              
2938             /* clean-up */
2939 7           hoedown_buffer_free(text);
2940 7           free_link_refs(doc->refs);
2941 7 100         if (footnotes_enabled) {
2942 1           free_footnote_list(&doc->footnotes_found, 1);
2943             free_footnote_list(&doc->footnotes_used, 0);
2944             }
2945              
2946 7 50         assert(doc->work_bufs[BUFFER_SPAN].size == 0);
2947 7 50         assert(doc->work_bufs[BUFFER_BLOCK].size == 0);
2948 7           }
2949              
2950             void
2951 0           hoedown_document_render_inline(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size)
2952             {
2953             size_t i = 0, mark;
2954 0           hoedown_buffer *text = hoedown_buffer_new(64);
2955              
2956             /* reset the references table */
2957 0           memset(&doc->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
2958              
2959             /* first pass: expand tabs and process newlines */
2960 0           hoedown_buffer_grow(text, size);
2961             while (1) {
2962             mark = i;
2963 0 0         while (i < size && data[i] != '\n' && data[i] != '\r')
    0          
    0          
2964 0           i++;
2965              
2966 0           expand_tabs(text, data + mark, i - mark);
2967              
2968 0 0         if (i >= size)
2969             break;
2970              
2971 0 0         while (i < size && (data[i] == '\n' || data[i] == '\r')) {
    0          
2972             /* add one \n per newline */
2973 0 0         if (data[i] == '\n' || (i + 1 < size && data[i + 1] != '\n'))
    0          
    0          
2974 0           hoedown_buffer_putc(text, '\n');
2975 0           i++;
2976             }
2977             }
2978              
2979             /* second pass: actual rendering */
2980 0           hoedown_buffer_grow(ob, text->size + (text->size >> 1));
2981              
2982 0 0         if (doc->md.doc_header)
2983 0           doc->md.doc_header(ob, 1, &doc->data);
2984              
2985 0           parse_inline(ob, doc, text->data, text->size);
2986              
2987 0 0         if (doc->md.doc_footer)
2988 0           doc->md.doc_footer(ob, 1, &doc->data);
2989              
2990             /* clean-up */
2991 0           hoedown_buffer_free(text);
2992              
2993 0 0         assert(doc->work_bufs[BUFFER_SPAN].size == 0);
2994 0 0         assert(doc->work_bufs[BUFFER_BLOCK].size == 0);
2995 0           }
2996              
2997             void
2998 0           hoedown_document_free(hoedown_document *doc)
2999             {
3000             size_t i;
3001              
3002 0 0         for (i = 0; i < (size_t)doc->work_bufs[BUFFER_SPAN].asize; ++i)
3003 0           hoedown_buffer_free(doc->work_bufs[BUFFER_SPAN].item[i]);
3004              
3005 0 0         for (i = 0; i < (size_t)doc->work_bufs[BUFFER_BLOCK].asize; ++i)
3006 0           hoedown_buffer_free(doc->work_bufs[BUFFER_BLOCK].item[i]);
3007              
3008 0           hoedown_stack_uninit(&doc->work_bufs[BUFFER_SPAN]);
3009 0           hoedown_stack_uninit(&doc->work_bufs[BUFFER_BLOCK]);
3010              
3011 0           free(doc);
3012 0           }