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