package parser import ( "bytes" "fmt" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" "strconv" ) type attribute struct { Name string Value interface{} } // ParseAttributes parses attributes into a map. // ParseAttributes returns a parsed map and true if could parse // attributes, otherwise nil and false. func ParseAttributes(reader text.Reader) (map[string]interface{}, bool) { savedLine, savedPosition := reader.Position() reader.SkipSpaces() if reader.Peek() != '{' { reader.SetPosition(savedLine, savedPosition) return nil, false } reader.Advance(1) m := map[string]interface{}{} for { if reader.Peek() == '}' { reader.Advance(1) return m, true } attr, ok := parseAttribute(reader) if !ok { reader.SetPosition(savedLine, savedPosition) return nil, false } if attr.Name == "class" { if v, ok := m["class"]; ok { if _, ok2 := v.([][]byte); !ok2 { m["class"] = [][]byte{v.([]byte)} } m["class"] = append(m["class"].([][]byte), util.StringToReadOnlyBytes(fmt.Sprintf("%v", attr.Value))) } else { m["class"] = util.StringToReadOnlyBytes(fmt.Sprintf("%v", attr.Value)) } } else { m[attr.Name] = attr.Value } 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 for ; i < len(line) && !util.IsSpace(line[i]) && (!util.IsPunct(line[i]) || line[i] == '_' || line[i] == '-'); i++ { } name := "class" if c == '#' { name = "id" } reader.Advance(i) return attribute{Name: name, Value: line[0:i]}, true } line, _ := reader.PeekLine() 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 := string(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 } 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 *bytes.Buffer) { 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 }