// IAL implements inline attributes.
package mmark
import (
"bytes"
"sort"
)
// One or more of these can be attached to block elements
type inlineAttr struct {
id string // #id
class map[string]bool // 0 or more .class
attr map[string]string // key=value pairs
}
func newInlineAttr() *inlineAttr {
return &inlineAttr{class: make(map[string]bool), attr: make(map[string]string)}
}
// Parsing and thus detecting an IAL.
// IAL can have #id, .class or key=value element seperated by spaces, that may be escaped
func (p *parser) isInlineAttr(data []byte) int {
esc := false
quote := false
ialB := 0
ial := newInlineAttr()
for i := 0; i < len(data); i++ {
switch data[i] {
case ' ':
if quote {
continue
}
chunk := data[ialB+1 : i]
if len(chunk) == 0 {
ialB = i
continue
}
switch {
case chunk[0] == '.':
ial.class[string(chunk[1:])] = true
case chunk[0] == '#':
ial.id = string(chunk[1:])
default:
k, v := parseKeyValue(chunk)
if k != "" {
ial.attr[k] = v
} else {
// this is illegal in an IAL, discard the posibility
return 0
}
}
ialB = i
case '"':
if esc {
esc = !esc
continue
}
quote = !quote
case '\\':
esc = !esc
case '}':
if esc {
esc = !esc
continue
}
chunk := data[ialB+1 : i]
if len(chunk) == 0 {
return i + 1
}
switch {
case chunk[0] == '.':
ial.class[string(chunk[1:])] = true
case chunk[0] == '#':
ial.id = string(chunk[1:])
default:
k, v := parseKeyValue(chunk)
if k != "" {
ial.attr[k] = v
} else {
// this is illegal in an IAL, discard the posibility
return 0
}
}
p.ial = p.ial.add(ial)
return i + 1
default:
esc = false
}
}
return 0
}
func parseKeyValue(chunk []byte) (string, string) {
chunks := bytes.SplitN(chunk, []byte{'='}, 2)
if len(chunks) != 2 {
return "", ""
}
chunks[1] = bytes.Replace(chunks[1], []byte{'"'}, nil, -1)
return string(chunks[0]), string(chunks[1])
}
// Add IAL to another, overwriting the #id, collapsing classes and attributes
func (i *inlineAttr) add(j *inlineAttr) *inlineAttr {
if i == nil {
return j
}
if j.id != "" {
i.id = j.id
}
for k, c := range j.class {
i.class[k] = c
}
for k, a := range j.attr {
i.attr[k] = a
}
return i
}
func (i *inlineAttr) SetAttr(key, value string) {
i.attr[key] = value
}
func (i *inlineAttr) SortClasses() []string {
keys := make([]string, 0, len(i.class))
for k := range i.class {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
func (i *inlineAttr) SortAttributes() []string {
keys := make([]string, 0, len(i.attr))
for k := range i.attr {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// GetOrDefaultAttr sets the value under key if is is not set or
// use the value already in there. The boolean returns indicates
// if the value has been overwritten.
func (i *inlineAttr) GetOrDefaultAttr(key, def string) bool {
v := i.attr[key]
if v != "" {
return false
}
if def == "" {
return false
}
i.attr[key] = def
return true
}
// GetOrDefaulClass sets the class class. The boolean returns indicates
// if the value has been overwritten.
func (i *inlineAttr) GetOrDefaultClass(class string) bool {
_, ok := i.class[class]
i.class[class] = true
return ok
}
// GetOrDefaultID sets the id in i if it is not set. The boolean
// indicates if the id as set in i.
func (i *inlineAttr) GetOrDefaultId(id string) bool {
if i.id != "" {
return false
}
if id == "" {
return false
}
i.id = id
return true
}
// This returning a " " is not particularly nice...
// Key returns the value of a specific key as a ' key="value"' string. If not found
// an string containing a space is returned.
func (i *inlineAttr) Key(key string) string {
if v, ok := i.attr[key]; ok {
return " " + key + "=\"" + v + "\""
}
return " "
}
// Value returns the value of a specific key as value. If not found
// an empty string is returned. TODO(miek): should be " " or change Key() above.
func (i *inlineAttr) Value(key string) string {
if v, ok := i.attr[key]; ok {
return v
}
return ""
}
// DropAttr will drop the attribute under key from i.
// The returned boolean indicates if the key was found in i.
func (i *inlineAttr) DropAttr(key string) bool {
_, ok := i.attr[key]
delete(i.attr, key)
return ok
}
// KeepAttr will drop all attributes, except the ones listed under keys.
func (i *inlineAttr) KeepAttr(keys []string) {
newattr := make(map[string]string)
for _, k := range keys {
if v, ok := i.attr[k]; ok {
newattr[k] = v
}
}
i.attr = newattr
}
// KeepClass will drop all classes, except the ones listed under keys.
func (i *inlineAttr) KeepClass(keys []string) {
newclass := make(map[string]bool)
for _, k := range keys {
if v, ok := i.class[k]; ok {
newclass[k] = v
}
}
i.class = newclass
}