1
2
3
4
5 package markdown
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 )
12
13 type Heading struct {
14 Position
15 Level int
16 Text *Text
17
18
19 ID string
20 }
21
22 func (b *Heading) PrintHTML(buf *bytes.Buffer) {
23 fmt.Fprintf(buf, "<h%d", b.Level)
24 if b.ID != "" {
25 fmt.Fprintf(buf, ` id="%s"`, htmlQuoteEscaper.Replace(b.ID))
26 }
27 buf.WriteByte('>')
28 b.Text.PrintHTML(buf)
29 fmt.Fprintf(buf, "</h%d>\n", b.Level)
30 }
31
32 func (b *Heading) printMarkdown(buf *bytes.Buffer, s mdState) {
33
34 buf.WriteString(s.prefix)
35 for i := 0; i < b.Level; i++ {
36 buf.WriteByte('#')
37 }
38 buf.WriteByte(' ')
39
40 s.prefix = ""
41 b.Text.printMarkdown(buf, s)
42 if b.ID != "" {
43
44
45 buf.Truncate(buf.Len() - 1)
46 fmt.Fprintf(buf, " {#%s}\n", b.ID)
47 }
48 }
49
50 func newATXHeading(p *parseState, s line) (line, bool) {
51 peek := s
52 var n int
53 if peek.trimHeading(&n) {
54 s := peek.string()
55 s = trimRightSpaceTab(s)
56
57 if t := strings.TrimRight(s, "#"); t != trimRightSpaceTab(t) || t == "" {
58 s = t
59 }
60 var id string
61 if p.HeadingIDs {
62
63
64
65
66
67 id, s = extractID(p, s)
68
69
70 for _, c := range id {
71 if c >= 0x80 || !isLetterDigit(byte(c)) {
72 p.corner = true
73 }
74 }
75 }
76 pos := Position{p.lineno, p.lineno}
77 p.doneBlock(&Heading{pos, n, p.newText(pos, s), id})
78 return line{}, true
79 }
80 return s, false
81 }
82
83
84
85
86
87
88 func extractID(p *parseState, s string) (id, s2 string) {
89 i := strings.LastIndexByte(s, '{')
90 if i < 0 {
91 return "", s
92 }
93 if i+1 >= len(s) || s[i+1] != '#' {
94 p.corner = true
95 return "", s
96 }
97 j := i + strings.IndexByte(s[i:], '}')
98 if j < 0 || trimRightSpaceTab(s[j+1:]) != "" {
99 return "", s
100 }
101 id = strings.TrimSpace(s[i+2 : j])
102 if id == "" {
103 p.corner = true
104 return "", s
105 }
106 return s[i+2 : j], s[:i]
107 }
108
109 func newSetextHeading(p *parseState, s line) (line, bool) {
110 var n int
111 peek := s
112 if p.nextB() == p.para() && peek.trimSetext(&n) {
113 p.closeBlock()
114 para, ok := p.last().(*Paragraph)
115 if !ok {
116 return s, false
117 }
118 p.deleteLast()
119 p.doneBlock(&Heading{Position{para.StartLine, p.lineno}, n, para.Text, ""})
120 return line{}, true
121 }
122 return s, false
123 }
124
125 func (s *line) trimHeading(width *int) bool {
126 t := *s
127 t.trimSpace(0, 3, false)
128 if !t.trim('#') {
129 return false
130 }
131 n := 1
132 for n < 6 && t.trim('#') {
133 n++
134 }
135 if !t.trimSpace(1, 1, true) {
136 return false
137 }
138 *width = n
139 *s = t
140 return true
141 }
142
143 func (s *line) trimSetext(n *int) bool {
144 t := *s
145 t.trimSpace(0, 3, false)
146 c := t.peek()
147 if c == '-' || c == '=' {
148 for t.trim(c) {
149 }
150 t.skipSpace()
151 if t.eof() {
152 if c == '=' {
153 *n = 1
154 } else {
155 *n = 2
156 }
157 return true
158 }
159 }
160 return false
161 }
162
View as plain text