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// Distributed 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 (parser *Parser) parseBlock(out *bytes.Buffer, data []byte) {
22 // this is called recursively: enforce a maximum depth
23 if parser.nesting >= parser.maxNesting {
24 return
25 }
26 parser.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 parser.isPrefixHeader(data) {
37 data = data[parser.blockPrefixHeader(out, data):]
38 continue
39 }
40
41 // block of preformatted HTML:
42 //
43 // <div>
44 // ...
45 // </div>
46 if data[0] == '<' && parser.r.BlockHtml != nil {
47 if i := parser.blockHtml(out, 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 := parser.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 parser.isHRule(data) {
67 if parser.r.HRule != nil {
68 parser.r.HRule(out, parser.r.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 parser.flags&EXTENSION_FENCED_CODE != 0 {
88 if i := parser.blockFencedCode(out, 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 parser.flags&EXTENSION_TABLES != 0 {
101 if i := parser.blockTable(out, 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 parser.blockQuotePrefix(data) > 0 {
112 data = data[parser.blockQuote(out, 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 parser.blockCodePrefix(data) > 0 {
125 data = data[parser.blockCode(out, 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 parser.blockUliPrefix(data) > 0 {
136 data = data[parser.blockList(out, data, 0):]
137 continue
138 }
139
140 // a numbered/ordered list:
141 //
142 // 1. Item 1
143 // 2. Item 2
144 if parser.blockOliPrefix(data) > 0 {
145 data = data[parser.blockList(out, 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[parser.blockParagraph(out, data):]
152 }
153
154 parser.nesting--
155}
156
157func (parser *Parser) isPrefixHeader(data []byte) bool {
158 if data[0] != '#' {
159 return false
160 }
161
162 if parser.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 (parser *Parser) blockPrefixHeader(out *bytes.Buffer, 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 parser.r.Header != nil {
193 work := func() bool {
194 parser.parseInline(out, data[i:end])
195 return true
196 }
197 parser.r.Header(out, work, level, parser.r.Opaque)
198 }
199 }
200 return skip
201}
202
203func (parser *Parser) 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 (parser *Parser) blockHtml(out *bytes.Buffer, data []byte, doRender 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 := parser.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 = parser.isEmpty(data[i:])
260 }
261
262 if j > 0 {
263 size := i + j
264 if doRender && parser.r.BlockHtml != nil {
265 parser.r.BlockHtml(out, data[:size], parser.r.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 = parser.isEmpty(data[i:])
284 if j > 0 {
285 size := i + j
286 if doRender && parser.r.BlockHtml != nil {
287 parser.r.BlockHtml(out, data[:size], parser.r.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 = parser.blockHtmlFindEnd(curtag, 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 doRender && parser.r.BlockHtml != nil {
333 parser.r.BlockHtml(out, data[:i], parser.r.Opaque)
334 }
335
336 return i
337}
338
339func (parser *Parser) 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 blockTags[key] {
349 return key, true
350 }
351 return "", false
352}
353
354func (parser *Parser) blockHtmlFindEnd(tag string, 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 = parser.isEmpty(data[i:]); w == 0 {
368 return 0 // non-blank after tag
369 }
370 }
371 i += w
372 w = 0
373
374 if parser.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
375 if i < len(data) {
376 w = parser.isEmpty(data[i:])
377 }
378 } else {
379 if i < len(data) {
380 if w = parser.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 (parser *Parser) 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 (parser *Parser) 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 (parser *Parser) 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 syntaxStart := i
482
483 if i < len(data) && data[i] == '{' {
484 i++
485 syntaxStart++
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[syntaxStart]) {
499 syntaxStart++
500 syn--
501 }
502
503 for syn > 0 && isspace(data[syntaxStart+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[syntaxStart : syntaxStart+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 (parser *Parser) blockFencedCode(out *bytes.Buffer, data []byte) int {
530 var lang *string
531 beg, marker := parser.isFencedCode(data, &lang, "")
532 if beg == 0 {
533 return 0
534 }
535
536 var work bytes.Buffer
537
538 for beg < len(data) {
539 fenceEnd, _ := parser.isFencedCode(data[beg:], nil, marker)
540 if fenceEnd != 0 {
541 beg += fenceEnd
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 parser.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 parser.r.BlockCode != nil {
570 syntax := ""
571 if lang != nil {
572 syntax = *lang
573 }
574
575 parser.r.BlockCode(out, work.Bytes(), syntax, parser.r.Opaque)
576 }
577
578 return beg
579}
580
581func (parser *Parser) blockTable(out *bytes.Buffer, data []byte) int {
582 var headerWork bytes.Buffer
583 i, columns, colData := parser.blockTableHeader(&headerWork, data)
584 if i == 0 {
585 return 0
586 }
587
588 var bodyWork bytes.Buffer
589
590 for i < len(data) {
591 pipes, rowStart := 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 = rowStart
600 break
601 }
602
603 parser.blockTableRow(&bodyWork, data[rowStart:i], columns, colData)
604 i++
605 }
606
607 if parser.r.Table != nil {
608 parser.r.Table(out, headerWork.Bytes(), bodyWork.Bytes(), colData, parser.r.Opaque)
609 }
610
611 return i
612}
613
614func (parser *Parser) blockTableHeader(out *bytes.Buffer, data []byte) (size int, columns int, columnData []int) {
615 i, pipes := 0, 0
616 columnData = []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, columnData
625 }
626
627 headerEnd := 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 columnData = make([]int, columns)
639
640 // parse the header underline
641 i++
642 if i < len(data) && data[i] == '|' {
643 i++
644 }
645
646 underEnd := i
647 for underEnd < len(data) && data[underEnd] != '\n' {
648 underEnd++
649 }
650
651 col := 0
652 for ; col < columns && i < underEnd; col++ {
653 dashes := 0
654
655 for i < underEnd && (data[i] == ' ' || data[i] == '\t') {
656 i++
657 }
658
659 if data[i] == ':' {
660 i++
661 columnData[col] |= TABLE_ALIGNMENT_LEFT
662 dashes++
663 }
664
665 for i < underEnd && data[i] == '-' {
666 i++
667 dashes++
668 }
669
670 if i < underEnd && data[i] == ':' {
671 i++
672 columnData[col] |= TABLE_ALIGNMENT_RIGHT
673 dashes++
674 }
675
676 for i < underEnd && (data[i] == ' ' || data[i] == '\t') {
677 i++
678 }
679
680 if i < underEnd && 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, columnData
693 }
694
695 parser.blockTableRow(out, data[:headerEnd], columns, columnData)
696 size = underEnd + 1
697 return
698}
699
700func (parser *Parser) blockTableRow(out *bytes.Buffer, data []byte, columns int, colData []int) {
701 i, col := 0, 0
702 var rowWork 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 cellStart := i
714
715 for i < len(data) && data[i] != '|' {
716 i++
717 }
718
719 cellEnd := i - 1
720
721 for cellEnd > cellStart && isspace(data[cellEnd]) {
722 cellEnd--
723 }
724
725 var cellWork bytes.Buffer
726 parser.parseInline(&cellWork, data[cellStart:cellEnd+1])
727
728 if parser.r.TableCell != nil {
729 cdata := 0
730 if col < len(colData) {
731 cdata = colData[col]
732 }
733 parser.r.TableCell(&rowWork, cellWork.Bytes(), cdata, parser.r.Opaque)
734 }
735
736 i++
737 }
738
739 for ; col < columns; col++ {
740 emptyCell := []byte{}
741 if parser.r.TableCell != nil {
742 cdata := 0
743 if col < len(colData) {
744 cdata = colData[col]
745 }
746 parser.r.TableCell(&rowWork, emptyCell, cdata, parser.r.Opaque)
747 }
748 }
749
750 if parser.r.TableRow != nil {
751 parser.r.TableRow(out, rowWork.Bytes(), parser.r.Opaque)
752 }
753}
754
755// returns blockquote prefix length
756func (parser *Parser) 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 (parser *Parser) blockQuote(out *bytes.Buffer, 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 := parser.blockQuotePrefix(data[beg:]); pre > 0 {
780 beg += pre // skip prefix
781 } else {
782 // empty line followed by non-quote line
783 if parser.isEmpty(data[beg:]) > 0 &&
784 (end >= len(data) ||
785 (parser.blockQuotePrefix(data[end:]) == 0 && parser.isEmpty(data[end:]) == 0)) {
786 break
787 }
788 }
789
790 if beg < end { // copy into the in-place working buffer
791 work.Write(data[beg:end])
792 }
793 beg = end
794 }
795
796 parser.parseBlock(&block, work.Bytes())
797 if parser.r.BlockQuote != nil {
798 parser.r.BlockQuote(out, block.Bytes(), parser.r.Opaque)
799 }
800 return end
801}
802
803// returns prefix length for block code
804func (parser *Parser) blockCodePrefix(data []byte) int {
805 if len(data) > 0 && data[0] == '\t' {
806 return 1
807 }
808 if len(data) > 3 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
809 return 4
810 }
811 return 0
812}
813
814func (parser *Parser) blockCode(out *bytes.Buffer, data []byte) int {
815 var work bytes.Buffer
816
817 beg, end := 0, 0
818 for beg < len(data) {
819 for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
820 }
821
822 if pre := parser.blockCodePrefix(data[beg:end]); pre > 0 {
823 beg += pre
824 } else {
825 if parser.isEmpty(data[beg:end]) == 0 {
826 // non-empty non-prefixed line breaks the pre
827 break
828 }
829 }
830
831 if beg < end {
832 // verbatim copy to the working buffer, escaping entities
833 if parser.isEmpty(data[beg:end]) > 0 {
834 work.WriteByte('\n')
835 } else {
836 work.Write(data[beg:end])
837 }
838 }
839 beg = end
840 }
841
842 // trim all the \n off the end of work
843 workbytes := work.Bytes()
844 n := 0
845 for len(workbytes) > n && workbytes[len(workbytes)-n-1] == '\n' {
846 n++
847 }
848 if n > 0 {
849 work.Truncate(len(workbytes) - n)
850 }
851
852 work.WriteByte('\n')
853
854 if parser.r.BlockCode != nil {
855 parser.r.BlockCode(out, work.Bytes(), "", parser.r.Opaque)
856 }
857
858 return beg
859}
860
861// returns unordered list item prefix
862func (parser *Parser) blockUliPrefix(data []byte) int {
863 i := 0
864
865 // start with up to 3 spaces
866 for i < len(data) && i < 3 && data[i] == ' ' {
867 i++
868 }
869
870 // need a *, +, or - followed by a space/tab
871 if i+1 >= len(data) ||
872 (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
873 (data[i+1] != ' ' && data[i+1] != '\t') {
874 return 0
875 }
876 return i + 2
877}
878
879// returns ordered list item prefix
880func (parser *Parser) blockOliPrefix(data []byte) int {
881 i := 0
882
883 // start with up to 3 spaces
884 for i < len(data) && i < 3 && data[i] == ' ' {
885 i++
886 }
887
888 // count the digits
889 start := i
890 for i < len(data) && data[i] >= '0' && data[i] <= '9' {
891 i++
892 }
893
894 // we need >= 1 digits followed by a dot and a space/tab
895 if start == i || data[i] != '.' || i+1 >= len(data) ||
896 (data[i+1] != ' ' && data[i+1] != '\t') {
897 return 0
898 }
899 return i + 2
900}
901
902// parse ordered or unordered list block
903func (parser *Parser) blockList(out *bytes.Buffer, data []byte, flags int) int {
904 i := 0
905 work := func() bool {
906 j := 0
907 for i < len(data) {
908 j = parser.blockListItem(out, data[i:], &flags)
909 i += j
910
911 if j == 0 || flags&LIST_ITEM_END_OF_LIST != 0 {
912 break
913 }
914 }
915 return true
916 }
917
918 if parser.r.List != nil {
919 parser.r.List(out, work, flags, parser.r.Opaque)
920 }
921 return i
922}
923
924// parse a single list item
925// assumes initial prefix is already removed
926func (parser *Parser) blockListItem(out *bytes.Buffer, data []byte, flags *int) int {
927 // keep track of the first indentation prefix
928 beg, end, pre, sublist, orgpre, i := 0, 0, 0, 0, 0, 0
929
930 for orgpre < 3 && orgpre < len(data) && data[orgpre] == ' ' {
931 orgpre++
932 }
933
934 beg = parser.blockUliPrefix(data)
935 if beg == 0 {
936 beg = parser.blockOliPrefix(data)
937 }
938 if beg == 0 {
939 return 0
940 }
941
942 // skip leading whitespace on first line
943 for beg < len(data) && (data[beg] == ' ' || data[beg] == '\t') {
944 beg++
945 }
946
947 // skip to the beginning of the following line
948 end = beg
949 for end < len(data) && data[end-1] != '\n' {
950 end++
951 }
952
953 // get working buffers
954 var work bytes.Buffer
955 var inter bytes.Buffer
956
957 // put the first line into the working buffer
958 work.Write(data[beg:end])
959 beg = end
960
961 // process the following lines
962 containsBlankLine, containsBlock := false, false
963 for beg < len(data) {
964 end++
965
966 for end < len(data) && data[end-1] != '\n' {
967 end++
968 }
969
970 // process an empty line
971 if parser.isEmpty(data[beg:end]) > 0 {
972 containsBlankLine = true
973 beg = end
974 continue
975 }
976
977 // calculate the indentation
978 i = 0
979 for i < 4 && beg+i < end && data[beg+i] == ' ' {
980 i++
981 }
982
983 pre = i
984 if data[beg] == '\t' {
985 i = 1
986 pre = TAB_SIZE_DEFAULT
987 if parser.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
988 pre = TAB_SIZE_EIGHT
989 }
990 }
991
992 chunk := data[beg+i : end]
993
994 // check for a nested list item
995 if (parser.blockUliPrefix(chunk) > 0 && !parser.isHRule(chunk)) ||
996 parser.blockOliPrefix(chunk) > 0 {
997 if containsBlankLine {
998 containsBlock = true
999 }
1000
1001 // the following item must have the same indentation
1002 if pre == orgpre {
1003 break
1004 }
1005
1006 if sublist == 0 {
1007 sublist = work.Len()
1008 }
1009 } else {
1010 // how about a nested prefix header?
1011 if parser.isPrefixHeader(chunk) {
1012 // only nest headers that are indented
1013 if containsBlankLine && i < 4 && data[beg] != '\t' {
1014 *flags |= LIST_ITEM_END_OF_LIST
1015 break
1016 }
1017 containsBlock = true
1018 } else {
1019 // only join stuff after empty lines when indented
1020 if containsBlankLine && i < 4 && data[beg] != '\t' {
1021 *flags |= LIST_ITEM_END_OF_LIST
1022 break
1023 } else {
1024 if containsBlankLine {
1025 work.WriteByte('\n')
1026 containsBlock = true
1027 }
1028 }
1029 }
1030 }
1031
1032 containsBlankLine = false
1033
1034 // add the line into the working buffer without prefix
1035 work.Write(data[beg+i : end])
1036 beg = end
1037 }
1038
1039 // render li contents
1040 if containsBlock {
1041 *flags |= LIST_ITEM_CONTAINS_BLOCK
1042 }
1043
1044 workbytes := work.Bytes()
1045 if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 {
1046 // intermediate render of block li
1047 if sublist > 0 && sublist < len(workbytes) {
1048 parser.parseBlock(&inter, workbytes[:sublist])
1049 parser.parseBlock(&inter, workbytes[sublist:])
1050 } else {
1051 parser.parseBlock(&inter, workbytes)
1052 }
1053 } else {
1054 // intermediate render of inline li
1055 if sublist > 0 && sublist < len(workbytes) {
1056 parser.parseInline(&inter, workbytes[:sublist])
1057 parser.parseBlock(&inter, workbytes[sublist:])
1058 } else {
1059 parser.parseInline(&inter, workbytes)
1060 }
1061 }
1062
1063 // render li itself
1064 if parser.r.ListItem != nil {
1065 parser.r.ListItem(out, inter.Bytes(), *flags, parser.r.Opaque)
1066 }
1067
1068 return beg
1069}
1070
1071// render a single paragraph that has already been parsed out
1072func (parser *Parser) renderParagraph(out *bytes.Buffer, data []byte) {
1073 // trim leading whitespace
1074 beg := 0
1075 for beg < len(data) && isspace(data[beg]) {
1076 beg++
1077 }
1078
1079 // trim trailing whitespace
1080 end := len(data)
1081 for end > beg && isspace(data[end-1]) {
1082 end--
1083 }
1084 if end == beg || parser.r.Paragraph == nil {
1085 return
1086 }
1087
1088 work := func() bool {
1089 parser.parseInline(out, data[beg:end])
1090 return true
1091 }
1092 parser.r.Paragraph(out, work, parser.r.Opaque)
1093}
1094
1095func (parser *Parser) blockParagraph(out *bytes.Buffer, data []byte) int {
1096 // prev: index of 1st char of previous line
1097 // line: index of 1st char of current line
1098 // i: index of cursor/end of current line
1099 var prev, line, i int
1100
1101 // keep going until we find something to mark the end of the paragraph
1102 for i < len(data) {
1103 // mark the beginning of the current line
1104 prev = line
1105 current := data[i:]
1106 line = i
1107
1108 // did we find a blank line marking the end of the paragraph?
1109 if n := parser.isEmpty(current); n > 0 {
1110 parser.renderParagraph(out, data[:i])
1111 return i + n
1112 }
1113
1114 // an underline under some text marks a header, so our paragraph ended on prev line
1115 if i > 0 && parser.r.Header != nil {
1116 if level := parser.isUnderlinedHeader(current); level > 0 {
1117 // render the paragraph
1118 parser.renderParagraph(out, data[:prev])
1119
1120 // ignore leading and trailing whitespace
1121 eol := i - 1
1122 for prev < eol && (data[prev] == ' ' || data[prev] == '\t') {
1123 prev++
1124 }
1125 for eol > prev && (data[eol-1] == ' ' || data[eol-1] == '\t') {
1126 eol--
1127 }
1128
1129 // render the header
1130 // this ugly double closure avoids forcing variables onto the heap
1131 work := func(o *bytes.Buffer, p *Parser, d []byte) func() bool {
1132 return func() bool {
1133 p.parseInline(o, d)
1134 return true
1135 }
1136 }(out, parser, data[prev:eol])
1137 parser.r.Header(out, work, level, parser.r.Opaque)
1138
1139 // find the end of the underline
1140 for ; i < len(data) && data[i] != '\n'; i++ {
1141 }
1142 return i
1143 }
1144 }
1145
1146 // if the next line starts a block of HTML, then the paragraph ends here
1147 if parser.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
1148 if data[i] == '<' && parser.r.BlockHtml != nil && parser.blockHtml(out, current, false) > 0 {
1149 // rewind to before the HTML block
1150 parser.renderParagraph(out, data[:i])
1151 return i
1152 }
1153 }
1154
1155 // if there's a prefixed header or a horizontal rule after this, paragraph is over
1156 if parser.isPrefixHeader(current) || parser.isHRule(current) {
1157 parser.renderParagraph(out, data[:i])
1158 return i
1159 }
1160
1161 // otherwise, scan to the beginning of the next line
1162 i++
1163 for i < len(data) && data[i-1] != '\n' {
1164 i++
1165 }
1166 }
1167
1168 parser.renderParagraph(out, data[:i])
1169 return i
1170}