|
- package parser
-
- import (
- "bytes"
- "io"
- "strconv"
-
- "github.com/yuin/goldmark/text"
- "github.com/yuin/goldmark/util"
- )
-
- var attrNameID = []byte("id")
- var attrNameClass = []byte("class")
-
- // An Attribute is an attribute of the markdown elements
- type Attribute struct {
- Name []byte
- Value interface{}
- }
-
- // An Attributes is a collection of attributes.
- type Attributes []Attribute
-
- // Find returns a (value, true) if an attribute correspond with given name is found, otherwise (nil, false).
- 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
- }
-
- // ParseAttributes parses attributes into a map.
- // ParseAttributes returns a parsed attributes and true if could parse
- // attributes, otherwise nil and 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] == '-'); 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) // 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 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
- }
|