1 |
yakumo_izuru |
1.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 |
|
|
} |