gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

attribute.go (7080B)


      1 package parser
      2 
      3 import (
      4 	"bytes"
      5 	"io"
      6 	"strconv"
      7 
      8 	"github.com/yuin/goldmark/text"
      9 	"github.com/yuin/goldmark/util"
     10 )
     11 
     12 var attrNameID = []byte("id")
     13 var attrNameClass = []byte("class")
     14 
     15 // An Attribute is an attribute of the markdown elements
     16 type Attribute struct {
     17 	Name  []byte
     18 	Value interface{}
     19 }
     20 
     21 // An Attributes is a collection of attributes.
     22 type Attributes []Attribute
     23 
     24 // Find returns a (value, true) if an attribute correspond with given name is found, otherwise (nil, false).
     25 func (as Attributes) Find(name []byte) (interface{}, bool) {
     26 	for _, a := range as {
     27 		if bytes.Equal(a.Name, name) {
     28 			return a.Value, true
     29 		}
     30 	}
     31 	return nil, false
     32 }
     33 
     34 func (as Attributes) findUpdate(name []byte, cb func(v interface{}) interface{}) bool {
     35 	for i, a := range as {
     36 		if bytes.Equal(a.Name, name) {
     37 			as[i].Value = cb(a.Value)
     38 			return true
     39 		}
     40 	}
     41 	return false
     42 }
     43 
     44 // ParseAttributes parses attributes into a map.
     45 // ParseAttributes returns a parsed attributes and true if could parse
     46 // attributes, otherwise nil and false.
     47 func ParseAttributes(reader text.Reader) (Attributes, bool) {
     48 	savedLine, savedPosition := reader.Position()
     49 	reader.SkipSpaces()
     50 	if reader.Peek() != '{' {
     51 		reader.SetPosition(savedLine, savedPosition)
     52 		return nil, false
     53 	}
     54 	reader.Advance(1)
     55 	attrs := Attributes{}
     56 	for {
     57 		if reader.Peek() == '}' {
     58 			reader.Advance(1)
     59 			return attrs, true
     60 		}
     61 		attr, ok := parseAttribute(reader)
     62 		if !ok {
     63 			reader.SetPosition(savedLine, savedPosition)
     64 			return nil, false
     65 		}
     66 		if bytes.Equal(attr.Name, attrNameClass) {
     67 			if !attrs.findUpdate(attrNameClass, func(v interface{}) interface{} {
     68 				ret := make([]byte, 0, len(v.([]byte))+1+len(attr.Value.([]byte)))
     69 				ret = append(ret, v.([]byte)...)
     70 				return append(append(ret, ' '), attr.Value.([]byte)...)
     71 			}) {
     72 				attrs = append(attrs, attr)
     73 			}
     74 		} else {
     75 			attrs = append(attrs, attr)
     76 		}
     77 		reader.SkipSpaces()
     78 		if reader.Peek() == ',' {
     79 			reader.Advance(1)
     80 			reader.SkipSpaces()
     81 		}
     82 	}
     83 }
     84 
     85 func parseAttribute(reader text.Reader) (Attribute, bool) {
     86 	reader.SkipSpaces()
     87 	c := reader.Peek()
     88 	if c == '#' || c == '.' {
     89 		reader.Advance(1)
     90 		line, _ := reader.PeekLine()
     91 		i := 0
     92 		// HTML5 allows any kind of characters as id, but XHTML restricts characters for id.
     93 		// CommonMark is basically defined for XHTML(even though it is legacy).
     94 		// So we restrict id characters.
     95 		for ; i < len(line) && !util.IsSpace(line[i]) &&
     96 			(!util.IsPunct(line[i]) || line[i] == '_' || line[i] == '-' || line[i] == ':' || line[i] == '.'); i++ {
     97 		}
     98 		name := attrNameClass
     99 		if c == '#' {
    100 			name = attrNameID
    101 		}
    102 		reader.Advance(i)
    103 		return Attribute{Name: name, Value: line[0:i]}, true
    104 	}
    105 	line, _ := reader.PeekLine()
    106 	if len(line) == 0 {
    107 		return Attribute{}, false
    108 	}
    109 	c = line[0]
    110 	if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
    111 		c == '_' || c == ':') {
    112 		return Attribute{}, false
    113 	}
    114 	i := 0
    115 	for ; i < len(line); i++ {
    116 		c = line[i]
    117 		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
    118 			(c >= '0' && c <= '9') ||
    119 			c == '_' || c == ':' || c == '.' || c == '-') {
    120 			break
    121 		}
    122 	}
    123 	name := line[:i]
    124 	reader.Advance(i)
    125 	reader.SkipSpaces()
    126 	c = reader.Peek()
    127 	if c != '=' {
    128 		return Attribute{}, false
    129 	}
    130 	reader.Advance(1)
    131 	reader.SkipSpaces()
    132 	value, ok := parseAttributeValue(reader)
    133 	if !ok {
    134 		return Attribute{}, false
    135 	}
    136 	if bytes.Equal(name, attrNameClass) {
    137 		if _, ok = value.([]byte); !ok {
    138 			return Attribute{}, false
    139 		}
    140 	}
    141 	return Attribute{Name: name, Value: value}, true
    142 }
    143 
    144 func parseAttributeValue(reader text.Reader) (interface{}, bool) {
    145 	reader.SkipSpaces()
    146 	c := reader.Peek()
    147 	var value interface{}
    148 	ok := false
    149 	switch c {
    150 	case text.EOF:
    151 		return Attribute{}, false
    152 	case '{':
    153 		value, ok = ParseAttributes(reader)
    154 	case '[':
    155 		value, ok = parseAttributeArray(reader)
    156 	case '"':
    157 		value, ok = parseAttributeString(reader)
    158 	default:
    159 		if c == '-' || c == '+' || util.IsNumeric(c) {
    160 			value, ok = parseAttributeNumber(reader)
    161 		} else {
    162 			value, ok = parseAttributeOthers(reader)
    163 		}
    164 	}
    165 	if !ok {
    166 		return nil, false
    167 	}
    168 	return value, true
    169 }
    170 
    171 func parseAttributeArray(reader text.Reader) ([]interface{}, bool) {
    172 	reader.Advance(1) // skip [
    173 	ret := []interface{}{}
    174 	for i := 0; ; i++ {
    175 		c := reader.Peek()
    176 		comma := false
    177 		if i != 0 && c == ',' {
    178 			reader.Advance(1)
    179 			comma = true
    180 		}
    181 		if c == ']' {
    182 			if !comma {
    183 				reader.Advance(1)
    184 				return ret, true
    185 			}
    186 			return nil, false
    187 		}
    188 		reader.SkipSpaces()
    189 		value, ok := parseAttributeValue(reader)
    190 		if !ok {
    191 			return nil, false
    192 		}
    193 		ret = append(ret, value)
    194 		reader.SkipSpaces()
    195 	}
    196 }
    197 
    198 func parseAttributeString(reader text.Reader) ([]byte, bool) {
    199 	reader.Advance(1) // skip "
    200 	line, _ := reader.PeekLine()
    201 	i := 0
    202 	l := len(line)
    203 	var buf bytes.Buffer
    204 	for i < l {
    205 		c := line[i]
    206 		if c == '\\' && i != l-1 {
    207 			n := line[i+1]
    208 			switch n {
    209 			case '"', '/', '\\':
    210 				buf.WriteByte(n)
    211 				i += 2
    212 			case 'b':
    213 				buf.WriteString("\b")
    214 				i += 2
    215 			case 'f':
    216 				buf.WriteString("\f")
    217 				i += 2
    218 			case 'n':
    219 				buf.WriteString("\n")
    220 				i += 2
    221 			case 'r':
    222 				buf.WriteString("\r")
    223 				i += 2
    224 			case 't':
    225 				buf.WriteString("\t")
    226 				i += 2
    227 			default:
    228 				buf.WriteByte('\\')
    229 				i++
    230 			}
    231 			continue
    232 		}
    233 		if c == '"' {
    234 			reader.Advance(i + 1)
    235 			return buf.Bytes(), true
    236 		}
    237 		buf.WriteByte(c)
    238 		i++
    239 	}
    240 	return nil, false
    241 }
    242 
    243 func scanAttributeDecimal(reader text.Reader, w io.ByteWriter) {
    244 	for {
    245 		c := reader.Peek()
    246 		if util.IsNumeric(c) {
    247 			w.WriteByte(c)
    248 		} else {
    249 			return
    250 		}
    251 		reader.Advance(1)
    252 	}
    253 }
    254 
    255 func parseAttributeNumber(reader text.Reader) (float64, bool) {
    256 	sign := 1
    257 	c := reader.Peek()
    258 	if c == '-' {
    259 		sign = -1
    260 		reader.Advance(1)
    261 	} else if c == '+' {
    262 		reader.Advance(1)
    263 	}
    264 	var buf bytes.Buffer
    265 	if !util.IsNumeric(reader.Peek()) {
    266 		return 0, false
    267 	}
    268 	scanAttributeDecimal(reader, &buf)
    269 	if buf.Len() == 0 {
    270 		return 0, false
    271 	}
    272 	c = reader.Peek()
    273 	if c == '.' {
    274 		buf.WriteByte(c)
    275 		reader.Advance(1)
    276 		scanAttributeDecimal(reader, &buf)
    277 	}
    278 	c = reader.Peek()
    279 	if c == 'e' || c == 'E' {
    280 		buf.WriteByte(c)
    281 		reader.Advance(1)
    282 		c = reader.Peek()
    283 		if c == '-' || c == '+' {
    284 			buf.WriteByte(c)
    285 			reader.Advance(1)
    286 		}
    287 		scanAttributeDecimal(reader, &buf)
    288 	}
    289 	f, err := strconv.ParseFloat(buf.String(), 10)
    290 	if err != nil {
    291 		return 0, false
    292 	}
    293 	return float64(sign) * f, true
    294 }
    295 
    296 var bytesTrue = []byte("true")
    297 var bytesFalse = []byte("false")
    298 var bytesNull = []byte("null")
    299 
    300 func parseAttributeOthers(reader text.Reader) (interface{}, bool) {
    301 	line, _ := reader.PeekLine()
    302 	c := line[0]
    303 	if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
    304 		c == '_' || c == ':') {
    305 		return nil, false
    306 	}
    307 	i := 0
    308 	for ; i < len(line); i++ {
    309 		c := line[i]
    310 		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
    311 			(c >= '0' && c <= '9') ||
    312 			c == '_' || c == ':' || c == '.' || c == '-') {
    313 			break
    314 		}
    315 	}
    316 	value := line[:i]
    317 	reader.Advance(i)
    318 	if bytes.Equal(value, bytesTrue) {
    319 		return true, true
    320 	}
    321 	if bytes.Equal(value, bytesFalse) {
    322 		return false, true
    323 	}
    324 	if bytes.Equal(value, bytesNull) {
    325 		return nil, true
    326 	}
    327 	return value, true
    328 }