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 "html"
19 "regexp"
20
21 "github.com/shurcooL/sanitized_anchor_name"
22)
23
24const (
25 charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
26 escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
27)
28
29var (
30 reBackslashOrAmp = regexp.MustCompile("[\\&]")
31 reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
32)
33
34// Parse block-level data.
35// Note: this function and many that it calls assume that
36// the input buffer ends with a newline.
37func (p *Markdown) block(data []byte) {
38 // this is called recursively: enforce a maximum depth
39 if p.nesting >= p.maxNesting {
40 return
41 }
42 p.nesting++
43
44 // parse out one block-level construct at a time
45 for len(data) > 0 {
46 // prefixed heading:
47 //
48 // # Heading 1
49 // ## Heading 2
50 // ...
51 // ###### Heading 6
52 if p.isPrefixHeading(data) {
53 data = data[p.prefixHeading(data):]
54 continue
55 }
56
57 // block of preformatted HTML:
58 //
59 // <div>
60 // ...
61 // </div>
62 if data[0] == '<' {
63 if i := p.html(data, true); i > 0 {
64 data = data[i:]
65 continue
66 }
67 }
68
69 // title block
70 //
71 // % stuff
72 // % more stuff
73 // % even more stuff
74 if p.extensions&Titleblock != 0 {
75 if data[0] == '%' {
76 if i := p.titleBlock(data, true); i > 0 {
77 data = data[i:]
78 continue
79 }
80 }
81 }
82
83 // blank lines. note: returns the # of bytes to skip
84 if i := p.isEmpty(data); i > 0 {
85 data = data[i:]
86 continue
87 }
88
89 // indented code block:
90 //
91 // func max(a, b int) int {
92 // if a > b {
93 // return a
94 // }
95 // return b
96 // }
97 if p.codePrefix(data) > 0 {
98 data = data[p.code(data):]
99 continue
100 }
101
102 // fenced code block:
103 //
104 // ``` go
105 // func fact(n int) int {
106 // if n <= 1 {
107 // return n
108 // }
109 // return n * fact(n-1)
110 // }
111 // ```
112 if p.extensions&FencedCode != 0 {
113 if i := p.fencedCodeBlock(data, true); i > 0 {
114 data = data[i:]
115 continue
116 }
117 }
118
119 // horizontal rule:
120 //
121 // ------
122 // or
123 // ******
124 // or
125 // ______
126 if p.isHRule(data) {
127 p.addBlock(HorizontalRule, nil)
128 var i int
129 for i = 0; i < len(data) && data[i] != '\n'; i++ {
130 }
131 data = data[i:]
132 continue
133 }
134
135 // block quote:
136 //
137 // > A big quote I found somewhere
138 // > on the web
139 if p.quotePrefix(data) > 0 {
140 data = data[p.quote(data):]
141 continue
142 }
143
144 // table:
145 //
146 // Name | Age | Phone
147 // ------|-----|---------
148 // Bob | 31 | 555-1234
149 // Alice | 27 | 555-4321
150 if p.extensions&Tables != 0 {
151 if i := p.table(data); i > 0 {
152 data = data[i:]
153 continue
154 }
155 }
156
157 // an itemized/unordered list:
158 //
159 // * Item 1
160 // * Item 2
161 //
162 // also works with + or -
163 if p.uliPrefix(data) > 0 {
164 data = data[p.list(data, 0):]
165 continue
166 }
167
168 // a numbered/ordered list:
169 //
170 // 1. Item 1
171 // 2. Item 2
172 if p.oliPrefix(data) > 0 {
173 data = data[p.list(data, ListTypeOrdered):]
174 continue
175 }
176
177 // definition lists:
178 //
179 // Term 1
180 // : Definition a
181 // : Definition b
182 //
183 // Term 2
184 // : Definition c
185 if p.extensions&DefinitionLists != 0 {
186 if p.dliPrefix(data) > 0 {
187 data = data[p.list(data, ListTypeDefinition):]
188 continue
189 }
190 }
191
192 // anything else must look like a normal paragraph
193 // note: this finds underlined headings, too
194 data = data[p.paragraph(data):]
195 }
196
197 p.nesting--
198}
199
200func (p *Markdown) addBlock(typ NodeType, content []byte) *Node {
201 p.closeUnmatchedBlocks()
202 container := p.addChild(typ, 0)
203 container.content = content
204 return container
205}
206
207func (p *Markdown) isPrefixHeading(data []byte) bool {
208 if data[0] != '#' {
209 return false
210 }
211
212 if p.extensions&SpaceHeadings != 0 {
213 level := 0
214 for level < 6 && level < len(data) && data[level] == '#' {
215 level++
216 }
217 if level == len(data) || data[level] != ' ' {
218 return false
219 }
220 }
221 return true
222}
223
224func (p *Markdown) prefixHeading(data []byte) int {
225 level := 0
226 for level < 6 && level < len(data) && data[level] == '#' {
227 level++
228 }
229 i := skipChar(data, level, ' ')
230 end := skipUntilChar(data, i, '\n')
231 skip := end
232 id := ""
233 if p.extensions&HeadingIDs != 0 {
234 j, k := 0, 0
235 // find start/end of heading id
236 for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
237 }
238 for k = j + 1; k < end && data[k] != '}'; k++ {
239 }
240 // extract heading id iff found
241 if j < end && k < end {
242 id = string(data[j+2 : k])
243 end = j
244 skip = k + 1
245 for end > 0 && data[end-1] == ' ' {
246 end--
247 }
248 }
249 }
250 for end > 0 && data[end-1] == '#' {
251 if isBackslashEscaped(data, end-1) {
252 break
253 }
254 end--
255 }
256 for end > 0 && data[end-1] == ' ' {
257 end--
258 }
259 if end > i {
260 if id == "" && p.extensions&AutoHeadingIDs != 0 {
261 id = sanitized_anchor_name.Create(string(data[i:end]))
262 }
263 block := p.addBlock(Heading, data[i:end])
264 block.HeadingID = id
265 block.Level = level
266 }
267 return skip
268}
269
270func (p *Markdown) isUnderlinedHeading(data []byte) int {
271 // test of level 1 heading
272 if data[0] == '=' {
273 i := skipChar(data, 1, '=')
274 i = skipChar(data, i, ' ')
275 if i < len(data) && data[i] == '\n' {
276 return 1
277 }
278 return 0
279 }
280
281 // test of level 2 heading
282 if data[0] == '-' {
283 i := skipChar(data, 1, '-')
284 i = skipChar(data, i, ' ')
285 if i < len(data) && data[i] == '\n' {
286 return 2
287 }
288 return 0
289 }
290
291 return 0
292}
293
294func (p *Markdown) titleBlock(data []byte, doRender bool) int {
295 if data[0] != '%' {
296 return 0
297 }
298 splitData := bytes.Split(data, []byte("\n"))
299 var i int
300 for idx, b := range splitData {
301 if !bytes.HasPrefix(b, []byte("%")) {
302 i = idx // - 1
303 break
304 }
305 }
306
307 data = bytes.Join(splitData[0:i], []byte("\n"))
308 consumed := len(data)
309 data = bytes.TrimPrefix(data, []byte("% "))
310 data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
311 block := p.addBlock(Heading, data)
312 block.Level = 1
313 block.IsTitleblock = true
314
315 return consumed
316}
317
318func (p *Markdown) html(data []byte, doRender bool) int {
319 var i, j int
320
321 // identify the opening tag
322 if data[0] != '<' {
323 return 0
324 }
325 curtag, tagfound := p.htmlFindTag(data[1:])
326
327 // handle special cases
328 if !tagfound {
329 // check for an HTML comment
330 if size := p.htmlComment(data, doRender); size > 0 {
331 return size
332 }
333
334 // check for an <hr> tag
335 if size := p.htmlHr(data, doRender); size > 0 {
336 return size
337 }
338
339 // no special case recognized
340 return 0
341 }
342
343 // look for an unindented matching closing tag
344 // followed by a blank line
345 found := false
346 /*
347 closetag := []byte("\n</" + curtag + ">")
348 j = len(curtag) + 1
349 for !found {
350 // scan for a closing tag at the beginning of a line
351 if skip := bytes.Index(data[j:], closetag); skip >= 0 {
352 j += skip + len(closetag)
353 } else {
354 break
355 }
356
357 // see if it is the only thing on the line
358 if skip := p.isEmpty(data[j:]); skip > 0 {
359 // see if it is followed by a blank line/eof
360 j += skip
361 if j >= len(data) {
362 found = true
363 i = j
364 } else {
365 if skip := p.isEmpty(data[j:]); skip > 0 {
366 j += skip
367 found = true
368 i = j
369 }
370 }
371 }
372 }
373 */
374
375 // if not found, try a second pass looking for indented match
376 // but not if tag is "ins" or "del" (following original Markdown.pl)
377 if !found && curtag != "ins" && curtag != "del" {
378 i = 1
379 for i < len(data) {
380 i++
381 for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
382 i++
383 }
384
385 if i+2+len(curtag) >= len(data) {
386 break
387 }
388
389 j = p.htmlFindEnd(curtag, data[i-1:])
390
391 if j > 0 {
392 i += j - 1
393 found = true
394 break
395 }
396 }
397 }
398
399 if !found {
400 return 0
401 }
402
403 // the end of the block has been found
404 if doRender {
405 // trim newlines
406 end := i
407 for end > 0 && data[end-1] == '\n' {
408 end--
409 }
410 finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
411 }
412
413 return i
414}
415
416func finalizeHTMLBlock(block *Node) {
417 block.Literal = block.content
418 block.content = nil
419}
420
421// HTML comment, lax form
422func (p *Markdown) htmlComment(data []byte, doRender bool) int {
423 i := p.inlineHTMLComment(data)
424 // needs to end with a blank line
425 if j := p.isEmpty(data[i:]); j > 0 {
426 size := i + j
427 if doRender {
428 // trim trailing newlines
429 end := size
430 for end > 0 && data[end-1] == '\n' {
431 end--
432 }
433 block := p.addBlock(HTMLBlock, data[:end])
434 finalizeHTMLBlock(block)
435 }
436 return size
437 }
438 return 0
439}
440
441// HR, which is the only self-closing block tag considered
442func (p *Markdown) htmlHr(data []byte, doRender bool) int {
443 if len(data) < 4 {
444 return 0
445 }
446 if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
447 return 0
448 }
449 if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
450 // not an <hr> tag after all; at least not a valid one
451 return 0
452 }
453 i := 3
454 for i < len(data) && data[i] != '>' && data[i] != '\n' {
455 i++
456 }
457 if i < len(data) && data[i] == '>' {
458 i++
459 if j := p.isEmpty(data[i:]); j > 0 {
460 size := i + j
461 if doRender {
462 // trim newlines
463 end := size
464 for end > 0 && data[end-1] == '\n' {
465 end--
466 }
467 finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
468 }
469 return size
470 }
471 }
472 return 0
473}
474
475func (p *Markdown) htmlFindTag(data []byte) (string, bool) {
476 i := 0
477 for i < len(data) && isalnum(data[i]) {
478 i++
479 }
480 key := string(data[:i])
481 if _, ok := blockTags[key]; ok {
482 return key, true
483 }
484 return "", false
485}
486
487func (p *Markdown) htmlFindEnd(tag string, data []byte) int {
488 // assume data[0] == '<' && data[1] == '/' already tested
489 if tag == "hr" {
490 return 2
491 }
492 // check if tag is a match
493 closetag := []byte("</" + tag + ">")
494 if !bytes.HasPrefix(data, closetag) {
495 return 0
496 }
497 i := len(closetag)
498
499 // check that the rest of the line is blank
500 skip := 0
501 if skip = p.isEmpty(data[i:]); skip == 0 {
502 return 0
503 }
504 i += skip
505 skip = 0
506
507 if i >= len(data) {
508 return i
509 }
510
511 if p.extensions&LaxHTMLBlocks != 0 {
512 return i
513 }
514 if skip = p.isEmpty(data[i:]); skip == 0 {
515 // following line must be blank
516 return 0
517 }
518
519 return i + skip
520}
521
522func (*Markdown) isEmpty(data []byte) int {
523 // it is okay to call isEmpty on an empty buffer
524 if len(data) == 0 {
525 return 0
526 }
527
528 var i int
529 for i = 0; i < len(data) && data[i] != '\n'; i++ {
530 if data[i] != ' ' && data[i] != '\t' {
531 return 0
532 }
533 }
534 if i < len(data) && data[i] == '\n' {
535 i++
536 }
537 return i
538}
539
540func (*Markdown) isHRule(data []byte) bool {
541 i := 0
542
543 // skip up to three spaces
544 for i < 3 && data[i] == ' ' {
545 i++
546 }
547
548 // look at the hrule char
549 if data[i] != '*' && data[i] != '-' && data[i] != '_' {
550 return false
551 }
552 c := data[i]
553
554 // the whole line must be the char or whitespace
555 n := 0
556 for i < len(data) && data[i] != '\n' {
557 switch {
558 case data[i] == c:
559 n++
560 case data[i] != ' ':
561 return false
562 }
563 i++
564 }
565
566 return n >= 3
567}
568
569// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
570// and returns the end index if so, or 0 otherwise. It also returns the marker found.
571// If syntax is not nil, it gets set to the syntax specified in the fence line.
572func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) {
573 i, size := 0, 0
574
575 // skip up to three spaces
576 for i < len(data) && i < 3 && data[i] == ' ' {
577 i++
578 }
579
580 // check for the marker characters: ~ or `
581 if i >= len(data) {
582 return 0, ""
583 }
584 if data[i] != '~' && data[i] != '`' {
585 return 0, ""
586 }
587
588 c := data[i]
589
590 // the whole line must be the same char or whitespace
591 for i < len(data) && data[i] == c {
592 size++
593 i++
594 }
595
596 // the marker char must occur at least 3 times
597 if size < 3 {
598 return 0, ""
599 }
600 marker = string(data[i-size : i])
601
602 // if this is the end marker, it must match the beginning marker
603 if oldmarker != "" && marker != oldmarker {
604 return 0, ""
605 }
606
607 // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
608 // into one, always get the syntax, and discard it if the caller doesn't care.
609 if syntax != nil {
610 syn := 0
611 i = skipChar(data, i, ' ')
612
613 if i >= len(data) {
614 if i == len(data) {
615 return i, marker
616 }
617 return 0, ""
618 }
619
620 syntaxStart := i
621
622 if data[i] == '{' {
623 i++
624 syntaxStart++
625
626 for i < len(data) && data[i] != '}' && data[i] != '\n' {
627 syn++
628 i++
629 }
630
631 if i >= len(data) || data[i] != '}' {
632 return 0, ""
633 }
634
635 // strip all whitespace at the beginning and the end
636 // of the {} block
637 for syn > 0 && isspace(data[syntaxStart]) {
638 syntaxStart++
639 syn--
640 }
641
642 for syn > 0 && isspace(data[syntaxStart+syn-1]) {
643 syn--
644 }
645
646 i++
647 } else {
648 for i < len(data) && !isspace(data[i]) {
649 syn++
650 i++
651 }
652 }
653
654 *syntax = string(data[syntaxStart : syntaxStart+syn])
655 }
656
657 i = skipChar(data, i, ' ')
658 if i >= len(data) || data[i] != '\n' {
659 if i == len(data) {
660 return i, marker
661 }
662 return 0, ""
663 }
664 return i + 1, marker // Take newline into account.
665}
666
667// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
668// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
669// If doRender is true, a final newline is mandatory to recognize the fenced code block.
670func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
671 var syntax string
672 beg, marker := isFenceLine(data, &syntax, "")
673 if beg == 0 || beg >= len(data) {
674 return 0
675 }
676
677 var work bytes.Buffer
678 work.Write([]byte(syntax))
679 work.WriteByte('\n')
680
681 for {
682 // safe to assume beg < len(data)
683
684 // check for the end of the code block
685 fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
686 if fenceEnd != 0 {
687 beg += fenceEnd
688 break
689 }
690
691 // copy the current line
692 end := skipUntilChar(data, beg, '\n') + 1
693
694 // did we reach the end of the buffer without a closing marker?
695 if end >= len(data) {
696 return 0
697 }
698
699 // verbatim copy to the working buffer
700 if doRender {
701 work.Write(data[beg:end])
702 }
703 beg = end
704 }
705
706 if doRender {
707 block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
708 block.IsFenced = true
709 finalizeCodeBlock(block)
710 }
711
712 return beg
713}
714
715func unescapeChar(str []byte) []byte {
716 if str[0] == '\\' {
717 return []byte{str[1]}
718 }
719 return []byte(html.UnescapeString(string(str)))
720}
721
722func unescapeString(str []byte) []byte {
723 if reBackslashOrAmp.Match(str) {
724 return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar)
725 }
726 return str
727}
728
729func finalizeCodeBlock(block *Node) {
730 if block.IsFenced {
731 newlinePos := bytes.IndexByte(block.content, '\n')
732 firstLine := block.content[:newlinePos]
733 rest := block.content[newlinePos+1:]
734 block.Info = unescapeString(bytes.Trim(firstLine, "\n"))
735 block.Literal = rest
736 } else {
737 block.Literal = block.content
738 }
739 block.content = nil
740}
741
742func (p *Markdown) table(data []byte) int {
743 table := p.addBlock(Table, nil)
744 i, columns := p.tableHeader(data)
745 if i == 0 {
746 p.tip = table.Parent
747 table.Unlink()
748 return 0
749 }
750
751 p.addBlock(TableBody, nil)
752
753 for i < len(data) {
754 pipes, rowStart := 0, i
755 for ; i < len(data) && data[i] != '\n'; i++ {
756 if data[i] == '|' {
757 pipes++
758 }
759 }
760
761 if pipes == 0 {
762 i = rowStart
763 break
764 }
765
766 // include the newline in data sent to tableRow
767 if i < len(data) && data[i] == '\n' {
768 i++
769 }
770 p.tableRow(data[rowStart:i], columns, false)
771 }
772
773 return i
774}
775
776// check if the specified position is preceded by an odd number of backslashes
777func isBackslashEscaped(data []byte, i int) bool {
778 backslashes := 0
779 for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
780 backslashes++
781 }
782 return backslashes&1 == 1
783}
784
785func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
786 i := 0
787 colCount := 1
788 for i = 0; i < len(data) && data[i] != '\n'; i++ {
789 if data[i] == '|' && !isBackslashEscaped(data, i) {
790 colCount++
791 }
792 }
793
794 // doesn't look like a table header
795 if colCount == 1 {
796 return
797 }
798
799 // include the newline in the data sent to tableRow
800 j := i
801 if j < len(data) && data[j] == '\n' {
802 j++
803 }
804 header := data[:j]
805
806 // column count ignores pipes at beginning or end of line
807 if data[0] == '|' {
808 colCount--
809 }
810 if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
811 colCount--
812 }
813
814 columns = make([]CellAlignFlags, colCount)
815
816 // move on to the header underline
817 i++
818 if i >= len(data) {
819 return
820 }
821
822 if data[i] == '|' && !isBackslashEscaped(data, i) {
823 i++
824 }
825 i = skipChar(data, i, ' ')
826
827 // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
828 // and trailing | optional on last column
829 col := 0
830 for i < len(data) && data[i] != '\n' {
831 dashes := 0
832
833 if data[i] == ':' {
834 i++
835 columns[col] |= TableAlignmentLeft
836 dashes++
837 }
838 for i < len(data) && data[i] == '-' {
839 i++
840 dashes++
841 }
842 if i < len(data) && data[i] == ':' {
843 i++
844 columns[col] |= TableAlignmentRight
845 dashes++
846 }
847 for i < len(data) && data[i] == ' ' {
848 i++
849 }
850 if i == len(data) {
851 return
852 }
853 // end of column test is messy
854 switch {
855 case dashes < 3:
856 // not a valid column
857 return
858
859 case data[i] == '|' && !isBackslashEscaped(data, i):
860 // marker found, now skip past trailing whitespace
861 col++
862 i++
863 for i < len(data) && data[i] == ' ' {
864 i++
865 }
866
867 // trailing junk found after last column
868 if col >= colCount && i < len(data) && data[i] != '\n' {
869 return
870 }
871
872 case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
873 // something else found where marker was required
874 return
875
876 case data[i] == '\n':
877 // marker is optional for the last column
878 col++
879
880 default:
881 // trailing junk found after last column
882 return
883 }
884 }
885 if col != colCount {
886 return
887 }
888
889 p.addBlock(TableHead, nil)
890 p.tableRow(header, columns, true)
891 size = i
892 if size < len(data) && data[size] == '\n' {
893 size++
894 }
895 return
896}
897
898func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) {
899 p.addBlock(TableRow, nil)
900 i, col := 0, 0
901
902 if data[i] == '|' && !isBackslashEscaped(data, i) {
903 i++
904 }
905
906 for col = 0; col < len(columns) && i < len(data); col++ {
907 for i < len(data) && data[i] == ' ' {
908 i++
909 }
910
911 cellStart := i
912
913 for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
914 i++
915 }
916
917 cellEnd := i
918
919 // skip the end-of-cell marker, possibly taking us past end of buffer
920 i++
921
922 for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' {
923 cellEnd--
924 }
925
926 cell := p.addBlock(TableCell, data[cellStart:cellEnd])
927 cell.IsHeader = header
928 cell.Align = columns[col]
929 }
930
931 // pad it out with empty columns to get the right number
932 for ; col < len(columns); col++ {
933 cell := p.addBlock(TableCell, nil)
934 cell.IsHeader = header
935 cell.Align = columns[col]
936 }
937
938 // silently ignore rows with too many cells
939}
940
941// returns blockquote prefix length
942func (p *Markdown) quotePrefix(data []byte) int {
943 i := 0
944 for i < 3 && i < len(data) && data[i] == ' ' {
945 i++
946 }
947 if i < len(data) && data[i] == '>' {
948 if i+1 < len(data) && data[i+1] == ' ' {
949 return i + 2
950 }
951 return i + 1
952 }
953 return 0
954}
955
956// blockquote ends with at least one blank line
957// followed by something without a blockquote prefix
958func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool {
959 if p.isEmpty(data[beg:]) <= 0 {
960 return false
961 }
962 if end >= len(data) {
963 return true
964 }
965 return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
966}
967
968// parse a blockquote fragment
969func (p *Markdown) quote(data []byte) int {
970 block := p.addBlock(BlockQuote, nil)
971 var raw bytes.Buffer
972 beg, end := 0, 0
973 for beg < len(data) {
974 end = beg
975 // Step over whole lines, collecting them. While doing that, check for
976 // fenced code and if one's found, incorporate it altogether,
977 // irregardless of any contents inside it
978 for end < len(data) && data[end] != '\n' {
979 if p.extensions&FencedCode != 0 {
980 if i := p.fencedCodeBlock(data[end:], false); i > 0 {
981 // -1 to compensate for the extra end++ after the loop:
982 end += i - 1
983 break
984 }
985 }
986 end++
987 }
988 if end < len(data) && data[end] == '\n' {
989 end++
990 }
991 if pre := p.quotePrefix(data[beg:]); pre > 0 {
992 // skip the prefix
993 beg += pre
994 } else if p.terminateBlockquote(data, beg, end) {
995 break
996 }
997 // this line is part of the blockquote
998 raw.Write(data[beg:end])
999 beg = end
1000 }
1001 p.block(raw.Bytes())
1002 p.finalize(block)
1003 return end
1004}
1005
1006// returns prefix length for block code
1007func (p *Markdown) codePrefix(data []byte) int {
1008 if len(data) >= 1 && data[0] == '\t' {
1009 return 1
1010 }
1011 if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
1012 return 4
1013 }
1014 return 0
1015}
1016
1017func (p *Markdown) code(data []byte) int {
1018 var work bytes.Buffer
1019
1020 i := 0
1021 for i < len(data) {
1022 beg := i
1023 for i < len(data) && data[i] != '\n' {
1024 i++
1025 }
1026 if i < len(data) && data[i] == '\n' {
1027 i++
1028 }
1029
1030 blankline := p.isEmpty(data[beg:i]) > 0
1031 if pre := p.codePrefix(data[beg:i]); pre > 0 {
1032 beg += pre
1033 } else if !blankline {
1034 // non-empty, non-prefixed line breaks the pre
1035 i = beg
1036 break
1037 }
1038
1039 // verbatim copy to the working buffer
1040 if blankline {
1041 work.WriteByte('\n')
1042 } else {
1043 work.Write(data[beg:i])
1044 }
1045 }
1046
1047 // trim all the \n off the end of work
1048 workbytes := work.Bytes()
1049 eol := len(workbytes)
1050 for eol > 0 && workbytes[eol-1] == '\n' {
1051 eol--
1052 }
1053 if eol != len(workbytes) {
1054 work.Truncate(eol)
1055 }
1056
1057 work.WriteByte('\n')
1058
1059 block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
1060 block.IsFenced = false
1061 finalizeCodeBlock(block)
1062
1063 return i
1064}
1065
1066// returns unordered list item prefix
1067func (p *Markdown) uliPrefix(data []byte) int {
1068 i := 0
1069 // start with up to 3 spaces
1070 for i < len(data) && i < 3 && data[i] == ' ' {
1071 i++
1072 }
1073 if i >= len(data)-1 {
1074 return 0
1075 }
1076 // need one of {'*', '+', '-'} followed by a space or a tab
1077 if (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
1078 (data[i+1] != ' ' && data[i+1] != '\t') {
1079 return 0
1080 }
1081 return i + 2
1082}
1083
1084// returns ordered list item prefix
1085func (p *Markdown) oliPrefix(data []byte) int {
1086 i := 0
1087
1088 // start with up to 3 spaces
1089 for i < 3 && i < len(data) && data[i] == ' ' {
1090 i++
1091 }
1092
1093 // count the digits
1094 start := i
1095 for i < len(data) && data[i] >= '0' && data[i] <= '9' {
1096 i++
1097 }
1098 if start == i || i >= len(data)-1 {
1099 return 0
1100 }
1101
1102 // we need >= 1 digits followed by a dot and a space or a tab
1103 if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') {
1104 return 0
1105 }
1106 return i + 2
1107}
1108
1109// returns definition list item prefix
1110func (p *Markdown) dliPrefix(data []byte) int {
1111 if len(data) < 2 {
1112 return 0
1113 }
1114 i := 0
1115 // need a ':' followed by a space or a tab
1116 if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') {
1117 return 0
1118 }
1119 for i < len(data) && data[i] == ' ' {
1120 i++
1121 }
1122 return i + 2
1123}
1124
1125// parse ordered or unordered list block
1126func (p *Markdown) list(data []byte, flags ListType) int {
1127 i := 0
1128 flags |= ListItemBeginningOfList
1129 block := p.addBlock(List, nil)
1130 block.ListFlags = flags
1131 block.Tight = true
1132
1133 for i < len(data) {
1134 skip := p.listItem(data[i:], &flags)
1135 if flags&ListItemContainsBlock != 0 {
1136 block.ListData.Tight = false
1137 }
1138 i += skip
1139 if skip == 0 || flags&ListItemEndOfList != 0 {
1140 break
1141 }
1142 flags &= ^ListItemBeginningOfList
1143 }
1144
1145 above := block.Parent
1146 finalizeList(block)
1147 p.tip = above
1148 return i
1149}
1150
1151// Returns true if block ends with a blank line, descending if needed
1152// into lists and sublists.
1153func endsWithBlankLine(block *Node) bool {
1154 // TODO: figure this out. Always false now.
1155 for block != nil {
1156 //if block.lastLineBlank {
1157 //return true
1158 //}
1159 t := block.Type
1160 if t == List || t == Item {
1161 block = block.LastChild
1162 } else {
1163 break
1164 }
1165 }
1166 return false
1167}
1168
1169func finalizeList(block *Node) {
1170 block.open = false
1171 item := block.FirstChild
1172 for item != nil {
1173 // check for non-final list item ending with blank line:
1174 if endsWithBlankLine(item) && item.Next != nil {
1175 block.ListData.Tight = false
1176 break
1177 }
1178 // recurse into children of list item, to see if there are spaces
1179 // between any of them:
1180 subItem := item.FirstChild
1181 for subItem != nil {
1182 if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) {
1183 block.ListData.Tight = false
1184 break
1185 }
1186 subItem = subItem.Next
1187 }
1188 item = item.Next
1189 }
1190}
1191
1192// Parse a single list item.
1193// Assumes initial prefix is already removed if this is a sublist.
1194func (p *Markdown) listItem(data []byte, flags *ListType) int {
1195 // keep track of the indentation of the first line
1196 itemIndent := 0
1197 if data[0] == '\t' {
1198 itemIndent += 4
1199 } else {
1200 for itemIndent < 3 && data[itemIndent] == ' ' {
1201 itemIndent++
1202 }
1203 }
1204
1205 var bulletChar byte = '*'
1206 i := p.uliPrefix(data)
1207 if i == 0 {
1208 i = p.oliPrefix(data)
1209 } else {
1210 bulletChar = data[i-2]
1211 }
1212 if i == 0 {
1213 i = p.dliPrefix(data)
1214 // reset definition term flag
1215 if i > 0 {
1216 *flags &= ^ListTypeTerm
1217 }
1218 }
1219 if i == 0 {
1220 // if in definition list, set term flag and continue
1221 if *flags&ListTypeDefinition != 0 {
1222 *flags |= ListTypeTerm
1223 } else {
1224 return 0
1225 }
1226 }
1227
1228 // skip leading whitespace on first line
1229 for i < len(data) && data[i] == ' ' {
1230 i++
1231 }
1232
1233 // find the end of the line
1234 line := i
1235 for i > 0 && i < len(data) && data[i-1] != '\n' {
1236 i++
1237 }
1238
1239 // get working buffer
1240 var raw bytes.Buffer
1241
1242 // put the first line into the working buffer
1243 raw.Write(data[line:i])
1244 line = i
1245
1246 // process the following lines
1247 containsBlankLine := false
1248 sublist := 0
1249
1250gatherlines:
1251 for line < len(data) {
1252 i++
1253
1254 // find the end of this line
1255 for i < len(data) && data[i-1] != '\n' {
1256 i++
1257 }
1258
1259 // if it is an empty line, guess that it is part of this item
1260 // and move on to the next line
1261 if p.isEmpty(data[line:i]) > 0 {
1262 containsBlankLine = true
1263 line = i
1264 continue
1265 }
1266
1267 // calculate the indentation
1268 indent := 0
1269 indentIndex := 0
1270 if data[line] == '\t' {
1271 indentIndex++
1272 indent += 4
1273 } else {
1274 for indent < 4 && line+indent < i && data[line+indent] == ' ' {
1275 indent++
1276 indentIndex++
1277 }
1278 }
1279
1280 chunk := data[line+indentIndex : i]
1281
1282 // evaluate how this line fits in
1283 switch {
1284 // is this a nested list item?
1285 case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
1286 p.oliPrefix(chunk) > 0 ||
1287 p.dliPrefix(chunk) > 0:
1288
1289 if containsBlankLine {
1290 *flags |= ListItemContainsBlock
1291 }
1292
1293 // to be a nested list, it must be indented more
1294 // if not, it is the next item in the same list
1295 if indent <= itemIndent {
1296 break gatherlines
1297 }
1298
1299 // is this the first item in the nested list?
1300 if sublist == 0 {
1301 sublist = raw.Len()
1302 }
1303
1304 // is this a nested prefix heading?
1305 case p.isPrefixHeading(chunk):
1306 // if the heading is not indented, it is not nested in the list
1307 // and thus ends the list
1308 if containsBlankLine && indent < 4 {
1309 *flags |= ListItemEndOfList
1310 break gatherlines
1311 }
1312 *flags |= ListItemContainsBlock
1313
1314 // anything following an empty line is only part
1315 // of this item if it is indented 4 spaces
1316 // (regardless of the indentation of the beginning of the item)
1317 case containsBlankLine && indent < 4:
1318 if *flags&ListTypeDefinition != 0 && i < len(data)-1 {
1319 // is the next item still a part of this list?
1320 next := i
1321 for next < len(data) && data[next] != '\n' {
1322 next++
1323 }
1324 for next < len(data)-1 && data[next] == '\n' {
1325 next++
1326 }
1327 if i < len(data)-1 && data[i] != ':' && data[next] != ':' {
1328 *flags |= ListItemEndOfList
1329 }
1330 } else {
1331 *flags |= ListItemEndOfList
1332 }
1333 break gatherlines
1334
1335 // a blank line means this should be parsed as a block
1336 case containsBlankLine:
1337 raw.WriteByte('\n')
1338 *flags |= ListItemContainsBlock
1339 }
1340
1341 // if this line was preceded by one or more blanks,
1342 // re-introduce the blank into the buffer
1343 if containsBlankLine {
1344 containsBlankLine = false
1345 raw.WriteByte('\n')
1346 }
1347
1348 // add the line into the working buffer without prefix
1349 raw.Write(data[line+indentIndex : i])
1350
1351 line = i
1352 }
1353
1354 rawBytes := raw.Bytes()
1355
1356 block := p.addBlock(Item, nil)
1357 block.ListFlags = *flags
1358 block.Tight = false
1359 block.BulletChar = bulletChar
1360 block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
1361
1362 // render the contents of the list item
1363 if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 {
1364 // intermediate render of block item, except for definition term
1365 if sublist > 0 {
1366 p.block(rawBytes[:sublist])
1367 p.block(rawBytes[sublist:])
1368 } else {
1369 p.block(rawBytes)
1370 }
1371 } else {
1372 // intermediate render of inline item
1373 if sublist > 0 {
1374 child := p.addChild(Paragraph, 0)
1375 child.content = rawBytes[:sublist]
1376 p.block(rawBytes[sublist:])
1377 } else {
1378 child := p.addChild(Paragraph, 0)
1379 child.content = rawBytes
1380 }
1381 }
1382 return line
1383}
1384
1385// render a single paragraph that has already been parsed out
1386func (p *Markdown) renderParagraph(data []byte) {
1387 if len(data) == 0 {
1388 return
1389 }
1390
1391 // trim leading spaces
1392 beg := 0
1393 for data[beg] == ' ' {
1394 beg++
1395 }
1396
1397 end := len(data)
1398 // trim trailing newline
1399 if data[len(data)-1] == '\n' {
1400 end--
1401 }
1402
1403 // trim trailing spaces
1404 for end > beg && data[end-1] == ' ' {
1405 end--
1406 }
1407
1408 p.addBlock(Paragraph, data[beg:end])
1409}
1410
1411func (p *Markdown) paragraph(data []byte) int {
1412 // prev: index of 1st char of previous line
1413 // line: index of 1st char of current line
1414 // i: index of cursor/end of current line
1415 var prev, line, i int
1416 tabSize := TabSizeDefault
1417 if p.extensions&TabSizeEight != 0 {
1418 tabSize = TabSizeDouble
1419 }
1420 // keep going until we find something to mark the end of the paragraph
1421 for i < len(data) {
1422 // mark the beginning of the current line
1423 prev = line
1424 current := data[i:]
1425 line = i
1426
1427 // did we find a reference or a footnote? If so, end a paragraph
1428 // preceding it and report that we have consumed up to the end of that
1429 // reference:
1430 if refEnd := isReference(p, current, tabSize); refEnd > 0 {
1431 p.renderParagraph(data[:i])
1432 return i + refEnd
1433 }
1434
1435 // did we find a blank line marking the end of the paragraph?
1436 if n := p.isEmpty(current); n > 0 {
1437 // did this blank line followed by a definition list item?
1438 if p.extensions&DefinitionLists != 0 {
1439 if i < len(data)-1 && data[i+1] == ':' {
1440 return p.list(data[prev:], ListTypeDefinition)
1441 }
1442 }
1443
1444 p.renderParagraph(data[:i])
1445 return i + n
1446 }
1447
1448 // an underline under some text marks a heading, so our paragraph ended on prev line
1449 if i > 0 {
1450 if level := p.isUnderlinedHeading(current); level > 0 {
1451 // render the paragraph
1452 p.renderParagraph(data[:prev])
1453
1454 // ignore leading and trailing whitespace
1455 eol := i - 1
1456 for prev < eol && data[prev] == ' ' {
1457 prev++
1458 }
1459 for eol > prev && data[eol-1] == ' ' {
1460 eol--
1461 }
1462
1463 id := ""
1464 if p.extensions&AutoHeadingIDs != 0 {
1465 id = sanitized_anchor_name.Create(string(data[prev:eol]))
1466 }
1467
1468 block := p.addBlock(Heading, data[prev:eol])
1469 block.Level = level
1470 block.HeadingID = id
1471
1472 // find the end of the underline
1473 for i < len(data) && data[i] != '\n' {
1474 i++
1475 }
1476 return i
1477 }
1478 }
1479
1480 // if the next line starts a block of HTML, then the paragraph ends here
1481 if p.extensions&LaxHTMLBlocks != 0 {
1482 if data[i] == '<' && p.html(current, false) > 0 {
1483 // rewind to before the HTML block
1484 p.renderParagraph(data[:i])
1485 return i
1486 }
1487 }
1488
1489 // if there's a prefixed heading or a horizontal rule after this, paragraph is over
1490 if p.isPrefixHeading(current) || p.isHRule(current) {
1491 p.renderParagraph(data[:i])
1492 return i
1493 }
1494
1495 // if there's a fenced code block, paragraph is over
1496 if p.extensions&FencedCode != 0 {
1497 if p.fencedCodeBlock(current, false) > 0 {
1498 p.renderParagraph(data[:i])
1499 return i
1500 }
1501 }
1502
1503 // if there's a definition list item, prev line is a definition term
1504 if p.extensions&DefinitionLists != 0 {
1505 if p.dliPrefix(current) != 0 {
1506 ret := p.list(data[prev:], ListTypeDefinition)
1507 return ret
1508 }
1509 }
1510
1511 // if there's a list after this, paragraph is over
1512 if p.extensions&NoEmptyLineBeforeBlock != 0 {
1513 if p.uliPrefix(current) != 0 ||
1514 p.oliPrefix(current) != 0 ||
1515 p.quotePrefix(current) != 0 ||
1516 p.codePrefix(current) != 0 {
1517 p.renderParagraph(data[:i])
1518 return i
1519 }
1520 }
1521
1522 // otherwise, scan to the beginning of the next line
1523 nl := bytes.IndexByte(data[i:], '\n')
1524 if nl >= 0 {
1525 i += nl + 1
1526 } else {
1527 i += len(data[i:])
1528 }
1529 }
1530
1531 p.renderParagraph(data[:i])
1532 return i
1533}
1534
1535func skipChar(data []byte, start int, char byte) int {
1536 i := start
1537 for i < len(data) && data[i] == char {
1538 i++
1539 }
1540 return i
1541}
1542
1543func skipUntilChar(text []byte, start int, char byte) int {
1544 i := start
1545 for i < len(text) && text[i] != char {
1546 i++
1547 }
1548 return i
1549}