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 |
} |