github.com/golazy/golazy@v0.0.7-0.20221012133820-968fe65a0b65/lazyview/nodes/element.go (about) 1 package nodes 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 8 "github.com/golazy/golazy/lazysupport" 9 ) 10 11 var Beautify = true 12 13 type Element struct { 14 tag string 15 children []io.WriterTo 16 attributes []Attr // Attr List of element attributes 17 } 18 19 func (r *Element) add(something interface{}) { 20 switch v := something.(type) { 21 case Element: 22 r.children = append(r.children, v) 23 case Raw: 24 r.children = append(r.children, v) 25 case string: 26 r.children = append(r.children, Text(v)) 27 case Attr: 28 r.attributes = append(r.attributes, v) 29 case []Element: 30 for _, arg := range v { 31 r.add(arg) 32 } 33 case []io.WriterTo: 34 for _, arg := range v { 35 r.add(arg) 36 } 37 case []interface{}: 38 for _, arg := range v { 39 r.add(arg) 40 } 41 case io.WriterTo: 42 r.children = append(r.children, v) 43 case nil: 44 45 default: 46 panic(fmt.Errorf("don't recognize that: %#v", v)) 47 } 48 } 49 50 type writeSession struct { 51 io.Writer 52 n int64 53 err error 54 level int 55 } 56 57 func (w *writeSession) NewLine() { 58 if Beautify { 59 w.WriteS("\n") 60 } 61 } 62 63 func (w *writeSession) WriteS(s string) (n int, err error) { 64 return w.Write([]byte(s)) 65 } 66 67 func (w *writeSession) Write(data []byte) (n int, err error) { 68 if w.err != nil { 69 return 70 } 71 n, err = w.Writer.Write(data) 72 w.n += int64(n) 73 w.err = err 74 return 75 } 76 77 // voidElements don't require a closing tag neither need to be self close 78 var voidElements = lazysupport.NewSet( 79 "area", 80 "base", 81 "br", 82 "col", 83 "embed", 84 "hr", 85 "img", 86 "input", 87 "keygen", 88 "link", 89 "meta", 90 "param", 91 "source", 92 "track", 93 "wbr", 94 ) 95 96 // html elements that don't require a closing tag 97 var skipCloseTag = lazysupport.NewSet( 98 "html", 99 "head", 100 "body", 101 "p", 102 "li", 103 "dt", 104 "dd", 105 "option", 106 "thead", 107 "th", 108 "tbody", 109 "tr", 110 "td", 111 "tfoot", 112 "colgroup", 113 ) 114 115 // https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements 116 var inlineElements = lazysupport.NewSet( 117 "a", 118 "abbr", 119 "acronym", 120 "audio", 121 "b", 122 "bdi", 123 "bdo", 124 "big", 125 "br", 126 "button", 127 "canvas", 128 "cite", 129 "code", 130 "data", 131 "datalist", 132 "del", 133 "dfn", 134 "em", 135 "embed", 136 "i", 137 "iframe", 138 "img", 139 "input", 140 "ins", 141 "kbd", 142 "label", 143 "map", 144 "mark", 145 "meter", 146 "noscript", 147 "object", 148 "output", 149 "picture", 150 "progress", 151 "q", 152 "ruby", 153 "s", 154 "samp", 155 "script", 156 "select", 157 "slot", 158 "small", 159 "span", 160 "strong", 161 "sub", 162 "sup", 163 "svg", 164 "template", 165 "textarea", 166 "time", 167 "u", 168 "tt", 169 "var", 170 "video", 171 "wbr", 172 // Plus some that are not styled like the ones in head 173 "title", 174 "meta", 175 // Plus some that are rendered as block by usually formated as onelines 176 "h1", 177 "h2", 178 "h3", 179 "h4", 180 "h5", 181 "h6", 182 "h7", 183 ) 184 185 func (r Element) isInline() bool { 186 for _, e := range r.children { 187 switch child := e.(type) { 188 case Element: 189 if !child.isInline() { 190 return false 191 } 192 case Text: 193 default: 194 return false 195 } 196 } 197 return inlineElements.Has(r.tag) 198 } 199 200 // Rule to render a the content of a tag inline 201 // The tag is title, p, b, strong, i, em,li or there are no Elements inside 202 203 func (r Element) writeOpenTag(session *writeSession) { 204 if Beautify { 205 for i := 0; i < session.level; i++ { 206 session.WriteS(" ") 207 } 208 } 209 if r.tag == "html" { 210 session.WriteS("<!DOCTYPE html>") 211 session.NewLine() 212 } 213 214 // Open tag 215 session.WriteS("<" + r.tag) 216 217 // Process atributes 218 for _, attr := range r.attributes { 219 session.WriteS(" ") 220 attr.WriteTo(session) 221 } 222 session.WriteS(">") 223 } 224 225 // WriteTo writes the current string to the writer w 226 func (r Element) WriteTo(w io.Writer) (n64 int64, err error) { 227 228 var session *writeSession 229 230 if s, ok := w.(*writeSession); ok { 231 session = s 232 } else { 233 session = &writeSession{Writer: w, level: 0} 234 } 235 236 r.writeOpenTag(session) 237 238 if voidElements.Has(r.tag) { 239 return session.n, session.err 240 } 241 242 // Content 243 isInline := r.isInline() 244 if !isInline { 245 session.level = session.level + 1 246 } 247 for _, c := range r.children { 248 if r.tag == "html" { 249 session.NewLine() 250 c.WriteTo(session) 251 continue 252 } 253 if !isInline { 254 session.NewLine() 255 } 256 c.WriteTo(session) 257 } 258 if !isInline { 259 session.NewLine() 260 session.level = session.level - 1 261 } 262 263 // Some elements 264 if skipCloseTag.Has(r.tag) { 265 //session.WriteS("\n") 266 return session.n, session.err 267 } 268 269 // Close tag 270 session.WriteS("</" + r.tag + ">") 271 return session.n, session.err 272 } 273 274 func (r Element) String() string { 275 buf := &strings.Builder{} 276 r.WriteTo(buf) 277 return buf.String() 278 } 279 280 // NewElement creates a new element with the provided tagname and the provided options 281 // The options can be: 282 // 283 // * An Attr that will be render 284 // * A string or Text 285 // * Another Element 286 // * Any WriterTo interface 287 // Attributes are output in order 288 // The rest is output in the same order as received 289 func NewElement(tagname string, options ...interface{}) Element { 290 r := Element{ 291 tag: tagname, 292 } 293 r.add(options) 294 return r 295 }