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 }