User:SD0001/parseAllTemplates.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.
/**
 * 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. 
 * 
 * Usage: Can be executed from the browser console or within another script.
 *
 * @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.
 * 
 * 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.
 * 
 */

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;

};

window.parseAllTemplates = parseAllTemplates;