mediawiki-extensions-Templa.../modules/ext.templateDataGenerator.sourceHandler.js
Moriel Schottlender 3820295f23 Reorganize api calls and add sourceHandler and error message
Add a sourceHandler that deals with all source-related actions
like fetching template source and validating the existing
TemplateData string.

Also add a MessageDialog warning the user in case the TemplateData
is invalid. In this case, the user will be given the option either
to correct the JSON by hand or to start the editor to replace the
existing TemplateData with a new one.

Bug: T91730
Bug: T91325
Change-Id: I4e6d04f02565a02d8dcf5c70a575ab6433caa27f
2015-03-18 18:21:02 +00:00

272 lines
7.8 KiB
JavaScript

/**
* TemplateData Source Handler
*
* Loads and validates the templatedata and template parameters
* whether in the page itself or from its parent.
*
* @class
* @extends OO.EventEmitter
*
* @constructor
* @param {Object} config Configuration object
* @cfg {string} [fullPageName] The full name of the current page
* @cfg {string} [parentPage] The name of the parent page
* @cfg {string} [isPageSubLevel] The page is sub-level of another template
*/
mw.TemplateData.SourceHandler = function MWTemplateDataSourceHander( config ) {
config = config || {};
// Mixin constructors
OO.EventEmitter.call( this );
this.apiCache = {};
this.templateSourceCodePromise = null;
this.templateSourceCodeParams = [];
// Config
this.setParentPage( config.parentPage );
this.setPageSubLevel( config.isPageSubLevel );
this.setFullPageName( config.fullPageName );
};
/* Setup */
OO.initClass( mw.TemplateData.SourceHandler );
OO.mixinClass( mw.TemplateData.SourceHandler, OO.EventEmitter );
/**
* Get information from the mediaWiki API
* @param {string} page Page name
* @param {boolean} [getTemplateData] Fetch the templatedata in the page.
* @return {jQuery.Promise} API promise
*/
mw.TemplateData.SourceHandler.prototype.getApi = function ( page, getTemplateData ) {
var config,
type = getTemplateData ? 'templatedata' : 'query',
api = new mw.Api(),
baseConfig = {
action: type,
titles: page,
redirects: getTemplateData ? 1 : 0
};
if ( type === 'query' ) {
config = $.extend( baseConfig, {
prop: 'revisions',
rvprop: 'content',
indexpageids: '1'
} );
}
// Cache
if ( !this.apiCache[page] || !this.apiCache[page][type] ) {
this.apiCache[page] = this.apiCache[page] || {};
this.apiCache[page][type] = api.get( config );
}
return this.apiCache[page][type];
};
/**
* Go over the current wikitext and build a new model.
*
* @param {string} [wikitext] Source of the template.
* @return {jQuery.Promise} Promise resolving into a new mw.TemplateData.Model
* or is rejected if the model was impossible to create.
*/
mw.TemplateData.SourceHandler.prototype.buildModel = function ( wikitext ) {
var tdObject;
// Get the TemplateData and parse it
tdObject = this.parseModelFromString( wikitext );
if ( !tdObject ) {
// The json object is invalid. There's no need to continue.
return $.Deferred().reject();
}
// Get parameters from source code
// Mostly used for the import option
return this.getParametersFromTemplateSource( wikitext )
// This is always successful by definition
.then( function ( templateSourceCodeParams ) {
return new mw.TemplateData.Model.static.newFromObject(
tdObject,
templateSourceCodeParams
);
} );
};
/**
* Retrieve parameters from the template code from source in this order:
*
* 1. Check if there's a template in the given 'wikitext' parameter. If not,
* 2. Check if there's a template in the current page. If not,
* 3. Check if the page is a subpage and go up a level to check for template code. If none found,
* 4. Repeat until we are in the root of the template
* 5. Save the name of the page where the template is taken from
*
* Cache the templateCodePromise so we don't have to do this all over again on each
* template code request.
*
* @param {string} [wikitext] Optional. Source of the template.
* @return {jQuery.Promise} Promise resolving into template parameter array
*/
mw.TemplateData.SourceHandler.prototype.getParametersFromTemplateSource = function ( wikitext ) {
var params = [],
sourceHandler = this;
if ( !this.templateSourceCodePromise ) {
// Check given page text first
if ( wikitext ) {
params = this.extractParametersFromTemplateCode( wikitext );
}
if ( params.length > 0 ) {
// There are parameters found; Resolve.
this.templateSourceCodePromise = $.Deferred().resolve( params );
} else if ( this.isPageSubLevel() && this.getParentPage() ) {
// Get the content of the parent
this.templateSourceCodePromise = this.getApi( this.getParentPage() ).then(
function ( resp ) {
var pageContent = '';
// Verify that we have a sane response from the API.
// This is particularly important for unit tests, since the
// requested page from the API is the Qunit module and has no content
if (
resp.query.pages[resp.query.pageids[0]].revisions &&
resp.query.pages[resp.query.pageids[0]].revisions[0]
) {
pageContent = resp.query.pages[resp.query.pageids[0]].revisions[0]['*'];
}
return sourceHandler.extractParametersFromTemplateCode( pageContent );
},
function () {
// Resolve an empty parameters array
return $.Deferred().resolve( [] );
}
);
} else {
// No template found. Resolve to empty array of parameters
this.templateSourceCodePromise = $.Deferred().resolve( [] );
}
}
return this.templateSourceCodePromise;
};
/**
* Retrieve template parameters from the template code.
*
* Adapted from https://he.wikipedia.org/wiki/MediaWiki:Gadget-TemplateParamWizard.js
*
* @param {string} templateSource Source of the template.
* @return {jQuery.Promise} A promise that resolves into an
* array of parameters that appear in the template code
*/
mw.TemplateData.SourceHandler.prototype.extractParametersFromTemplateCode = function ( templateCode ) {
var matches,
paramNames = [],
paramExtractor = /{{3,}(.*?)[<|}]/mg;
while ( ( matches = paramExtractor.exec( templateCode ) ) !== null ) {
if ( $.inArray( matches[1], paramNames ) === -1 ) {
paramNames.push( $.trim( matches[1] ) );
}
}
return paramNames;
};
/**
* Look for a templatedata json string and convert it into
* the object, if it exists.
*
* @param {string} templateDataString Wikitext templatedata string
* @return {Object|null} The parsed json string. Empty if no
* templatedata string was found. Null if the json string
* failed to parse.
*/
mw.TemplateData.SourceHandler.prototype.parseModelFromString = function ( templateDataString ) {
var parts;
parts = templateDataString.match(
/<templatedata>([\s\S]*?)<\/templatedata>/i
);
// Check if <templatedata> exists
if ( parts && parts[1] && $.trim( parts[1] ).length > 0 ) {
// Parse the json string
try {
return $.parseJSON( $.trim( parts[1] ) );
} catch ( err ) {
return null;
}
} else {
// Return empty model
return { params: {} };
}
};
/**
* Set the page as a sub page of the main template
* @param {boolean} isSubLevel Page is sublevel
*/
mw.TemplateData.SourceHandler.prototype.setPageSubLevel = function ( isSubLevel ) {
this.subLevel = !!isSubLevel;
};
/**
* Set the page as a sub page of the main template
* @return {boolean} Page is sublevel
*/
mw.TemplateData.SourceHandler.prototype.isPageSubLevel = function () {
return this.subLevel;
};
/**
* Get full page name
* @param {string} pageName Page name
*/
mw.TemplateData.SourceHandler.prototype.setFullPageName = function ( pageName ) {
this.fullPageName = pageName || '';
};
/**
* Get page full name
* @return {string} Page full name
*/
mw.TemplateData.SourceHandler.prototype.getFullPageName = function () {
return this.fullPageName;
};
/**
* Set parent page
* @param {string} Parent page
*/
mw.TemplateData.SourceHandler.prototype.setParentPage = function ( parent ) {
this.parentPage = parent || '' ;
};
/**
* Get parent page
* @return {string} Parent page
*/
mw.TemplateData.SourceHandler.prototype.getParentPage = function () {
return this.parentPage;
};
/**
* Set template source code parameters
* @param {string[]} params Parameters from the template source code
*/
mw.TemplateData.SourceHandler.prototype.setTemplateSourceCodeParams = function ( params ) {
this.templateSourceCodeParams = params;
};
/**
* Set template source code parameters
* @return {string[]} Parameters from the template source code
*/
mw.TemplateData.SourceHandler.prototype.getTemplateSourceCodeParams = function () {
return this.templateSourceCodeParams;
};