-/**
+/*!
* Experimental advanced wikitext parser-emitter.
* See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs
*
* @author mflaschen@wikimedia.org
*/
( function ( mw, $ ) {
+ /**
+ * @class mw.jqueryMsg
+ * @singleton
+ */
+
var oldParser,
slice = Array.prototype.slice,
parserDefaults = {
*
* Object elements of children (jQuery, HTMLElement, TextNode, etc.) will be left as is.
*
+ * @private
* @param {jQuery} $parent Parent node wrapped by jQuery
* @param {Object|string|Array} children What to append, with the same possible types as jQuery
* @return {jQuery} $parent
/**
* Decodes the main HTML entities, those encoded by mw.html.escape.
*
+ * @private
* @param {string} encode Encoded string
* @return {string} String with those entities decoded
*/
/**
* Given parser options, return a function that parses a key and replacements, returning jQuery object
+ *
+ * Try to parse a key and optional replacements, returning a jQuery object that may be a tree of jQuery nodes.
+ * If there was an error parsing, return the key and the error message (wrapped in jQuery). This should put the error right into
+ * the interface, without causing the page to halt script execution, and it hopefully should be clearer how to fix it.
+ * @private
* @param {Object} parser options
- * @return {Function} accepting ( String message key, String replacement1, String replacement2 ... ) and returning {jQuery}
+ * @return {Function}
+ * @return {Array} return.args First element is the key, replacements may be in array in 2nd element, or remaining elements.
+ * @return {jQuery} return.return
*/
function getFailableParserFn( options ) {
var parser = new mw.jqueryMsg.parser( options );
- /**
- * Try to parse a key and optional replacements, returning a jQuery object that may be a tree of jQuery nodes.
- * If there was an error parsing, return the key and the error message (wrapped in jQuery). This should put the error right into
- * the interface, without causing the page to halt script execution, and it hopefully should be clearer how to fix it.
- *
- * @param {Array} first element is the key, replacements may be in array in 2nd element, or remaining elements.
- * @return {jQuery}
- */
+
return function ( args ) {
var key = args[0],
argsArray = $.isArray( args[1] ) ? args[1] : slice.call( args, 1 );
mw.jqueryMsg = {};
/**
- * Class method.
* Returns a function suitable for use as a global, to construct strings from the message key (and optional replacements).
* e.g.
+ *
* window.gM = mediaWiki.parser.getMessageFunction( options );
* $( 'p#headline' ).html( gM( 'hello-user', username ) );
*
* Like the old gM() function this returns only strings, so it destroys any bindings. If you want to preserve bindings use the
* jQuery plugin version instead. This is only included for backwards compatibility with gM().
*
- * @param {Array} parser options
- * @return {Function} function suitable for assigning to window.gM
+ * N.B. replacements are variadic arguments or an array in second parameter. In other words:
+ * somefunction( a, b, c, d )
+ * is equivalent to
+ * somefunction( a, [b, c, d] )
+ *
+ * @param {Object} options parser options
+ * @return {Function} Function suitable for assigning to window.gM
+ * @return {string} return.key Message key.
+ * @return {Array|Mixed} return.replacements Optional variable replacements (variadically or an array).
+ * @return {string} return.return Rendered HTML.
*/
mw.jqueryMsg.getMessageFunction = function ( options ) {
var failableParserFn = getFailableParserFn( options ),
format = parserDefaults.format;
}
- /**
- * N.B. replacements are variadic arguments or an array in second parameter. In other words:
- * somefunction( a, b, c, d )
- * is equivalent to
- * somefunction( a, [b, c, d] )
- *
- * @param {string} key Message key.
- * @param {Array|mixed} replacements Optional variable replacements (variadically or an array).
- * @return {string} Rendered HTML.
- */
return function () {
var failableResult = failableParserFn( arguments );
if ( format === 'text' || format === 'escaped' ) {
};
/**
- * Class method.
* Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to
* the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links.
* e.g.
+ *
* $.fn.msg = mediaWiki.parser.getJqueryPlugin( options );
* var userlink = $( '<a>' ).click( function () { alert( "hello!!" ) } );
* $( 'p#headline' ).msg( 'hello-user', userlink );
*
- * @param {Array} parser options
- * @return {Function} function suitable for assigning to jQuery plugin, such as $.fn.msg
+ * N.B. replacements are variadic arguments or an array in second parameter. In other words:
+ * somefunction( a, b, c, d )
+ * is equivalent to
+ * somefunction( a, [b, c, d] )
+ *
+ * We append to 'this', which in a jQuery plugin context will be the selected elements.
+ *
+ * @param {Object} options Parser options
+ * @return {Function} Function suitable for assigning to jQuery plugin, such as jQuery#msg
+ * @return {string} return.key Message key.
+ * @return {Array|Mixed} return.replacements Optional variable replacements (variadically or an array).
+ * @return {jQuery} return.return
*/
mw.jqueryMsg.getPlugin = function ( options ) {
var failableParserFn = getFailableParserFn( options );
- /**
- * N.B. replacements are variadic arguments or an array in second parameter. In other words:
- * somefunction( a, b, c, d )
- * is equivalent to
- * somefunction( a, [b, c, d] )
- *
- * We append to 'this', which in a jQuery plugin context will be the selected elements.
- * @param {string} key Message key.
- * @param {Array|mixed} replacements Optional variable replacements (variadically or an array).
- * @return {jQuery} this
- */
+
return function () {
var $target = this.empty();
// TODO: Simply appendWithoutParsing( $target, failableParserFn( arguments ).contents() )
/**
* The parser itself.
* Describes an object, whose primary duty is to .parse() message keys.
- * @param {Array} options
+ *
+ * @class
+ * @private
+ * @param {Object} options
*/
mw.jqueryMsg.parser = function ( options ) {
this.settings = $.extend( {}, parserDefaults, options );
*
* The two parts of the key are separated by colon. For example:
*
- * "message-key:true": ast
+ * "message-key:true": ast
*
* if they key is "message-key" and onlyCurlyBraceTransform is true.
*
* This cache is shared by all instances of mw.jqueryMsg.parser.
*
+ * NOTE: We promise, it's static - when you create this empty object
+ * in the prototype, each new instance of the class gets a reference
+ * to the same object.
+ *
* @static
+ * @property {Object}
*/
astCache: {},
* Where the magic happens.
* Parses a message from the key, and swaps in replacements as necessary, wraps in jQuery
* If an error is thrown, returns original key, and logs the error
- * @param {String} key Message key.
+ * @param {string} key Message key.
* @param {Array} replacements Variable replacements for $1, $2... $n
* @return {jQuery}
*/
parse: function ( key, replacements ) {
return this.emitter.emit( this.getAst( key ), replacements );
},
+
/**
* Fetch the message string associated with a key, return parsed structure. Memoized.
* Note that we pass '[' + key + ']' back for a missing message here.
- * @param {String} key
- * @return {String|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing
+ * @param {string} key
+ * @return {string|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing
*/
getAst: function ( key ) {
var cacheKey = [key, this.settings.onlyCurlyBraceTransform].join( ':' ), wikiText;
* CAVEAT: This does not parse all wikitext. It could be more efficient, but it's pretty good already.
* n.b. We want to move this functionality to the server. Nothing here is required to be on the client.
*
- * @param {String} message string wikitext
+ * @param {string} input Message string wikitext
* @throws Error
* @return {Mixed} abstract syntax tree
*/
// =========================================================
// parsing combinators - could be a library on its own
// =========================================================
- // Try parsers until one works, if none work return null
+
+ /**
+ * Try parsers until one works, if none work return null
+ * @private
+ * @param {Function[]} ps
+ * @return {string|null}
+ */
function choice( ps ) {
return function () {
var i, result;
return null;
};
}
- // try several ps in a row, all must succeed or return null
- // this is the only eager one
+
+ /**
+ * Try several ps in a row, all must succeed or return null.
+ * This is the only eager one.
+ * @private
+ * @param {Function[]} ps
+ * @return {string|null}
+ */
function sequence( ps ) {
var i, res,
originalPos = pos,
}
return result;
}
- // run the same parser over and over until it fails.
- // must succeed a minimum of n times or return null
+
+ /**
+ * Run the same parser over and over until it fails.
+ * Must succeed a minimum of n times or return null.
+ * @private
+ * @param {number} n
+ * @param {Function} p
+ * @return {string|null}
+ */
function nOrMore( n, p ) {
return function () {
var originalPos = pos,
return result;
};
}
- // There is a general pattern -- parse a thing, if that worked, apply transform, otherwise return null.
- // But using this as a combinator seems to cause problems when combined with nOrMore().
- // May be some scoping issue
+
+ /**
+ * There is a general pattern -- parse a thing, if that worked, apply transform, otherwise return null.
+ *
+ * TODO: But using this as a combinator seems to cause problems when combined with #nOrMore().
+ * May be some scoping issue
+ *
+ * @private
+ * @param {Function} p
+ * @param {Function} fn
+ * @return {string|null}
+ */
function transform( p, fn ) {
return function () {
var result = p();
return result === null ? null : fn( result );
};
}
- // Helpers -- just make ps out of simpler JS builtin types
+
+ /**
+ * Just make parsers out of simpler JS builtin types
+ * @private
+ * @param {string} s
+ * @return {Function}
+ * @return {string} return.return
+ */
function makeStringParser( s ) {
var len = s.length;
return function () {
* The regex being passed in should start with a ^ to anchor it to the start
* of the string.
*
+ * @private
* @param {RegExp} regex anchored regex
* @return {Function} function to parse input based on the regex
*/
};
}
- /**
- * ===================================================================
- * General patterns above this line -- wikitext specific parsers below
- * ===================================================================
- */
+ // ===================================================================
+ // General patterns above this line -- wikitext specific parsers below
+ // ===================================================================
+
// Parsing functions follow. All parsing functions work like this:
// They don't accept any arguments.
// Instead, they just operate non destructively on the string 'input'
* Parsing has been applied depth-first we can assume that all nodes here are single nodes
* Must return a single node to parents -- a jQuery with synthetic span
* However, unwrap any other synthetic spans in our children and pass them upwards
- * @param {Array} nodes - mixed, some single nodes, some arrays of nodes
+ * @param {Mixed[]} nodes Some single nodes, some arrays of nodes
* @return {jQuery}
*/
concat: function ( nodes ) {
* Note that we expect the parsed parameter to be zero-based. i.e. $1 should have become [ 0 ].
* if the specified parameter is not found return the same string
* (e.g. "$99" -> parameter 98 -> not found -> return "$99" )
+ *
* TODO: Throw error if nodes.length > 1 ?
- * @param {Array} of one element, integer, n >= 0
+ *
+ * @param {Array} nodes List of one element, integer, n >= 0
+ * @param {Array} replacements
* @return {String} replacement
*/
replace: function ( nodes, replacements ) {
/**
* Converts array of HTML element key value pairs to object
*
- * @param {Array} nodes array of consecutive key value pairs, with index 2 * n being a name and 2 * n + 1 the associated value
- * @return {Object} object mapping attribute name to attribute value
+ * @param {Array} nodes Array of consecutive key value pairs, with index 2 * n being a
+ * name and 2 * n + 1 the associated value
+ * @return {Object} Object mapping attribute name to attribute value
*/
htmlattributes: function ( nodes ) {
var i, len, mapping = {};
/**
* Handles an (already-validated) HTML element.
*
- * @param {Array} nodes nodes to process when creating element
+ * @param {Array} nodes Nodes to process when creating element
* @return {jQuery|Array} jQuery node for valid HTML or array for disallowed element
*/
htmlelement: function ( nodes ) {
/**
* Transform parsed structure into external link
* If the href is a jQuery object, treat it as "enclosing" the link text.
- * ... function, treat it as the click handler
- * ... string, treat it as a URI
+ *
+ * - ... function, treat it as the click handler.
+ * - ... string, treat it as a URI.
+ *
* TODO: throw an error if nodes.length > 2 ?
- * @param {Array} of two elements, {jQuery|Function|String} and {String}
+ *
+ * @param {Array} nodes List of two elements, {jQuery|Function|String} and {String}
* @return {jQuery}
*/
extlink: function ( nodes ) {
* as url), but we don't want to run the regular replace here-on: inserting a
* url as href-attribute of a link will automatically escape it already, so
* we don't want replace to (manually) escape it as well.
- * TODO throw error if nodes.length > 1 ?
- * @param {Array} of one element, integer, n >= 0
- * @return {String} replacement
+ *
+ * TODO: throw error if nodes.length > 1 ?
+ *
+ * @param {Array} nodes List of one element, integer, n >= 0
+ * @return {string} replacement
*/
extlinkparam: function ( nodes, replacements ) {
var replacement,
* Transform parsed structure into pluralization
* n.b. The first node may be a non-integer (for instance, a string representing an Arabic number).
* So convert it back with the current language's convertNumber.
- * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ]
- * @return {String} selected pluralized form according to current language
+ * @param {Array} nodes List of nodes, [ {string|number}, {string}, {string} ... ]
+ * @return {string} selected pluralized form according to current language
*/
plural: function ( nodes ) {
var forms, count;
* The first node is either a string, which can be "male" or "female",
* or a User object (not a username).
*
- * @param {Array} of nodes, [ {String|mw.User}, {String}, {String}, {String} ]
- * @return {String} selected gender form according to current language
+ * @param {Array} nodes List of nodes, [ {string|mw.User}, {string}, {string}, {string} ]
+ * @return {string} selected gender form according to current language
*/
gender: function ( nodes ) {
var gender, forms;
/**
* Transform parsed structure into grammar conversion.
- * Invoked by putting {{grammar:form|word}} in a message
- * @param {Array} of nodes [{Grammar case eg: genitive}, {String word}]
- * @return {String} selected grammatical form according to current language
+ * Invoked by putting `{{grammar:form|word}}` in a message
+ * @param {Array} nodes List of nodes [{Grammar case eg: genitive}, {string word}]
+ * @return {string} selected grammatical form according to current language
*/
grammar: function ( nodes ) {
var form = nodes[0],
/**
* Tranform parsed structure into a int: (interface language) message include
- * Invoked by putting {{int:othermessage}} into a message
- * @param {Array} of nodes
+ * Invoked by putting `{{int:othermessage}}` into a message
+ * @param {Array} nodes List of nodes
* @return {string} Other message
*/
int: function ( nodes ) {
/**
* Takes an unformatted number (arab, no group separators and . as decimal separator)
* and outputs it in the localized digit script and formatted with decimal
- * separator, according to the current language
- * @param {Array} of nodes
- * @return {Number|String} formatted number
+ * separator, according to the current language.
+ * @param {Array} nodes List of nodes
+ * @return {number|string} Formatted number
*/
formatnum: function ( nodes ) {
var isInteger = ( nodes[1] && nodes[1] === 'R' ) ? true : false,
// But moving it to extensions breaks it (?!)
// Need to fix plugin so it could do attributes as well, then will be okay to remove this.
window.gM = mw.jqueryMsg.getMessageFunction();
+
+ /**
+ * @method
+ * @member jQuery
+ * @see mw.jqueryMsg#getPlugin
+ */
$.fn.msg = mw.jqueryMsg.getPlugin();
// Replace the default message parser with jqueryMsg