...

Source file src/github.com/yuin/goldmark/parser/attribute.go

Documentation: github.com/yuin/goldmark/parser

     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] == '_' ||
    97  				line[i] == '-' || line[i] == ':' || line[i] == '.'); i++ {
    98  		}
    99  		name := attrNameClass
   100  		if c == '#' {
   101  			name = attrNameID
   102  		}
   103  		reader.Advance(i)
   104  		return Attribute{Name: name, Value: line[0:i]}, true
   105  	}
   106  	line, _ := reader.PeekLine()
   107  	if len(line) == 0 {
   108  		return Attribute{}, false
   109  	}
   110  	c = line[0]
   111  	if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
   112  		c == '_' || c == ':') {
   113  		return Attribute{}, false
   114  	}
   115  	i := 0
   116  	for ; i < len(line); i++ {
   117  		c = line[i]
   118  		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
   119  			(c >= '0' && c <= '9') ||
   120  			c == '_' || c == ':' || c == '.' || c == '-') {
   121  			break
   122  		}
   123  	}
   124  	name := line[:i]
   125  	reader.Advance(i)
   126  	reader.SkipSpaces()
   127  	c = reader.Peek()
   128  	if c != '=' {
   129  		return Attribute{}, false
   130  	}
   131  	reader.Advance(1)
   132  	reader.SkipSpaces()
   133  	value, ok := parseAttributeValue(reader)
   134  	if !ok {
   135  		return Attribute{}, false
   136  	}
   137  	if bytes.Equal(name, attrNameClass) {
   138  		if _, ok = value.([]byte); !ok {
   139  			return Attribute{}, false
   140  		}
   141  	}
   142  	return Attribute{Name: name, Value: value}, true
   143  }
   144  
   145  func parseAttributeValue(reader text.Reader) (interface{}, bool) {
   146  	reader.SkipSpaces()
   147  	c := reader.Peek()
   148  	var value interface{}
   149  	var ok bool
   150  	switch c {
   151  	case text.EOF:
   152  		return Attribute{}, false
   153  	case '{':
   154  		value, ok = ParseAttributes(reader)
   155  	case '[':
   156  		value, ok = parseAttributeArray(reader)
   157  	case '"':
   158  		value, ok = parseAttributeString(reader)
   159  	default:
   160  		if c == '-' || c == '+' || util.IsNumeric(c) {
   161  			value, ok = parseAttributeNumber(reader)
   162  		} else {
   163  			value, ok = parseAttributeOthers(reader)
   164  		}
   165  	}
   166  	if !ok {
   167  		return nil, false
   168  	}
   169  	return value, true
   170  }
   171  
   172  func parseAttributeArray(reader text.Reader) ([]interface{}, bool) {
   173  	reader.Advance(1) // skip [
   174  	ret := []interface{}{}
   175  	for i := 0; ; i++ {
   176  		c := reader.Peek()
   177  		comma := false
   178  		if i != 0 && c == ',' {
   179  			reader.Advance(1)
   180  			comma = true
   181  		}
   182  		if c == ']' {
   183  			if !comma {
   184  				reader.Advance(1)
   185  				return ret, true
   186  			}
   187  			return nil, false
   188  		}
   189  		reader.SkipSpaces()
   190  		value, ok := parseAttributeValue(reader)
   191  		if !ok {
   192  			return nil, false
   193  		}
   194  		ret = append(ret, value)
   195  		reader.SkipSpaces()
   196  	}
   197  }
   198  
   199  func parseAttributeString(reader text.Reader) ([]byte, bool) {
   200  	reader.Advance(1) // skip "
   201  	line, _ := reader.PeekLine()
   202  	i := 0
   203  	l := len(line)
   204  	var buf bytes.Buffer
   205  	for i < l {
   206  		c := line[i]
   207  		if c == '\\' && i != l-1 {
   208  			n := line[i+1]
   209  			switch n {
   210  			case '"', '/', '\\':
   211  				buf.WriteByte(n)
   212  				i += 2
   213  			case 'b':
   214  				buf.WriteString("\b")
   215  				i += 2
   216  			case 'f':
   217  				buf.WriteString("\f")
   218  				i += 2
   219  			case 'n':
   220  				buf.WriteString("\n")
   221  				i += 2
   222  			case 'r':
   223  				buf.WriteString("\r")
   224  				i += 2
   225  			case 't':
   226  				buf.WriteString("\t")
   227  				i += 2
   228  			default:
   229  				buf.WriteByte('\\')
   230  				i++
   231  			}
   232  			continue
   233  		}
   234  		if c == '"' {
   235  			reader.Advance(i + 1)
   236  			return buf.Bytes(), true
   237  		}
   238  		buf.WriteByte(c)
   239  		i++
   240  	}
   241  	return nil, false
   242  }
   243  
   244  func scanAttributeDecimal(reader text.Reader, w io.ByteWriter) {
   245  	for {
   246  		c := reader.Peek()
   247  		if util.IsNumeric(c) {
   248  			_ = w.WriteByte(c)
   249  		} else {
   250  			return
   251  		}
   252  		reader.Advance(1)
   253  	}
   254  }
   255  
   256  func parseAttributeNumber(reader text.Reader) (float64, bool) {
   257  	sign := 1
   258  	c := reader.Peek()
   259  	if c == '-' {
   260  		sign = -1
   261  		reader.Advance(1)
   262  	} else if c == '+' {
   263  		reader.Advance(1)
   264  	}
   265  	var buf bytes.Buffer
   266  	if !util.IsNumeric(reader.Peek()) {
   267  		return 0, false
   268  	}
   269  	scanAttributeDecimal(reader, &buf)
   270  	if buf.Len() == 0 {
   271  		return 0, false
   272  	}
   273  	c = reader.Peek()
   274  	if c == '.' {
   275  		buf.WriteByte(c)
   276  		reader.Advance(1)
   277  		scanAttributeDecimal(reader, &buf)
   278  	}
   279  	c = reader.Peek()
   280  	if c == 'e' || c == 'E' {
   281  		buf.WriteByte(c)
   282  		reader.Advance(1)
   283  		c = reader.Peek()
   284  		if c == '-' || c == '+' {
   285  			buf.WriteByte(c)
   286  			reader.Advance(1)
   287  		}
   288  		scanAttributeDecimal(reader, &buf)
   289  	}
   290  	f, err := strconv.ParseFloat(buf.String(), 64)
   291  	if err != nil {
   292  		return 0, false
   293  	}
   294  	return float64(sign) * f, true
   295  }
   296  
   297  var bytesTrue = []byte("true")
   298  var bytesFalse = []byte("false")
   299  var bytesNull = []byte("null")
   300  
   301  func parseAttributeOthers(reader text.Reader) (interface{}, bool) {
   302  	line, _ := reader.PeekLine()
   303  	c := line[0]
   304  	if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
   305  		c == '_' || c == ':') {
   306  		return nil, false
   307  	}
   308  	i := 0
   309  	for ; i < len(line); i++ {
   310  		c := line[i]
   311  		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
   312  			(c >= '0' && c <= '9') ||
   313  			c == '_' || c == ':' || c == '.' || c == '-') {
   314  			break
   315  		}
   316  	}
   317  	value := line[:i]
   318  	reader.Advance(i)
   319  	if bytes.Equal(value, bytesTrue) {
   320  		return true, true
   321  	}
   322  	if bytes.Equal(value, bytesFalse) {
   323  		return false, true
   324  	}
   325  	if bytes.Equal(value, bytesNull) {
   326  		return nil, true
   327  	}
   328  	return value, true
   329  }
   330  

View as plain text