package jade import ( "bytes" "fmt" "go/parser" "html" "io" "log" "regexp" "strings" ) type TagNode struct { NodeType Pos tr *Tree Nodes []Node AttrName []string AttrCode []string TagName string tagType itemType tab int } func (t *Tree) newTag(pos Pos, name string, tagType itemType) *TagNode { return &TagNode{tr: t, NodeType: NodeTag, Pos: pos, TagName: name, tagType: tagType, tab: t.tab} } func (l *TagNode) append(n Node) { l.Nodes = append(l.Nodes, n) } func (l *TagNode) tree() *Tree { return l.tr } func (l *TagNode) attr(a, b string) { for k, v := range l.AttrName { if v == a { l.AttrCode[k] = fmt.Sprintf(tag__arg_add, l.AttrCode[k], b) return } } l.AttrName = append(l.AttrName, a) l.AttrCode = append(l.AttrCode, b) } func codeStrFmt(a string) (string, bool) { var ( str = []rune(a) lng = len(str) first = str[0] last = str[lng-1] unesc = false ) if first == 'ߐ' { // FIXME temporarily ߐ - [AttrEqualUn] Unescaped flag set in parseAttributes() str = append(str[:0], str[1:]...) lng -= 1 first = str[0] last = str[lng-1] unesc = true } switch first { case '"', '\'': if first == last { for k, v := range str[1 : lng-1] { if v == first && str[k] != '\\' { return "", false } } if unesc { return string(str[1 : lng-1]), true } return html.EscapeString(string(str[1 : lng-1])), true } case '`': if first == last { if !strings.ContainsAny(string(str[1:lng-1]), "`") { if unesc { return string(str[1 : lng-1]), true } return html.EscapeString(string(str[1 : lng-1])), true } } } return "", false } func query(a string) (string, bool) { var ( re = regexp.MustCompile(`^(.+)\?(.+):(.+)$`) match = re.FindStringSubmatch(a) ) if len(match) == 4 { for _, v := range match[1:4] { if _, err := parser.ParseExpr(v); err != nil { return "", false } } return "qf(" + match[1] + ", " + match[2] + ", " + match[3] + ")", true } return "", false } func (l *TagNode) String() string { var b = new(bytes.Buffer) l.WriteIn(b) return b.String() } func (l *TagNode) WriteIn(b io.Writer) { var ( attr = new(bytes.Buffer) ) if len(l.AttrName) > 0 { fmt.Fprint(attr, tag__arg_bgn) for k, name := range l.AttrName { if arg, ok := codeStrFmt(l.AttrCode[k]); ok { fmt.Fprintf(attr, tag__arg_str, name, arg) } else if !golang_mode { fmt.Fprintf(attr, tag__arg, name, l.AttrCode[k]) } else if _, err := parser.ParseExpr(l.AttrCode[k]); err == nil { fmt.Fprintf(attr, tag__arg, name, l.Pos, l.AttrCode[k]) } else if arg, ok := query(l.AttrCode[k]); ok { fmt.Fprintf(attr, tag__arg, name, l.Pos, arg) } else { log.Fatalln("Error tag attribute value ==> ", l.AttrCode[k]) } } fmt.Fprint(attr, tag__arg_end) } switch l.tagType { case itemTagVoid: fmt.Fprintf(b, tag__void, l.TagName, attr) case itemTagVoidInline: fmt.Fprintf(b, tag__void, l.TagName, attr) default: fmt.Fprintf(b, tag__bgn, l.TagName, attr) for _, inner := range l.Nodes { inner.WriteIn(b) } fmt.Fprintf(b, tag__end, l.TagName) } } func (l *TagNode) CopyTag() *TagNode { if l == nil { return l } n := l.tr.newTag(l.Pos, l.TagName, l.tagType) n.tab = l.tab n.AttrCode = l.AttrCode n.AttrName = l.AttrName for _, elem := range l.Nodes { n.append(elem.Copy()) } return n } func (l *TagNode) Copy() Node { return l.CopyTag() } // // type CondNode struct { NodeType Pos tr *Tree Nodes []Node cond string condType itemType tab int } func (t *Tree) newCond(pos Pos, cond string, condType itemType) *CondNode { return &CondNode{tr: t, NodeType: NodeCond, Pos: pos, cond: cond, condType: condType, tab: t.tab} } func (l *CondNode) append(n Node) { l.Nodes = append(l.Nodes, n) } func (l *CondNode) tree() *Tree { return l.tr } func (l *CondNode) String() string { var b = new(bytes.Buffer) l.WriteIn(b) return b.String() } func (l *CondNode) WriteIn(b io.Writer) { switch l.condType { case itemIf: fmt.Fprintf(b, cond__if, l.cond) case itemUnless: fmt.Fprintf(b, cond__unless, l.cond) case itemCase: fmt.Fprintf(b, cond__case, l.cond) case itemWhile: fmt.Fprintf(b, cond__while, l.cond) case itemFor, itemEach: if k, v, name, ok := l.parseForArgs(); ok { fmt.Fprintf(b, cond__for, k, v, name) } else { fmt.Fprintf(b, "\n{{ Error malformed each: %s }}", l.cond) } case itemForIfNotContain: if k, v, name, ok := l.parseForArgs(); ok { fmt.Fprintf(b, cond__for_if, name, k, v, name) } else { fmt.Fprintf(b, "\n{{ Error malformed each: %s }}", l.cond) } default: fmt.Fprintf(b, "{{ Error Cond %s }}", l.cond) } for _, n := range l.Nodes { n.WriteIn(b) } fmt.Fprint(b, cond__end) } func (l *CondNode) parseForArgs() (k, v, name string, ok bool) { sp := strings.SplitN(l.cond, " in ", 2) if len(sp) != 2 { return } name = strings.Trim(sp[1], " ") re := regexp.MustCompile(`^(\w+)\s*,\s*(\w+)$`) kv := re.FindAllStringSubmatch(strings.Trim(sp[0], " "), -1) if len(kv) == 1 && len(kv[0]) == 3 { k = kv[0][2] v = kv[0][1] ok = true return } r2 := regexp.MustCompile(`^\w+$`) kv2 := r2.FindAllString(strings.Trim(sp[0], " "), -1) if len(kv2) == 1 { k = "_" v = kv2[0] ok = true return } return } func (l *CondNode) CopyCond() *CondNode { if l == nil { return l } n := l.tr.newCond(l.Pos, l.cond, l.condType) n.tab = l.tab for _, elem := range l.Nodes { n.append(elem.Copy()) } return n } func (l *CondNode) Copy() Node { return l.CopyCond() } // // type CodeNode struct { NodeType Pos tr *Tree codeType itemType Code []byte // The text; may span newlines. tab int } func (t *Tree) newCode(pos Pos, text string, codeType itemType) *CodeNode { return &CodeNode{tr: t, NodeType: NodeCode, Pos: pos, Code: []byte(text), codeType: codeType, tab: t.tab} } func (t *CodeNode) String() string { var b = new(bytes.Buffer) t.WriteIn(b) return b.String() } func (t *CodeNode) WriteIn(b io.Writer) { switch t.codeType { case itemCode: fmt.Fprintf(b, code__longcode, t.Code) case itemCodeBuffered: if !golang_mode { fmt.Fprintf(b, code__buffered, t.Code) } else if cb, ok := codeStrFmt(string(t.Code)); ok { fmt.Fprintf(b, code__buffered, t.Pos, `"`+cb+`"`) } else { fmt.Fprintf(b, code__buffered, t.Pos, t.Code) } case itemCodeUnescaped: fmt.Fprintf(b, code__unescaped, t.Code) case itemElse: fmt.Fprintf(b, code__else) case itemElseIf: fmt.Fprintf(b, code__else_if, t.Code) case itemForElse: fmt.Fprintf(b, code__for_else) case itemCaseWhen: fmt.Fprintf(b, code__case_when, t.Code) case itemCaseDefault: fmt.Fprintf(b, code__case_def) case itemMixinBlock: fmt.Fprintf(b, code__mix_block) default: fmt.Fprintf(b, "{{ Error Code %s }}", t.Code) } } func (t *CodeNode) tree() *Tree { return t.tr } func (t *CodeNode) Copy() Node { return &CodeNode{tr: t.tr, NodeType: NodeCode, Pos: t.Pos, codeType: t.codeType, Code: append([]byte{}, t.Code...), tab: t.tab} } // // type BlockNode struct { NodeType Pos tr *Tree blockType itemType Name string tab int } func (t *Tree) newBlock(pos Pos, name string, textType itemType) *BlockNode { return &BlockNode{tr: t, NodeType: NodeBlock, Pos: pos, Name: name, blockType: textType, tab: t.tab} } func (t *BlockNode) String() string { var b = new(bytes.Buffer) t.WriteIn(b) return b.String() } func (t *BlockNode) WriteIn(b io.Writer) { var ( out_blk = t.tr.block[t.Name] out_pre, ok_pre = t.tr.block[t.Name+"_prepend"] out_app, ok_app = t.tr.block[t.Name+"_append"] ) if ok_pre { out_pre.WriteIn(b) } out_blk.WriteIn(b) if ok_app { out_app.WriteIn(b) } } func (t *BlockNode) tree() *Tree { return t.tr } func (t *BlockNode) Copy() Node { return &BlockNode{tr: t.tr, NodeType: NodeBlock, Pos: t.Pos, blockType: t.blockType, Name: t.Name, tab: t.tab} } // // type TextNode struct { NodeType Pos tr *Tree textType itemType Text []byte // The text; may span newlines. tab int } func (t *Tree) newText(pos Pos, text string, textType itemType) *TextNode { return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text), textType: textType, tab: t.tab} } func (t *TextNode) String() string { var b = new(bytes.Buffer) t.WriteIn(b) return b.String() } func (t *TextNode) WriteIn(b io.Writer) { switch t.textType { case itemComment: fmt.Fprintf(b, text__comment, t.Text) default: fmt.Fprintf(b, text__str, t.Text) } } func (t *TextNode) tree() *Tree { return t.tr } func (t *TextNode) Copy() Node { return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, textType: t.textType, Text: append([]byte{}, t.Text...), tab: t.tab} } // // type MixinNode struct { NodeType Pos tr *Tree Nodes []Node AttrName []string AttrCode []string AttrRest []string MixinName string block string tagType itemType tab int } func (t *Tree) newMixin(pos Pos) *MixinNode { return &MixinNode{tr: t, NodeType: NodeMixin, Pos: pos, tab: t.tab} } func (l *MixinNode) append(n Node) { l.Nodes = append(l.Nodes, n) } func (l *MixinNode) attr(a, b string) { l.AttrName = append(l.AttrName, a) l.AttrCode = append(l.AttrCode, b) } func (l *MixinNode) tree() *Tree { return l.tr } func (l *MixinNode) String() string { var b = new(bytes.Buffer) l.WriteIn(b) return b.String() } func (l *MixinNode) WriteIn(b io.Writer) { var ( attr = new(bytes.Buffer) an = len(l.AttrName) rest = len(l.AttrRest) ) if an > 0 { fmt.Fprintf(attr, mixin__var_bgn) fmt.Fprintf(attr, mixin__var_block, l.block) if rest > 0 { fmt.Fprintf(attr, mixin__var_rest, strings.TrimLeft(l.AttrName[an-1], "."), l.AttrRest) l.AttrName = l.AttrName[:an-1] } for k, name := range l.AttrName { fmt.Fprintf(attr, mixin__var, name, l.AttrCode[k]) } fmt.Fprintf(attr, mixin__var_end) } fmt.Fprintf(b, mixin__bgn, attr) for _, n := range l.Nodes { n.WriteIn(b) } fmt.Fprintf(b, mixin__end) } func (l *MixinNode) CopyMixin() *MixinNode { if l == nil { return l } n := l.tr.newMixin(l.Pos) n.tab = l.tab for _, elem := range l.Nodes { n.append(elem.Copy()) } return n } func (l *MixinNode) Copy() Node { return l.CopyMixin() } // // type DoctypeNode struct { NodeType Pos tr *Tree doctype string } func (t *Tree) newDoctype(pos Pos, text string) *DoctypeNode { doc := "" txt := strings.Trim(text, " ") if len(txt) > 0 { sls := strings.SplitN(txt, " ", 2) switch sls[0] { case "5", "html": doc = `` case "xml": doc = `` case "1.1", "xhtml": doc = `` case "basic": doc = `` case "strict": doc = `` case "frameset": doc = `` case "transitional": doc = `` case "mobile": doc = `` case "4", "4strict": doc = `` case "4frameset": doc = `` case "4transitional": doc = `` } if doc == "" { doc = fmt.Sprintf("", txt) } else if doc != "" && len(sls) == 2 { doc = fmt.Sprintf(doc, " "+sls[1]) } else { doc = fmt.Sprintf(doc, "") } } else { doc = `` } return &DoctypeNode{tr: t, NodeType: NodeDoctype, Pos: pos, doctype: doc} } func (d *DoctypeNode) String() string { return fmt.Sprintf(text__str, d.doctype) } func (d *DoctypeNode) WriteIn(b io.Writer) { b.Write([]byte(d.doctype)) } func (d *DoctypeNode) tree() *Tree { return d.tr } func (d *DoctypeNode) Copy() Node { return &DoctypeNode{tr: d.tr, NodeType: NodeDoctype, Pos: d.Pos, doctype: d.doctype} }