// 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 }