github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/htmlrender/dom/html.go (about)

     1  package dom
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  )
     7  
     8  type writer struct {
     9  	buf    bytes.Buffer
    10  	stack  []string
    11  	inattr bool
    12  	invoid bool
    13  }
    14  
    15  func NewWriter() Writer { return &writer{} }
    16  
    17  func (w *writer) String() string {
    18  	if len(w.stack) > 0 {
    19  		panic("writing is not finished")
    20  	}
    21  	return w.buf.String()
    22  }
    23  
    24  func (w *writer) Bytes() []byte {
    25  	if len(w.stack) > 0 {
    26  		panic("writing is not finished")
    27  	}
    28  	return w.buf.Bytes()
    29  }
    30  
    31  func (w *writer) Open(tag string) {
    32  	w.CloseAttributes()
    33  
    34  	w.stack = append(w.stack, tag)
    35  	w.invoid = voidElements[tag]
    36  	w.inattr = true
    37  
    38  	w.buf.WriteByte('<')
    39  	w.buf.WriteString(tag)
    40  }
    41  
    42  func (w *writer) CloseAttributes() {
    43  	if !w.inattr {
    44  		return
    45  	}
    46  
    47  	w.inattr = false
    48  	w.buf.WriteByte('>')
    49  }
    50  
    51  func (w *writer) Attr(name, value string) {
    52  	if !w.inattr {
    53  		panic("not in attributes section")
    54  	}
    55  
    56  	w.buf.WriteByte(' ')
    57  	w.buf.WriteString(name)
    58  	w.buf.WriteByte('=')
    59  	w.buf.WriteByte('"')
    60  	w.buf.WriteString(EscapeAttribute(value))
    61  	w.buf.WriteByte('"')
    62  }
    63  
    64  func (w *writer) Text(text string) {
    65  	w.CloseAttributes()
    66  	w.buf.WriteString(EscapeCharData(text))
    67  }
    68  
    69  func (w *writer) UnsafeWrite(text string) {
    70  	w.buf.WriteString(text)
    71  }
    72  
    73  func (w *writer) UnsafeContent(text string) {
    74  	w.CloseAttributes()
    75  	w.buf.WriteString(text)
    76  }
    77  
    78  func (w *writer) Close(tag string) {
    79  	w.CloseAttributes()
    80  
    81  	if len(w.stack) == 0 {
    82  		panic("no unclosed tags")
    83  	}
    84  
    85  	var current string
    86  	n := len(w.stack) - 1
    87  	current, w.stack = w.stack[n], w.stack[:n]
    88  	if current != tag {
    89  		panic(fmt.Sprintf("closing tag %v expected %v", tag, current))
    90  	}
    91  
    92  	w.invoid = (len(w.stack) > 0) && voidElements[w.stack[len(w.stack)-1]]
    93  	// void elements have only a single tag
    94  	if voidElements[tag] {
    95  		return
    96  	}
    97  
    98  	w.buf.WriteString("</")
    99  	w.buf.WriteString(tag)
   100  	w.buf.WriteByte('>')
   101  }
   102  
   103  // Section 12.1.2, "Elements", gives this list of void elements. Void elements
   104  // are those that can't have any contents.
   105  var voidElements = map[string]bool{
   106  	"area":    true,
   107  	"base":    true,
   108  	"br":      true,
   109  	"col":     true,
   110  	"command": true,
   111  	"embed":   true,
   112  	"hr":      true,
   113  	"img":     true,
   114  	"input":   true,
   115  	"keygen":  true,
   116  	"link":    true,
   117  	"meta":    true,
   118  	"param":   true,
   119  	"source":  true,
   120  	"track":   true,
   121  	"wbr":     true,
   122  }