Jump to content

User:SD0001/parseTemplate.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
/**
 * Returns an array of objects representing the usages of a given set of 
 * templates in the given wikitext. The object key-value pairs are the template 
 * |parameter=value pairs.
 * 
 * Piped links, nested templates, nowiki tags and even HTML comments in parameter 
 * values are adequately accounted for. 
 * 
 * If resolveRedirects is set as true, any transclusions of the redirects of the
 * templates would also be found. But since the API call needed to achieve this
 * makes the function asynchronous, you need to provide a callback function to 
 * execute upon completion. The function takes the result array as the argument.
 * 
 * Usage: Can be executed from the browser console or within another script.
 *
 * @param {string} wikitext  Wikitext in which to search for the templates
 * 
 * @param {(string[]|string)} templates  Name of the template page, or array of 
 * template page names. Need not necessarily be pages in template namespace, 
 * any page can be used. If no namespace is provided, it is assumed as Template:.
 * 
 * @param {boolean} [resolveRedirects=false]  Also check for transclusions of 
 * redirects of the specified `templates`?
 * 
 * @param {function} callback  Callback function to execute after processing, if 
 * resolveRedirects is set true. The result array is passed as a parameter to it. 
 * 
 * @returns {Object[]}  Returns only if `resolveRedirects` is unset
 * 
 * @throws if  (i)  API call is unsuccessful (resolveRedirects mode only)
 *            (ii)  The end of template is not found in the wikitext
 *	          (iii) resolveRedirects is set true but no callback is provided
 * 
 * ISSUES:
 * 1. It is possible that a template usage found be entirely within a comment or 
 *    nowiki tags.
 * 2. Very rare situations where, within a parameter value, there are nowiki tags 
 *    inside a comment, or vice-versa, will cause problems.
 * 
 * Found any other bug? Report at [[User talk:SD0001]] or via email.
 * 
 */

var parseTemplate = function (wikitext, templates, resolveRedirects, callback) {

	var strReplaceAt = function(string, index, char) {
		var a = string.split("");
		a[index] = char;
		return a.join("");
	};

	var pageNameRegex = function(name) {
		return '[' + name[0].toUpperCase() + name[0].toLowerCase() + ']' + mw.RegExp.escape(name.slice(1)).replace(/ |_/g,'[ _]');
	};

	var result = [];

	if (typeof templates === 'string') {
		templates = [ templates ];
	}

	var processTemplate = function(t) {

		var re_string;

		if (t.indexOf('Template:') === 0 || t.indexOf('template:') === 0) {
			re_string = '(?:[tT]emplate:)?' + pageNameRegex(t.slice(9));
		} else {
			// namespace name including colon
			var namespace = t.slice(0, t.indexOf(':') + 1);

			// page name after colon
			var pageName = t.slice(t.indexOf(':') + 1);
			re_string = pageNameRegex(namespace) + pageNameRegex(pageName);
		}

		var t_re = new RegExp( '(\\{\\{\\s*' + re_string + '\\s*)(\\||\\}\\})', 'g');

		var match = t_re.exec(wikitext);
		while (match) {

			var startIdx = match.index + match[1].length + 1;

			// number of unclosed braces
			var numUnclosed = 2;

			// are we inside a comment or between nowiki tags?
			var inCommentOrNowiki = false;

			var i;

			for ( i = startIdx; i < wikitext.length; i++ ) {
				if (! inCommentOrNowiki) {
					if (wikitext[i] === '{') {
						if(wikitext[i+1] === '{') {
							numUnclosed += 2;
							i++;
						}
					} else if (wikitext[i] === '}') {
						if(wikitext[i+1] === '}') {
							numUnclosed -= 2;
							i++;
							if(numUnclosed === 0) {
								break;
							}
						}
					} else if (wikitext[i] === '|') {
						if (numUnclosed > 2) {
							// swap out pipes in internal templates with \1 character
							wikitext = strReplaceAt(wikitext, i,'\1');
						}
					} else if (/^(<!--|<nowiki ?>)/.test(wikitext.slice(i, i + 9))) {
						inCommentOrNowiki = true;
						i += 3;
					}

				} else { // we are in a comment or nowiki
					if (wikitext[i] === '|') {
						// swap out pipes with \1 character
						wikitext = strReplaceAt(wikitext, i,'\1');
					} else if (/^(-->|<\/nowiki ?>)/.test(wikitext.slice(i, i + 10))) {
						inCommentOrNowiki = false;
						i += 2;
					}
				}
				
			}

			if (numUnclosed !== 0) {
				console.error('[parseTemplate] Failed to find closing }} of ' + t);
			}

			// text is the template text excluding the the starting {{ and ending }}
			var text = match[1].slice(2) + wikitext.slice(startIdx - 1, i - 1);

			// swap out pipe in links with \1 control character
			text = text.replace(/(\[\[[^\]]*?)\|(.*?\]\])/g, '$1\1$2');

			var chunks = text.split('|');
			var res = {};

			res[0] = chunks[0].trim();

			var unnamedIdx = 1;

			for (i=1; i < chunks.length; i++) {
				if (chunks[i].indexOf('=') === -1) {
					res[unnamedIdx++] = chunks[i].replace(/\1/g,'|').trim();
				} else {
					var key = chunks[i].slice(0, chunks[i].indexOf('=')).trim();
					var val = chunks[i].slice(chunks[i].indexOf('=') + 1).replace(/\1/g,'|').trim();
					// changed back '\1' in value to pipes
					res[key] = val;
				}
			}

			result.push(res);

			match = t_re.exec(wikitext);
		}

	};

	if(resolveRedirects && callback === undefined) {
		console.error('[parseTemplate] No callback function provided to execute after resolving of redirects');
	}

	templates.forEach(function (template, idx) {
		if (template.indexOf(':') === -1) {
		   template = "Template:" + template;
		}
		processTemplate(template);

		if(resolveRedirects) {
			$.get('/w/api.php', {
				"action": "query",
				"format": "xml",
				"prop": "linkshere",
				"titles": template,
				"lhshow": "redirect",
				"lhlimit": "max"
			}).done(function (response) {
				$(response).find('lh').each(function(idx,tag) {
					var template_redir = $(tag).attr('title');
					if($(tag).attr('ns') === '0') {
						template_redir = ':' + template_redir;
					}
					processTemplate(template_redir);
				});
				if (idx === templates.length - 1) {
					callback(result);
				}
			}).fail(function (response) {
					console.error('[parseTemplate] API call for getting redirects to {{' 
					+ template + '}} failed');
			});
		}
	});

	if(! resolveRedirects) {
		return result;
	}

};

window.parseTemplate = parseTemplate;
// </nowiki>