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