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
for ; i < len (line ) && !util .IsSpace (line [i ]) && (!util .IsPunct (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
}
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 )
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 )
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