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  }