github.com/vugu/vugu@v0.3.5/gen/parser-go.go (about)

     1  package gen
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  	"unicode"
    15  
    16  	// "github.com/vugu/vugu/internal/htmlx"
    17  	// "github.com/vugu/vugu/internal/htmlx/atom"
    18  	// "golang.org/x/net/html"
    19  	// "golang.org/x/net/html/atom"
    20  	"github.com/vugu/html"
    21  	"github.com/vugu/html/atom"
    22  	"github.com/vugu/vugu"
    23  )
    24  
    25  // ParserGo is a template parser that emits Go source code that will construct the appropriately wired VGNodes.
    26  type ParserGo struct {
    27  	PackageName string // name of package to use at top of files
    28  	StructType  string // just the struct name, no "*" (replaces ComponentType and DataType)
    29  	// ComponentType string // just the struct name, no "*"
    30  	// DataType      string // just the struct name, no "*"
    31  	OutDir  string // output dir
    32  	OutFile string // output file name with ".go" suffix
    33  
    34  	NoOptimizeStatic bool // set to true to disable optimization of static blocks of HTML into vg-html expressions
    35  	TinyGo           bool // set to true to enable TinyGo compatability changes to the generated code
    36  }
    37  
    38  func gofmt(pgm string) (string, error) {
    39  
    40  	// build up command to run
    41  	cmd := exec.Command("gofmt")
    42  
    43  	// I need to capture output
    44  	var fmtOutput bytes.Buffer
    45  	cmd.Stderr = &fmtOutput
    46  	cmd.Stdout = &fmtOutput
    47  
    48  	// also set up input pipe
    49  	read, write := io.Pipe()
    50  	defer write.Close() // make sure this always gets closed, it is safe to call more than once
    51  	cmd.Stdin = read
    52  
    53  	// copy down environment variables
    54  	cmd.Env = os.Environ()
    55  	// force wasm,js target
    56  	cmd.Env = append(cmd.Env, "GOOS=js")
    57  	cmd.Env = append(cmd.Env, "GOARCH=wasm")
    58  
    59  	// start gofmt
    60  	if err := cmd.Start(); err != nil {
    61  		return pgm, fmt.Errorf("can't run gofmt: %v", err)
    62  	}
    63  
    64  	// stream in the raw source
    65  	if _, err := write.Write([]byte(pgm)); err != nil && err != io.ErrClosedPipe {
    66  		return pgm, fmt.Errorf("gofmt failed: %v", err)
    67  	}
    68  
    69  	write.Close()
    70  
    71  	// wait until gofmt is done
    72  	if err := cmd.Wait(); err != nil {
    73  		return pgm, fmt.Errorf("go fmt error %v; full output: %s", err, string(fmtOutput.Bytes()))
    74  	}
    75  
    76  	return string(fmtOutput.Bytes()), nil
    77  }
    78  
    79  // Parse is an experiment...
    80  // r is the actual input, fname is only used to emit line directives
    81  func (p *ParserGo) Parse(r io.Reader, fname string) error {
    82  
    83  	state := &parseGoState{}
    84  
    85  	inRaw, err := ioutil.ReadAll(r)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	// use a tokenizer to peek at the first element and see if it's an HTML tag
    91  	state.isFullHTML = false
    92  	tmpZ := html.NewTokenizer(bytes.NewReader(inRaw))
    93  	for {
    94  		tt := tmpZ.Next()
    95  		if tt == html.ErrorToken {
    96  			return tmpZ.Err()
    97  		}
    98  		if tt != html.StartTagToken { // skip over non-tags
    99  			continue
   100  		}
   101  		t := tmpZ.Token()
   102  		if t.Data == "html" {
   103  			state.isFullHTML = true
   104  			break
   105  		}
   106  		break
   107  	}
   108  
   109  	// log.Printf("isFullHTML: %v", state.isFullHTML)
   110  
   111  	if state.isFullHTML {
   112  
   113  		n, err := html.Parse(bytes.NewReader(inRaw))
   114  		if err != nil {
   115  			return err
   116  		}
   117  		state.docNodeList = append(state.docNodeList, n) // docNodeList is just this one item
   118  
   119  	} else {
   120  
   121  		nlist, err := html.ParseFragment(bytes.NewReader(inRaw), &html.Node{
   122  			Type:     html.ElementNode,
   123  			DataAtom: atom.Div,
   124  			Data:     "div",
   125  		})
   126  		if err != nil {
   127  			return err
   128  		}
   129  
   130  		// only add elements
   131  		for _, n := range nlist {
   132  			if n.Type != html.ElementNode {
   133  				continue
   134  			}
   135  			// log.Printf("FRAGMENT: %#v", n)
   136  			state.docNodeList = append(state.docNodeList, n)
   137  		}
   138  
   139  	}
   140  
   141  	// run n through the optimizer and convert large chunks of static elements into
   142  	// vg-html attributes, this should provide a significiant performance boost for static HTML
   143  	if !p.NoOptimizeStatic {
   144  		for _, n := range state.docNodeList {
   145  			err = compactNodeTree(n)
   146  			if err != nil {
   147  				return err
   148  			}
   149  		}
   150  	}
   151  
   152  	// log.Printf("parsed document looks like so upon start of parsing:")
   153  	// for i, n := range state.docNodeList {
   154  	// 	var buf bytes.Buffer
   155  	// 	err := html.Render(&buf, n)
   156  	// 	if err != nil {
   157  	// 		return fmt.Errorf("error during debug render: %v", err)
   158  	// 	}
   159  	// 	log.Printf("state.docNodeList[%d]:\n%s", i, buf.Bytes())
   160  	// }
   161  
   162  	err = p.visitOverall(state)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	var buf bytes.Buffer
   168  	// log.Printf("goBuf.Len == %v", goBuf.Len())
   169  	buf.Write(state.goBuf.Bytes())
   170  	buf.Write(state.buildBuf.Bytes())
   171  	buf.Write(state.goBufBottom.Bytes())
   172  
   173  	outPath := filepath.Join(p.OutDir, p.OutFile)
   174  
   175  	fo, err := gofmt(buf.String())
   176  	if err != nil {
   177  
   178  		// if the gofmt errors, we still attempt to write out the non-fmt'ed output to the file, to assist in debugging
   179  		ioutil.WriteFile(outPath, buf.Bytes(), 0644)
   180  
   181  		return err
   182  	}
   183  
   184  	// run the import deduplicator
   185  	var dedupedBuf bytes.Buffer
   186  	err = dedupImports(bytes.NewReader([]byte(fo)), &dedupedBuf, p.OutFile)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	// write to final output file
   192  	err = ioutil.WriteFile(outPath, dedupedBuf.Bytes(), 0644)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  type codeChunk struct {
   201  	Line   int
   202  	Column int
   203  	Code   string
   204  }
   205  
   206  type parseGoState struct {
   207  	isFullHTML  bool         // is the first node an <html> tag
   208  	docNodeList []*html.Node // top level nodes parsed out of source file
   209  	goBuf       bytes.Buffer // additional Go code (at top)
   210  	buildBuf    bytes.Buffer // Build() method Go code (below)
   211  	goBufBottom bytes.Buffer // additional Go code that is put as the very last thing
   212  	// cssChunkList []codeChunk
   213  	// jsChunkList  []codeChunk
   214  	outIsSet bool // set to true when vgout.Out has been set for to the level node
   215  }
   216  
   217  func (p *ParserGo) visitOverall(state *parseGoState) error {
   218  
   219  	fmt.Fprintf(&state.goBuf, "package %s\n\n", p.PackageName)
   220  	fmt.Fprintf(&state.goBuf, "// Code generated by vugu via vugugen. Please regenerate instead of editing or add additional code in a separate file. DO NOT EDIT.\n\n")
   221  	fmt.Fprintf(&state.goBuf, "import %q\n", "fmt")
   222  	fmt.Fprintf(&state.goBuf, "import %q\n", "reflect")
   223  	fmt.Fprintf(&state.goBuf, "import %q\n", "github.com/vugu/vjson")
   224  	fmt.Fprintf(&state.goBuf, "import %q\n", "github.com/vugu/vugu")
   225  	fmt.Fprintf(&state.goBuf, "import js %q\n", "github.com/vugu/vugu/js")
   226  	fmt.Fprintf(&state.goBuf, "\n")
   227  
   228  	// TODO: we use a prefix like "vg" as our namespace; should document that user code should not use that prefix to avoid conflicts
   229  	fmt.Fprintf(&state.buildBuf, "func (c *%s) Build(vgin *vugu.BuildIn) (vgout *vugu.BuildOut) {\n", p.StructType)
   230  	fmt.Fprintf(&state.buildBuf, "    \n")
   231  	fmt.Fprintf(&state.buildBuf, "    vgout = &vugu.BuildOut{}\n")
   232  	fmt.Fprintf(&state.buildBuf, "    \n")
   233  	fmt.Fprintf(&state.buildBuf, "    var vgiterkey interface{}\n")
   234  	fmt.Fprintf(&state.buildBuf, "    _ = vgiterkey\n")
   235  	fmt.Fprintf(&state.buildBuf, "    var vgn *vugu.VGNode\n")
   236  	// fmt.Fprintf(&buildBuf, "    var vgparent *vugu.VGNode\n")
   237  
   238  	// NOTE: Use things that are lightweight here - e.g. don't do var _ = fmt.Sprintf because that brings in all of the
   239  	// (possibly quite large) formatting code, which might otherwise be avoided.
   240  	fmt.Fprintf(&state.goBufBottom, "// 'fix' unused imports\n")
   241  	fmt.Fprintf(&state.goBufBottom, "var _ fmt.Stringer\n")
   242  	fmt.Fprintf(&state.goBufBottom, "var _ reflect.Type\n")
   243  	fmt.Fprintf(&state.goBufBottom, "var _ vjson.RawMessage\n")
   244  	fmt.Fprintf(&state.goBufBottom, "var _ js.Value\n")
   245  	fmt.Fprintf(&state.goBufBottom, "\n")
   246  
   247  	// remove document node if present
   248  	if len(state.docNodeList) == 1 && state.docNodeList[0].Type == html.DocumentNode {
   249  		state.docNodeList = []*html.Node{state.docNodeList[0].FirstChild}
   250  	}
   251  
   252  	if state.isFullHTML {
   253  
   254  		if len(state.docNodeList) != 1 {
   255  			return fmt.Errorf("full HTML mode but not exactly 1 node found (found %d)", len(state.docNodeList))
   256  		}
   257  		err := p.visitHTML(state, state.docNodeList[0])
   258  		if err != nil {
   259  			return err
   260  		}
   261  
   262  	} else {
   263  
   264  		gotTopNode := false
   265  
   266  		for _, n := range state.docNodeList {
   267  
   268  			// ignore comments
   269  			if n.Type == html.CommentNode {
   270  				continue
   271  			}
   272  
   273  			if n.Type == html.TextNode {
   274  
   275  				// ignore whitespace text
   276  				if strings.TrimSpace(n.Data) == "" {
   277  					continue
   278  				}
   279  
   280  				// error on non-whitespace text
   281  				return fmt.Errorf("unexpected text outside any element: %q", n.Data)
   282  
   283  			}
   284  
   285  			// must be an element at this point
   286  			if n.Type != html.ElementNode {
   287  				return fmt.Errorf("unexpected node type %v; node=%#v", n.Type, n)
   288  			}
   289  
   290  			if isScriptOrStyle(n) {
   291  
   292  				err := p.visitScriptOrStyle(state, n)
   293  				if err != nil {
   294  					return err
   295  				}
   296  				continue
   297  			}
   298  
   299  			if gotTopNode {
   300  				return fmt.Errorf("Found more than one top level element: %s", n.Data)
   301  			}
   302  			gotTopNode = true
   303  
   304  			// handle top node
   305  
   306  			// check for forbidden top level tags
   307  			nodeName := strings.ToLower(n.Data)
   308  			if nodeName == "head" ||
   309  				nodeName == "body" {
   310  				return fmt.Errorf("component cannot use %q as top level tag", nodeName)
   311  			}
   312  
   313  			err := p.visitTopNode(state, n)
   314  			if err != nil {
   315  				return err
   316  			}
   317  			continue
   318  
   319  		}
   320  
   321  	}
   322  
   323  	// for _, chunk := range state.cssChunkList {
   324  	// 	// fmt.Fprintf(&buildBuf, "    out.AppendCSS(/*line %s:%d*/%q)\n\n", fname, chunk.Line, chunk.Code)
   325  	// 	// fmt.Fprintf(&state.buildBuf, "    out.AppendCSS(%q)\n\n", chunk.Code)
   326  	// 	_ = chunk
   327  	// 	panic("need to append whole node, not AppendCSS")
   328  	// }
   329  
   330  	// for _, chunk := range state.jsChunkList {
   331  	// 	// fmt.Fprintf(&buildBuf, "    out.AppendJS(/*line %s:%d*/%q)\n\n", fname, chunk.Line, chunk.Code)
   332  	// 	// fmt.Fprintf(&state.buildBuf, "    out.AppendJS(%q)\n\n", chunk.Code)
   333  	// 	_ = chunk
   334  	// 	panic("need to append whole node, not AppendJS")
   335  	// }
   336  
   337  	fmt.Fprintf(&state.buildBuf, "    return vgout\n")
   338  	fmt.Fprintf(&state.buildBuf, "}\n\n")
   339  
   340  	return nil
   341  }
   342  
   343  func (p *ParserGo) visitHTML(state *parseGoState, n *html.Node) error {
   344  
   345  	pOutputTag(state, n)
   346  	// fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v}\n", n.Type, n.Data, staticVGAttr(n.Attr))
   347  	// fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn) // root for output\n") // for first element we need to assign as Doc on BuildOut
   348  	// state.outIsSet = true
   349  
   350  	// dynamic attrs
   351  	writeDynamicAttributes(state, n)
   352  
   353  	fmt.Fprintf(&state.buildBuf, "{\n")
   354  	fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n") // vgparent set for this block to vgn
   355  
   356  	for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   357  
   358  		if childN.Type != html.ElementNode {
   359  			continue
   360  		}
   361  
   362  		var err error
   363  		if strings.ToLower(childN.Data) == "head" {
   364  			err = p.visitHead(state, childN)
   365  		} else if strings.ToLower(childN.Data) == "body" {
   366  			err = p.visitBody(state, childN)
   367  		} else {
   368  			return fmt.Errorf("unknown tag inside html %q", childN.Data)
   369  
   370  		}
   371  
   372  		if err != nil {
   373  			return err
   374  		}
   375  
   376  	}
   377  
   378  	fmt.Fprintf(&state.buildBuf, "}\n")
   379  
   380  	return nil
   381  }
   382  
   383  func (p *ParserGo) visitHead(state *parseGoState, n *html.Node) error {
   384  
   385  	pOutputTag(state, n)
   386  	// fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v}\n", n.Type, n.Data, staticVGAttr(n.Attr))
   387  	// fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn) // root for output\n") // for first element we need to assign as Doc on BuildOut
   388  	// state.outIsSet = true
   389  
   390  	// dynamic attrs
   391  	writeDynamicAttributes(state, n)
   392  
   393  	fmt.Fprintf(&state.buildBuf, "{\n")
   394  	fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n") // vgparent set for this block to vgn
   395  
   396  	for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   397  
   398  		if isScriptOrStyle(childN) {
   399  			err := p.visitScriptOrStyle(state, childN)
   400  			if err != nil {
   401  				return err
   402  			}
   403  			continue
   404  		}
   405  
   406  		err := p.visitDefaultByType(state, childN)
   407  		if err != nil {
   408  			return err
   409  		}
   410  
   411  	}
   412  
   413  	fmt.Fprintf(&state.buildBuf, "}\n")
   414  
   415  	return nil
   416  
   417  }
   418  
   419  func (p *ParserGo) visitBody(state *parseGoState, n *html.Node) error {
   420  
   421  	pOutputTag(state, n)
   422  	// fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v}\n", n.Type, n.Data, staticVGAttr(n.Attr))
   423  	// fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn) // root for output\n") // for first element we need to assign as Doc on BuildOut
   424  	// state.outIsSet = true
   425  
   426  	// dynamic attrs
   427  	writeDynamicAttributes(state, n)
   428  
   429  	fmt.Fprintf(&state.buildBuf, "{\n")
   430  	fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n") // vgparent set for this block to vgn
   431  
   432  	foundMountEl := false
   433  
   434  	for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   435  
   436  		// ignore whitespace and comments directly in body
   437  		if childN.Type != html.ElementNode {
   438  			continue
   439  		}
   440  
   441  		if isScriptOrStyle(childN) {
   442  			err := p.visitScriptOrStyle(state, childN)
   443  			if err != nil {
   444  				return err
   445  			}
   446  			continue
   447  		}
   448  
   449  		if foundMountEl {
   450  			return fmt.Errorf("element %q found after we already have a mount element", childN.Data)
   451  		}
   452  		foundMountEl = true
   453  
   454  		err := p.visitDefaultByType(state, childN)
   455  		if err != nil {
   456  			return err
   457  		}
   458  
   459  	}
   460  
   461  	fmt.Fprintf(&state.buildBuf, "}\n")
   462  
   463  	return nil
   464  
   465  }
   466  
   467  // visitScriptOrStyle calls visitJS, visitCSS or visitGo accordingly,
   468  // will error if the node does not correspond to one of those
   469  func (p *ParserGo) visitScriptOrStyle(state *parseGoState, n *html.Node) error {
   470  
   471  	nodeName := strings.ToLower(n.Data)
   472  
   473  	// script tag
   474  	if nodeName == "script" {
   475  
   476  		var mt string // mime type
   477  
   478  		ty := attrWithKey(n, "type")
   479  		if ty == nil {
   480  			// return fmt.Errorf("script tag without type attribute is not valid")
   481  			mt = ""
   482  		} else {
   483  			// tinygo support: just split on semi, don't need to import mime package
   484  			// mt, _, _ = mime.ParseMediaType(ty.Val)
   485  			mt = strings.Split(strings.TrimSpace(ty.Val), ";")[0]
   486  		}
   487  
   488  		// go code
   489  		if mt == "application/x-go" {
   490  			err := p.visitGo(state, n)
   491  			if err != nil {
   492  				return err
   493  			}
   494  			return nil
   495  		}
   496  
   497  		// component js (type attr omitted okay - means it is JS)
   498  		if mt == "text/javascript" || mt == "application/javascript" || mt == "" {
   499  			err := p.visitJS(state, n)
   500  			if err != nil {
   501  				return err
   502  			}
   503  			return nil
   504  		}
   505  
   506  		return fmt.Errorf("found script tag with invalid mime type %q", mt)
   507  
   508  	}
   509  
   510  	// component css
   511  	if nodeName == "style" || nodeName == "link" {
   512  		err := p.visitCSS(state, n)
   513  		if err != nil {
   514  			return err
   515  		}
   516  		return nil
   517  	}
   518  
   519  	return fmt.Errorf("element %q is not a valid script or style - %#v", n.Data, n)
   520  }
   521  
   522  func (p *ParserGo) visitJS(state *parseGoState, n *html.Node) error {
   523  
   524  	if n.Type != html.ElementNode {
   525  		return fmt.Errorf("visitJS, not an element node %#v", n)
   526  	}
   527  
   528  	nodeName := strings.ToLower(n.Data)
   529  
   530  	if nodeName != "script" {
   531  		return fmt.Errorf("visitJS, tag %q not a script", nodeName)
   532  	}
   533  
   534  	// see if there's a script inside, or if this is a script include
   535  	if n.FirstChild == nil {
   536  		// script include - we pretty much just let this through, don't care what the attrs are
   537  	} else {
   538  		// if there is a script inside, we do not allow attributes other than "type", to avoid
   539  		// people using features that might not be compatible with the funky stuff we have to do
   540  		// in vugu to make all this work
   541  
   542  		for _, a := range n.Attr {
   543  			if a.Key != "type" {
   544  				return fmt.Errorf("attribute %q not allowed on script tag that contains JS code", a.Key)
   545  			}
   546  			if a.Val != "text/javascript" && a.Val != "application/javascript" {
   547  				return fmt.Errorf("script type %q invalid (must be text/javascript)", a.Val)
   548  			}
   549  		}
   550  
   551  		// verify that all children are text nodes
   552  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   553  			if childN.Type != html.TextNode {
   554  				return fmt.Errorf("script tag contains non-text child: %#v", childN)
   555  			}
   556  		}
   557  
   558  	}
   559  
   560  	// allow control stuff, why not
   561  
   562  	// vg-for
   563  	if v, _ := vgForExpr(n); v.expr != "" {
   564  		if err := p.emitForExpr(state, n); err != nil {
   565  			return err
   566  		}
   567  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   568  	}
   569  
   570  	// vg-if
   571  	ife := vgIfExpr(n)
   572  	if ife != "" {
   573  		fmt.Fprintf(&state.buildBuf, "if %s {\n", ife)
   574  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   575  	}
   576  
   577  	// but then for the actual output, we append to vgout.JS, instead of parentNode
   578  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v}\n", n.Type, n.Data, staticVGAttr(n.Attr))
   579  
   580  	// output any text children
   581  	if n.FirstChild != nil {
   582  		fmt.Fprintf(&state.buildBuf, "{\n")
   583  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   584  			// NOTE: we already verified above that these are just text nodes
   585  			fmt.Fprintf(&state.buildBuf, "vgn.AppendChild(&vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v})\n", childN.Type, childN.Data, staticVGAttr(childN.Attr))
   586  		}
   587  		fmt.Fprintf(&state.buildBuf, "}\n")
   588  	}
   589  
   590  	fmt.Fprintf(&state.buildBuf, "vgout.AppendJS(vgn)\n")
   591  
   592  	// dynamic attrs
   593  	writeDynamicAttributes(state, n)
   594  
   595  	return nil
   596  }
   597  
   598  func (p *ParserGo) visitCSS(state *parseGoState, n *html.Node) error {
   599  
   600  	if n.Type != html.ElementNode {
   601  		return fmt.Errorf("visitCSS, not an element node %#v", n)
   602  	}
   603  
   604  	nodeName := strings.ToLower(n.Data)
   605  	switch nodeName {
   606  
   607  	case "link":
   608  
   609  		// okay as long as nothing is inside this node
   610  
   611  		if n.FirstChild != nil {
   612  			return fmt.Errorf("link tag should not have children")
   613  		}
   614  
   615  		// and it needs to have an href (url)
   616  		hrefAttr := attrWithKey(n, "href")
   617  		if hrefAttr == nil {
   618  			return fmt.Errorf("link tag must have href attribute but does not: %#v", n)
   619  		}
   620  
   621  	case "style":
   622  
   623  		// style must have child (will verify it is text below)
   624  		if n.FirstChild == nil {
   625  			return fmt.Errorf("style must have contents but does not: %#v", n)
   626  		}
   627  
   628  		// okay as long as only text nodes inside
   629  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   630  			if childN.Type != html.TextNode {
   631  				return fmt.Errorf("style tag contains non-text child: %#v", childN)
   632  			}
   633  		}
   634  
   635  	default:
   636  		return fmt.Errorf("visitCSS, unexpected tag name %q", nodeName)
   637  	}
   638  
   639  	// allow control stuff, why not
   640  
   641  	// vg-for
   642  	if v, _ := vgForExpr(n); v.expr != "" {
   643  		if err := p.emitForExpr(state, n); err != nil {
   644  			return err
   645  		}
   646  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   647  	}
   648  
   649  	// vg-if
   650  	ife := vgIfExpr(n)
   651  	if ife != "" {
   652  		fmt.Fprintf(&state.buildBuf, "if %s {\n", ife)
   653  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   654  	}
   655  
   656  	// but then for the actual output, we append to vgout.CSS, instead of parentNode
   657  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v}\n", n.Type, n.Data, staticVGAttr(n.Attr))
   658  
   659  	// output any text children
   660  	if n.FirstChild != nil {
   661  		fmt.Fprintf(&state.buildBuf, "{\n")
   662  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   663  			// NOTE: we already verified above that these are just text nodes
   664  			fmt.Fprintf(&state.buildBuf, "vgn.AppendChild(&vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v})\n", childN.Type, childN.Data, staticVGAttr(childN.Attr))
   665  		}
   666  		fmt.Fprintf(&state.buildBuf, "}\n")
   667  	}
   668  
   669  	fmt.Fprintf(&state.buildBuf, "vgout.AppendCSS(vgn)\n")
   670  
   671  	// dynamic attrs
   672  	writeDynamicAttributes(state, n)
   673  
   674  	return nil
   675  }
   676  
   677  func (p *ParserGo) visitGo(state *parseGoState, n *html.Node) error {
   678  
   679  	for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   680  		if childN.Type != html.TextNode {
   681  			return fmt.Errorf("unexpected node type %v inside of script tag", childN.Type)
   682  		}
   683  		// if childN.Line > 0 {
   684  		// 	fmt.Fprintf(&goBuf, "//line %s:%d\n", fname, childN.Line)
   685  		// }
   686  		state.goBuf.WriteString(childN.Data)
   687  	}
   688  
   689  	return nil
   690  }
   691  
   692  // visitTopNode handles the "mount point"
   693  func (p *ParserGo) visitTopNode(state *parseGoState, n *html.Node) error {
   694  
   695  	// handle the top element other than <html>
   696  
   697  	err := p.visitNodeJustElement(state, n)
   698  	if err != nil {
   699  		return err
   700  	}
   701  
   702  	return nil
   703  }
   704  
   705  // visitNodeElementAndCtrl handles an element that supports vg-if, vg-for etc
   706  func (p *ParserGo) visitNodeElementAndCtrl(state *parseGoState, n *html.Node) error {
   707  
   708  	// vg-for
   709  	if v, _ := vgForExpr(n); v.expr != "" {
   710  		if err := p.emitForExpr(state, n); err != nil {
   711  			return err
   712  		}
   713  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   714  	}
   715  
   716  	// vg-if
   717  	ife := vgIfExpr(n)
   718  	if ife != "" {
   719  		fmt.Fprintf(&state.buildBuf, "if %s {\n", ife)
   720  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   721  	}
   722  
   723  	err := p.visitNodeJustElement(state, n)
   724  	if err != nil {
   725  		return err
   726  	}
   727  
   728  	return nil
   729  }
   730  
   731  // visitNodeJustElement handles an element, ignoring any vg-if, vg-for (but it does handle vg-html - since that is not really "control" just a shorthand for it's contents)
   732  func (p *ParserGo) visitNodeJustElement(state *parseGoState, n *html.Node) error {
   733  
   734  	// regular element
   735  
   736  	// if n.Line > 0 {
   737  	// 	fmt.Fprintf(&buildBuf, "//line %s:%d\n", fname, n.Line)
   738  	// }
   739  
   740  	pOutputTag(state, n)
   741  	// fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q,Attr:%#v}\n", n.Type, n.Data, staticVGAttr(n.Attr))
   742  	// if state.outIsSet {
   743  	// 	fmt.Fprintf(&state.buildBuf, "vgparent.AppendChild(vgn)\n") // if not root, make AppendChild call
   744  	// } else {
   745  	// 	fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn) // root for output\n") // for first element we need to assign as Doc on BuildOut
   746  	// 	state.outIsSet = true
   747  	// }
   748  
   749  	// dynamic attrs
   750  	writeDynamicAttributes(state, n)
   751  
   752  	// vg-js-*
   753  	writeJSCallbackAttributes(state, n)
   754  
   755  	// js properties
   756  	propExprMap, propExprMapKeys := propVGAttrExpr(n)
   757  	for _, k := range propExprMapKeys {
   758  		valExpr := propExprMap[k]
   759  		fmt.Fprintf(&state.buildBuf, "{b, err := vjson.Marshal(%s); if err != nil { panic(err) }; vgn.Prop = append(vgn.Prop, vugu.VGProperty{Key:%q,JSONVal:vjson.RawMessage(b)})}\n", valExpr, k)
   760  	}
   761  
   762  	// vg-html
   763  	htmlExpr := vgHTMLExpr(n)
   764  	if htmlExpr != "" {
   765  		fmt.Fprintf(&state.buildBuf, "vgn.SetInnerHTML(%s)\n", htmlExpr)
   766  	}
   767  
   768  	// DOM events
   769  	eventMap, eventKeys := vgDOMEventExprs(n)
   770  	for _, k := range eventKeys {
   771  		expr := eventMap[k]
   772  		fmt.Fprintf(&state.buildBuf, "vgn.DOMEventHandlerSpecList = append(vgn.DOMEventHandlerSpecList, vugu.DOMEventHandlerSpec{\n")
   773  		fmt.Fprintf(&state.buildBuf, "EventType: %q,\n", k)
   774  		fmt.Fprintf(&state.buildBuf, "Func: func(event vugu.DOMEvent) { %s },\n", expr)
   775  		fmt.Fprintf(&state.buildBuf, "// TODO: implement capture, etc. mostly need to decide syntax\n")
   776  		fmt.Fprintf(&state.buildBuf, "})\n")
   777  	}
   778  
   779  	if n.FirstChild != nil {
   780  
   781  		fmt.Fprintf(&state.buildBuf, "{\n")
   782  		fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n") // vgparent set for this block to vgn
   783  
   784  		// iterate over children
   785  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   786  
   787  			err := p.visitDefaultByType(state, childN)
   788  			if err != nil {
   789  				return err
   790  			}
   791  		}
   792  
   793  		fmt.Fprintf(&state.buildBuf, "}\n")
   794  
   795  	}
   796  
   797  	return nil
   798  }
   799  
   800  func (p *ParserGo) visitDefaultByType(state *parseGoState, n *html.Node) error {
   801  
   802  	// handle child according to type
   803  	var err error
   804  	switch {
   805  	case n.Type == html.CommentNode:
   806  		err = p.visitNodeComment(state, n)
   807  	case n.Type == html.TextNode:
   808  		err = p.visitNodeText(state, n)
   809  	case n.Type == html.ElementNode:
   810  		if strings.Contains(n.Data, ":") {
   811  			// NOTE: this should check for a capital letter after the colon - this would distinguish
   812  			// svg:svg (valid regular HTML) from svg:Svg (a component reference)
   813  			err = p.visitNodeComponentElement(state, n)
   814  		} else if n.Data == "vg-comp" {
   815  			err = p.visitVGCompTag(state, n)
   816  		} else if n.Data == "vg-template" {
   817  			err = p.visitVGTemplateTag(state, n)
   818  		} else {
   819  			err = p.visitNodeElementAndCtrl(state, n)
   820  		}
   821  	default:
   822  		return fmt.Errorf("child node of unknown type %v: %#v", n.Type, n)
   823  	}
   824  
   825  	if err != nil {
   826  		return err
   827  	}
   828  
   829  	return nil
   830  }
   831  
   832  func (p *ParserGo) visitNodeText(state *parseGoState, n *html.Node) error {
   833  
   834  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q}\n", n.Type, n.Data)
   835  	fmt.Fprintf(&state.buildBuf, "vgparent.AppendChild(vgn)\n")
   836  
   837  	return nil
   838  }
   839  
   840  func (p *ParserGo) visitNodeComment(state *parseGoState, n *html.Node) error {
   841  
   842  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Data:%q}\n", n.Type, n.Data)
   843  	fmt.Fprintf(&state.buildBuf, "vgparent.AppendChild(vgn)\n")
   844  
   845  	return nil
   846  }
   847  
   848  // visitVGCompTag handles a vg-comp
   849  func (p *ParserGo) visitVGCompTag(state *parseGoState, n *html.Node) error {
   850  
   851  	// vg-for not allowed here
   852  
   853  	// vg-if is supported
   854  	ife := vgIfExpr(n)
   855  	if ife != "" {
   856  		fmt.Fprintf(&state.buildBuf, "if %s {\n", ife)
   857  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   858  	}
   859  
   860  	// for now, nothing else supported
   861  
   862  	// must have a "expr" which gives the Go expression which will result in a component
   863  	expr := vgCompExpr(n)
   864  	if expr == "" {
   865  		return fmt.Errorf("vg-comp must have an `expr` attribute with a Go expression in it")
   866  	}
   867  	fmt.Fprintf(&state.buildBuf, "{\n")
   868  	defer fmt.Fprintf(&state.buildBuf, "}\n")
   869  
   870  	fmt.Fprintf(&state.buildBuf, "var vgcomp vugu.Builder = %s\n", expr)
   871  	fmt.Fprintf(&state.buildBuf, "if vgcomp != nil {\n")
   872  	fmt.Fprintf(&state.buildBuf, "    vgin.BuildEnv.WireComponent(vgcomp)\n")
   873  	fmt.Fprintf(&state.buildBuf, "    vgout.Components = append(vgout.Components, vgcomp)\n")
   874  	fmt.Fprintf(&state.buildBuf, "    vgn = &vugu.VGNode{Component:vgcomp}\n")
   875  	fmt.Fprintf(&state.buildBuf, "    vgparent.AppendChild(vgn)\n")
   876  	fmt.Fprintf(&state.buildBuf, "}\n")
   877  
   878  	return nil
   879  }
   880  
   881  // visitVGTemplateTag handles vg-template
   882  func (p *ParserGo) visitVGTemplateTag(state *parseGoState, n *html.Node) error {
   883  
   884  	// vg-for
   885  	if v, _ := vgForExpr(n); v.expr != "" {
   886  		if err := p.emitForExpr(state, n); err != nil {
   887  			return err
   888  		}
   889  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   890  	}
   891  
   892  	// vg-if
   893  	ife := vgIfExpr(n)
   894  	if ife != "" {
   895  		fmt.Fprintf(&state.buildBuf, "if %s {\n", ife)
   896  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   897  	}
   898  
   899  	// output a node with type Element but empty data
   900  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d)} // <vg-template>\n", vugu.ElementNode)
   901  	fmt.Fprintf(&state.buildBuf, "vgparent.AppendChild(vgn)\n")
   902  
   903  	// and then only process children
   904  	if n.FirstChild != nil {
   905  
   906  		fmt.Fprintf(&state.buildBuf, "{\n")
   907  		fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n") // vgparent set for this block to vgn
   908  
   909  		// iterate over children
   910  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
   911  
   912  			err := p.visitDefaultByType(state, childN)
   913  			if err != nil {
   914  				return err
   915  			}
   916  		}
   917  
   918  		fmt.Fprintf(&state.buildBuf, "}\n")
   919  
   920  	}
   921  
   922  	return nil
   923  }
   924  
   925  // visitNodeComponentElement handles an element that is a call to a component
   926  func (p *ParserGo) visitNodeComponentElement(state *parseGoState, n *html.Node) error {
   927  
   928  	// components are just different so we handle all of our own vg-for vg-if and everything else
   929  
   930  	// vg-for
   931  	if v, _ := vgForExpr(n); v.expr != "" {
   932  		if err := p.emitForExpr(state, n); err != nil {
   933  			return err
   934  		}
   935  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   936  	}
   937  
   938  	// vg-if
   939  	ife := vgIfExpr(n)
   940  	if ife != "" {
   941  		fmt.Fprintf(&state.buildBuf, "if %s {\n", ife)
   942  		defer fmt.Fprintf(&state.buildBuf, "}\n")
   943  	}
   944  
   945  	nodeName := n.OrigData // use original case of element
   946  	nodeNameParts := strings.Split(nodeName, ":")
   947  	if len(nodeNameParts) != 2 {
   948  		return fmt.Errorf("invalid component tag name %q must contain exactly one colon", nodeName)
   949  	}
   950  
   951  	// x.Y or just Y depending on if in same package
   952  	typeExpr := strings.Join(nodeNameParts, ".")
   953  	pkgPrefix := nodeNameParts[0] + "." // needed so we can calc pkg name for pkg.WhateverEvent
   954  	if nodeNameParts[0] == p.PackageName {
   955  		typeExpr = nodeNameParts[1]
   956  		pkgPrefix = ""
   957  	}
   958  
   959  	compKeyID := compHashCounted(p.StructType + "." + n.OrigData)
   960  
   961  	fmt.Fprintf(&state.buildBuf, "{\n")
   962  	defer fmt.Fprintf(&state.buildBuf, "}\n")
   963  
   964  	keyExpr := vgKeyExpr(n)
   965  	if keyExpr != "" {
   966  		fmt.Fprintf(&state.buildBuf, "vgcompKey := vugu.MakeCompKey(0x%X^vgin.CurrentPositionHash(), %s)\n", compKeyID, keyExpr)
   967  	} else {
   968  		fmt.Fprintf(&state.buildBuf, "vgcompKey := vugu.MakeCompKey(0x%X^vgin.CurrentPositionHash(), vgiterkey)\n", compKeyID)
   969  	}
   970  	fmt.Fprintf(&state.buildBuf, "// ask BuildEnv for prior instance of this specific component\n")
   971  	fmt.Fprintf(&state.buildBuf, "vgcomp, _ := vgin.BuildEnv.CachedComponent(vgcompKey).(*%s)\n", typeExpr)
   972  	fmt.Fprintf(&state.buildBuf, "if vgcomp == nil {\n")
   973  	fmt.Fprintf(&state.buildBuf, "// create new one if needed\n")
   974  	fmt.Fprintf(&state.buildBuf, "vgcomp = new(%s)\n", typeExpr)
   975  	fmt.Fprintf(&state.buildBuf, "vgin.BuildEnv.WireComponent(vgcomp)\n")
   976  	fmt.Fprintf(&state.buildBuf, "}\n")
   977  	fmt.Fprintf(&state.buildBuf, "vgin.BuildEnv.UseComponent(vgcompKey, vgcomp) // ensure we can use this in the cache next time around\n")
   978  
   979  	// now that we have vgcomp with the right type and a correct value, we can declare the vg-var if specified
   980  	if vgv := vgVarExpr(n); vgv != "" {
   981  		fmt.Fprintf(&state.buildBuf, "var %s = vgcomp // vg-var\n", vgv)
   982  
   983  		// NOTE: It's a bit too much to have "unused variable" errors coming from a Vugu code-generated file,
   984  		// too far off the beaten path of making "type-safe HTML templates with Go".  It makes sense with
   985  		// hand-written Go code but I don't think so here.
   986  		fmt.Fprintf(&state.buildBuf, "_ = %s\n", vgv) // avoid unused var error
   987  	}
   988  
   989  	didAttrMap := false
   990  
   991  	// dynamic attrs
   992  	dynExprMap, dynExprMapKeys := dynamicVGAttrExpr(n)
   993  	for _, k := range dynExprMapKeys {
   994  		// if k == "" {
   995  		// 	return fmt.Errorf("invalid empty dynamic attribute name on component %#v", n)
   996  		// }
   997  
   998  		valExpr := dynExprMap[k]
   999  
  1000  		// if starts with upper case, it's a field name
  1001  		if hasUpperFirst(k) {
  1002  			fmt.Fprintf(&state.buildBuf, "vgcomp.%s = %s\n", k, valExpr)
  1003  		} else {
  1004  			// otherwise we use an "AttrMap"
  1005  			if !didAttrMap {
  1006  				didAttrMap = true
  1007  				fmt.Fprintf(&state.buildBuf, "vgcomp.AttrMap = make(map[string]interface{}, 8)\n")
  1008  			}
  1009  			fmt.Fprintf(&state.buildBuf, "vgcomp.AttrMap[%q] = %s\n", k, valExpr)
  1010  		}
  1011  
  1012  	}
  1013  
  1014  	// static attrs
  1015  	vgAttrs := staticVGAttr(n.Attr)
  1016  	for _, a := range vgAttrs {
  1017  		// if starts with upper case, it's a field name
  1018  		if hasUpperFirst(a.Key) {
  1019  			fmt.Fprintf(&state.buildBuf, "vgcomp.%s = %q\n", a.Key, a.Val)
  1020  		} else {
  1021  			// otherwise we use an "AttrMap"
  1022  			if !didAttrMap {
  1023  				didAttrMap = true
  1024  				fmt.Fprintf(&state.buildBuf, "vgcomp.AttrMap = make(map[string]interface{}, 8)\n")
  1025  			}
  1026  			fmt.Fprintf(&state.buildBuf, "vgcomp.AttrMap[%q] = %q\n", a.Key, a.Val)
  1027  		}
  1028  	}
  1029  
  1030  	// component events
  1031  	// NOTE: We keep component events really simple and the @ is just a thin wrapper around a field assignment:
  1032  	//     <pkg:Comp @Something="log.Println(event)"></pkg:Comp>
  1033  	// is shorthand for:
  1034  	//     <pkg:Comp :Something='func(event pkg.SomethingEvent) { log.Println(event) }'></pkg:Comp>
  1035  	//
  1036  	// I considered using the handler interface function approach for this, but it would mean
  1037  	// SomethingHandlerFunc would have to exist as a type, with a SomethingHandle method, which
  1038  	// implements a SomethingHandler interface, so the type of Comp.Something could be SomethingHandler,
  1039  	// and the emitted code could be vgcomp.Something = pkg.SomethingHandlerFunc(func...)
  1040  	// But that's two additional types and a method for every event.  I'm very concerned that it will
  1041  	// make component events feel crufty and arduous to implement (unless we could find a good way
  1042  	// to automatically generate those when missing - that's a possibility - actually I think
  1043  	// I'm going to try this, see https://github.com/vugu/vugu/issues/128).
  1044  	// But this this way with a func you can just do
  1045  	// type SomethingEvent struct { /* whatever relevant data */ } and then define your field on
  1046  	// your component as Something func(SomethingEvent) - still type-safe but very straightforward.
  1047  	// So far it seems like the best approach.
  1048  
  1049  	eventMap, eventKeys := vgEventExprs(n)
  1050  	for _, k := range eventKeys {
  1051  		expr := eventMap[k]
  1052  		// fmt.Fprintf(&state.buildBuf, "vgcomp.%s = func(event %s%sEvent){%s}\n", k, pkgPrefix, k, expr)
  1053  		// switched to using interfaces
  1054  		fmt.Fprintf(&state.buildBuf, "vgcomp.%s = %s%sFunc(func(event %s%sEvent){%s})\n", k, pkgPrefix, k, pkgPrefix, k, expr)
  1055  	}
  1056  
  1057  	// NOTE: vugugen:slot might come in really handy, have to work out the types involved - update: as it stands, this won't be needed.
  1058  
  1059  	// slots:
  1060  
  1061  	// scan children and see if it's default slot mode or vg-slot tags
  1062  	foundTagSlot, foundDefSlot := false, false
  1063  	var foundTagSlotNames []string
  1064  	for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
  1065  
  1066  		// non-ws text means default slot
  1067  		if childN.Type == html.TextNode {
  1068  			if strings.TrimSpace(childN.Data) != "" {
  1069  				foundDefSlot = true
  1070  			}
  1071  			continue
  1072  		}
  1073  
  1074  		// ignore comments
  1075  		if childN.Type == html.CommentNode {
  1076  			continue
  1077  		}
  1078  
  1079  		// should only be element at this point
  1080  		if childN.Type != html.ElementNode {
  1081  			return fmt.Errorf("in tag %q unexpected node found where slot expected: %#v", n.Data, childN)
  1082  		}
  1083  
  1084  		if childN.Data == "vg-slot" {
  1085  			foundTagSlot = true
  1086  			name := strings.TrimSpace(vgSlotName(childN))
  1087  			if name != "" {
  1088  				foundTagSlotNames = append(foundTagSlotNames, name)
  1089  			}
  1090  		} else {
  1091  			foundDefSlot = true
  1092  		}
  1093  	}
  1094  
  1095  	// now process slot(s) appropriately according to format
  1096  	switch {
  1097  
  1098  	case foundTagSlot && foundDefSlot:
  1099  		return fmt.Errorf("in tag %q found both vg-slot and other markup, only one or the other is allowed", n.Data)
  1100  
  1101  	case foundTagSlot:
  1102  
  1103  		// NOTE:
  1104  		// <vg-slot name="X"> will assign to vgcomp.X
  1105  		// <vg-slot name='X[Y]'> will assume X is of type map[string]Builder and create the map and then assign with X[Y] =
  1106  
  1107  		// find any names with map expressions and clear the maps
  1108  		sort.Strings(foundTagSlotNames)
  1109  		slotMapInited := make(map[string]bool)
  1110  		for _, slotName := range foundTagSlotNames {
  1111  			slotNameParts := strings.Split(slotName, "[") // check for map expr
  1112  			if len(slotNameParts) > 1 {                   // if map
  1113  				if slotMapInited[slotNameParts[0]] { // if not already initialized
  1114  					continue
  1115  				}
  1116  				slotMapInited[slotNameParts[0]] = true
  1117  
  1118  				// if nil create map, otherwise reuse
  1119  				fmt.Fprintf(&state.buildBuf, "if vgcomp.%s == nil {\n", slotNameParts[0])
  1120  				fmt.Fprintf(&state.buildBuf, "    vgcomp.%s = make(map[string]vugu.Builder)\n", slotNameParts[0])
  1121  				fmt.Fprintf(&state.buildBuf, "} else {\n")
  1122  				fmt.Fprintf(&state.buildBuf, "    for k := range vgcomp.%s { delete(vgcomp.%s, k) }\n", slotNameParts[0], slotNameParts[0])
  1123  				fmt.Fprintf(&state.buildBuf, "}\n")
  1124  			}
  1125  		}
  1126  
  1127  		// iterate over children
  1128  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
  1129  
  1130  			// ignore white space and coments
  1131  			if childN.Type == html.CommentNode ||
  1132  				(childN.Type == html.TextNode && strings.TrimSpace(childN.Data) == "") {
  1133  				continue
  1134  			}
  1135  
  1136  			if childN.Type != html.ElementNode { // should be impossible from foundTagSlot check above, just making sure
  1137  				panic(fmt.Errorf("unexpected non-element found where vg-slot should be: %#v", childN))
  1138  			}
  1139  
  1140  			if childN.Data != "vg-slot" { // should also be imposible
  1141  				panic(fmt.Errorf("unexpected element found where vg-slot should be: %#v", childN))
  1142  			}
  1143  
  1144  			slotName := strings.TrimSpace(vgSlotName(childN))
  1145  			if slotName == "" {
  1146  				return fmt.Errorf("found vg-slot tag without a 'name' attribute, the name is required")
  1147  			}
  1148  
  1149  			fmt.Fprintf(&state.buildBuf, "vgcomp.%s = vugu.NewBuilderFunc(func(vgin *vugu.BuildIn) (vgout *vugu.BuildOut) {\n", slotName)
  1150  			fmt.Fprintf(&state.buildBuf, "vgn := &vugu.VGNode{Type:vugu.VGNodeType(%d)}\n", vugu.ElementNode)
  1151  			fmt.Fprintf(&state.buildBuf, "vgout = &vugu.BuildOut{}\n")
  1152  			fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn)\n")
  1153  			fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n")
  1154  			fmt.Fprintf(&state.buildBuf, "\n")
  1155  
  1156  			// iterate over children and do the usual with each one
  1157  			for innerChildN := childN.FirstChild; innerChildN != nil; innerChildN = innerChildN.NextSibling {
  1158  				err := p.visitDefaultByType(state, innerChildN)
  1159  				if err != nil {
  1160  					return err
  1161  				}
  1162  			}
  1163  
  1164  			fmt.Fprintf(&state.buildBuf, "return\n")
  1165  			fmt.Fprintf(&state.buildBuf, "})\n")
  1166  
  1167  		}
  1168  
  1169  	case foundDefSlot:
  1170  		fmt.Fprintf(&state.buildBuf, "vgcomp.DefaultSlot = vugu.NewBuilderFunc(func(vgin *vugu.BuildIn) (vgout *vugu.BuildOut) {\n")
  1171  		// vgn is the equivalent of a vg-template tag and becomes the contents of vgout.Out and the vgparent
  1172  		fmt.Fprintf(&state.buildBuf, "vgn := &vugu.VGNode{Type:vugu.VGNodeType(%d)}\n", vugu.ElementNode)
  1173  		fmt.Fprintf(&state.buildBuf, "vgout = &vugu.BuildOut{}\n")
  1174  		fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn)\n")
  1175  		fmt.Fprintf(&state.buildBuf, "vgparent := vgn; _ = vgparent\n")
  1176  		fmt.Fprintf(&state.buildBuf, "\n")
  1177  
  1178  		// iterate over children and do the usual with each one
  1179  		for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
  1180  			err := p.visitDefaultByType(state, childN)
  1181  			if err != nil {
  1182  				return err
  1183  			}
  1184  		}
  1185  
  1186  		fmt.Fprintf(&state.buildBuf, "return\n")
  1187  		fmt.Fprintf(&state.buildBuf, "})\n")
  1188  
  1189  	default:
  1190  		// nothing meaningful inside this component tag
  1191  	}
  1192  
  1193  	// // keep track of contents for default slot
  1194  	// var defSlotNodes []*html.Node
  1195  	// defSlotMode := false // start off not in default slot mode and look for <vg-slot> tags
  1196  
  1197  	// // loop over all component children
  1198  	// for childN := n.FirstChild; childN != nil; childN = childN.NextSibling {
  1199  
  1200  	// 	if !defSlotMode {
  1201  
  1202  	// 		// anything not an element just add to the list for default
  1203  	// 		if childN.Type != html.ElementNode {
  1204  	// 			defSlotNodes = append(defSlotNodes, childN)
  1205  	// 			continue
  1206  	// 		}
  1207  
  1208  	// 		if childN.Data == "vg-slot" {
  1209  
  1210  	// 		}
  1211  
  1212  	// 	}
  1213  
  1214  	// }
  1215  
  1216  	// ignore whitespace
  1217  	// first non-slot, non-ws child, assume "DefaultSlot" (or whatever name) and consume rest of children
  1218  	// if vg-slot, then consume with specified name
  1219  	// <vg-slot name="SomeSlot"> <!-- field name syntax
  1220  	// <vg-slot name='SomeDynaSlot' index='"Row.FirstName"'> <!-- expression syntax, HM, NO
  1221  	// <vg-slot index='SomeDynaSlot["Row.FirstName"]'> <!-- maybe this - still annoying that we have to limit it to a map expression, but whatever
  1222  	// emit vgcomp.SlotName = vugu.NewBuilderFunc(func(vgin *vugu.BuildIn) (vgout *BuildOut, vgerr error) { ... })
  1223  	// and descend into children
  1224  
  1225  	fmt.Fprintf(&state.buildBuf, "vgout.Components = append(vgout.Components, vgcomp)\n")
  1226  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Component:vgcomp}\n")
  1227  	fmt.Fprintf(&state.buildBuf, "vgparent.AppendChild(vgn)\n")
  1228  
  1229  	return nil
  1230  	// return fmt.Errorf("component tag not yet supported (%q)", nodeName)
  1231  }
  1232  
  1233  // NOTE: caller is responsible for emitting the closing curly bracket
  1234  func (p *ParserGo) emitForExpr(state *parseGoState, n *html.Node) error {
  1235  
  1236  	forattr, err := vgForExpr(n)
  1237  	if err != nil {
  1238  		return err
  1239  	}
  1240  	forx := forattr.expr
  1241  	if forx == "" {
  1242  		return errors.New("no for expression, code should not be calling emitForExpr when no vg-for is present")
  1243  	}
  1244  
  1245  	// cases to select vgiterkey:
  1246  	// * check for vg-key attribute
  1247  	// * _, v := // replace _ with vgiterkey
  1248  	// * key, value := // unused vars, use 'key' as iter val
  1249  	// * k, v := // detect `k` and use as iterval
  1250  
  1251  	vgiterkeyx := vgKeyExpr(n)
  1252  
  1253  	// determine iteration variables
  1254  	var iterkey, iterval string
  1255  	if !strings.Contains(forx, ":=") {
  1256  		// make it so `w` is a shorthand for `key, value := range w`
  1257  		iterkey, iterval = "key", "value"
  1258  		forx = "key, value := range " + forx
  1259  	} else {
  1260  		// extract iteration variables
  1261  		var (
  1262  			itervars [2]string
  1263  			iteridx  int
  1264  		)
  1265  		for _, c := range forx {
  1266  			if c == ':' {
  1267  				break
  1268  			}
  1269  			if c == ',' {
  1270  				iteridx++
  1271  				continue
  1272  			}
  1273  			if unicode.IsSpace(c) {
  1274  				continue
  1275  			}
  1276  			itervars[iteridx] += string(c)
  1277  		}
  1278  
  1279  		iterkey = itervars[0]
  1280  		iterval = itervars[1]
  1281  	}
  1282  
  1283  	// detect "_, k := " form combined with no vg-key specified and replace
  1284  	if vgiterkeyx == "" && iterkey == "_" {
  1285  		iterkey = "vgiterkeyt"
  1286  		forx = "vgiterkeyt " + forx[1:]
  1287  	}
  1288  
  1289  	// if still no vgiterkeyx use the first identifier
  1290  	if vgiterkeyx == "" {
  1291  		vgiterkeyx = iterkey
  1292  	}
  1293  
  1294  	fmt.Fprintf(&state.buildBuf, "for %s {\n", forx)
  1295  	fmt.Fprintf(&state.buildBuf, "var vgiterkey interface{} = %s\n", vgiterkeyx)
  1296  	fmt.Fprintf(&state.buildBuf, "_ = vgiterkey\n")
  1297  	if !forattr.noshadow {
  1298  		if iterkey != "_" && iterkey != "vgiterkeyt" {
  1299  			fmt.Fprintf(&state.buildBuf, "%[1]s := %[1]s\n", iterkey)
  1300  			fmt.Fprintf(&state.buildBuf, "_ = %s\n", iterkey)
  1301  		}
  1302  		if iterval != "_" && iterval != "" {
  1303  			fmt.Fprintf(&state.buildBuf, "%[1]s := %[1]s\n", iterval)
  1304  			fmt.Fprintf(&state.buildBuf, "_ = %s\n", iterval)
  1305  		}
  1306  	}
  1307  
  1308  	return nil
  1309  }
  1310  
  1311  func hasUpperFirst(s string) bool {
  1312  	for _, c := range s {
  1313  		return unicode.IsUpper(c)
  1314  	}
  1315  	return false
  1316  }
  1317  
  1318  // isScriptOrStyle returns true if this is a "script", "style" or "link" tag
  1319  func isScriptOrStyle(n *html.Node) bool {
  1320  	if n.Type != html.ElementNode {
  1321  		return false
  1322  	}
  1323  	switch strings.ToLower(n.Data) {
  1324  	case "script", "style", "link":
  1325  		return true
  1326  	}
  1327  	return false
  1328  }
  1329  
  1330  func pOutputTag(state *parseGoState, n *html.Node) {
  1331  	fmt.Fprintf(&state.buildBuf, "vgn = &vugu.VGNode{Type:vugu.VGNodeType(%d),Namespace:%q,Data:%q,Attr:%#v}\n", n.Type, n.Namespace, n.Data, staticVGAttr(n.Attr))
  1332  	if state.outIsSet {
  1333  		fmt.Fprintf(&state.buildBuf, "vgparent.AppendChild(vgn)\n") // if not root, make AppendChild call
  1334  	} else {
  1335  		fmt.Fprintf(&state.buildBuf, "vgout.Out = append(vgout.Out, vgn) // root for output\n") // for first element we need to assign as Doc on BuildOut
  1336  		state.outIsSet = true
  1337  	}
  1338  
  1339  }
  1340  
  1341  func attrWithKey(n *html.Node, key string) *html.Attribute {
  1342  	for i := range n.Attr {
  1343  		if n.Attr[i].Key == key {
  1344  			return &n.Attr[i]
  1345  		}
  1346  	}
  1347  	return nil
  1348  }
  1349  
  1350  func writeDynamicAttributes(state *parseGoState, n *html.Node) {
  1351  	dynExprMap, dynExprMapKeys := dynamicVGAttrExpr(n)
  1352  	for _, k := range dynExprMapKeys {
  1353  		valExpr := dynExprMap[k]
  1354  		if k == "" || k == "vg-attr" {
  1355  			fmt.Fprintf(&state.buildBuf, "vgn.AddAttrList(%s)\n", valExpr)
  1356  		} else {
  1357  			fmt.Fprintf(&state.buildBuf, "vgn.AddAttrInterface(%q,%s)\n", k, valExpr)
  1358  		}
  1359  	}
  1360  }
  1361  
  1362  // writeJSCallbackAttributes handles vg-js-create and vg-js-populate
  1363  func writeJSCallbackAttributes(state *parseGoState, n *html.Node) {
  1364  	m := jsCallbackVGAttrExpr(n)
  1365  	createStmt := m["vg-js-create"]
  1366  	if createStmt != "" {
  1367  		fmt.Fprintf(&state.buildBuf, "vgn.JSCreateHandler = vugu.JSValueFunc(func(value js.Value) { %s })\n", createStmt)
  1368  	}
  1369  	populateStmt := m["vg-js-populate"]
  1370  	if populateStmt != "" {
  1371  		fmt.Fprintf(&state.buildBuf, "vgn.JSPopulateHandler = vugu.JSValueFunc(func(value js.Value) { %s })\n", populateStmt)
  1372  	}
  1373  }