block.go (view raw)
1//
2// Black Friday Markdown Processor
3// Originally based on http://github.com/tanoku/upskirt
4// by Russ Ross <russ@russross.com>
5//
6
7//
8// Functions to parse block-level elements.
9//
10
11package blackfriday
12
13import (
14 "bytes"
15)
16
17// parse block-level data
18func parseBlock(out *bytes.Buffer, rndr *render, data []byte) {
19 if rndr.nesting >= rndr.maxNesting {
20 return
21 }
22 rndr.nesting++
23
24 for len(data) > 0 {
25 if isPrefixHeader(rndr, data) {
26 data = data[blockPrefixHeader(out, rndr, data):]
27 continue
28 }
29 if data[0] == '<' && rndr.mk.blockhtml != nil {
30 if i := blockHtml(out, rndr, data, true); i > 0 {
31 data = data[i:]
32 continue
33 }
34 }
35 if i := isEmpty(data); i > 0 {
36 data = data[i:]
37 continue
38 }
39 if isHrule(data) {
40 if rndr.mk.hrule != nil {
41 rndr.mk.hrule(out, rndr.mk.opaque)
42 }
43 var i int
44 for i = 0; i < len(data) && data[i] != '\n'; i++ {
45 }
46 data = data[i:]
47 continue
48 }
49 if rndr.flags&EXTENSION_FENCED_CODE != 0 {
50 if i := blockFencedCode(out, rndr, data); i > 0 {
51 data = data[i:]
52 continue
53 }
54 }
55 if rndr.flags&EXTENSION_TABLES != 0 {
56 if i := blockTable(out, rndr, data); i > 0 {
57 data = data[i:]
58 continue
59 }
60 }
61 if blockQuotePrefix(data) > 0 {
62 data = data[blockQuote(out, rndr, data):]
63 continue
64 }
65 if blockCodePrefix(data) > 0 {
66 data = data[blockCode(out, rndr, data):]
67 continue
68 }
69 if blockUliPrefix(data) > 0 {
70 data = data[blockList(out, rndr, data, 0):]
71 continue
72 }
73 if blockOliPrefix(data) > 0 {
74 data = data[blockList(out, rndr, data, LIST_TYPE_ORDERED):]
75 continue
76 }
77
78 data = data[blockParagraph(out, rndr, data):]
79 }
80
81 rndr.nesting--
82}
83
84func isPrefixHeader(rndr *render, data []byte) bool {
85 if data[0] != '#' {
86 return false
87 }
88
89 if rndr.flags&EXTENSION_SPACE_HEADERS != 0 {
90 level := 0
91 for level < len(data) && level < 6 && data[level] == '#' {
92 level++
93 }
94 if level < len(data) && data[level] != ' ' && data[level] != '\t' {
95 return false
96 }
97 }
98 return true
99}
100
101func blockPrefixHeader(out *bytes.Buffer, rndr *render, data []byte) int {
102 level := 0
103 for level < len(data) && level < 6 && data[level] == '#' {
104 level++
105 }
106 i, end := 0, 0
107 for i = level; i < len(data) && (data[i] == ' ' || data[i] == '\t'); i++ {
108 }
109 for end = i; end < len(data) && data[end] != '\n'; end++ {
110 }
111 skip := end
112 for end > 0 && data[end-1] == '#' {
113 end--
114 }
115 for end > 0 && (data[end-1] == ' ' || data[end-1] == '\t') {
116 end--
117 }
118 if end > i {
119 work := bytes.NewBuffer(nil)
120 parseInline(work, rndr, data[i:end])
121 if rndr.mk.header != nil {
122 rndr.mk.header(out, work.Bytes(), level, rndr.mk.opaque)
123 }
124 }
125 return skip
126}
127
128func isUnderlinedHeader(data []byte) int {
129 i := 0
130
131 // test of level 1 header
132 if data[i] == '=' {
133 for i = 1; i < len(data) && data[i] == '='; i++ {
134 }
135 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
136 i++
137 }
138 if i >= len(data) || data[i] == '\n' {
139 return 1
140 } else {
141 return 0
142 }
143 }
144
145 // test of level 2 header
146 if data[i] == '-' {
147 for i = 1; i < len(data) && data[i] == '-'; i++ {
148 }
149 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
150 i++
151 }
152 if i >= len(data) || data[i] == '\n' {
153 return 2
154 } else {
155 return 0
156 }
157 }
158
159 return 0
160}
161
162func blockHtml(out *bytes.Buffer, rndr *render, data []byte, do_render bool) int {
163 var i, j int
164
165 // identify the opening tag
166 if len(data) < 2 || data[0] != '<' {
167 return 0
168 }
169 curtag, tagfound := blockHtmlFindTag(data[1:])
170
171 // handle special cases
172 if !tagfound {
173
174 // HTML comment, laxist form
175 if len(data) > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-' {
176 i = 5
177
178 for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
179 i++
180 }
181 i++
182
183 if i < len(data) {
184 j = isEmpty(data[i:])
185 }
186
187 if j > 0 {
188 size := i + j
189 if do_render && rndr.mk.blockhtml != nil {
190 rndr.mk.blockhtml(out, data[:size], rndr.mk.opaque)
191 }
192 return size
193 }
194 }
195
196 // HR, which is the only self-closing block tag considered
197 if len(data) > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R') {
198 i = 3
199 for i < len(data) && data[i] != '>' {
200 i++
201 }
202
203 if i+1 < len(data) {
204 i++
205 j = isEmpty(data[i:])
206 if j > 0 {
207 size := i + j
208 if do_render && rndr.mk.blockhtml != nil {
209 rndr.mk.blockhtml(out, data[:size], rndr.mk.opaque)
210 }
211 return size
212 }
213 }
214 }
215
216 // no special case recognized
217 return 0
218 }
219
220 // look for an unindented matching closing tag
221 // followed by a blank line
222 i = 1
223 found := false
224
225 // if not found, try a second pass looking for indented match
226 // but not if tag is "ins" or "del" (following original Markdown.pl)
227 if curtag != "ins" && curtag != "del" {
228 i = 1
229 for i < len(data) {
230 i++
231 for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
232 i++
233 }
234
235 if i+2+len(curtag) >= len(data) {
236 break
237 }
238
239 j = blockHtmlFindEnd(curtag, rndr, data[i-1:])
240
241 if j > 0 {
242 i += j - 1
243 found = true
244 break
245 }
246 }
247 }
248
249 if !found {
250 return 0
251 }
252
253 // the end of the block has been found
254 if do_render && rndr.mk.blockhtml != nil {
255 rndr.mk.blockhtml(out, data[:i], rndr.mk.opaque)
256 }
257
258 return i
259}
260
261func blockHtmlFindTag(data []byte) (string, bool) {
262 i := 0
263 for i < len(data) && ((data[i] >= '0' && data[i] <= '9') || (data[i] >= 'A' && data[i] <= 'Z') || (data[i] >= 'a' && data[i] <= 'z')) {
264 i++
265 }
266 if i >= len(data) {
267 return "", false
268 }
269 key := string(data[:i])
270 if block_tags[key] {
271 return key, true
272 }
273 return "", false
274}
275
276func blockHtmlFindEnd(tag string, rndr *render, data []byte) int {
277 // assume data[0] == '<' && data[1] == '/' already tested
278
279 // check if tag is a match
280 if len(tag)+3 >= len(data) || bytes.Compare(data[2:2+len(tag)], []byte(tag)) != 0 || data[len(tag)+2] != '>' {
281 return 0
282 }
283
284 // check white lines
285 i := len(tag) + 3
286 w := 0
287 if i < len(data) {
288 if w = isEmpty(data[i:]); w == 0 {
289 return 0 // non-blank after tag
290 }
291 }
292 i += w
293 w = 0
294
295 if rndr.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
296 if i < len(data) {
297 w = isEmpty(data[i:])
298 }
299 } else {
300 if i < len(data) {
301 if w = isEmpty(data[i:]); w == 0 {
302 return 0 // non-blank line after tag line
303 }
304 }
305 }
306
307 return i + w
308}
309
310func isEmpty(data []byte) int {
311 var i int
312 for i = 0; i < len(data) && data[i] != '\n'; i++ {
313 if data[i] != ' ' && data[i] != '\t' {
314 return 0
315 }
316 }
317 return i + 1
318}
319
320func isHrule(data []byte) bool {
321 // skip initial spaces
322 if len(data) < 3 {
323 return false
324 }
325 i := 0
326 if data[0] == ' ' {
327 i++
328 if data[1] == ' ' {
329 i++
330 if data[2] == ' ' {
331 i++
332 }
333 }
334 }
335
336 // look at the hrule char
337 if i+2 >= len(data) || (data[i] != '*' && data[i] != '-' && data[i] != '_') {
338 return false
339 }
340 c := data[i]
341
342 // the whole line must be the char or whitespace
343 n := 0
344 for i < len(data) && data[i] != '\n' {
345 switch {
346 case data[i] == c:
347 n++
348 case data[i] != ' ' && data[i] != '\t':
349 return false
350 }
351 i++
352 }
353
354 return n >= 3
355}
356
357func isFencedCode(data []byte, syntax **string) int {
358 i, n := 0, 0
359
360 // skip initial spaces
361 if len(data) < 3 {
362 return 0
363 }
364 if data[0] == ' ' {
365 i++
366 if data[1] == ' ' {
367 i++
368 if data[2] == ' ' {
369 i++
370 }
371 }
372 }
373
374 // look at the hrule char
375 if i+2 >= len(data) || !(data[i] == '~' || data[i] == '`') {
376 return 0
377 }
378
379 c := data[i]
380
381 // the whole line must be the char or whitespace
382 for i < len(data) && data[i] == c {
383 n++
384 i++
385 }
386
387 if n < 3 {
388 return 0
389 }
390
391 if syntax != nil {
392 syn := 0
393
394 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
395 i++
396 }
397
398 syntax_start := i
399
400 if i < len(data) && data[i] == '{' {
401 i++
402 syntax_start++
403
404 for i < len(data) && data[i] != '}' && data[i] != '\n' {
405 syn++
406 i++
407 }
408
409 if i == len(data) || data[i] != '}' {
410 return 0
411 }
412
413 // string all whitespace at the beginning and the end
414 // of the {} block
415 for syn > 0 && isspace(data[syntax_start]) {
416 syntax_start++
417 syn--
418 }
419
420 for syn > 0 && isspace(data[syntax_start+syn-1]) {
421 syn--
422 }
423
424 i++
425 } else {
426 for i < len(data) && !isspace(data[i]) {
427 syn++
428 i++
429 }
430 }
431
432 language := string(data[syntax_start : syntax_start+syn])
433 *syntax = &language
434 }
435
436 for i < len(data) && data[i] != '\n' {
437 if !isspace(data[i]) {
438 return 0
439 }
440 i++
441 }
442
443 return i + 1
444}
445
446func blockFencedCode(out *bytes.Buffer, rndr *render, data []byte) int {
447 var lang *string
448 beg := isFencedCode(data, &lang)
449 if beg == 0 {
450 return 0
451 }
452
453 work := bytes.NewBuffer(nil)
454
455 for beg < len(data) {
456 fence_end := isFencedCode(data[beg:], nil)
457 if fence_end != 0 {
458 beg += fence_end
459 break
460 }
461
462 var end int
463 for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
464 }
465
466 if beg < end {
467 // verbatim copy to the working buffer, escaping entities
468 if isEmpty(data[beg:]) > 0 {
469 work.WriteByte('\n')
470 } else {
471 work.Write(data[beg:end])
472 }
473 }
474 beg = end
475 }
476
477 if work.Len() > 0 && work.Bytes()[work.Len()-1] != '\n' {
478 work.WriteByte('\n')
479 }
480
481 if rndr.mk.blockcode != nil {
482 syntax := ""
483 if lang != nil {
484 syntax = *lang
485 }
486
487 rndr.mk.blockcode(out, work.Bytes(), syntax, rndr.mk.opaque)
488 }
489
490 return beg
491}
492
493func blockTable(out *bytes.Buffer, rndr *render, data []byte) int {
494 header_work := bytes.NewBuffer(nil)
495 i, columns, col_data := blockTableHeader(header_work, rndr, data)
496 if i > 0 {
497 body_work := bytes.NewBuffer(nil)
498
499 for i < len(data) {
500 pipes, row_start := 0, i
501 for ; i < len(data) && data[i] != '\n'; i++ {
502 if data[i] == '|' {
503 pipes++
504 }
505 }
506
507 if pipes == 0 || i == len(data) {
508 i = row_start
509 break
510 }
511
512 blockTableRow(body_work, rndr, data[row_start:i], columns, col_data)
513 i++
514 }
515
516 if rndr.mk.table != nil {
517 rndr.mk.table(out, header_work.Bytes(), body_work.Bytes(), col_data, rndr.mk.opaque)
518 }
519 }
520
521 return i
522}
523
524func blockTableHeader(out *bytes.Buffer, rndr *render, data []byte) (size int, columns int, column_data []int) {
525 i, pipes := 0, 0
526 column_data = []int{}
527 for i = 0; i < len(data) && data[i] != '\n'; i++ {
528 if data[i] == '|' {
529 pipes++
530 }
531 }
532
533 if i == len(data) || pipes == 0 {
534 return 0, 0, column_data
535 }
536
537 header_end := i
538
539 if data[0] == '|' {
540 pipes--
541 }
542
543 if i > 2 && data[i-1] == '|' {
544 pipes--
545 }
546
547 columns = pipes + 1
548 column_data = make([]int, columns)
549
550 // parse the header underline
551 i++
552 if i < len(data) && data[i] == '|' {
553 i++
554 }
555
556 under_end := i
557 for under_end < len(data) && data[under_end] != '\n' {
558 under_end++
559 }
560
561 col := 0
562 for ; col < columns && i < under_end; col++ {
563 dashes := 0
564
565 for i < under_end && (data[i] == ' ' || data[i] == '\t') {
566 i++
567 }
568
569 if data[i] == ':' {
570 i++
571 column_data[col] |= TABLE_ALIGNMENT_LEFT
572 dashes++
573 }
574
575 for i < under_end && data[i] == '-' {
576 i++
577 dashes++
578 }
579
580 if i < under_end && data[i] == ':' {
581 i++
582 column_data[col] |= TABLE_ALIGNMENT_RIGHT
583 dashes++
584 }
585
586 for i < under_end && (data[i] == ' ' || data[i] == '\t') {
587 i++
588 }
589
590 if i < under_end && data[i] != '|' {
591 break
592 }
593
594 if dashes < 3 {
595 break
596 }
597
598 i++
599 }
600
601 if col < columns {
602 return 0, 0, column_data
603 }
604
605 blockTableRow(out, rndr, data[:header_end], columns, column_data)
606 size = under_end + 1
607 return
608}
609
610func blockTableRow(out *bytes.Buffer, rndr *render, data []byte, columns int, col_data []int) {
611 i, col := 0, 0
612 row_work := bytes.NewBuffer(nil)
613
614 if i < len(data) && data[i] == '|' {
615 i++
616 }
617
618 for col = 0; col < columns && i < len(data); col++ {
619 for i < len(data) && isspace(data[i]) {
620 i++
621 }
622
623 cell_start := i
624
625 for i < len(data) && data[i] != '|' {
626 i++
627 }
628
629 cell_end := i - 1
630
631 for cell_end > cell_start && isspace(data[cell_end]) {
632 cell_end--
633 }
634
635 cell_work := bytes.NewBuffer(nil)
636 parseInline(cell_work, rndr, data[cell_start:cell_end+1])
637
638 if rndr.mk.tableCell != nil {
639 cdata := 0
640 if col < len(col_data) {
641 cdata = col_data[col]
642 }
643 rndr.mk.tableCell(row_work, cell_work.Bytes(), cdata, rndr.mk.opaque)
644 }
645
646 i++
647 }
648
649 for ; col < columns; col++ {
650 empty_cell := []byte{}
651 if rndr.mk.tableCell != nil {
652 cdata := 0
653 if col < len(col_data) {
654 cdata = col_data[col]
655 }
656 rndr.mk.tableCell(row_work, empty_cell, cdata, rndr.mk.opaque)
657 }
658 }
659
660 if rndr.mk.tableRow != nil {
661 rndr.mk.tableRow(out, row_work.Bytes(), rndr.mk.opaque)
662 }
663}
664
665// returns blockquote prefix length
666func blockQuotePrefix(data []byte) int {
667 i := 0
668 for i < len(data) && i < 3 && data[i] == ' ' {
669 i++
670 }
671 if i < len(data) && data[i] == '>' {
672 if i+1 < len(data) && (data[i+1] == ' ' || data[i+1] == '\t') {
673 return i + 2
674 }
675 return i + 1
676 }
677 return 0
678}
679
680// parse a blockquote fragment
681func blockQuote(out *bytes.Buffer, rndr *render, data []byte) int {
682 block := bytes.NewBuffer(nil)
683 work := bytes.NewBuffer(nil)
684 beg, end := 0, 0
685 for beg < len(data) {
686 for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
687 }
688
689 if pre := blockQuotePrefix(data[beg:]); pre > 0 {
690 beg += pre // skip prefix
691 } else {
692 // empty line followed by non-quote line
693 if isEmpty(data[beg:]) > 0 && (end >= len(data) || (blockQuotePrefix(data[end:]) == 0 && isEmpty(data[end:]) == 0)) {
694 break
695 }
696 }
697
698 if beg < end { // copy into the in-place working buffer
699 work.Write(data[beg:end])
700 }
701 beg = end
702 }
703
704 parseBlock(block, rndr, work.Bytes())
705 if rndr.mk.blockquote != nil {
706 rndr.mk.blockquote(out, block.Bytes(), rndr.mk.opaque)
707 }
708 return end
709}
710
711// returns prefix length for block code
712func blockCodePrefix(data []byte) int {
713 if len(data) > 0 && data[0] == '\t' {
714 return 1
715 }
716 if len(data) > 3 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
717 return 4
718 }
719 return 0
720}
721
722func blockCode(out *bytes.Buffer, rndr *render, data []byte) int {
723 work := bytes.NewBuffer(nil)
724
725 beg, end := 0, 0
726 for beg < len(data) {
727 for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
728 }
729
730 if pre := blockCodePrefix(data[beg:end]); pre > 0 {
731 beg += pre
732 } else {
733 if isEmpty(data[beg:end]) == 0 {
734 // non-empty non-prefixed line breaks the pre
735 break
736 }
737 }
738
739 if beg < end {
740 // verbatim copy to the working buffer, escaping entities
741 if isEmpty(data[beg:end]) > 0 {
742 work.WriteByte('\n')
743 } else {
744 work.Write(data[beg:end])
745 }
746 }
747 beg = end
748 }
749
750 // trim all the \n off the end of work
751 workbytes := work.Bytes()
752 n := 0
753 for len(workbytes) > n && workbytes[len(workbytes)-n-1] == '\n' {
754 n++
755 }
756 if n > 0 {
757 work = bytes.NewBuffer(workbytes[:len(workbytes)-n])
758 }
759
760 work.WriteByte('\n')
761
762 if rndr.mk.blockcode != nil {
763 rndr.mk.blockcode(out, work.Bytes(), "", rndr.mk.opaque)
764 }
765
766 return beg
767}
768
769// returns unordered list item prefix
770func blockUliPrefix(data []byte) int {
771 i := 0
772 for i < len(data) && i < 3 && data[i] == ' ' {
773 i++
774 }
775 if i+1 >= len(data) || (data[i] != '*' && data[i] != '+' && data[i] != '-') || (data[i+1] != ' ' && data[i+1] != '\t') {
776 return 0
777 }
778 return i + 2
779}
780
781// returns ordered list item prefix
782func blockOliPrefix(data []byte) int {
783 i := 0
784 for i < len(data) && i < 3 && data[i] == ' ' {
785 i++
786 }
787 if i >= len(data) || data[i] < '0' || data[i] > '9' {
788 return 0
789 }
790 for i < len(data) && data[i] >= '0' && data[i] <= '9' {
791 i++
792 }
793 if i+1 >= len(data) || data[i] != '.' || (data[i+1] != ' ' && data[i+1] != '\t') {
794 return 0
795 }
796 return i + 2
797}
798
799// parse ordered or unordered list block
800func blockList(out *bytes.Buffer, rndr *render, data []byte, flags int) int {
801 work := bytes.NewBuffer(nil)
802
803 i, j := 0, 0
804 for i < len(data) {
805 j = blockListItem(work, rndr, data[i:], &flags)
806 i += j
807
808 if j == 0 || flags&LIST_ITEM_END_OF_LIST != 0 {
809 break
810 }
811 }
812
813 if rndr.mk.list != nil {
814 rndr.mk.list(out, work.Bytes(), flags, rndr.mk.opaque)
815 }
816 return i
817}
818
819// parse a single list item
820// assumes initial prefix is already removed
821func blockListItem(out *bytes.Buffer, rndr *render, data []byte, flags *int) int {
822 // keep track of the first indentation prefix
823 beg, end, pre, sublist, orgpre, i := 0, 0, 0, 0, 0, 0
824
825 for orgpre < 3 && orgpre < len(data) && data[orgpre] == ' ' {
826 orgpre++
827 }
828
829 beg = blockUliPrefix(data)
830 if beg == 0 {
831 beg = blockOliPrefix(data)
832 }
833 if beg == 0 {
834 return 0
835 }
836
837 // skip leading whitespace on first line
838 for beg < len(data) && data[beg] == ' ' {
839 beg++
840 }
841
842 // skip to the beginning of the following line
843 end = beg
844 for end < len(data) && data[end-1] != '\n' {
845 end++
846 }
847
848 // get working buffers
849 work := bytes.NewBuffer(nil)
850 inter := bytes.NewBuffer(nil)
851
852 // put the first line into the working buffer
853 work.Write(data[beg:end])
854 beg = end
855
856 // process the following lines
857 in_empty, has_inside_empty := false, false
858 for beg < len(data) {
859 end++
860
861 for end < len(data) && data[end-1] != '\n' {
862 end++
863 }
864
865 // process an empty line
866 if isEmpty(data[beg:end]) > 0 {
867 in_empty = true
868 beg = end
869 continue
870 }
871
872 // calculate the indentation
873 i = 0
874 for i < 4 && beg+i < end && data[beg+i] == ' ' {
875 i++
876 }
877
878 pre = i
879 if data[beg] == '\t' {
880 i = 1
881 pre = 8
882 }
883
884 // check for a new item
885 chunk := data[beg+i : end]
886 if (blockUliPrefix(chunk) > 0 && !isHrule(chunk)) || blockOliPrefix(chunk) > 0 {
887 if in_empty {
888 has_inside_empty = true
889 }
890
891 if pre == orgpre { // the following item must have the same indentation
892 break
893 }
894
895 if sublist == 0 {
896 sublist = work.Len()
897 }
898 } else {
899 // only join indented stuff after empty lines
900 if in_empty && i < 4 && data[beg] != '\t' {
901 *flags |= LIST_ITEM_END_OF_LIST
902 break
903 } else {
904 if in_empty {
905 work.WriteByte('\n')
906 has_inside_empty = true
907 }
908 }
909 }
910
911 in_empty = false
912
913 // add the line into the working buffer without prefix
914 work.Write(data[beg+i : end])
915 beg = end
916 }
917
918 // render li contents
919 if has_inside_empty {
920 *flags |= LIST_ITEM_CONTAINS_BLOCK
921 }
922
923 workbytes := work.Bytes()
924 if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 {
925 // intermediate render of block li
926 if sublist > 0 && sublist < len(workbytes) {
927 parseBlock(inter, rndr, workbytes[:sublist])
928 parseBlock(inter, rndr, workbytes[sublist:])
929 } else {
930 parseBlock(inter, rndr, workbytes)
931 }
932 } else {
933 // intermediate render of inline li
934 if sublist > 0 && sublist < len(workbytes) {
935 parseInline(inter, rndr, workbytes[:sublist])
936 parseBlock(inter, rndr, workbytes[sublist:])
937 } else {
938 parseInline(inter, rndr, workbytes)
939 }
940 }
941
942 // render li itself
943 if rndr.mk.listitem != nil {
944 rndr.mk.listitem(out, inter.Bytes(), *flags, rndr.mk.opaque)
945 }
946
947 return beg
948}
949
950func blockParagraph(out *bytes.Buffer, rndr *render, data []byte) int {
951 i, end, level := 0, 0, 0
952
953 for i < len(data) {
954 for end = i + 1; end < len(data) && data[end-1] != '\n'; end++ {
955 }
956
957 if isEmpty(data[i:]) > 0 {
958 break
959 }
960 if level = isUnderlinedHeader(data[i:]); level > 0 {
961 break
962 }
963
964 if rndr.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
965 if data[i] == '<' && rndr.mk.blockhtml != nil && blockHtml(out, rndr, data[i:], false) > 0 {
966 end = i
967 break
968 }
969 }
970
971 if isPrefixHeader(rndr, data[i:]) || isHrule(data[i:]) {
972 end = i
973 break
974 }
975
976 i = end
977 }
978
979 work := data
980 size := i
981 for size > 0 && work[size-1] == '\n' {
982 size--
983 }
984
985 if level == 0 {
986 tmp := bytes.NewBuffer(nil)
987 parseInline(tmp, rndr, work[:size])
988 if rndr.mk.paragraph != nil {
989 rndr.mk.paragraph(out, tmp.Bytes(), rndr.mk.opaque)
990 }
991 } else {
992 if size > 0 {
993 beg := 0
994 i = size
995 size--
996
997 for size > 0 && work[size] != '\n' {
998 size--
999 }
1000
1001 beg = size + 1
1002 for size > 0 && work[size-1] == '\n' {
1003 size--
1004 }
1005
1006 if size > 0 {
1007 tmp := bytes.NewBuffer(nil)
1008 parseInline(tmp, rndr, work[:size])
1009 if rndr.mk.paragraph != nil {
1010 rndr.mk.paragraph(out, tmp.Bytes(), rndr.mk.opaque)
1011 }
1012
1013 work = work[beg:]
1014 size = i - beg
1015 } else {
1016 size = i
1017 }
1018 }
1019
1020 header_work := bytes.NewBuffer(nil)
1021 parseInline(header_work, rndr, work[:size])
1022
1023 if rndr.mk.header != nil {
1024 rndr.mk.header(out, header_work.Bytes(), level, rndr.mk.opaque)
1025 }
1026 }
1027
1028 return end
1029}