1
2
3
4
5
6
7
8 package blackfriday
9
10 import (
11 "bytes"
12 "fmt"
13 "io"
14 "strings"
15 "unicode/utf8"
16 )
17
18
19
20
21
22
23
24 const Version = "2.0"
25
26
27
28 type Extensions int
29
30
31
32 const (
33 NoExtensions Extensions = 0
34 NoIntraEmphasis Extensions = 1 << iota
35 Tables
36 FencedCode
37 Autolink
38 Strikethrough
39 LaxHTMLBlocks
40 SpaceHeadings
41 HardLineBreak
42 TabSizeEight
43 Footnotes
44 NoEmptyLineBeforeBlock
45 HeadingIDs
46 Titleblock
47 AutoHeadingIDs
48 BackslashLineBreak
49 DefinitionLists
50
51 CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
52 SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
53
54 CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
55 Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
56 BackslashLineBreak | DefinitionLists
57 )
58
59
60 type ListType int
61
62
63
64
65 const (
66 ListTypeOrdered ListType = 1 << iota
67 ListTypeDefinition
68 ListTypeTerm
69
70 ListItemContainsBlock
71 ListItemBeginningOfList
72 ListItemEndOfList
73 )
74
75
76 type CellAlignFlags int
77
78
79
80
81 const (
82 TableAlignmentLeft CellAlignFlags = 1 << iota
83 TableAlignmentRight
84 TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
85 )
86
87
88 const (
89 TabSizeDefault = 4
90 TabSizeDouble = 8
91 )
92
93
94
95 var blockTags = map[string]struct{}{
96 "blockquote": {},
97 "del": {},
98 "div": {},
99 "dl": {},
100 "fieldset": {},
101 "form": {},
102 "h1": {},
103 "h2": {},
104 "h3": {},
105 "h4": {},
106 "h5": {},
107 "h6": {},
108 "iframe": {},
109 "ins": {},
110 "math": {},
111 "noscript": {},
112 "ol": {},
113 "pre": {},
114 "p": {},
115 "script": {},
116 "style": {},
117 "table": {},
118 "ul": {},
119
120
121 "address": {},
122 "article": {},
123 "aside": {},
124 "canvas": {},
125 "figcaption": {},
126 "figure": {},
127 "footer": {},
128 "header": {},
129 "hgroup": {},
130 "main": {},
131 "nav": {},
132 "output": {},
133 "progress": {},
134 "section": {},
135 "video": {},
136 }
137
138
139
140
141
142
143 type Renderer interface {
144
145
146
147
148 RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
149
150
151
152
153
154
155
156
157
158
159
160
161 RenderHeader(w io.Writer, ast *Node)
162
163
164 RenderFooter(w io.Writer, ast *Node)
165 }
166
167
168
169 type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
170
171
172
173 type Markdown struct {
174 renderer Renderer
175 referenceOverride ReferenceOverrideFunc
176 refs map[string]*reference
177 inlineCallback [256]inlineParser
178 extensions Extensions
179 nesting int
180 maxNesting int
181 insideLink bool
182
183
184
185
186 notes []*reference
187
188 doc *Node
189 tip *Node
190 oldTip *Node
191 lastMatchedContainer *Node
192 allClosed bool
193 }
194
195 func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
196 if p.referenceOverride != nil {
197 r, overridden := p.referenceOverride(refid)
198 if overridden {
199 if r == nil {
200 return nil, false
201 }
202 return &reference{
203 link: []byte(r.Link),
204 title: []byte(r.Title),
205 noteID: 0,
206 hasBlock: false,
207 text: []byte(r.Text)}, true
208 }
209 }
210
211 ref, found = p.refs[strings.ToLower(refid)]
212 return ref, found
213 }
214
215 func (p *Markdown) finalize(block *Node) {
216 above := block.Parent
217 block.open = false
218 p.tip = above
219 }
220
221 func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
222 return p.addExistingChild(NewNode(node), offset)
223 }
224
225 func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
226 for !p.tip.canContain(node.Type) {
227 p.finalize(p.tip)
228 }
229 p.tip.AppendChild(node)
230 p.tip = node
231 return node
232 }
233
234 func (p *Markdown) closeUnmatchedBlocks() {
235 if !p.allClosed {
236 for p.oldTip != p.lastMatchedContainer {
237 parent := p.oldTip.Parent
238 p.finalize(p.oldTip)
239 p.oldTip = parent
240 }
241 p.allClosed = true
242 }
243 }
244
245
246
247
248
249
250
251
252
253 type Reference struct {
254
255 Link string
256
257 Title string
258
259
260 Text string
261 }
262
263
264
265
266
267 type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
268
269
270
271 func New(opts ...Option) *Markdown {
272 var p Markdown
273 for _, opt := range opts {
274 opt(&p)
275 }
276 p.refs = make(map[string]*reference)
277 p.maxNesting = 16
278 p.insideLink = false
279 docNode := NewNode(Document)
280 p.doc = docNode
281 p.tip = docNode
282 p.oldTip = docNode
283 p.lastMatchedContainer = docNode
284 p.allClosed = true
285
286 p.inlineCallback[' '] = maybeLineBreak
287 p.inlineCallback['*'] = emphasis
288 p.inlineCallback['_'] = emphasis
289 if p.extensions&Strikethrough != 0 {
290 p.inlineCallback['~'] = emphasis
291 }
292 p.inlineCallback['`'] = codeSpan
293 p.inlineCallback['\n'] = lineBreak
294 p.inlineCallback['['] = link
295 p.inlineCallback['<'] = leftAngle
296 p.inlineCallback['\\'] = escape
297 p.inlineCallback['&'] = entity
298 p.inlineCallback['!'] = maybeImage
299 p.inlineCallback['^'] = maybeInlineFootnote
300 if p.extensions&Autolink != 0 {
301 p.inlineCallback['h'] = maybeAutoLink
302 p.inlineCallback['m'] = maybeAutoLink
303 p.inlineCallback['f'] = maybeAutoLink
304 p.inlineCallback['H'] = maybeAutoLink
305 p.inlineCallback['M'] = maybeAutoLink
306 p.inlineCallback['F'] = maybeAutoLink
307 }
308 if p.extensions&Footnotes != 0 {
309 p.notes = make([]*reference, 0)
310 }
311 return &p
312 }
313
314
315 type Option func(*Markdown)
316
317
318 func WithRenderer(r Renderer) Option {
319 return func(p *Markdown) {
320 p.renderer = r
321 }
322 }
323
324
325
326 func WithExtensions(e Extensions) Option {
327 return func(p *Markdown) {
328 p.extensions = e
329 }
330 }
331
332
333 func WithNoExtensions() Option {
334 return func(p *Markdown) {
335 p.extensions = NoExtensions
336 p.renderer = NewHTMLRenderer(HTMLRendererParameters{
337 Flags: HTMLFlagsNone,
338 })
339 }
340 }
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356 func WithRefOverride(o ReferenceOverrideFunc) Option {
357 return func(p *Markdown) {
358 p.referenceOverride = o
359 }
360 }
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381 func Run(input []byte, opts ...Option) []byte {
382 r := NewHTMLRenderer(HTMLRendererParameters{
383 Flags: CommonHTMLFlags,
384 })
385 optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
386 optList = append(optList, opts...)
387 parser := New(optList...)
388 ast := parser.Parse(input)
389 var buf bytes.Buffer
390 parser.renderer.RenderHeader(&buf, ast)
391 ast.Walk(func(node *Node, entering bool) WalkStatus {
392 return parser.renderer.RenderNode(&buf, node, entering)
393 })
394 parser.renderer.RenderFooter(&buf, ast)
395 return buf.Bytes()
396 }
397
398
399
400
401
402
403 func (p *Markdown) Parse(input []byte) *Node {
404 p.block(input)
405
406 for p.tip != nil {
407 p.finalize(p.tip)
408 }
409
410 p.doc.Walk(func(node *Node, entering bool) WalkStatus {
411 if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
412 p.inline(node, node.content)
413 node.content = nil
414 }
415 return GoToNext
416 })
417 p.parseRefsToAST()
418 return p.doc
419 }
420
421 func (p *Markdown) parseRefsToAST() {
422 if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
423 return
424 }
425 p.tip = p.doc
426 block := p.addBlock(List, nil)
427 block.IsFootnotesList = true
428 block.ListFlags = ListTypeOrdered
429 flags := ListItemBeginningOfList
430
431
432
433
434 for i := 0; i < len(p.notes); i++ {
435 ref := p.notes[i]
436 p.addExistingChild(ref.footnote, 0)
437 block := ref.footnote
438 block.ListFlags = flags | ListTypeOrdered
439 block.RefLink = ref.link
440 if ref.hasBlock {
441 flags |= ListItemContainsBlock
442 p.block(ref.title)
443 } else {
444 p.inline(block, ref.title)
445 }
446 flags &^= ListItemBeginningOfList | ListItemContainsBlock
447 }
448 above := block.Parent
449 finalizeList(block)
450 p.tip = above
451 block.Walk(func(node *Node, entering bool) WalkStatus {
452 if node.Type == Paragraph || node.Type == Heading {
453 p.inline(node, node.content)
454 node.content = nil
455 }
456 return GoToNext
457 })
458 }
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526 type reference struct {
527 link []byte
528 title []byte
529 noteID int
530 hasBlock bool
531 footnote *Node
532
533 text []byte
534 }
535
536 func (r *reference) String() string {
537 return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
538 r.link, r.title, r.text, r.noteID, r.hasBlock)
539 }
540
541
542
543
544
545
546 func isReference(p *Markdown, data []byte, tabSize int) int {
547
548 if len(data) < 4 {
549 return 0
550 }
551 i := 0
552 for i < 3 && data[i] == ' ' {
553 i++
554 }
555
556 noteID := 0
557
558
559 if data[i] != '[' {
560 return 0
561 }
562 i++
563 if p.extensions&Footnotes != 0 {
564 if i < len(data) && data[i] == '^' {
565
566
567 noteID = 1
568 i++
569 }
570 }
571 idOffset := i
572 for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
573 i++
574 }
575 if i >= len(data) || data[i] != ']' {
576 return 0
577 }
578 idEnd := i
579
580
581 if noteID == 0 && idOffset == idEnd {
582 return 0
583 }
584
585 i++
586 if i >= len(data) || data[i] != ':' {
587 return 0
588 }
589 i++
590 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
591 i++
592 }
593 if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
594 i++
595 if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
596 i++
597 }
598 }
599 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
600 i++
601 }
602 if i >= len(data) {
603 return 0
604 }
605
606 var (
607 linkOffset, linkEnd int
608 titleOffset, titleEnd int
609 lineEnd int
610 raw []byte
611 hasBlock bool
612 )
613
614 if p.extensions&Footnotes != 0 && noteID != 0 {
615 linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
616 lineEnd = linkEnd
617 } else {
618 linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
619 }
620 if lineEnd == 0 {
621 return 0
622 }
623
624
625
626 ref := &reference{
627 noteID: noteID,
628 hasBlock: hasBlock,
629 }
630
631 if noteID > 0 {
632
633 ref.link = data[idOffset:idEnd]
634
635 ref.title = raw
636 } else {
637 ref.link = data[linkOffset:linkEnd]
638 ref.title = data[titleOffset:titleEnd]
639 }
640
641
642 id := string(bytes.ToLower(data[idOffset:idEnd]))
643
644 p.refs[id] = ref
645
646 return lineEnd
647 }
648
649 func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
650
651 if data[i] == '<' {
652 i++
653 }
654 linkOffset = i
655 for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
656 i++
657 }
658 linkEnd = i
659 if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
660 linkOffset++
661 linkEnd--
662 }
663
664
665 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
666 i++
667 }
668 if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
669 return
670 }
671
672
673 if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
674 lineEnd = i
675 }
676 if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
677 lineEnd++
678 }
679
680
681 if lineEnd > 0 {
682 i = lineEnd + 1
683 for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
684 i++
685 }
686 }
687
688
689 if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
690 i++
691 titleOffset = i
692
693
694 for i < len(data) && data[i] != '\n' && data[i] != '\r' {
695 i++
696 }
697 if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
698 titleEnd = i + 1
699 } else {
700 titleEnd = i
701 }
702
703
704 i--
705 for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
706 i--
707 }
708 if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
709 lineEnd = titleEnd
710 titleEnd = i
711 }
712 }
713
714 return
715 }
716
717
718
719
720
721
722
723 func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
724 if i == 0 || len(data) == 0 {
725 return
726 }
727
728
729 for i < len(data) && data[i] == ' ' {
730 i++
731 }
732
733 blockStart = i
734
735
736 blockEnd = i
737 for i < len(data) && data[i-1] != '\n' {
738 i++
739 }
740
741
742 var raw bytes.Buffer
743
744
745 raw.Write(data[blockEnd:i])
746 blockEnd = i
747
748
749 containsBlankLine := false
750
751 gatherLines:
752 for blockEnd < len(data) {
753 i++
754
755
756 for i < len(data) && data[i-1] != '\n' {
757 i++
758 }
759
760
761
762 if p.isEmpty(data[blockEnd:i]) > 0 {
763 containsBlankLine = true
764 blockEnd = i
765 continue
766 }
767
768 n := 0
769 if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
770
771
772 break gatherLines
773 }
774
775
776 if containsBlankLine {
777 raw.WriteByte('\n')
778 containsBlankLine = false
779 }
780
781
782 raw.Write(data[blockEnd+n : i])
783 hasBlock = true
784
785 blockEnd = i
786 }
787
788 if data[blockEnd-1] != '\n' {
789 raw.WriteByte('\n')
790 }
791
792 contents = raw.Bytes()
793
794 return
795 }
796
797
798
799
800
801
802
803
804
805 func ispunct(c byte) bool {
806 for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
807 if c == r {
808 return true
809 }
810 }
811 return false
812 }
813
814
815 func isspace(c byte) bool {
816 return ishorizontalspace(c) || isverticalspace(c)
817 }
818
819
820 func ishorizontalspace(c byte) bool {
821 return c == ' ' || c == '\t'
822 }
823
824
825 func isverticalspace(c byte) bool {
826 return c == '\n' || c == '\r' || c == '\f' || c == '\v'
827 }
828
829
830 func isletter(c byte) bool {
831 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
832 }
833
834
835
836 func isalnum(c byte) bool {
837 return (c >= '0' && c <= '9') || isletter(c)
838 }
839
840
841
842 func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
843
844 i, prefix := 0, 0
845 slowcase := false
846 for i = 0; i < len(line); i++ {
847 if line[i] == '\t' {
848 if prefix == i {
849 prefix++
850 } else {
851 slowcase = true
852 break
853 }
854 }
855 }
856
857
858 if !slowcase {
859 for i = 0; i < prefix*tabSize; i++ {
860 out.WriteByte(' ')
861 }
862 out.Write(line[prefix:])
863 return
864 }
865
866
867
868 column := 0
869 i = 0
870 for i < len(line) {
871 start := i
872 for i < len(line) && line[i] != '\t' {
873 _, size := utf8.DecodeRune(line[i:])
874 i += size
875 column++
876 }
877
878 if i > start {
879 out.Write(line[start:i])
880 }
881
882 if i >= len(line) {
883 break
884 }
885
886 for {
887 out.WriteByte(' ')
888 column++
889 if column%tabSize == 0 {
890 break
891 }
892 }
893
894 i++
895 }
896 }
897
898
899
900 func isIndented(data []byte, indentSize int) int {
901 if len(data) == 0 {
902 return 0
903 }
904 if data[0] == '\t' {
905 return 1
906 }
907 if len(data) < indentSize {
908 return 0
909 }
910 for i := 0; i < indentSize; i++ {
911 if data[i] != ' ' {
912 return 0
913 }
914 }
915 return indentSize
916 }
917
918
919 func slugify(in []byte) []byte {
920 if len(in) == 0 {
921 return in
922 }
923 out := make([]byte, 0, len(in))
924 sym := false
925
926 for _, ch := range in {
927 if isalnum(ch) {
928 sym = false
929 out = append(out, ch)
930 } else if sym {
931 continue
932 } else {
933 out = append(out, '-')
934 sym = true
935 }
936 }
937 var a, b int
938 var ch byte
939 for a, ch = range out {
940 if ch != '-' {
941 break
942 }
943 }
944 for b = len(out) - 1; b > 0; b-- {
945 if out[b] != '-' {
946 break
947 }
948 }
949 return out[a : b+1]
950 }
951
View as plain text