mediawiki-extensions-Visual.../modules/parser/ext.util.TokenCollector.js
Gabriel Wicke 6e21f6bb27 Forward-port Cite extension
* Adapted Cite extension to use current interfaces and token formats
* Improved TokenCollector

Change-Id: I20419b19edd9bbad2c2abf17a2ff1411b99c0c04
2012-05-03 13:22:01 +02:00

125 lines
4.1 KiB
JavaScript

/**
* Small utility class that encapsulates the common 'collect all tokens
* starting from a token of type x until token of type y or (optionally) the
* end-of-input'. Only supported for synchronous in-order transformation
* stages (SyncTokenTransformManager), as async out-of-order expansions
* would wreak havoc with this kind of collector.
*
* @author Gabriel Wicke <gwicke@wikimedia.org>
*
* Calls the passed-in callback with the collected tokens.
*
* XXX: optionally support nested delimiters using a stack?
*/
/**
* @class
* @constructor
* @param {Object} SyncTokenTransformManager to register with
* @param {Function} Transform function, called like this:
* transform( tokens, cb, manager ) with
* tokens: chunk of tokens
* cb: function, returnTokens ( tokens, notYetDone ) with notYetDone
* indicating the last chunk of an async return.
* manager: TokenTransformManager, provides the args etc.
* @param {Boolean} Match the 'end' tokens as closing tag as well (accept
* unclosed sections).
* @param {Nummber} Numerical rank of the tranform
* @param {String} Token type to register for ('tag', 'text' etc)
* @param {String} (optional, only for token type 'tag'): tag name.
*/
function TokenCollector ( manager, transformation, toEnd, rank, type, name ) {
this.transformation = transformation;
this.manager = manager;
this.rank = rank;
this.type = type;
this.name = name;
this.toEnd = toEnd;
this.tokens = [];
this.isActive = false;
manager.addTransform( this._onDelimiterToken.bind( this ), rank, type, name );
}
/**
* Register any collector with slightly lower priority than the start/end token type
* XXX: This feels a bit hackish, a list-of-registrations per rank might be
* better.
*/
TokenCollector.prototype._anyDelta = 0.00001;
/**
* Handle the delimiter token.
* XXX: Adjust to sync phase callback when that is modified!
*/
TokenCollector.prototype._onDelimiterToken = function ( token, frame, cb ) {
if ( token.constructor === SelfclosingTagTk && !this.isActive ) {
this.manager.env.dp( 'skipping collection on ', token );
// just ignore it
return { token: token }; //this.transformation( [token, token] );
} else if ( this.isActive &&
( token.constructor === EndTagTk || token.constructor === EOFTk ) ) {
this.manager.env.dp( 'finishing collection on ', token );
var res;
// finish processing
this.tokens.push ( token );
this.isActive = false;
this.manager.removeTransform( this.rank + this._anyDelta, 'any' );
this.manager.removeTransform( this.rank, 'end' );
if ( token.constructor !== EOFTk || this.toEnd ) {
// end token
res = this.transformation ( this.tokens );
this.tokens = [];
// Transformation can be either sync or async, but receives all collected
// tokens instead of a single token.
return res;
// XXX sync version: return tokens
} else {
// just return collected tokens
res = this.tokens;
this.tokens = [];
return { tokens: res };
}
} else if ( token.constructor !== EOFTk ) {
if ( this.isActive ) {
this.manager.env.dp( 'already active: ', token );
} else {
// start collection
this.manager.env.dp( 'starting collection on ', token );
this.manager.addTransform( this._onAnyToken.bind ( this ),
this.rank + this._anyDelta, 'any' );
this.manager.addTransform( this._onDelimiterToken.bind( this ),
this.rank, 'end' );
this.isActive = true;
}
this.tokens.push ( token );
// Did not encounter a matching end token before the end, and are not
// supposed to collect to the end. So just return the tokens verbatim.
return { };
} else {
// pass through end token
this.tokens = [];
this.isActive = false;
return { token: token };
}
};
/**
* Handle 'any' token in between delimiter tokens. Activated when
* encountering the delimiter token, and collects all tokens until the end
* token is reached.
*/
TokenCollector.prototype._onAnyToken = function ( token, frame, cb ) {
// Simply collect anything ordinary in between
this.tokens.push( token );
return { };
};
if (typeof module == "object") {
module.exports.TokenCollector = TokenCollector;
}