package parser
import (
"bytes"
"io"
"strconv"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
var attrNameID = []byte ("id" )
var attrNameClass = []byte ("class" )
type Attribute struct {
Name []byte
Value interface {}
}
type Attributes []Attribute
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
}
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 ] == '-' || 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 {}
var ok bool
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 (), 64 )
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
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .