attribute.go

  1package parser
  2
  3import (
  4	"bytes"
  5
  6	"github.com/gomarkdown/markdown/ast"
  7)
  8
  9// attribute parses a (potential) block attribute and adds it to p.
 10func (p *Parser) attribute(data []byte) []byte {
 11	if len(data) < 3 {
 12		return data
 13	}
 14	i := 0
 15	if data[i] != '{' {
 16		return data
 17	}
 18	i++
 19
 20	// last character must be a } otherwise it's not an attribute
 21	end := skipUntilChar(data, i, '\n')
 22	if data[end-1] != '}' {
 23		return data
 24	}
 25
 26	i = skipSpace(data, i)
 27	b := &ast.Attribute{Attrs: make(map[string][]byte)}
 28
 29	esc := false
 30	quote := false
 31	trail := 0
 32Loop:
 33	for ; i < len(data); i++ {
 34		switch data[i] {
 35		case ' ', '\t', '\f', '\v':
 36			if quote {
 37				continue
 38			}
 39			chunk := data[trail+1 : i]
 40			if len(chunk) == 0 {
 41				trail = i
 42				continue
 43			}
 44			switch {
 45			case chunk[0] == '.':
 46				b.Classes = append(b.Classes, chunk[1:])
 47			case chunk[0] == '#':
 48				b.ID = chunk[1:]
 49			default:
 50				k, v := keyValue(chunk)
 51				if k != nil && v != nil {
 52					b.Attrs[string(k)] = v
 53				} else {
 54					// this is illegal in an attribute
 55					return data
 56				}
 57			}
 58			trail = i
 59		case '"':
 60			if esc {
 61				esc = !esc
 62				continue
 63			}
 64			quote = !quote
 65		case '\\':
 66			esc = !esc
 67		case '}':
 68			if esc {
 69				esc = !esc
 70				continue
 71			}
 72			chunk := data[trail+1 : i]
 73			if len(chunk) == 0 {
 74				return data
 75			}
 76			switch {
 77			case chunk[0] == '.':
 78				b.Classes = append(b.Classes, chunk[1:])
 79			case chunk[0] == '#':
 80				b.ID = chunk[1:]
 81			default:
 82				k, v := keyValue(chunk)
 83				if k != nil && v != nil {
 84					b.Attrs[string(k)] = v
 85				} else {
 86					return data
 87				}
 88			}
 89			i++
 90			break Loop
 91		default:
 92			esc = false
 93		}
 94	}
 95
 96	p.attr = b
 97	return data[i:]
 98}
 99
100// key="value" quotes are mandatory.
101func keyValue(data []byte) ([]byte, []byte) {
102	chunk := bytes.SplitN(data, []byte{'='}, 2)
103	if len(chunk) != 2 {
104		return nil, nil
105	}
106	key := chunk[0]
107	value := chunk[1]
108
109	if len(value) < 3 || len(key) == 0 {
110		return nil, nil
111	}
112	if value[0] != '"' || value[len(value)-1] != '"' {
113		return key, nil
114	}
115	return key, value[1 : len(value)-1]
116}