attribute.go (7080B)
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 // An Attribute is an attribute of the markdown elements 16 type Attribute struct { 17 Name []byte 18 Value interface{} 19 } 20 21 // An Attributes is a collection of attributes. 22 type Attributes []Attribute 23 24 // Find returns a (value, true) if an attribute correspond with given name is found, otherwise (nil, false). 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 // ParseAttributes parses attributes into a map. 45 // ParseAttributes returns a parsed attributes and true if could parse 46 // attributes, otherwise nil and false. 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 // HTML5 allows any kind of characters as id, but XHTML restricts characters for id. 93 // CommonMark is basically defined for XHTML(even though it is legacy). 94 // So we restrict id characters. 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) // skip [ 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) // skip " 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 }