mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 22:32:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			329 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			329 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						|
		// HTML5 allows any kind of characters as id, but XHTML restricts characters for id.
 | 
						|
		// CommonMark is basically defined for XHTML(even though it is legacy).
 | 
						|
		// So we restrict id characters.
 | 
						|
		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) // 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(), 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
 | 
						|
}
 |