mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Add a new attribute parser
This commit is contained in:
parent
23ace7da03
commit
dd1590372c
2 changed files with 293 additions and 0 deletions
2
go.mod
2
go.mod
|
|
@ -1 +1,3 @@
|
||||||
module github.com/yuin/goldmark
|
module github.com/yuin/goldmark
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
|
||||||
291
parser/attribute.go
Normal file
291
parser/attribute.go
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue