stevenbooru/vendor/src/github.com/yosssi/ace/html_tag.go

306 lines
5.9 KiB
Go

package ace
import (
"bytes"
"fmt"
"io"
"strings"
)
// Tag names
const (
tagNameDiv = "div"
)
// Attribute names
const (
attributeNameID = "id"
)
// htmlAttribute represents an HTML attribute.
type htmlAttribute struct {
key string
value string
}
// htmlTag represents an HTML tag.
type htmlTag struct {
elementBase
tagName string
id string
classes []string
containPlainText bool
insertBr bool
attributes []htmlAttribute
textValue string
}
// WriteTo writes data to w.
func (e *htmlTag) WriteTo(w io.Writer) (int64, error) {
var bf bytes.Buffer
// Write an open tag.
bf.WriteString(lt)
bf.WriteString(e.tagName)
// Write an id.
if e.id != "" {
bf.WriteString(space)
bf.WriteString(attributeNameID)
bf.WriteString(equal)
bf.WriteString(doubleQuote)
bf.WriteString(e.id)
bf.WriteString(doubleQuote)
}
// Write classes.
if len(e.classes) > 0 {
bf.WriteString(space)
bf.WriteString(e.opts.AttributeNameClass)
bf.WriteString(equal)
bf.WriteString(doubleQuote)
for i, class := range e.classes {
if i > 0 {
bf.WriteString(space)
}
bf.WriteString(class)
}
bf.WriteString(doubleQuote)
}
// Write attributes.
if len(e.attributes) > 0 {
for _, a := range e.attributes {
bf.WriteString(space)
bf.WriteString(a.key)
if a.value != "" {
bf.WriteString(equal)
bf.WriteString(doubleQuote)
bf.WriteString(a.value)
bf.WriteString(doubleQuote)
}
}
}
bf.WriteString(gt)
// Write a text value
if e.textValue != "" {
bf.WriteString(e.textValue)
}
if e.containPlainText {
bf.WriteString(lf)
}
// Write children's HTML.
if i, err := e.writeChildren(&bf); err != nil {
return i, err
}
// Write a close tag.
if !e.noCloseTag() {
bf.WriteString(lt)
bf.WriteString(slash)
bf.WriteString(e.tagName)
bf.WriteString(gt)
}
// Write the buffer.
i, err := w.Write(bf.Bytes())
return int64(i), err
}
// ContainPlainText returns the HTML tag's containPlainText field.
func (e *htmlTag) ContainPlainText() bool {
return e.containPlainText
}
// InsertBr returns true if the br tag is inserted to the line.
func (e *htmlTag) InsertBr() bool {
return e.insertBr
}
// setAttributes parses the tokens and set attributes to the element.
func (e *htmlTag) setAttributes() error {
parsedTokens := e.parseTokens()
var i int
var token string
var setTextValue bool
// Set attributes to the element.
for i, token = range parsedTokens {
kv := strings.Split(token, equal)
if len(kv) < 2 {
setTextValue = true
break
}
k := kv[0]
v := strings.Join(kv[1:], equal)
// Remove the prefix and suffix of the double quotes.
if len(v) > 1 && strings.HasPrefix(v, doubleQuote) && strings.HasSuffix(v, doubleQuote) {
v = v[1 : len(v)-1]
}
switch k {
case attributeNameID:
if e.id != "" {
return fmt.Errorf("multiple IDs are specified [file: %s][line: %d]", e.ln.fileName(), e.ln.no)
}
e.id = v
case e.opts.AttributeNameClass:
e.classes = append(e.classes, strings.Split(v, space)...)
default:
e.attributes = append(e.attributes, htmlAttribute{k, v})
}
}
// Set a text value to the element.
if setTextValue {
e.textValue = strings.Join(parsedTokens[i:], space)
}
return nil
}
// noCloseTag returns true is the HTML tag has no close tag.
func (e *htmlTag) noCloseTag() bool {
for _, name := range e.opts.NoCloseTagNames {
if e.tagName == name {
return true
}
}
return false
}
// newHTMLTag creates and returns an HTML tag.
func newHTMLTag(ln *line, rslt *result, src *source, parent element, opts *Options) (*htmlTag, error) {
if len(ln.tokens) < 1 {
return nil, fmt.Errorf("an HTML tag is not specified [file: %s][line: %d]", ln.fileName(), ln.no)
}
s := ln.tokens[0]
tagName := extractTagName(s)
id, err := extractID(s, ln)
if err != nil {
return nil, err
}
classes := extractClasses(s)
e := &htmlTag{
elementBase: newElementBase(ln, rslt, src, parent, opts),
tagName: tagName,
id: id,
classes: classes,
containPlainText: strings.HasSuffix(s, dot),
insertBr: strings.HasSuffix(s, doubleDot),
attributes: make([]htmlAttribute, 0, 2),
}
if err := e.setAttributes(); err != nil {
return nil, err
}
return e, nil
}
// extractTag extracts and returns a tag.
func extractTagName(s string) string {
tagName := strings.Split(strings.Split(s, sharp)[0], dot)[0]
if tagName == "" {
tagName = tagNameDiv
}
return tagName
}
// extractID extracts and returns an ID.
func extractID(s string, ln *line) (string, error) {
tokens := strings.Split(s, sharp)
l := len(tokens)
if l < 2 {
return "", nil
}
if l > 2 {
return "", fmt.Errorf("multiple IDs are specified [file: %s][line: %d]", ln.fileName(), ln.no)
}
return strings.Split(tokens[1], dot)[0], nil
}
// extractClasses extracts and returns classes.
func extractClasses(s string) []string {
var classes []string
for i, token := range strings.Split(s, dot) {
if i == 0 {
continue
}
class := strings.Split(token, sharp)[0]
if class == "" {
continue
}
classes = append(classes, class)
}
return classes
}
// parseTokens parses the tokens and return them
func (e *htmlTag) parseTokens() []string {
var inQuote bool
var inDelim bool
var tokens []string
var token string
str := strings.Join(e.ln.tokens[1:], space)
for _, chr := range str {
switch c := string(chr); c {
case space:
if inQuote || inDelim {
token += c
} else {
tokens = append(tokens, token)
token = ""
}
case doubleQuote:
if !inDelim {
if inQuote {
inQuote = false
} else {
inQuote = true
}
}
token += c
default:
token += c
if inDelim {
if strings.HasSuffix(token, e.opts.DelimRight) {
inDelim = false
}
} else {
if strings.HasSuffix(token, e.opts.DelimLeft) {
inDelim = true
}
}
}
}
if len(token) > 0 {
tokens = append(tokens, token)
}
return tokens
}