Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformManager.js |
— | — | @@ -486,7 +486,28 @@ |
487 | 487 | |
488 | 488 | for ( var i = 0; i < tokensLength; i++ ) { |
489 | 489 | token = tokens[i]; |
| 490 | + |
| 491 | + if ( token.attribs ) { |
| 492 | + // TODO: Transform token attributes (that need it) before general token processing. |
| 493 | + // Sync return -> continue as-is. |
| 494 | + // Async return -> return with async |
| 495 | + // per-item callback: function ( res ) { |
| 496 | + var atm = new AttributeTransformManager ( |
| 497 | + this, |
| 498 | + this._returnAttributes.bind( this, token, cb ) |
| 499 | + ); |
490 | 500 | |
| 501 | + if ( atm.process( token.attribs ) ) { |
| 502 | + // async attribute expansion ongoing |
| 503 | + activeAccum = accum; |
| 504 | + accum = new TokenAccumulator( this, activeAccum.getParentCB( 'sibling' ) ); |
| 505 | + cb = accum.getParentCB( 'child' ); |
| 506 | + // Only transform token once attributes are fully processed |
| 507 | + // _returnAttributes will call transformTokens if sync flag is not set |
| 508 | + continue; |
| 509 | + } |
| 510 | + } |
| 511 | + |
491 | 512 | switch ( token.constructor ) { |
492 | 513 | case String: |
493 | 514 | res = this._transformToken( token, phaseEndRank, ts.text, cb ); |
— | — | @@ -545,6 +566,8 @@ |
546 | 567 | accum = new TokenAccumulator( this, activeAccum.getParentCB( 'sibling' ) ); |
547 | 568 | cb = accum.getParentCB( 'child' ); |
548 | 569 | } |
| 570 | + |
| 571 | + |
549 | 572 | } |
550 | 573 | |
551 | 574 | // Return finished tokens directly to caller, and indicate if further |
— | — | @@ -590,6 +613,36 @@ |
591 | 614 | } |
592 | 615 | }; |
593 | 616 | |
| 617 | +AsyncTokenTransformManager.prototype._returnAttributes |
| 618 | + = function ( token, cb, attributes, async ) |
| 619 | +{ |
| 620 | + this.env.dp( 'AsyncTokenTransformManager._returnAttributes: ' + |
| 621 | + JSON.stringify( token, null, 2 ) + |
| 622 | + JSON.stringify( attributes, null, 2 ) ); |
| 623 | + token.attribs = attributes; |
| 624 | + |
| 625 | + |
| 626 | + if ( async ) { |
| 627 | + // Attributes were expanded asynchronously, so now we still need to |
| 628 | + // process the token. |
| 629 | + var res = this.transformTokens( [token], cb ); |
| 630 | + if ( res.async ) { |
| 631 | + // Token transformation is again asynchronous. There is already a |
| 632 | + // TokenAccumulator associated with this token, so we only need to |
| 633 | + // be concerned with synchronously returned tokens. |
| 634 | + if ( res.tokens ) { |
| 635 | + // Pass on already-returned tokens to the cb |
| 636 | + cb( res.tokens, true ); |
| 637 | + } |
| 638 | + } else if ( res.token ) { |
| 639 | + cb ( [res.token], false ); |
| 640 | + } else { |
| 641 | + cb ( res.tokens, false ); |
| 642 | + } |
| 643 | + } |
| 644 | +}; |
| 645 | + |
| 646 | + |
594 | 647 | /** |
595 | 648 | * Callback for the end event emitted from the tokenizer. |
596 | 649 | * Either signals the end of input to the tail of an ongoing asynchronous |
— | — | @@ -764,34 +817,52 @@ |
765 | 818 | for ( var i = 0, l = attributes.length; i < l; i++ ) { |
766 | 819 | var kv = { key: [], value: [] }; |
767 | 820 | this.kvs.push( kv ); |
| 821 | + var cur = attributes[i]; |
768 | 822 | |
769 | | - // Assume that the return is async, will be decremented in callback |
770 | | - this.outstanding += 2; |
| 823 | + if ( cur.k.constructor !== String ) { |
| 824 | + // Assume that the return is async, will be decremented in callback |
| 825 | + this.outstanding++; |
771 | 826 | |
772 | | - // transform the key |
773 | | - pipe = this.manager.getAttributePipeline( this.manager.args ); |
774 | | - pipe.addListener( 'chunk', |
775 | | - this.onChunk.bind( this, this._returnAttributeKey.bind( this, i ) ) |
776 | | - ); |
777 | | - pipe.addListener( 'end', |
778 | | - this.onEnd.bind( this, this._returnAttributeKey.bind( this, i ) ) |
779 | | - ); |
780 | | - pipe.process( attributes[i].k.concat([{type:'END'}]) ); |
| 827 | + // transform the key |
| 828 | + pipe = this.manager.getAttributePipeline( this.manager.args ); |
| 829 | + pipe.addListener( 'chunk', |
| 830 | + this.onChunk.bind( this, this._returnAttributeKey.bind( this, i ) ) |
| 831 | + ); |
| 832 | + pipe.addListener( 'end', |
| 833 | + this.onEnd.bind( this, this._returnAttributeKey.bind( this, i ) ) |
| 834 | + ); |
| 835 | + pipe.process( attributes[i].k.concat([{type:'END'}]) ); |
| 836 | + } else { |
| 837 | + kv.key = cur.k; |
| 838 | + } |
781 | 839 | |
782 | | - // transform the value |
783 | | - pipe = this.manager.getAttributePipeline( this.manager.args ); |
784 | | - pipe.addListener( 'chunk', |
785 | | - this.onChunk.bind( this, this._returnAttributeValue.bind( this, i ) ) |
786 | | - ); |
787 | | - pipe.addListener( 'end', |
788 | | - this.onEnd.bind( this, this._returnAttributeValue.bind( this, i ) ) |
789 | | - ); |
790 | | - //console.log('starting attribute transform of ' + JSON.stringify( attributes[i].v ) ); |
791 | | - pipe.process( attributes[i].v.concat([{type:'END'}]) ); |
| 840 | + if ( cur.v.constructor !== String ) { |
| 841 | + // Assume that the return is async, will be decremented in callback |
| 842 | + this.outstanding++; |
| 843 | + |
| 844 | + // transform the value |
| 845 | + pipe = this.manager.getAttributePipeline( this.manager.args ); |
| 846 | + pipe.addListener( 'chunk', |
| 847 | + this.onChunk.bind( this, this._returnAttributeValue.bind( this, i ) ) |
| 848 | + ); |
| 849 | + pipe.addListener( 'end', |
| 850 | + this.onEnd.bind( this, this._returnAttributeValue.bind( this, i ) ) |
| 851 | + ); |
| 852 | + //console.log('starting attribute transform of ' + JSON.stringify( attributes[i].v ) ); |
| 853 | + pipe.process( cur.v.concat([{type:'END'}]) ); |
| 854 | + } else { |
| 855 | + kv.value = cur.v; |
| 856 | + } |
792 | 857 | } |
793 | 858 | this.outstanding--; |
794 | 859 | if ( this.outstanding === 0 ) { |
795 | 860 | this._returnAttributes(); |
| 861 | + // synchronous / done |
| 862 | + return false; |
| 863 | + } else { |
| 864 | + // async, will call back |
| 865 | + this.async = true; |
| 866 | + return true; |
796 | 867 | } |
797 | 868 | }; |
798 | 869 | |
— | — | @@ -805,7 +876,7 @@ |
806 | 877 | |
807 | 878 | // and call the callback with the result |
808 | 879 | //console.log('AttributeTransformManager._returnAttributes: ' + out ); |
809 | | - this.callback( out ); |
| 880 | + this.callback( out, this.async ); |
810 | 881 | }; |
811 | 882 | |
812 | 883 | /** |
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.ParserFunctions.js |
— | — | @@ -80,9 +80,11 @@ |
81 | 81 | }; |
82 | 82 | |
83 | 83 | ParserFunctions.prototype['pf_#tag'] = function ( target, argList, argDict ) { |
84 | | - return [ new TagTk( target ), |
85 | | - argList[0].v, |
86 | | - new EndTagTk( target ) ]; |
| 84 | + // XXX: handle things like {{#tag:nowiki|{{{input1|[[shouldnotbelink]]}}}}} |
| 85 | + // https://www.mediawiki.org/wiki/Future/Parser_development#Token_stream_transforms |
| 86 | + return [ new TagTk( target ) ] |
| 87 | + .concat( argList[0].v, |
| 88 | + [ new EndTagTk( target ) ] ); |
87 | 89 | }; |
88 | 90 | |
89 | 91 | // A first approximation, anyway.. |
Index: trunk/extensions/VisualEditor/modules/parser/pegTokenizer.pegjs.txt |
— | — | @@ -365,7 +365,7 @@ |
366 | 366 | // Returns either a list of tokens, or a plain string (if nothing is to be |
367 | 367 | // processed). |
368 | 368 | preprocessor_text |
369 | | - = r:( t:[^'<~[{\n\r\t|!\]} &=]+ { return t.join(''); } |
| 369 | + = r:( t:[^<~[{\n\r\t|!\]} &=]+ { return t.join(''); } |
370 | 370 | / directive |
371 | 371 | / !inline_breaks text_char )+ { |
372 | 372 | return flatten_string ( r ); |
— | — | @@ -790,10 +790,11 @@ |
791 | 791 | = "{{" target:template_param_text |
792 | 792 | params:(newline? "|" newline? p:template_param { return p })* |
793 | 793 | "}}" { |
794 | | - target = flatten( target ); |
795 | | - var obj = new SelfclosingTagTk( 'template' ); |
796 | | - obj.orderedArgs = params; |
797 | | - obj.target = target; |
| 794 | + // Insert target as first positional attribute, so that it can be |
| 795 | + // generically expanded. The TemplateHandler then needs to shift it out |
| 796 | + // again. |
| 797 | + params.unshift( { k: '', v: flatten( target ) } ); |
| 798 | + var obj = new SelfclosingTagTk( 'template', params ); |
798 | 799 | //console.log( 'tokenizer template ' + JSON.stringify( target )); |
799 | 800 | return obj; |
800 | 801 | } |
— | — | @@ -808,18 +809,8 @@ |
809 | 810 | params:( newline? "|" newline? p:template_param { return p })* |
810 | 811 | "}}}" { |
811 | 812 | name = flatten( name ); |
812 | | - var obj = new SelfclosingTagTk( 'templatearg', [] ); |
813 | | - obj.argname= name; |
814 | | - if (params && params.length) { |
815 | | - // HACK, not final. |
816 | | - //obj.attribs.push(new KV('data-defaultvalue', params[0][1])); |
817 | | - //console.log(JSON.stringify(params, null, 2)); |
818 | | - //obj.attribs.push(new KV('data-json-args', JSON.stringify(params))); |
819 | | - obj.defaultvalue = params[0].v.length ? params[0].v : |
820 | | - [ '' ]; |
821 | | - } else { |
822 | | - obj.defaultvalue = []; |
823 | | - } |
| 813 | + params.unshift( { k: '', v: name } ); |
| 814 | + var obj = new SelfclosingTagTk( 'templatearg', params ); |
824 | 815 | //console.log( 'tokenizer tplarg ' + JSON.stringify( obj, null, 2 )); |
825 | 816 | return obj; |
826 | 817 | } |
— | — | @@ -863,7 +854,8 @@ |
864 | 855 | wikilink |
865 | 856 | = "[[" |
866 | 857 | ! url |
867 | | - target:link_target |
| 858 | + //target:link_target |
| 859 | + target:preprocessor_text |
868 | 860 | lcontent:( "|" lt:link_text { return lt } )* |
869 | 861 | "]]" |
870 | 862 | // XXX In real MediaWiki, this is a language-dependent positive character |
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.Sanitizer.js |
— | — | @@ -13,6 +13,7 @@ |
14 | 14 | |
15 | 15 | |
16 | 16 | function Sanitizer ( manager ) { |
| 17 | + this.manager = manager; |
17 | 18 | this.register( manager ); |
18 | 19 | } |
19 | 20 | |
— | — | @@ -33,7 +34,11 @@ |
34 | 35 | return { token: token }; |
35 | 36 | } |
36 | 37 | var hrefKV = this.manager.env.lookupKV( token.attribs, 'href' ); |
| 38 | + // FIXME: Should the flattening of attributes to text be performed as part |
| 39 | + // of the AttributeTransformManager processing? This certainly is not the |
| 40 | + // right place! |
37 | 41 | if ( hrefKV !== null ) { |
| 42 | + hrefKV.v = this.manager.env.tokensToString( hrefKV.v ); |
38 | 43 | var bits = hrefKV.v.match( /(.*?\/\/)([^\/]+)(\/?.*)/ ); |
39 | 44 | if ( bits ) { |
40 | 45 | proto = bits[1]; |
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.TemplateHandler.js |
— | — | @@ -65,7 +65,6 @@ |
66 | 66 | cb: cb, |
67 | 67 | origToken: token, |
68 | 68 | resultTokens: [], |
69 | | - attribsAsync: true, |
70 | 69 | overallAsync: false, |
71 | 70 | expandDone: false |
72 | 71 | }, |
— | — | @@ -73,68 +72,37 @@ |
74 | 73 | i = 0, |
75 | 74 | res; |
76 | 75 | |
77 | | - var attributes = [ {k: [''] , v: token.target} ] |
78 | | - .concat( this._nameArgs( token.orderedArgs ) ); |
| 76 | + tplExpandData.target = token.attribs.shift().v; |
| 77 | + tplExpandData.expandedArgs = this._nameArgs( token.attribs ); |
79 | 78 | |
80 | | - //this.manager.env.dp( 'before AttributeTransformManager: ' + |
81 | | - // JSON.stringify( attributes, null, 2 ) ); |
82 | | - new AttributeTransformManager( |
83 | | - this.manager, |
84 | | - this._returnAttributes.bind( this, tplExpandData ) |
85 | | - ).process( attributes ); |
86 | | - |
87 | | - // Unblock finish |
88 | | - if ( ! tplExpandData.attribsAsync ) { |
89 | | - // Attributes were transformed synchronously |
90 | | - this.manager.env.dp ( |
91 | | - 'sync attribs for ' + JSON.stringify( tplExpandData.target ), |
92 | | - tplExpandData.expandedArgs |
93 | | - ); |
94 | | - // All attributes are fully expanded synchronously (no IO was needed) |
95 | | - return this._expandTemplate ( tplExpandData ); |
96 | | - } else { |
97 | | - // Async attribute expansion is going on |
98 | | - this.manager.env.dp( 'async return for ' + JSON.stringify( token )); |
99 | | - tplExpandData.overallAsync = true; |
100 | | - return { async: true }; |
101 | | - } |
| 79 | + // Attributes were transformed synchronously |
| 80 | + //this.manager.env.dp ( |
| 81 | + // 'sync attribs for ' + JSON.stringify( tplExpandData.target ), |
| 82 | + // tplExpandData.expandedArgs |
| 83 | + //); |
| 84 | + // All attributes are fully expanded synchronously (no IO was needed) |
| 85 | + return this._expandTemplate ( tplExpandData ); |
102 | 86 | }; |
103 | 87 | |
104 | 88 | /** |
105 | 89 | * Create positional (number) keys for arguments without explicit keys |
106 | 90 | */ |
107 | | -TemplateHandler.prototype._nameArgs = function ( orderedArgs ) { |
| 91 | +TemplateHandler.prototype._nameArgs = function ( attribs ) { |
108 | 92 | var n = 1, |
109 | 93 | out = []; |
110 | | - for ( var i = 0, l = orderedArgs.length; i < l; i++ ) { |
| 94 | + for ( var i = 0, l = attribs.length; i < l; i++ ) { |
111 | 95 | // FIXME: Also check for whitespace-only named args! |
112 | | - if ( ! orderedArgs[i].k.length ) { |
113 | | - out.push( {k: [ n.toString() ], v: orderedArgs[i].v } ); |
| 96 | + if ( ! attribs[i].k.length ) { |
| 97 | + out.push( {k: [ n.toString() ], v: attribs[i].v } ); |
114 | 98 | n++; |
115 | 99 | } else { |
116 | | - out.push( orderedArgs[i] ); |
| 100 | + out.push( attribs[i] ); |
117 | 101 | } |
118 | 102 | } |
119 | 103 | this.manager.env.dp( '_nameArgs: ' + JSON.stringify( out ) ); |
120 | 104 | return out; |
121 | 105 | }; |
122 | 106 | |
123 | | -/** |
124 | | - * Callback for argument (including target) expansion in AttributeTransformManager |
125 | | - */ |
126 | | -TemplateHandler.prototype._returnAttributes = function ( tplExpandData, |
127 | | - attributes ) |
128 | | -{ |
129 | | - this.manager.env.dp( 'TemplateHandler._returnAttributes: ' + JSON.stringify(attributes) ); |
130 | | - // Remove the target from the attributes |
131 | | - tplExpandData.attribsAsync = false; |
132 | | - tplExpandData.target = attributes[0].v; |
133 | | - attributes.shift(); |
134 | | - tplExpandData.expandedArgs = attributes; |
135 | | - if ( tplExpandData.overallAsync ) { |
136 | | - this._expandTemplate ( tplExpandData ); |
137 | | - } |
138 | | -}; |
139 | 107 | |
140 | 108 | /** |
141 | 109 | * Fetch, tokenize and token-transform a template after all arguments and the |
— | — | @@ -246,9 +214,10 @@ |
247 | 215 | // expandDone is set to true in our _onEnd handler. |
248 | 216 | if ( tplExpandData.overallAsync || |
249 | 217 | ! tplExpandData.expandDone ) { |
250 | | - tplExpandData.overallAsync = true; |
251 | 218 | this.manager.env.dp( 'Async return from _expandTemplate for ' + |
252 | | - JSON.stringify ( tplExpandData.target ) ); |
| 219 | + JSON.stringify ( tplExpandData.target ) + |
| 220 | + JSON.stringify( tplExpandData, null, 2 )); |
| 221 | + tplExpandData.overallAsync = true; |
253 | 222 | return { async: true }; |
254 | 223 | } else { |
255 | 224 | this.manager.env.dp( 'Sync return from _expandTemplate for ' + |
— | — | @@ -319,12 +288,13 @@ |
320 | 289 | // @fixme normalize name? |
321 | 290 | var self = this; |
322 | 291 | if ( title in this.manager.env.pageCache ) { |
323 | | - // Unwind the stack |
324 | | - process.nextTick( |
325 | | - function () { |
326 | | - callback( self.manager.env.pageCache[title], title ); |
327 | | - } |
328 | | - ); |
| 292 | + callback( self.manager.env.pageCache[title], title ); |
| 293 | + //// Unwind the stack |
| 294 | + //process.nextTick( |
| 295 | + // function () { |
| 296 | + // callback( self.manager.env.pageCache[title], title ); |
| 297 | + // } |
| 298 | + //); |
329 | 299 | } else if ( ! this.manager.env.fetchTemplates ) { |
330 | 300 | callback( 'Warning: Page/template fetching disabled, and no cache for ' + |
331 | 301 | title, title ); |
— | — | @@ -354,53 +324,26 @@ |
355 | 325 | * Expand template arguments with tokens from the containing frame. |
356 | 326 | */ |
357 | 327 | TemplateHandler.prototype.onTemplateArg = function ( token, frame, cb ) { |
358 | | - |
359 | | - var attributes = [{k: token.argname, v: token.defaultvalue}]; |
360 | | - |
361 | | - token.resultTokens = false; |
362 | | - |
363 | | - new AttributeTransformManager ( |
364 | | - this.manager, |
365 | | - this._returnArgAttributes.bind( this, token, cb, frame ) |
366 | | - ).process( attributes ); |
367 | | - |
368 | | - if ( token.resultTokens !== false ) { |
369 | | - // synchronous return |
370 | | - //console.log( 'synchronous attribute expand: ' + JSON.stringify( token.resultTokens ) ); |
371 | | - |
372 | | - return { tokens: token.resultTokens }; |
373 | | - } else { |
374 | | - //console.log( 'asynchronous attribute expand: ' + JSON.stringify( token, null, 2 ) ); |
375 | | - // asynchronous return |
376 | | - token.resultTokens = []; |
377 | | - return { async: true }; |
378 | | - } |
379 | | -}; |
380 | | - |
381 | | -TemplateHandler.prototype._returnArgAttributes = function ( token, cb, frame, attributes ) { |
382 | | - //console.log( '_returnArgAttributes: ' + JSON.stringify( attributes )); |
383 | | - var argName = this.manager.env.tokensToString( attributes[0].k ).trim(), |
384 | | - defaultValue = attributes[0].v, |
| 328 | + var argName = this.manager.env.tokensToString( token.attribs.shift().v ).trim(), |
385 | 329 | res; |
| 330 | + |
386 | 331 | if ( argName in this.manager.args ) { |
387 | 332 | // return tokens for argument |
388 | 333 | //console.log( 'templateArg found: ' + argName + |
389 | 334 | // ' vs. ' + JSON.stringify( this.manager.args ) ); |
390 | 335 | res = this.manager.args[argName]; |
391 | 336 | } else { |
| 337 | + var defaultValue = token.attribs[0]; |
392 | 338 | this.manager.env.dp( 'templateArg not found: ' + argName + |
393 | | - ' vs. ' + JSON.stringify( this.manager.args ) ); |
394 | | - if ( defaultValue.length ) { |
395 | | - res = defaultValue; |
| 339 | + ' vs. ' + JSON.stringify( defaultValue ) ); |
| 340 | + if ( defaultValue && ! defaultValue.k.length && defaultValue.v.length ) { |
| 341 | + res = defaultValue.v; |
396 | 342 | } else { |
397 | 343 | res = [ '{{{' + argName + '}}}' ]; |
398 | 344 | } |
399 | 345 | } |
400 | | - if ( token.resultTokens !== false ) { |
401 | | - cb( res ); |
402 | | - } else { |
403 | | - token.resultTokens = res; |
404 | | - } |
| 346 | + return { tokens: res }; |
| 347 | + |
405 | 348 | }; |
406 | 349 | |
407 | 350 | |