User:Evad37/Module/parseAllTemplates.js

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.
/**
 * @module parseAllTemplates
 * @description Returns an array of objects representing all templates used 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.
 * Derived from <https://en.wikipedia.org/wiki/User:SD0001/parseAllTemplates.js>.
 * @author User:SD0001
 * @version 1.0.0
 * @exports {Function} parseAllTemplates
 *   @param {String} wikitext  Wikitext in which to search for the templates
 *   @returns {Object[]} The 0 key holds the name of the template. The remaining 
 *   key-value pairs are the template |parameter=value pairs.
 * @todo ISSUES:
 * 1. 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.
 */
Window.exportScriptModule('parseAllTemplates', (function() {
	var parseAllTemplates = function (wikitext) {
	
		var strReplaceAt = function(string, index, char) {
			return string.slice(0,index) + char + string.slice(index + 1);
		};
	
		var result = [];
		
		var processTemplateText = function (startIdx, endIdx) {
			var text = wikitext.slice(startIdx, endIdx);
	
			// swap out pipe in links with \1 control character
			text = text.replace(/(\[\[[^\]]*?)\|(.*?\]\])/g, '$1\1$2')
			// [[File: ]] can have multiple pipes, let's do this a couple of times more
				.replace(/(\[\[File:[^\]]*?)\|(.*?\]\])/g, '$1\1$2')
				.replace(/(\[\[File:[^\]]*?)\|(.*?\]\])/g, '$1\1$2');
	
			var chunks = text.split('|');
			var res = {};
	
			// name of the template as used in the wikitext is saved as 0th index of the object
			res[0] = chunks[0].trim();
	
			var unnamedIdx = 1;
			for (var i=1; i < chunks.length; i++) {
				var indexOfEqualTo = chunks[i].indexOf('=');
				if (indexOfEqualTo === -1) {
					res[unnamedIdx++] = chunks[i].replace(/\1/g,'|').trim();
				} else {
					var key = chunks[i].slice(0, indexOfEqualTo).trim();
					if (key.indexOf('{{') !== -1) {
						res[unnamedIdx++] = chunks[i].replace(/\1/g,'|').trim();
						continue;
					}
					var val = chunks[i].slice(indexOfEqualTo + 1).replace(/\1/g,'|').trim();
					// changed back '\1' in value to pipes
					res[key] = val;
				}
			}
			result.push(res);
		};
	
		var n = wikitext.length;
		
		// number of unclosed braces
		var numUnclosed = 0;
	
		// are we inside a comment or between nowiki tags?
		var inCommentOrNowiki = false;
	
		var startIdx, endIdx;
		
		for (var i=0; i<n; i++) {
			
			if (! inCommentOrNowiki) {
				
				if (wikitext[i] === '{' && wikitext[i+1] === '{') {
					if (numUnclosed === 0) {
						startIdx = i+2;
					}
					numUnclosed += 2;
					i++;
				} else if (wikitext[i] === '}' && wikitext[i+1] === '}') {
					if (numUnclosed === 2) {
						endIdx = i;
						processTemplateText(startIdx, endIdx);
					}
					numUnclosed -= 2;
					i++;
				} else if (wikitext[i] === '|' && numUnclosed > 2) {
					// swap out pipes in nested 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;
				}
			}
	
		}
	
		return result;
	
	};
	
	return parseAllTemplates;
})());