UNPKG

6.08 kBJavaScriptView Raw
1var sax = require('sax')
2
3var selfClosingTags = [ 'meta', 'img', 'link', 'input', 'area', 'base', 'col', 'br', 'hr' ]
4var booleanAttributes = ["checked","disabled","draggable","hidden", "multiple","required","scoped","selected"]
5
6var matchRequire = /require (.+) as (.+)/
7var matchResource = /resource (.+) as (.+)/
8
9
10module.exports = function(rawView){
11 var parser = sax.parser(false, {lowercasetags: true, normalize: true})
12
13 var result = {c: []}
14
15 // stack
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 // by stack
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){ // throw away content if element inner is bound
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 // this is also set on bound nodes, but on the inner part, so don't double up
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
225var blockAttributes = [ 't:repeat', 't:when', 't:if', 't:unless', 't:context', 't:as', 't:whitespace' ]
226function isBlock(node){
227 return blockAttributes.some(function(attr){
228 return attr in node.attributes
229 })
230}
231
232var boundAttributes = [ 't:bind', 't:view', 't:content' ]
233function isBound(node){
234 return boundAttributes.some(function(attr){
235 return attr in node.attributes
236 })
237}
238
239function isPlaceholder(nodeOrTag){
240 return (nodeOrTag.name || nodeOrTag) == 't:placeholder'
241}
242
243
244function isSelfClosing(nodeOrTag){
245 var tag = (nodeOrTag.name || nodeOrTag)
246 return !!~selfClosingTags.indexOf(tag)
247}
248
249function escapeHTML(s) {
250 return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
251}
252
253function escapeAttribute(s) {
254 return escapeHTML(s).replace(/"/g, '&quot;')
255}
\No newline at end of file