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