66
|
1 package parser
|
|
2
|
|
3 import (
|
|
4 "regexp"
|
|
5 "strings"
|
|
6 )
|
|
7
|
|
8 var selfClosingTags = [...]string{
|
|
9 "meta",
|
|
10 "img",
|
|
11 "link",
|
|
12 "input",
|
|
13 "source",
|
|
14 "area",
|
|
15 "base",
|
|
16 "col",
|
|
17 "br",
|
|
18 "hr",
|
|
19 }
|
|
20
|
|
21 var doctypes = map[string]string{
|
|
22 "5": `<!DOCTYPE html>`,
|
|
23 "default": `<!DOCTYPE html>`,
|
|
24 "xml": `<?xml version="1.0" encoding="utf-8" ?>`,
|
|
25 "transitional": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`,
|
|
26 "strict": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">`,
|
|
27 "frameset": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">`,
|
|
28 "1.1": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
|
|
29 "basic": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">`,
|
|
30 "mobile": `<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">`,
|
|
31 }
|
|
32
|
|
33 type Node interface {
|
|
34 Pos() SourcePosition
|
|
35 }
|
|
36
|
|
37 type SourcePosition struct {
|
|
38 LineNum int
|
|
39 ColNum int
|
|
40 TokenLength int
|
|
41 Filename string
|
|
42 }
|
|
43
|
|
44 func (s *SourcePosition) Pos() SourcePosition {
|
|
45 return *s
|
|
46 }
|
|
47
|
|
48 type Doctype struct {
|
|
49 SourcePosition
|
|
50 Value string
|
|
51 }
|
|
52
|
|
53 func newDoctype(value string) *Doctype {
|
|
54 dt := new(Doctype)
|
|
55 dt.Value = value
|
|
56 return dt
|
|
57 }
|
|
58
|
|
59 func (d *Doctype) String() string {
|
|
60 if defined := doctypes[d.Value]; len(defined) != 0 {
|
|
61 return defined
|
|
62 }
|
|
63
|
|
64 return `<!DOCTYPE ` + d.Value + `>`
|
|
65 }
|
|
66
|
|
67 type Comment struct {
|
|
68 SourcePosition
|
|
69 Value string
|
|
70 Block *Block
|
|
71 Silent bool
|
|
72 }
|
|
73
|
|
74 func newComment(value string) *Comment {
|
|
75 dt := new(Comment)
|
|
76 dt.Value = value
|
|
77 dt.Block = nil
|
|
78 dt.Silent = false
|
|
79 return dt
|
|
80 }
|
|
81
|
|
82 type Text struct {
|
|
83 SourcePosition
|
|
84 Value string
|
|
85 Raw bool
|
|
86 }
|
|
87
|
|
88 func newText(value string, raw bool) *Text {
|
|
89 dt := new(Text)
|
|
90 dt.Value = value
|
|
91 dt.Raw = raw
|
|
92 return dt
|
|
93 }
|
|
94
|
|
95 type Block struct {
|
|
96 SourcePosition
|
|
97 Children []Node
|
|
98 }
|
|
99
|
|
100 func newBlock() *Block {
|
|
101 block := new(Block)
|
|
102 block.Children = make([]Node, 0)
|
|
103 return block
|
|
104 }
|
|
105
|
|
106 func (b *Block) push(node Node) {
|
|
107 b.Children = append(b.Children, node)
|
|
108 }
|
|
109
|
|
110 func (b *Block) pushFront(node Node) {
|
|
111 b.Children = append([]Node{node}, b.Children...)
|
|
112 }
|
|
113
|
|
114 func (b *Block) CanInline() bool {
|
|
115 if len(b.Children) == 0 {
|
|
116 return true
|
|
117 }
|
|
118
|
|
119 allText := true
|
|
120
|
|
121 for _, child := range b.Children {
|
|
122 if txt, ok := child.(*Text); !ok || txt.Raw {
|
|
123 allText = false
|
|
124 break
|
|
125 }
|
|
126 }
|
|
127
|
|
128 return allText
|
|
129 }
|
|
130
|
|
131 const (
|
|
132 NamedBlockDefault = iota
|
|
133 NamedBlockAppend
|
|
134 NamedBlockPrepend
|
|
135 )
|
|
136
|
|
137 type NamedBlock struct {
|
|
138 Block
|
|
139 Name string
|
|
140 Modifier int
|
|
141 }
|
|
142
|
|
143 func newNamedBlock(name string) *NamedBlock {
|
|
144 bb := new(NamedBlock)
|
|
145 bb.Name = name
|
|
146 bb.Block.Children = make([]Node, 0)
|
|
147 bb.Modifier = NamedBlockDefault
|
|
148 return bb
|
|
149 }
|
|
150
|
|
151 type Attribute struct {
|
|
152 SourcePosition
|
|
153 Name string
|
|
154 Value string
|
|
155 IsRaw bool
|
|
156 Condition string
|
|
157 }
|
|
158
|
|
159 type Tag struct {
|
|
160 SourcePosition
|
|
161 Block *Block
|
|
162 Name string
|
|
163 IsInterpolated bool
|
|
164 Attributes []Attribute
|
|
165 }
|
|
166
|
|
167 func newTag(name string) *Tag {
|
|
168 tag := new(Tag)
|
|
169 tag.Block = nil
|
|
170 tag.Name = name
|
|
171 tag.Attributes = make([]Attribute, 0)
|
|
172 tag.IsInterpolated = false
|
|
173 return tag
|
|
174
|
|
175 }
|
|
176
|
|
177 func (t *Tag) IsSelfClosing() bool {
|
|
178 for _, tag := range selfClosingTags {
|
|
179 if tag == t.Name {
|
|
180 return true
|
|
181 }
|
|
182 }
|
|
183
|
|
184 return false
|
|
185 }
|
|
186
|
|
187 func (t *Tag) IsRawText() bool {
|
|
188 return t.Name == "style" || t.Name == "script"
|
|
189 }
|
|
190
|
|
191 type Condition struct {
|
|
192 SourcePosition
|
|
193 Positive *Block
|
|
194 Negative *Block
|
|
195 Expression string
|
|
196 }
|
|
197
|
|
198 func newCondition(exp string) *Condition {
|
|
199 cond := new(Condition)
|
|
200 cond.Expression = exp
|
|
201 return cond
|
|
202 }
|
|
203
|
|
204 type Each struct {
|
|
205 SourcePosition
|
|
206 X string
|
|
207 Y string
|
|
208 Expression string
|
|
209 Block *Block
|
|
210 }
|
|
211
|
|
212 func newEach(exp string) *Each {
|
|
213 each := new(Each)
|
|
214 each.Expression = exp
|
|
215 return each
|
|
216 }
|
|
217
|
|
218 type Assignment struct {
|
|
219 SourcePosition
|
|
220 X string
|
|
221 Expression string
|
|
222 }
|
|
223
|
|
224 func newAssignment(x, expression string) *Assignment {
|
|
225 assgn := new(Assignment)
|
|
226 assgn.X = x
|
|
227 assgn.Expression = expression
|
|
228 return assgn
|
|
229 }
|
|
230
|
|
231 type Mixin struct {
|
|
232 SourcePosition
|
|
233 Block *Block
|
|
234 Name string
|
|
235 Args []string
|
|
236 }
|
|
237
|
|
238 func newMixin(name, args string) *Mixin {
|
|
239 mixin := new(Mixin)
|
|
240 mixin.Name = name
|
|
241
|
|
242 delExp := regexp.MustCompile(`,\s`)
|
|
243 mixin.Args = delExp.Split(args, -1)
|
|
244
|
|
245 for i := 0; i < len(mixin.Args); i++ {
|
|
246 mixin.Args[i] = strings.TrimSpace(mixin.Args[i])
|
|
247 if mixin.Args[i] == "" {
|
|
248 mixin.Args = append(mixin.Args[:i], mixin.Args[i+1:]...)
|
|
249 i--
|
|
250 }
|
|
251 }
|
|
252
|
|
253 return mixin
|
|
254 }
|
|
255
|
|
256 type MixinCall struct {
|
|
257 SourcePosition
|
|
258 Name string
|
|
259 Args []string
|
|
260 }
|
|
261
|
|
262 func newMixinCall(name, args string) *MixinCall {
|
|
263 mixinCall := new(MixinCall)
|
|
264 mixinCall.Name = name
|
|
265
|
|
266 if args != "" {
|
|
267 const t = "%s"
|
|
268 quoteExp := regexp.MustCompile(`"(.*?)"`)
|
|
269 delExp := regexp.MustCompile(`,\s`)
|
|
270
|
|
271 quotes := quoteExp.FindAllString(args, -1)
|
|
272 replaced := quoteExp.ReplaceAllString(args, t)
|
|
273 mixinCall.Args = delExp.Split(replaced, -1)
|
|
274
|
|
275 qi := 0
|
|
276 for i, arg := range mixinCall.Args {
|
|
277 if arg == t {
|
|
278 mixinCall.Args[i] = quotes[qi]
|
|
279 qi++
|
|
280 }
|
|
281 }
|
|
282 }
|
|
283
|
|
284 return mixinCall
|
|
285 }
|