diff --git a/sui/api/request.go b/sui/api/request.go index 767bbb13..2a402a89 100644 --- a/sui/api/request.go +++ b/sui/api/request.go @@ -123,7 +123,12 @@ func (r *Request) Render() (string, int, error) { data["$global"] = global } - parser := core.NewTemplateParser(data, nil) + printData := false + if r.Query != nil && r.Query.Has("__sui_print_data") { + printData = true + } + + parser := core.NewTemplateParser(data, &core.ParserOption{PrintData: printData}) html, err := parser.Render(c.HTML) if err != nil { return "", 500, fmt.Errorf("render error, please re-complie the page %s", err.Error()) diff --git a/sui/core/build.go b/sui/core/build.go index 7d7d6851..e92bade0 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -52,13 +52,25 @@ func (page *Page) Build(option *BuildOption) (*goquery.Document, []string, error } // BuildForImport build the page for import -func (page *Page) BuildForImport(option *BuildOption, slots map[string]interface{}) (string, string, string, []string, error) { +func (page *Page) BuildForImport(option *BuildOption, slots map[string]interface{}, attrs map[string]string) (string, string, string, []string, error) { warnings := []string{} html, err := page.BuildHTML(option) if err != nil { warnings = append(warnings, err.Error()) } + data := map[string]interface{}{} + if slots != nil { + for k, v := range slots { + data[k] = v + } + } + + if attrs != nil { + data["$prop"] = attrs + page.Attrs = attrs + } + // Add Style & Script & Warning doc, err := NewDocument([]byte(html)) if err != nil { @@ -83,7 +95,6 @@ func (page *Page) BuildForImport(option *BuildOption, slots map[string]interface } body := doc.Selection.Find("body") - if body.Length() > 1 { body.SetHtml("
" + html + "
") } @@ -96,7 +107,7 @@ func (page *Page) BuildForImport(option *BuildOption, slots map[string]interface } // Replace the slots - html, _ = Data(slots).ReplaceUse(slotRe, html) + html, _ = Data(data).ReplaceUse(slotRe, html) return html, style, script, warnings, nil } @@ -122,6 +133,7 @@ func (page *Page) parse(doc *goquery.Document, option *BuildOption, warnings []s continue } + sel.SetAttr("parsed", "true") ipage, err := tmpl.Page(name) if err != nil { sel.ReplaceWith(fmt.Sprintf("", err.Error())) @@ -136,6 +148,7 @@ func (page *Page) parse(doc *goquery.Document, option *BuildOption, warnings []s continue } + // Set the parent slots := map[string]interface{}{} for _, slot := range sel.Find("slot").Nodes { slotSel := goquery.NewDocumentFromNode(slot) @@ -150,15 +163,37 @@ func (page *Page) parse(doc *goquery.Document, option *BuildOption, warnings []s slots[slotName] = strings.TrimSpace(slotHTML) } + // Set Attrs + attrs := map[string]string{} + if sel.Length() > 0 { + if page.Attrs != nil { + parentProps := Data{"$prop": page.Attrs} + for k, v := range page.Attrs { + if k == "is" { + continue + } + attrs[k], _ = parentProps.ReplaceUse(slotRe, v) + } + } else { + for _, attr := range sel.Nodes[0].Attr { + if attr.Key == "is" { + continue + } + attrs[attr.Key] = attr.Val + } + } + } + + p := ipage.Get() namespace := fmt.Sprintf("__page_%s_%d", strings.ReplaceAll(name, "/", "_"), idx) - html, style, script, warns, err := ipage.Get().BuildForImport(&BuildOption{ + html, style, script, warns, err := p.BuildForImport(&BuildOption{ SSR: option.SSR, AssetRoot: option.AssetRoot, IgnoreAssetRoot: option.IgnoreAssetRoot, KeepPageTag: option.KeepPageTag, IgnoreDocument: true, Namespace: namespace, - }, slots) + }, slots, attrs) if err != nil { sel.ReplaceWith(fmt.Sprintf("", err.Error())) @@ -184,6 +219,11 @@ func (page *Page) parse(doc *goquery.Document, option *BuildOption, warnings []s } sel.SetAttr("s:slots", slotsAttr) + + // Set Attrs + for k, v := range attrs { + sel.SetAttr(k, v) + } continue } sel.ReplaceWithHtml(fmt.Sprintf("\n%s\n%s\n%s\n", style, html, script)) diff --git a/sui/core/data.go b/sui/core/data.go index e94359d8..e7cc67d0 100644 --- a/sui/core/data.go +++ b/sui/core/data.go @@ -28,6 +28,9 @@ var options = []expr.Option{ func (data Data) New(stmt string) (*vm.Program, error) { stmt = strings.TrimSpace(strings.TrimRight(strings.TrimLeft(stmt, "{{ "), "}}")) stmt = strings.TrimSpace(strings.TrimRight(strings.TrimLeft(stmt, "[{ "), "}]")) + // ' => ' " => " + stmt = strings.ReplaceAll(stmt, "'", "'") + stmt = strings.ReplaceAll(stmt, """, "\"") return expr.Compile(stmt, append([]expr.Option{expr.Env(data)}, options...)...) } diff --git a/sui/core/parser.go b/sui/core/parser.go index 84738df8..28a34971 100644 --- a/sui/core/parser.go +++ b/sui/core/parser.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/PuerkitoBio/goquery" + jsoniter "github.com/json-iterator/go" "golang.org/x/net/html" ) @@ -28,8 +29,9 @@ type Mapping struct { // ParserOption parser option type ParserOption struct { - Editor bool `json:"editor,omitempty"` - Preview bool `json:"preview,omitempty"` + Editor bool `json:"editor,omitempty"` + Preview bool `json:"preview,omitempty"` + PrintData bool `json:"print_data,omitempty"` } // NewTemplateParser create a new template parser @@ -50,12 +52,18 @@ func NewTemplateParser(data Data, option *ParserOption) *TemplateParser { // Render parses and renders the HTML template func (parser *TemplateParser) Render(html string) (string, error) { - reader := bytes.NewReader([]byte(fmt.Sprintf("%s", html))) + + if !strings.Contains(html, "%s`, html) + } + + reader := bytes.NewReader([]byte(html)) doc, err := goquery.NewDocumentFromReader(reader) if err != nil { return "", err } - root := doc.Selection.Find("root") + + root := doc.Selection.Find("html") parser.parseNode(root.Nodes[0]) // Replace the nodes @@ -64,9 +72,36 @@ func (parser *TemplateParser) Render(html string) (string, error) { delete(parser.replace, sel) } - // fmt.Println(root.Html()) + // Print the data + jsPrintData := "" + if parser.option != nil && parser.option.PrintData { + jsPrintData = "console.log(__sui_data);\n" + } + + // Append the data to the body + body := doc.Find("body") + if body.Length() > 0 { + data, err := jsoniter.MarshalToString(parser.data) + if err != nil { + data, _ = jsoniter.MarshalToString(map[string]string{"error": err.Error()}) + } + body.AppendHtml("\n", + ) + } + + // For editor + if parser.option != nil && parser.option.Editor { + return doc.Find("body").Html() + } + + // fmt.Println(doc.Html()) // fmt.Println(parser.errors) - return root.Html() + return doc.Html() } // Parse parses and renders the HTML template @@ -108,6 +143,37 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) { if _, exist := sel.Attr("s:for"); exist { parser.forStatementNode(sel) } + + // Parse the attributes + parser.parseElementAttrs(sel) +} + +func (parser *TemplateParser) parseElementAttrs(sel *goquery.Selection) { + if len(sel.Nodes) < 0 { + return + } + + if sel.AttrOr("parsed", "false") == "true" { + return + } + + attrs := sel.Nodes[0].Attr + for _, attr := range attrs { + parser.sequence = parser.sequence + 1 + res, hasStmt := parser.data.Replace(attr.Val) + if hasStmt { + bindings := strings.TrimSpace(attr.Val) + key := fmt.Sprintf("%v", parser.sequence) + parser.mapping[attr.Key] = Mapping{ + Key: key, + Type: "attr", + Value: bindings, + } + sel.SetAttr(attr.Key, res) + bindname := fmt.Sprintf("s:bind:%s", attr.Key) + sel.SetAttr(bindname, bindings) + } + } } func (parser *TemplateParser) parseTextNode(node *html.Node) { diff --git a/sui/core/types.go b/sui/core/types.go index 3e779c29..3f0ecd34 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -25,15 +25,16 @@ type Setting struct { // Page is the struct for the page type Page struct { - Route string `json:"route"` - Name string `json:"name,omitempty"` - TemplateID string `json:"-"` - SuiID string `json:"-"` - Config *PageConfig `json:"-"` - Path string `json:"-"` - Codes SourceCodes `json:"-"` - Document []byte `json:"-"` - GlobalData []byte `json:"-"` + Route string `json:"route"` + Name string `json:"name,omitempty"` + TemplateID string `json:"-"` + SuiID string `json:"-"` + Config *PageConfig `json:"-"` + Path string `json:"-"` + Codes SourceCodes `json:"-"` + Document []byte `json:"-"` + GlobalData []byte `json:"-"` + Attrs map[string]string `json:"-"` } // PageTreeNode is the struct for the page tree node