...

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] == '_' || 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  }
   329  

View as plain text