Index: trunk/extensions/ParserPlayground/ParserPlayground.php |
— | — | @@ -65,8 +65,9 @@ |
66 | 66 | 'lib.pegjs.js', |
67 | 67 | 'jquery.nodetree.js', |
68 | 68 | 'ext.parserPlayground.hashMap.js', |
69 | | - 'ext.parserPlayground.fakeParser.js', |
70 | 69 | 'ext.parserPlayground.classicParser.js', |
| 70 | + 'ext.parserPlayground.serializer.js', |
| 71 | + 'ext.parserPlayground.renderer.js', |
71 | 72 | 'ext.parserPlayground.pegParser.js', |
72 | 73 | 'ext.parserPlayground.js', |
73 | 74 | ), |
Index: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.fakeParser.js |
— | — | @@ -1,396 +0,0 @@ |
2 | | -/** |
3 | | - * @param {ParserContext} context |
4 | | - */ |
5 | | -function FakeParser(context) { |
6 | | - // whee |
7 | | - this.context = context || {}; |
8 | | -} |
9 | | - |
10 | | -/** |
11 | | - * @param {string} text |
12 | | - * @param {function(tree, error)} callback |
13 | | - */ |
14 | | -FakeParser.prototype.parseToTree = function(text, callback) { |
15 | | - // quick and crappy :D |
16 | | - var lines = text.split("\n"); |
17 | | - var blocks = []; |
18 | | - var matches; |
19 | | - /** |
20 | | - * Subparse of inline links within a paragraph etc. |
21 | | - * @param {string} line |
22 | | - * @return {object[]} list of content subblocks |
23 | | - */ |
24 | | - var linksParse = function(line) { |
25 | | - var bits = line.split('[['), |
26 | | - parts = []; |
27 | | - if (bits[0] != '') { |
28 | | - parts.push({ |
29 | | - type: 'text', |
30 | | - text: bits[0] |
31 | | - }); |
32 | | - } |
33 | | - for (var i = 1; i < bits.length; i++) { |
34 | | - var bit = bits[i]; |
35 | | - var bracketPos = bit.indexOf(']]'); |
36 | | - if (bracketPos === -1) { |
37 | | - // not a link oh noooooo |
38 | | - parts.push({ |
39 | | - type: 'text', |
40 | | - text: '[[' + bit |
41 | | - }); |
42 | | - } else { |
43 | | - var link = bit.substr(0, bracketPos); |
44 | | - var tail = bit.substr(bracketPos + 2); |
45 | | - var linkbits = link.split('|'); |
46 | | - if (linkbits.length == 1) { |
47 | | - parts.push({ |
48 | | - type: 'link', |
49 | | - target: link |
50 | | - }); |
51 | | - } else { |
52 | | - parts.push({ |
53 | | - type: 'link', |
54 | | - target: linkbits[0], |
55 | | - text: linkbits.slice(1).join('|') // @fixme multiples for images etc |
56 | | - }); |
57 | | - } |
58 | | - if (tail !== '') { |
59 | | - parts.push({ |
60 | | - type: 'text', |
61 | | - text: tail |
62 | | - }); |
63 | | - } |
64 | | - } |
65 | | - } |
66 | | - return parts; |
67 | | - }; |
68 | | - /** |
69 | | - * Subparse of all inline stuff within a paragraph etc. |
70 | | - * @param {string} line |
71 | | - * @return {object[]} list of content subblocks |
72 | | - */ |
73 | | - var inlineParse = function(line) { |
74 | | - var parts = []; |
75 | | - var bits = line.split('<ref'); |
76 | | - var re = /^([^>]*)>(.*)<\/ref\s*>(.*)/; |
77 | | - var re2 = /^([^>]*)\/>(.*)/; |
78 | | - if (bits[0] != '') { |
79 | | - // text before... |
80 | | - $.merge(parts, linksParse(bits[0])); |
81 | | - } |
82 | | - $.each(bits.slice(1), function(i, bit) { |
83 | | - var matches; |
84 | | - var after; |
85 | | - if ((matches = re.exec(bit)) != null) { |
86 | | - var params = matches[1], text = matches[2]; |
87 | | - after = matches[3]; |
88 | | - parts.push({ |
89 | | - type: 'ext', |
90 | | - name: 'ref', |
91 | | - params: params, |
92 | | - content: (text == '') ? [] : linksParse(text) |
93 | | - }); |
94 | | - } else if ((matches = re2.exec(bit)) != null) { |
95 | | - var params = matches[1]; |
96 | | - after = matches[2]; |
97 | | - parts.push({ |
98 | | - type: 'ext', |
99 | | - name: 'ref', |
100 | | - params: params |
101 | | - }); |
102 | | - } else { |
103 | | - after = '<ref' + bit; |
104 | | - } |
105 | | - if (after != '') { |
106 | | - $.merge(parts, linksParse(after)); |
107 | | - } |
108 | | - }); |
109 | | - return parts; |
110 | | - }; |
111 | | - $.each(lines, function(i, line) { |
112 | | - if (line == '') { |
113 | | - blocks.push({ |
114 | | - type: 'break' |
115 | | - }); |
116 | | - } else if (matches = /^(={1,6})(.*)\1$/.exec(line)) { |
117 | | - blocks.push({ |
118 | | - type: 'h', |
119 | | - level: matches[1].length, |
120 | | - text: matches[2] |
121 | | - }); |
122 | | - } else { |
123 | | - var parts = inlineParse(line); |
124 | | - blocks.push({ |
125 | | - type: 'para', |
126 | | - content: parts |
127 | | - }); |
128 | | - } |
129 | | - }); |
130 | | - var tree = { |
131 | | - type: 'page', |
132 | | - content: blocks |
133 | | - }; |
134 | | - callback(tree, null); |
135 | | -}; |
136 | | - |
137 | | -/** |
138 | | - * @param {object} tree |
139 | | - * @param {function(tree, error)} callback |
140 | | - */ |
141 | | -FakeParser.prototype.expandTree = function(tree, callback) { |
142 | | - // no-op! |
143 | | - callback(tree, null); |
144 | | -}; |
145 | | - |
146 | | -/** |
147 | | - * @param {object} tree |
148 | | - * @param {function(domnode, error)} callback |
149 | | - * @param {HashMap} inspectorMap |
150 | | - */ |
151 | | -FakeParser.prototype.treeToHtml = function(tree, callback, inspectorMap) { |
152 | | - var self = this; |
153 | | - var subParseArray = function(listOfTrees, node) { |
154 | | - $.each(listOfTrees, function(i, subtree) { |
155 | | - self.treeToHtml(subtree, function(subnode, err) { |
156 | | - if (subnode) { |
157 | | - node.append(subnode); |
158 | | - } |
159 | | - }, inspectorMap); |
160 | | - }); |
161 | | - }; |
162 | | - var node; |
163 | | - switch (tree.type) { |
164 | | - case 'page': |
165 | | - // A sequence of block-level elements... |
166 | | - var page = $('<div class="parseNode"></div>'); |
167 | | - subParseArray(tree.content, page); |
168 | | - if (self.context.refs) { |
169 | | - // We're at the end; drop all the remaining refs! |
170 | | - subParseArray([{ |
171 | | - type: 'ext', |
172 | | - name: 'references' |
173 | | - }], page); |
174 | | - } |
175 | | - node = page[0]; |
176 | | - break; |
177 | | - case 'para': |
178 | | - // A single-line paragraph. |
179 | | - var para = $('<p class="parseNode"></p>'); |
180 | | - subParseArray(tree.content, para); |
181 | | - node = para[0]; |
182 | | - break; |
183 | | - case 'break': |
184 | | - // Just a stub in the parse tree. |
185 | | - break; |
186 | | - case 'text': |
187 | | - // hack hack |
188 | | - node = document.createTextNode(tree.text); |
189 | | - break; |
190 | | - case 'link': |
191 | | - var link = $('<a class="parseNode"></a>'); |
192 | | - link.text(tree.text || tree.target); |
193 | | - link.attr('href', '/wiki/' + tree.target); // hack |
194 | | - node = link[0]; |
195 | | - break; |
196 | | - case 'extlink': |
197 | | - var link = $('<a class="parseNode"></a>'); |
198 | | - link.text(tree.text || tree.target); // fixme? #d links, freelinks etc |
199 | | - link.attr('href', tree.target); // hack: validate etc |
200 | | - node = link[0]; |
201 | | - break; |
202 | | - case 'h': |
203 | | - var h = $('<h' + tree.level + ' class="parseNode"></h' + tree.level + '>').text(tree.text); |
204 | | - node = h[0]; |
205 | | - break; |
206 | | - case 'i': |
207 | | - var h = $('<i class="parseNode"></i>').text(tree.text); // hack -- use contents[] |
208 | | - node = h[0]; |
209 | | - break; |
210 | | - case 'template': |
211 | | - var t = $('<span class="parseNode template"></span>').text('{{' + tree.target); |
212 | | - if ('params' in tree) { |
213 | | - $.each(tree.params, function(i, param) { |
214 | | - var str = param.contents; |
215 | | - if ('name' in param) { |
216 | | - str = param.name + '=' + str; |
217 | | - } |
218 | | - var p = $('<span></span>').text('|' + str); |
219 | | - t.append(p); |
220 | | - }); |
221 | | - } |
222 | | - t.append('}}'); |
223 | | - node = t[0]; |
224 | | - break; |
225 | | - case 'ext': |
226 | | - if (tree.name == 'ref') { |
227 | | - // Save the reference for later! |
228 | | - // @fixme names etc? |
229 | | - if (self.context.refs === undefined) { |
230 | | - self.context.refs = []; |
231 | | - } |
232 | | - self.context.refs.push(tree); |
233 | | - var refNum = self.context.refs.length; |
234 | | - var ref = $('<span class="ref parseNode">[</span>'); |
235 | | - $('<a></a>') |
236 | | - .text(refNum + '') |
237 | | - .attr('src', '#ref-' + refNum) |
238 | | - .appendTo(ref); |
239 | | - ref.append(']'); |
240 | | - node = ref[0]; |
241 | | - } else if (tree.name == 'references') { |
242 | | - // Force inline expansion of references with a given group |
243 | | - // @fixme support multiple groups etc |
244 | | - var references = $('<ol class="references parseNode"></ol>'); |
245 | | - var oldRefs = self.context.refs; |
246 | | - self.context.refs = []; |
247 | | - $.each(oldRefs, function(i, subtree) { |
248 | | - var ref = $('<li class="ref parseNode" id="ref-' + i + '"></li>'); |
249 | | - if ('content' in subtree) { |
250 | | - subParseArray(subtree.content, ref); |
251 | | - } |
252 | | - references.append(ref); |
253 | | - }); |
254 | | - node = references[0]; |
255 | | - } else if (tree.name == 'cite') { |
256 | | - // Kinda like a ref but inline. |
257 | | - // @fixme validate and output the tag parameters |
258 | | - var cite = $('<span class="cite parseNode"></span>'); |
259 | | - if ('content' in tree) { |
260 | | - subParseArray(tree.content, cite); |
261 | | - } |
262 | | - node = cite[0]; |
263 | | - } else { |
264 | | - // @fixme unrecognized exts should output as text + rendered contents? |
265 | | - callback(null, 'Unrecognized extension in parse tree'); |
266 | | - return; |
267 | | - } |
268 | | - break; |
269 | | - default: |
270 | | - callback(null, 'Unrecognized parse tree node'); |
271 | | - return; |
272 | | - } |
273 | | - if (node) { |
274 | | - if (node.nodeType == 1) { |
275 | | - $(node).data('parseNode', tree); // assign the node for the tree inspector |
276 | | - if (inspectorMap) { |
277 | | - inspectorMap.put(tree, node); // store for reverse lookup |
278 | | - } |
279 | | - } |
280 | | - callback(node); |
281 | | - } else { |
282 | | - callback(null); // hmmmm |
283 | | - } |
284 | | -}; |
285 | | - |
286 | | -/** |
287 | | - * Collapse a parse tree back to source, if possible. |
288 | | - * Ideally should exactly match the original source; |
289 | | - * at minimum the resulting source should parse into |
290 | | - * a tree that's identical to the current one. |
291 | | - * |
292 | | - * @param {object} tree |
293 | | - * @param {function(text, error)} callback |
294 | | - */ |
295 | | -FakeParser.prototype.treeToSource = function(tree, callback) { |
296 | | - var self = this; |
297 | | - var subParseArray = function(listOfTrees) { |
298 | | - var str = ''; |
299 | | - $.each(listOfTrees, function(i, subtree) { |
300 | | - self.treeToSource(subtree, function(substr, err) { |
301 | | - if (substr) { |
302 | | - str += substr; |
303 | | - } |
304 | | - }); |
305 | | - }); |
306 | | - return str; |
307 | | - }; |
308 | | - var src; |
309 | | - if (typeof tree === "string") { |
310 | | - callback(tree); |
311 | | - return; |
312 | | - } |
313 | | - switch (tree.type) { |
314 | | - case 'page': |
315 | | - src = subParseArray(tree.content); |
316 | | - break; |
317 | | - case 'para': |
318 | | - // A single-line paragraph. |
319 | | - src = subParseArray(tree.content) + '\n'; |
320 | | - break; |
321 | | - case 'break': |
322 | | - src = '\n'; |
323 | | - break; |
324 | | - case 'text': |
325 | | - // In the real world, there might be escaping. |
326 | | - src = tree.text; |
327 | | - break; |
328 | | - case 'link': |
329 | | - src = '[['; |
330 | | - src += tree.target; |
331 | | - if (tree.text) { |
332 | | - src += '|'; |
333 | | - src += tree.text; |
334 | | - } |
335 | | - src += ']]'; |
336 | | - break; |
337 | | - case 'h': |
338 | | - var stub = ''; |
339 | | - for (var i = 0; i < tree.level; i++) { |
340 | | - stub += '='; |
341 | | - } |
342 | | - src = stub + tree.text + stub + '\n'; |
343 | | - break; |
344 | | - case 'ext': |
345 | | - src = '<' + tree.name; |
346 | | - if (tree.params) { |
347 | | - for (var i = 0; i < tree.params.length; i++) { |
348 | | - var param = tree.params[i]; |
349 | | - src += ' '; |
350 | | - src += param.name + '='; |
351 | | - if ('quote' in param) { |
352 | | - src += param.quote; |
353 | | - } |
354 | | - src += param.text; |
355 | | - if ('quote' in param) { |
356 | | - src += param.quote; |
357 | | - } |
358 | | - } |
359 | | - } |
360 | | - if ('content' in tree) { |
361 | | - src += '>'; |
362 | | - src += subParseArray(tree.content); |
363 | | - src += '</' + tree.name + '>'; |
364 | | - } else { |
365 | | - src += '/>'; |
366 | | - } |
367 | | - break; |
368 | | - case 'template': |
369 | | - src = '{{' + tree.target; |
370 | | - if (tree.params) { |
371 | | - for (var i = 0; i < tree.params.length; i++) { |
372 | | - var param = tree.params[i]; |
373 | | - src += '|'; |
374 | | - if ('name' in param) { |
375 | | - src += param.name + '='; |
376 | | - } |
377 | | - src += param.contents; |
378 | | - } |
379 | | - } |
380 | | - src += '}}'; |
381 | | - break; |
382 | | - case 'i': |
383 | | - src = "''" + tree.text + "''"; |
384 | | - break; |
385 | | - case 'extlink': |
386 | | - src = '[' + tree.target + ' ' + tree.text + ']'; |
387 | | - break; |
388 | | - default: |
389 | | - callback(null, 'Unrecognized parse tree node'); |
390 | | - return; |
391 | | - } |
392 | | - if (src) { |
393 | | - callback(src); |
394 | | - } else { |
395 | | - callback(null); // hmmmm |
396 | | - } |
397 | | -}; |
Index: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.renderer.js |
— | — | @@ -0,0 +1,147 @@ |
| 2 | +/** |
| 3 | + * @param {ParserContext} context |
| 4 | + */ |
| 5 | +function MWTreeRenderer(context) { |
| 6 | + // whee |
| 7 | + this.context = context || {}; |
| 8 | +} |
| 9 | + |
| 10 | +/** |
| 11 | + * @param {object} tree |
| 12 | + * @param {function(domnode, error)} callback |
| 13 | + * @param {HashMap} inspectorMap |
| 14 | + */ |
| 15 | +MWTreeRenderer.prototype.treeToHtml = function(tree, callback, inspectorMap) { |
| 16 | + var self = this; |
| 17 | + var subParseArray = function(listOfTrees, node) { |
| 18 | + $.each(listOfTrees, function(i, subtree) { |
| 19 | + self.treeToHtml(subtree, function(subnode, err) { |
| 20 | + if (subnode) { |
| 21 | + node.append(subnode); |
| 22 | + } |
| 23 | + }, inspectorMap); |
| 24 | + }); |
| 25 | + }; |
| 26 | + var node; |
| 27 | + switch (tree.type) { |
| 28 | + case 'page': |
| 29 | + // A sequence of block-level elements... |
| 30 | + var page = $('<div class="parseNode"></div>'); |
| 31 | + subParseArray(tree.content, page); |
| 32 | + if (self.context.refs) { |
| 33 | + // We're at the end; drop all the remaining refs! |
| 34 | + subParseArray([{ |
| 35 | + type: 'ext', |
| 36 | + name: 'references' |
| 37 | + }], page); |
| 38 | + } |
| 39 | + node = page[0]; |
| 40 | + break; |
| 41 | + case 'para': |
| 42 | + // A single-line paragraph. |
| 43 | + var para = $('<p class="parseNode"></p>'); |
| 44 | + subParseArray(tree.content, para); |
| 45 | + node = para[0]; |
| 46 | + break; |
| 47 | + case 'break': |
| 48 | + // Just a stub in the parse tree. |
| 49 | + break; |
| 50 | + case 'text': |
| 51 | + // hack hack |
| 52 | + node = document.createTextNode(tree.text); |
| 53 | + break; |
| 54 | + case 'link': |
| 55 | + var link = $('<a class="parseNode"></a>'); |
| 56 | + link.text(tree.text || tree.target); |
| 57 | + link.attr('href', '/wiki/' + tree.target); // hack |
| 58 | + node = link[0]; |
| 59 | + break; |
| 60 | + case 'extlink': |
| 61 | + var link = $('<a class="parseNode"></a>'); |
| 62 | + link.text(tree.text || tree.target); // fixme? #d links, freelinks etc |
| 63 | + link.attr('href', tree.target); // hack: validate etc |
| 64 | + node = link[0]; |
| 65 | + break; |
| 66 | + case 'h': |
| 67 | + var h = $('<h' + tree.level + ' class="parseNode"></h' + tree.level + '>').text(tree.text); |
| 68 | + node = h[0]; |
| 69 | + break; |
| 70 | + case 'i': |
| 71 | + var h = $('<i class="parseNode"></i>').text(tree.text); // hack -- use contents[] |
| 72 | + node = h[0]; |
| 73 | + break; |
| 74 | + case 'template': |
| 75 | + var t = $('<span class="parseNode template"></span>').text('{{' + tree.target); |
| 76 | + if ('params' in tree) { |
| 77 | + $.each(tree.params, function(i, param) { |
| 78 | + var str = param.contents; |
| 79 | + if ('name' in param) { |
| 80 | + str = param.name + '=' + str; |
| 81 | + } |
| 82 | + var p = $('<span></span>').text('|' + str); |
| 83 | + t.append(p); |
| 84 | + }); |
| 85 | + } |
| 86 | + t.append('}}'); |
| 87 | + node = t[0]; |
| 88 | + break; |
| 89 | + case 'ext': |
| 90 | + if (tree.name == 'ref') { |
| 91 | + // Save the reference for later! |
| 92 | + // @fixme names etc? |
| 93 | + if (self.context.refs === undefined) { |
| 94 | + self.context.refs = []; |
| 95 | + } |
| 96 | + self.context.refs.push(tree); |
| 97 | + var refNum = self.context.refs.length; |
| 98 | + var ref = $('<span class="ref parseNode">[</span>'); |
| 99 | + $('<a></a>') |
| 100 | + .text(refNum + '') |
| 101 | + .attr('src', '#ref-' + refNum) |
| 102 | + .appendTo(ref); |
| 103 | + ref.append(']'); |
| 104 | + node = ref[0]; |
| 105 | + } else if (tree.name == 'references') { |
| 106 | + // Force inline expansion of references with a given group |
| 107 | + // @fixme support multiple groups etc |
| 108 | + var references = $('<ol class="references parseNode"></ol>'); |
| 109 | + var oldRefs = self.context.refs; |
| 110 | + self.context.refs = []; |
| 111 | + $.each(oldRefs, function(i, subtree) { |
| 112 | + var ref = $('<li class="ref parseNode" id="ref-' + i + '"></li>'); |
| 113 | + if ('content' in subtree) { |
| 114 | + subParseArray(subtree.content, ref); |
| 115 | + } |
| 116 | + references.append(ref); |
| 117 | + }); |
| 118 | + node = references[0]; |
| 119 | + } else if (tree.name == 'cite') { |
| 120 | + // Kinda like a ref but inline. |
| 121 | + // @fixme validate and output the tag parameters |
| 122 | + var cite = $('<span class="cite parseNode"></span>'); |
| 123 | + if ('content' in tree) { |
| 124 | + subParseArray(tree.content, cite); |
| 125 | + } |
| 126 | + node = cite[0]; |
| 127 | + } else { |
| 128 | + // @fixme unrecognized exts should output as text + rendered contents? |
| 129 | + callback(null, 'Unrecognized extension in parse tree'); |
| 130 | + return; |
| 131 | + } |
| 132 | + break; |
| 133 | + default: |
| 134 | + callback(null, 'Unrecognized parse tree node'); |
| 135 | + return; |
| 136 | + } |
| 137 | + if (node) { |
| 138 | + if (node.nodeType == 1) { |
| 139 | + $(node).data('parseNode', tree); // assign the node for the tree inspector |
| 140 | + if (inspectorMap) { |
| 141 | + inspectorMap.put(tree, node); // store for reverse lookup |
| 142 | + } |
| 143 | + } |
| 144 | + callback(node); |
| 145 | + } else { |
| 146 | + callback(null); // hmmmm |
| 147 | + } |
| 148 | +}; |
Property changes on: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.renderer.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 149 | + native |
Index: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.pegParser.js |
— | — | @@ -1,17 +1,18 @@ |
2 | 2 | /** |
3 | | - * Wrap a parser generated with pegjs into the FakeParser class, |
4 | | - * so will use FakeParser's HTML output and round-tripping functions. |
| 3 | + * Parser for wikitext to provisional temp structure, using PEG.js and |
| 4 | + * a separate PEG grammar file (pegParser.pegjs.txt) |
5 | 5 | * |
| 6 | + * Use along with the MWTreeRenderer and MWTreeSerializer classes for |
| 7 | + * HTML output and source round-tripping. |
| 8 | + * |
6 | 9 | * If installed as a user script or to customize, set parserPlaygroundPegPage |
7 | 10 | * to point at the MW page name containing the parser peg definition; default |
8 | 11 | * is 'MediaWiki:Gadget-ParserPlayground-PegParser.pegjs'. |
9 | 12 | */ |
10 | 13 | function PegParser(options) { |
11 | | - FakeParser.call(this, options); |
| 14 | + this.options = options; |
12 | 15 | } |
13 | 16 | |
14 | | -$.extend(PegParser.prototype, FakeParser.prototype); |
15 | | - |
16 | 17 | PegParser.src = false; |
17 | 18 | |
18 | 19 | PegParser.prototype.parseToTree = function(text, callback) { |
— | — | @@ -28,6 +29,15 @@ |
29 | 30 | }); |
30 | 31 | } |
31 | 32 | |
| 33 | +/** |
| 34 | + * @param {object} tree |
| 35 | + * @param {function(tree, error)} callback |
| 36 | + */ |
| 37 | +PegParser.prototype.expandTree = function(tree, callback) { |
| 38 | + // no-op! |
| 39 | + callback(tree, null); |
| 40 | +}; |
| 41 | + |
32 | 42 | PegParser.prototype.initSource = function(callback) { |
33 | 43 | if (PegParser.src) { |
34 | 44 | callback(); |
Index: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.serializer.js |
— | — | @@ -0,0 +1,121 @@ |
| 2 | +/** |
| 3 | + * @param {ParserContext} context |
| 4 | + */ |
| 5 | +function MWTreeSerializer(context) { |
| 6 | + // whee |
| 7 | + this.context = context || {}; |
| 8 | +} |
| 9 | + |
| 10 | + |
| 11 | +/** |
| 12 | + * Collapse a parse tree back to source, if possible. |
| 13 | + * Ideally should exactly match the original source; |
| 14 | + * at minimum the resulting source should parse into |
| 15 | + * a tree that's identical to the current one. |
| 16 | + * |
| 17 | + * @param {object} tree |
| 18 | + * @param {function(text, error)} callback |
| 19 | + */ |
| 20 | +MWTreeSerializer.prototype.treeToSource = function(tree, callback) { |
| 21 | + var self = this; |
| 22 | + var subParseArray = function(listOfTrees) { |
| 23 | + var str = ''; |
| 24 | + $.each(listOfTrees, function(i, subtree) { |
| 25 | + self.treeToSource(subtree, function(substr, err) { |
| 26 | + if (substr) { |
| 27 | + str += substr; |
| 28 | + } |
| 29 | + }); |
| 30 | + }); |
| 31 | + return str; |
| 32 | + }; |
| 33 | + var src; |
| 34 | + if (typeof tree === "string") { |
| 35 | + callback(tree); |
| 36 | + return; |
| 37 | + } |
| 38 | + switch (tree.type) { |
| 39 | + case 'page': |
| 40 | + src = subParseArray(tree.content); |
| 41 | + break; |
| 42 | + case 'para': |
| 43 | + // A single-line paragraph. |
| 44 | + src = subParseArray(tree.content) + '\n'; |
| 45 | + break; |
| 46 | + case 'break': |
| 47 | + src = '\n'; |
| 48 | + break; |
| 49 | + case 'text': |
| 50 | + // In the real world, there might be escaping. |
| 51 | + src = tree.text; |
| 52 | + break; |
| 53 | + case 'link': |
| 54 | + src = '[['; |
| 55 | + src += tree.target; |
| 56 | + if (tree.text) { |
| 57 | + src += '|'; |
| 58 | + src += tree.text; |
| 59 | + } |
| 60 | + src += ']]'; |
| 61 | + break; |
| 62 | + case 'h': |
| 63 | + var stub = ''; |
| 64 | + for (var i = 0; i < tree.level; i++) { |
| 65 | + stub += '='; |
| 66 | + } |
| 67 | + src = stub + tree.text + stub + '\n'; |
| 68 | + break; |
| 69 | + case 'ext': |
| 70 | + src = '<' + tree.name; |
| 71 | + if (tree.params) { |
| 72 | + for (var i = 0; i < tree.params.length; i++) { |
| 73 | + var param = tree.params[i]; |
| 74 | + src += ' '; |
| 75 | + src += param.name + '='; |
| 76 | + if ('quote' in param) { |
| 77 | + src += param.quote; |
| 78 | + } |
| 79 | + src += param.text; |
| 80 | + if ('quote' in param) { |
| 81 | + src += param.quote; |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + if ('content' in tree) { |
| 86 | + src += '>'; |
| 87 | + src += subParseArray(tree.content); |
| 88 | + src += '</' + tree.name + '>'; |
| 89 | + } else { |
| 90 | + src += '/>'; |
| 91 | + } |
| 92 | + break; |
| 93 | + case 'template': |
| 94 | + src = '{{' + tree.target; |
| 95 | + if (tree.params) { |
| 96 | + for (var i = 0; i < tree.params.length; i++) { |
| 97 | + var param = tree.params[i]; |
| 98 | + src += '|'; |
| 99 | + if ('name' in param) { |
| 100 | + src += param.name + '='; |
| 101 | + } |
| 102 | + src += param.contents; |
| 103 | + } |
| 104 | + } |
| 105 | + src += '}}'; |
| 106 | + break; |
| 107 | + case 'i': |
| 108 | + src = "''" + tree.text + "''"; |
| 109 | + break; |
| 110 | + case 'extlink': |
| 111 | + src = '[' + tree.target + ' ' + tree.text + ']'; |
| 112 | + break; |
| 113 | + default: |
| 114 | + callback(null, 'Unrecognized parse tree node'); |
| 115 | + return; |
| 116 | + } |
| 117 | + if (src) { |
| 118 | + callback(src); |
| 119 | + } else { |
| 120 | + callback(null); // hmmmm |
| 121 | + } |
| 122 | +}; |
Property changes on: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.serializer.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 123 | + native |
Index: trunk/extensions/ParserPlayground/modules/ext.parserPlayground.js |
— | — | @@ -8,11 +8,9 @@ |
9 | 9 | * |
10 | 10 | * Adds a fold-out section in the editor (using enhanced toolbar) to swap view of: |
11 | 11 | * - Source (your regular editable text) |
12 | | - * - MediaWiki parser (parsed page as full HTML) |
13 | | - * - Preprocessor tree (tree view of XML preprocessor tree; shows limited pre-parsing breakdown) |
14 | | - * - FakeParser (a very primitive parser class in this gadget) |
15 | | - * - FakeParser's parse tree |
16 | | - * - FakeParser's output and parse tree side-by-side. |
| 12 | + * - MediaWiki parser (parsed page as full HTML, with XML parse tree inspector) |
| 13 | + * - PegParser (a very primitive parser class in this gadget), with |
| 14 | + * tree inspector and primitive editing |
17 | 15 | * |
18 | 16 | * The parsed views update to match the current editor state when you bump over to them. |
19 | 17 | * In side-by-side view, matching items are highlighted on the two sides, and clicking |
— | — | @@ -123,7 +121,16 @@ |
124 | 122 | 'action': { |
125 | 123 | 'type': 'callback', |
126 | 124 | 'execute': function( context ) { |
127 | | - context.parserPlayground.parser = new parserClass(); |
| 125 | + var pp = context.parserPlayground; |
| 126 | + pp.parser = new parserClass(); |
| 127 | + // hack |
| 128 | + if (pp.parser instanceof MediaWikiParser) { |
| 129 | + pp.serializer = pp.parser; |
| 130 | + pp.renderer = pp.parser; |
| 131 | + } else { |
| 132 | + pp.serializer = new MWTreeSerializer(); |
| 133 | + pp.renderer = new MWTreeRenderer(); |
| 134 | + } |
128 | 135 | context.parserPlayground.fn.initDisplay(); |
129 | 136 | $.cookie('pp-editmode', className, { |
130 | 137 | expires: 30, |
— | — | @@ -152,13 +159,12 @@ |
153 | 160 | } |
154 | 161 | }; |
155 | 162 | addParserModes(listItems, MediaWikiParser, 'MediaWikiParser'); |
156 | | - addParserModes(listItems, FakeParser, 'FakeParser'); |
157 | 163 | addParserModes(listItems, PegParser, 'PegParser', '<p>Peg-based parser plus FakeParser\'s output. <a href="http://pegjs.majda.cz/documentation">pegjs documentation</a>; edit and reselect to reparse with updated parser</p>'); |
158 | 164 | |
159 | 165 | window.setTimeout(function() { |
160 | 166 | var context = editor.data('wikiEditor-context'); |
161 | 167 | var pp = context.parserPlayground = { |
162 | | - parser: new FakeParser(), |
| 168 | + parser: undefined, |
163 | 169 | tree: undefined, |
164 | 170 | useInspector: false, |
165 | 171 | fn: { |
— | — | @@ -196,7 +202,7 @@ |
197 | 203 | pp.treeMap.put( node, el ); |
198 | 204 | }); |
199 | 205 | } |
200 | | - pp.parser.treeToHtml(pp.tree, function(node, err) { |
| 206 | + pp.renderer.treeToHtml(pp.tree, function(node, err) { |
201 | 207 | var $dest = context.$parserContainer.find('div'); |
202 | 208 | $dest.empty().append(node); |
203 | 209 | context.parserPlayground.fn.setupEditor(context.$parserContainer); |
— | — | @@ -224,7 +230,7 @@ |
225 | 231 | pp.fn.hide(); |
226 | 232 | }; |
227 | 233 | if (pp.parser && pp.tree) { |
228 | | - pp.parser.treeToSource( pp.tree, function( src, err ) { |
| 234 | + pp.serializer.treeToSource( pp.tree, function( src, err ) { |
229 | 235 | context.$textarea.val( src ); |
230 | 236 | finish(); |
231 | 237 | }); |
— | — | @@ -257,8 +263,7 @@ |
258 | 264 | var node = $(this).data('parseNode'); |
259 | 265 | if ( node ) { |
260 | 266 | // Ok, not 100% kosher right now but... :D |
261 | | - var parser = context.parserPlayground.parser; |
262 | | - parser.treeToSource(node, function(src, err) { |
| 267 | + pp.serializer.treeToSource(node, function(src, err) { |
263 | 268 | //alert( src ); |
264 | 269 | pp.sel = { |
265 | 270 | node: node, |