-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
package parser
import (
"bytes"
"io"
"strconv"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
var attrNameID = []byte("id")
var attrNameClass = []byte("class")
// An Attribute is an attribute of the markdown elements
type Attribute struct {
Name []byte
Value interface{}
}
// An Attributes is a collection of attributes.
type Attributes []Attribute
// Find returns a (value, true) if an attribute correspond with given name is found, otherwise (nil, false).
func (as Attributes) Find(name []byte) (interface{}, bool) {
for _, a := range as {
if bytes.Equal(a.Name, name) {
return a.Value, true
}
}
return nil, false
}
func (as Attributes) findUpdate(name []byte, cb func(v interface{}) interface{}) bool {
for i, a := range as {
if bytes.Equal(a.Name, name) {
as[i].Value = cb(a.Value)
return true
}
}
return false
}
// ParseAttributes parses attributes into a map.
// ParseAttributes returns a parsed attributes and true if could parse
// attributes, otherwise nil and false.
func ParseAttributes(reader text.Reader) (Attributes, bool) {
savedLine, savedPosition := reader.Position()
reader.SkipSpaces()
if reader.Peek() != '{' {
reader.SetPosition(savedLine, savedPosition)
return nil, false
}
reader.Advance(1)
attrs := Attributes{}
for {
if reader.Peek() == '}' {
reader.Advance(1)
return attrs, true
}
attr, ok := parseAttribute(reader)
if !ok {
reader.SetPosition(savedLine, savedPosition)
return nil, false
}
if bytes.Equal(attr.Name, attrNameClass) {
if !attrs.findUpdate(attrNameClass, func(v interface{}) interface{} {
ret := make([]byte, 0, len(v.([]byte))+1+len(attr.Value.([]byte)))
ret = append(ret, v.([]byte)...)
return append(append(ret, ' '), attr.Value.([]byte)...)
}) {
attrs = append(attrs, attr)
}
} else {
attrs = append(attrs, attr)
}
reader.SkipSpaces()
if reader.Peek() == ',' {
reader.Advance(1)
reader.SkipSpaces()
}
}
}
func parseAttribute(reader text.Reader) (Attribute, bool) {
reader.SkipSpaces()
c := reader.Peek()
if c == '#' || c == '.' {
reader.Advance(1)
line, _ := reader.PeekLine()
i := 0
// HTML5 allows any kind of characters as id, but XHTML restricts characters for id.
// CommonMark is basically defined for XHTML(even though it is legacy).
// So we restrict id characters.
for ; i < len(line) && !util.IsSpace(line[i]) &&
(!util.IsPunct(line[i]) || line[i] == '_' || line[i] == '-' || line[i] == ':' || line[i] == '.'); i++ {
}
name := attrNameClass
if c == '#' {
name = attrNameID
}
reader.Advance(i)
return Attribute{Name: name, Value: line[0:i]}, true
}
line, _ := reader.PeekLine()
if len(line) == 0 {
return Attribute{}, false
}
c = line[0]
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
c == '_' || c == ':') {
return Attribute{}, false
}
i := 0
for ; i < len(line); i++ {
c = line[i]
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' || c == ':' || c == '.' || c == '-') {
break
}
}
name := line[:i]
reader.Advance(i)
reader.SkipSpaces()
c = reader.Peek()
if c != '=' {
return Attribute{}, false
}
reader.Advance(1)
reader.SkipSpaces()
value, ok := parseAttributeValue(reader)
if !ok {
return Attribute{}, false
}
if bytes.Equal(name, attrNameClass) {
if _, ok = value.([]byte); !ok {
return Attribute{}, false
}
}
return Attribute{Name: name, Value: value}, true
}
func parseAttributeValue(reader text.Reader) (interface{}, bool) {
reader.SkipSpaces()
c := reader.Peek()
var value interface{}
ok := false
switch c {
case text.EOF:
return Attribute{}, false
case '{':
value, ok = ParseAttributes(reader)
case '[':
value, ok = parseAttributeArray(reader)
case '"':
value, ok = parseAttributeString(reader)
default:
if c == '-' || c == '+' || util.IsNumeric(c) {
value, ok = parseAttributeNumber(reader)
} else {
value, ok = parseAttributeOthers(reader)
}
}
if !ok {
return nil, false
}
return value, true
}
func parseAttributeArray(reader text.Reader) ([]interface{}, bool) {
reader.Advance(1) // skip [
ret := []interface{}{}
for i := 0; ; i++ {
c := reader.Peek()
comma := false
if i != 0 && c == ',' {
reader.Advance(1)
comma = true
}
if c == ']' {
if !comma {
reader.Advance(1)
return ret, true
}
return nil, false
}
reader.SkipSpaces()
value, ok := parseAttributeValue(reader)
if !ok {
return nil, false
}
ret = append(ret, value)
reader.SkipSpaces()
}
}
func parseAttributeString(reader text.Reader) ([]byte, bool) {
reader.Advance(1) // skip "
line, _ := reader.PeekLine()
i := 0
l := len(line)
var buf bytes.Buffer
for i < l {
c := line[i]
if c == '\\' && i != l-1 {
n := line[i+1]
switch n {
case '"', '/', '\\':
buf.WriteByte(n)
i += 2
case 'b':
buf.WriteString("\b")
i += 2
case 'f':
buf.WriteString("\f")
i += 2
case 'n':
buf.WriteString("\n")
i += 2
case 'r':
buf.WriteString("\r")
i += 2
case 't':
buf.WriteString("\t")
i += 2
default:
buf.WriteByte('\\')
i++
}
continue
}
if c == '"' {
reader.Advance(i + 1)
return buf.Bytes(), true
}
buf.WriteByte(c)
i++
}
return nil, false
}
func scanAttributeDecimal(reader text.Reader, w io.ByteWriter) {
for {
c := reader.Peek()
if util.IsNumeric(c) {
w.WriteByte(c)
} else {
return
}
reader.Advance(1)
}
}
func parseAttributeNumber(reader text.Reader) (float64, bool) {
sign := 1
c := reader.Peek()
if c == '-' {
sign = -1
reader.Advance(1)
} else if c == '+' {
reader.Advance(1)
}
var buf bytes.Buffer
if !util.IsNumeric(reader.Peek()) {
return 0, false
}
scanAttributeDecimal(reader, &buf)
if buf.Len() == 0 {
return 0, false
}
c = reader.Peek()
if c == '.' {
buf.WriteByte(c)
reader.Advance(1)
scanAttributeDecimal(reader, &buf)
}
c = reader.Peek()
if c == 'e' || c == 'E' {
buf.WriteByte(c)
reader.Advance(1)
c = reader.Peek()
if c == '-' || c == '+' {
buf.WriteByte(c)
reader.Advance(1)
}
scanAttributeDecimal(reader, &buf)
}
f, err := strconv.ParseFloat(buf.String(), 10)
if err != nil {
return 0, false
}
return float64(sign) * f, true
}
var bytesTrue = []byte("true")
var bytesFalse = []byte("false")
var bytesNull = []byte("null")
func parseAttributeOthers(reader text.Reader) (interface{}, bool) {
line, _ := reader.PeekLine()
c := line[0]
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
c == '_' || c == ':') {
return nil, false
}
i := 0
for ; i < len(line); i++ {
c := line[i]
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' || c == ':' || c == '.' || c == '-') {
break
}
}
value := line[:i]
reader.Advance(i)
if bytes.Equal(value, bytesTrue) {
return true, true
}
if bytes.Equal(value, bytesFalse) {
return false, true
}
if bytes.Equal(value, bytesNull) {
return nil, true
}
return value, true
}