1 | var sax = require('sax')
|
2 |
|
3 | var selfClosingTags = [ 'meta', 'img', 'link', 'input', 'area', 'base', 'col', 'br', 'hr' ]
|
4 | var booleanAttributes = ["checked","disabled","draggable","hidden", "multiple","required","scoped","selected"]
|
5 |
|
6 | var matchRequire = /require (.+) as (.+)/
|
7 | var matchResource = /resource (.+) as (.+)/
|
8 |
|
9 |
|
10 | module.exports = function(rawView){
|
11 | var parser = sax.parser(false, {lowercasetags: true, normalize: true})
|
12 |
|
13 | var result = {c: []}
|
14 |
|
15 |
|
16 | var current = result
|
17 | var depth = 0
|
18 | var stack = []
|
19 | var block = {}
|
20 | var blockStack = []
|
21 | var depthStack = []
|
22 | function stepIn(inside){
|
23 | var inner = {c: []}
|
24 | current.c.push(inner)
|
25 | stack.push(current)
|
26 | blockStack.push(block)
|
27 | if (inside){
|
28 | depthStack.push(depth + 'in')
|
29 | } else {
|
30 | depthStack.push(depth)
|
31 | }
|
32 | current = inner
|
33 | block = {}
|
34 | }
|
35 | function stepOut(){
|
36 | depthStack.pop()
|
37 | current = stack.pop()
|
38 | block = blockStack.pop()
|
39 | }
|
40 |
|
41 |
|
42 | var byDepthStack = []
|
43 | var byStack = []
|
44 | var currentBy = null
|
45 | function pushBy(query){
|
46 | byStack.push(currentBy)
|
47 | byDepthStack.push(depth)
|
48 | currentBy = query
|
49 | }
|
50 | function popBy(query){
|
51 | byDepthStack.pop()
|
52 | currentBy = byStack.pop()
|
53 | }
|
54 |
|
55 |
|
56 | function write(html){
|
57 | if (current.q && !current.v){
|
58 | return false
|
59 | }
|
60 | if (typeof html == 'string'){
|
61 | var last = current.c[current.c.length-1]
|
62 | if (typeof last == 'string'){
|
63 | current.c[current.c.length-1] += html
|
64 | } else {
|
65 | current.c.push(html)
|
66 | }
|
67 | } else {
|
68 | current.c.push(html)
|
69 | }
|
70 | }
|
71 |
|
72 | parser.ondoctype = function(doctype){
|
73 | write('<!doctype ' + doctype + '>')
|
74 | }
|
75 |
|
76 | parser.onopentag = function(node){
|
77 | depth+=1
|
78 |
|
79 | if (node.attributes['t:by']){
|
80 | pushBy(node.attributes['t:by'])
|
81 | }
|
82 |
|
83 | if (isBlock(node)){
|
84 |
|
85 | stepIn()
|
86 |
|
87 | if (node.attributes['t:repeat']){
|
88 | current['r'] = node.attributes['t:repeat']
|
89 | }
|
90 |
|
91 | if (node.attributes['t:as']){
|
92 | current.as = node.attributes['t:as']
|
93 | }
|
94 |
|
95 | if (node.attributes['t:whitespace']){
|
96 | block.whitespace = node.attributes['t:whitespace']
|
97 | }
|
98 |
|
99 | if (!isBound(node)){
|
100 |
|
101 | if (node.attributes['t:view']){
|
102 | current['v'] = node.attributes['t:view']
|
103 | }
|
104 | }
|
105 |
|
106 | if (node.attributes['t:if']){
|
107 | current['f'] = current['f'] || {}
|
108 | current['f'][node.attributes['t:if']] = {$present: true}
|
109 | }
|
110 |
|
111 | if (node.attributes['t:unless']){
|
112 | current['f'] = current['f'] || {}
|
113 | current['f'][node.attributes['t:unless']] = {$present: false}
|
114 | }
|
115 |
|
116 | if (node.attributes['t:when'] && currentBy){
|
117 | current['f'] = current['f'] || {}
|
118 |
|
119 | if (node.attributes['t:when'].indexOf('|') < 0){
|
120 | current['f'][currentBy] = node.attributes['t:when']
|
121 | } else {
|
122 | current['f'][currentBy] = {$only: node.attributes['t:when'].split('|')}
|
123 | }
|
124 | }
|
125 | }
|
126 |
|
127 | if (!isPlaceholder(node)){
|
128 | write('<' + node.name)
|
129 | Object.keys(node.attributes).forEach(function(key){
|
130 | if (key.slice(0,7) === 't:bind:'){
|
131 | var k = key.slice(7)
|
132 | var v = node.attributes[key]
|
133 | if (~booleanAttributes.indexOf(k)){
|
134 | var filter = {}
|
135 | filter[v] = {$present: true}
|
136 | write({c: [' ' + k], f: filter})
|
137 | } else {
|
138 | write(' ' + k + '="')
|
139 | write({q: v, e: 'attr'})
|
140 | write('"')
|
141 | }
|
142 | } else if (key.slice(0,2) !== 't:'){
|
143 | write(' ' + key + '="' + escapeAttribute(node.attributes[key]) + '"')
|
144 | }
|
145 | })
|
146 | if (isSelfClosing(node)){
|
147 | write('/')
|
148 | }
|
149 | write('>')
|
150 | }
|
151 |
|
152 |
|
153 | if (isBound(node)){
|
154 | stepIn(true)
|
155 | if (node.attributes['t:view']){
|
156 | current.v = node.attributes['t:view']
|
157 | }
|
158 | if (node.attributes['t:bind']){
|
159 | current.q = node.attributes['t:bind']
|
160 | }
|
161 | if ('t:content' in node.attributes){
|
162 | current.vc = true
|
163 | }
|
164 | }
|
165 | }
|
166 |
|
167 | parser.ontext = function(text){
|
168 | if (block.whitespace === 'none' && typeof text === 'string'){
|
169 | text = text.trim()
|
170 | }
|
171 |
|
172 | write(escapeHTML(text))
|
173 | }
|
174 |
|
175 | parser.onclosetag = function(tag){
|
176 | if (depthStack[depthStack.length-1] == depth + 'in'){
|
177 |
|
178 | if (!current.c.length){
|
179 | delete current.c
|
180 | }
|
181 |
|
182 | if (current.by) {
|
183 | delete current.by
|
184 | }
|
185 |
|
186 | stepOut()
|
187 | }
|
188 |
|
189 | if (!isSelfClosing(tag) && !isPlaceholder(tag)){
|
190 | write('</' + tag + '>')
|
191 | }
|
192 |
|
193 | if (depthStack[depthStack.length-1] == depth){
|
194 | stepOut()
|
195 | }
|
196 |
|
197 | if (byDepthStack[byDepthStack.length-1] == depth){
|
198 | popBy()
|
199 | }
|
200 |
|
201 | depth-=1
|
202 | }
|
203 |
|
204 | parser.onscript = function(text){
|
205 | write(text)
|
206 | }
|
207 |
|
208 | parser.onprocessinginstruction = function(node){
|
209 | var match;
|
210 | if (match = matchRequire.exec(node.body)){
|
211 | result.requires = result.requires || {}
|
212 | result.requires[match[2].trim()] = match[1].slice(1,-1)
|
213 | } else if (match = matchResource.exec(node.body)){
|
214 | result.resources = result.resources || {}
|
215 | result.resources[match[2].trim()] = match[1].slice(1,-1)
|
216 | }
|
217 | }
|
218 |
|
219 | parser.write(rawView)
|
220 | return result
|
221 | }
|
222 |
|
223 |
|
224 |
|
225 | var blockAttributes = [ 't:repeat', 't:when', 't:if', 't:unless', 't:context', 't:as', 't:whitespace' ]
|
226 | function isBlock(node){
|
227 | return blockAttributes.some(function(attr){
|
228 | return attr in node.attributes
|
229 | })
|
230 | }
|
231 |
|
232 | var boundAttributes = [ 't:bind', 't:view', 't:content' ]
|
233 | function isBound(node){
|
234 | return boundAttributes.some(function(attr){
|
235 | return attr in node.attributes
|
236 | })
|
237 | }
|
238 |
|
239 | function isPlaceholder(nodeOrTag){
|
240 | return (nodeOrTag.name || nodeOrTag) == 't:placeholder'
|
241 | }
|
242 |
|
243 |
|
244 | function isSelfClosing(nodeOrTag){
|
245 | var tag = (nodeOrTag.name || nodeOrTag)
|
246 | return !!~selfClosingTags.indexOf(tag)
|
247 | }
|
248 |
|
249 | function escapeHTML(s) {
|
250 | return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
251 | }
|
252 |
|
253 | function escapeAttribute(s) {
|
254 | return escapeHTML(s).replace(/"/g, '"')
|
255 | } |
\ | No newline at end of file |