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