Jump to content

User:MJL/ARA-light.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>
// Everything is encapsulated in a private namespace object---``JJJ'':
var JJJ = JJJ || {};

$(document).ready(function() 
{
	//initialize Constants
	JJJ.Constants = getARAConstants();

	//only execute on the edit page
	if (!JJJ.Constants.IS_EDIT_PAGE || JJJ.Constants.IS_JS_PAGE || JJJ.Constants.ARTICLE_TEXT_BOX_ELEMENT == null)
		return;
		
	//init functions and rules
	JJJ.Functions = getARAFunctions();
	JJJ.Rules     = getARARules();
	
	//init UI
	$('#editform').prepend(JJJ.Constants.SUGGESTION_BOX_DIV);
	$('#wpSummaryLabel').prepend(JJJ.Constants.ADD_TO_SUMMARY_DIV);
	
	JJJ.Functions.scan();                                   //init scan now
	JJJ.Functions.observeWikiText(JJJ.Constants.delayScan); // ... and every time the user pauses typing
});

function getARAConstants()
{
	var ARA_Constants = ARA_Constants || {};

	//article text box element
	ARA_Constants.ARTICLE_TEXT_BOX_ELEMENT = $("#wpTextbox1");
	
	//are we on an Edit page?
	ARA_Constants.IS_EDIT_PAGE = mw.config.get('wgAction') === 'edit' || mw.config.get('wgAction') === 'submit';
	
	//are we on a JS page?
	ARA_Constants.IS_JS_PAGE = mw.config.get('wgRelevantPageName').endsWith('.js');
	
	//the ARA Suggestion box, which appears above the editing section
	ARA_Constants.SUGGESTION_BOX_DIV = $('<div>', {'id':'suggestionBox.ARA', 'style':'border:dashed #ccc 1px;color:#888;'});
	
	//the Add to Summary box, which appears near the edit summary
	ARA_Constants.ADD_TO_SUMMARY_DIV = $('<div>', {'id':'addToSummaryBox.ARA', 'style':'border:dashed #ccc 1px;color:#888;display:none;'});
	
	ARA_Constants.DEFAULT_MAX_SUGGESTIONS = 8;
	ARA_Constants.maxSuggestions = ARA_Constants.DEFAULT_MAX_SUGGESTIONS;
	ARA_Constants.suggestions; // : Suggestion[]
	ARA_Constants.appliedSuggestions = {}; // : Map<String, int>
	
	ARA_Constants.scannedText = null; // remember what we scan, to check if it is
	                       // still the same when we try to fix it
	
	ARA_Constants.BIG_THRESHOLD = 100 * 1024;
	ARA_Constants.isBigScanConfirmed = false; // is the warning about a big article confirmed
	ARA_Constants.isTalkPageScanConfirmed = false;
	
	ARA_Constants.scanTimeoutId = null; // a timeout is set after a keystroke and before
	                         // a scan, this variable tracks its id
	                         
	return ARA_Constants;
}

function getARAFunctions()
{
	var ARA_Functions = ARA_Functions || {};

	// Browsers offer means to highlight text between two given offsets (``start''
	// and ``end'') in a textarea, but some of them do not automatically scroll to it.
	// This function is an attempt to simulate cross-browser selection and scrolling.
	ARA_Functions.setSelectionRange = function (ta, start, end) {
		// Initialise static variables used within this function
		var _static = arguments.callee; // this is the Function we are in.  It will be used as a poor man's function-local static scope.
		if (ta.setSelectionRange) {
			// Guess the vertical scroll offset by creating a
			// separate hidden clone of the original textarea, filling it with the text
			// before ``start'' and computing its height.
			if (_static.NEWLINES == null) {
				_static.NEWLINES = '\n'; // 64 of them should be enough.
				for (var i = 0; i < 6; i++) {
					_static.NEWLINES += _static.NEWLINES;
				}
			}
			if (_static.helperTextarea == null) {
				_static.helperTextarea = document.createElement('TEXTAREA');
				_static.helperTextarea.style.display = 'none';
				document.body.appendChild(_static.helperTextarea);
			}
			var hta = _static.helperTextarea;
			hta.style.display = '';
			hta.style.width = ta.clientWidth + 'px';
			hta.style.height = ta.clientHeight + 'px';
			hta.value = _static.NEWLINES.substring(0, ta.rows) + ta.value.substring(0, start);
			var yOffset = hta.scrollHeight;
			hta.style.display = 'none';
			ta.focus();
			ta.setSelectionRange(start, end);
			if (yOffset > ta.clientHeight) {
				yOffset -= Math.floor(ta.clientHeight / 2);
				ta.scrollTop = yOffset;
				// Opera does not support setting the scrollTop property
				if (ta.scrollTop != yOffset) {
					// todo: Warn the user or apply a workaround
				}
			} else {
				ta.scrollTop = 0;
			}
		} else {
			// IE incorrectly counts '\r\n' as a signle character
			start -= ta.value.substring(0, start).split('\r').length - 1;
			end -= ta.value.substring(0, end).split('\r').length - 1;
			var range = ta.createTextRange();
			range.collapse(true);
			range.moveStart('character', start);
			range.moveEnd('character', end - start);
			range.select();
		}
	};
	
	// getPosition(e), observe(e, x, f), stopObserving(e, x, f),
	// and stopEvent(event) are inspired by the prototype.js framework
	// http://prototypejs.org/
	ARA_Functions.getPosition = function (e) {
		var x = 0;
		var y = 0;
		do {
			x += e.offsetLeft || 0;
			y += e.offsetTop  || 0;
			e = e.offsetParent;
		} while (e);
		return {x: x, y: y};
	};
	
	ARA_Functions.observe = function (e, eventName, f) {
		if (e.addEventListener) {
			e.addEventListener(eventName, f, false);
		} else {
			e.attachEvent('on' + eventName, f);
		}
	};
	
	ARA_Functions.stopObserving = function (e, eventName, f) {
		if (e.removeEventListener) {
			e.removeEventListener(eventName, f, false);
		} else {
			e.detachEvent('on' + eventName, f);
		}
	};
	
	ARA_Functions.stopEvent = function (event) {
		if (event.preventDefault) {
			event.preventDefault();
			event.stopPropagation();
		} else {
			event.returnValue = false;
			event.cancelBubble = true;
		}
	};
	
	// ARA_Functions.anchor() is a shortcut to creating a link as a DOM node:
	ARA_Functions.anchor = function (text, href, title) {
		var e = document.createElement('A');
		e.href = href;
		e.appendChild(document.createTextNode(text));
		e.title = title || '';
		return e;
	};
	
	// ARA_Functions.link() produces the HTML for a link to a Wikipedia article as a string.
	// It is convenient to embed in a help popup.
	ARA_Functions.hlink = function (toWhat, text) {
		var wgServer = window.wgServer || 'http://wiki.riteme.site';
		var wgArticlePath = window.wgArticlePath || '/wiki/$1';
		var url = (wgServer + wgArticlePath).replace('$1', toWhat);
		return '<a href="' + url + '" target="_blank">' + (text || toWhat) + '</a>';
	};
	
	// === Helpers a la functional programming ===
	// A higher-order function---produces a cached version of a one-arg function.
	ARA_Functions.makeCached = function (f) {
		var cache = {}; // a closure; the cache is private for f
		return function (x) {
			return (cache[x] != null) ? cache[x] : (cache[x] = f(x));
		};
	};
	
	// === Editor compatibility layer ===
	// Controlling access to wpTextbox1 helps abstract out compatibility
	// with editors like wikEd (http://wiki.riteme.site/wiki/User:Cacycle/wikEd)
	
	ARA_Functions.getWikiText = function () {
		if (window.wikEdUseWikEd) {
			var obj = {sel: WikEdGetSelection()};
			WikEdParseDOM(obj, wikEdFrameBody);
			return obj.plain;
		}
		return JJJ.Constants.ARTICLE_TEXT_BOX_ELEMENT.val();
	};
	
	ARA_Functions.setWikiText = function (s) {
		if (window.wikEdUseWikEd) {
			// todo: wikEd compatibility
			alert(JJJ.Functions._('Changing text in wikEd is not yet supported.'));
			return;
		};
		JJJ.Constants.ARTICLE_TEXT_BOX_ELEMENT.val(s);
	};
	
	ARA_Functions.focusWikiText = function () {
		if (window.wikEdUseWikEd) {
			wikEdFrameWindow.focus();
			return;
		}
		JJJ.Constants.ARTICLE_TEXT_BOX_ELEMENT.focus();
	};
	
	ARA_Functions.selectWikiText = function (start, end) {
		if (window.wikEdUseWikEd) {
			var obj = x = {sel: WikEdGetSelection(), changed: {}};
			WikEdParseDOM(obj, wikEdFrameBody);
			var i = 0;
			while ((obj.plainStart[i + 1] != null) && (obj.plainStart[i + 1] <= start)) {
				i++;
			}
			var j = i;
			while ((obj.plainStart[j + 1] != null) && (obj.plainStart[j + 1] <= end)) {
				j++;
			}
			obj.changed.range = document.createRange();
			obj.changed.range.setStart(obj.plainNode[i], start - obj.plainStart[i]);
			obj.changed.range.setEnd(obj.plainNode[j], end - obj.plainStart[j]);
			WikEdRemoveAllRanges(obj.sel);
			obj.sel.addRange(obj.changed.range);
			return;
		}
		ARA_Functions.setSelectionRange(document.getElementById(JJJ.Constants.ARTICLE_TEXT_BOX_ELEMENT.prop('id')), start, end);
	};
	
	ARA_Functions.observeWikiText = function (callback) {
		// todo: wikEd compatibility
		ARA_Functions.observe(document.getElementById(JJJ.Constants.ARTICLE_TEXT_BOX_ELEMENT.prop('id')), 'keyup', JJJ.Functions.delayScan);
	};
	
	// === Interaction with the user ===
	// ARA_Functions.scan() analyses the text and handles how the proposals are reflected in the UI.
	ARA_Functions.scan = function (force) 
	{
		JJJ.Constants.scanTimeoutId = null;
		
		//get article text
		var s = JJJ.Functions.getWikiText();
		
		//determine if we actually need to scan
		if ((s === JJJ.Constants.scannedText) && !force)
			return; // Nothing to do, we've already scanned the very same text
	
		JJJ.Constants.scannedText = s;
		
		//remove all current suggestions
		JJJ.Constants.SUGGESTION_BOX_DIV.empty();
		
		// Warn about scanning a big article
		if ((s.length > JJJ.Constants.BIG_THRESHOLD) && !JJJ.Constants.isBigScanConfirmed) {
			JJJ.Constants.SUGGESTION_BOX_DIV.append(document.createTextNode(
					JJJ.Functions._('This article is rather long.  ARA may consume a lot of '
					+ 'RAM and CPU resources while trying to parse the text.  You could limit '
					+ 'your edit to a single section, or ')
			));
			JJJ.Constants.SUGGESTION_BOX_DIV.append(JJJ.Functions.anchor(
					JJJ.Functions._('scan the text anyway.'),
					'javascript: JJJ.Constants.isBigScanConfirmed = true; JJJ.Functions.scan(true); void(0);',
					JJJ.Functions._('Ignore this warning.')
			));
			return;
		}
		// Warn about scanning a talk page
		if ((   mw.config.get('wgCanonicalNamespace') === 'Talk'
			 || mw.config.get('wgCanonicalNamespace') === 'User_talk' 
			 || mw.config.get('wgCanonicalNamespace') === 'Project_talk'
			 || mw.config.get('wgCanonicalNamespace') === 'MediaWiki_talk'
			)
		    && !JJJ.Constants.isTalkPageScanConfirmed
		) 
		{
			JJJ.Constants.SUGGESTION_BOX_DIV.append(document.createTextNode(
					JJJ.Functions._('ARA is disabled on talk pages, because ' +
					'it might suggest changing other users\' comments.  That would be ' +
					'something against talk page conventions.  If you promise to be ' +
					'careful, you can ')
			));
			JJJ.Constants.SUGGESTION_BOX_DIV.append(JJJ.Functions.anchor(
					JJJ.Functions._('scan the text anyway.'),
					'javascript: JJJ.Constants.isTalkPageScanConfirmed = true; JJJ.Functions.scan(true); void(0);',
					JJJ.Functions._('Ignore this warning.')
			));
			return;
		}
		
		//get suggestions
		JJJ.Constants.suggestions = JJJ.Functions.getSuggestions(s);
		
		//if there aren't any suggestions, say so
		if (JJJ.Constants.suggestions.length === 0) 
		{
			JJJ.Constants.SUGGESTION_BOX_DIV.append(document.createTextNode(
					JJJ.Functions._('OK \u2014 ARA found no referencing issues.') // U+2014 is an mdash
			));
			return;
		}
		var nSuggestions = Math.min(JJJ.Constants.maxSuggestions, JJJ.Constants.suggestions.length);
		JJJ.Constants.SUGGESTION_BOX_DIV.append(document.createTextNode(
			(JJJ.Constants.suggestions.length == 1)
					? JJJ.Functions._('1 suggestion: ')
					: JJJ.Functions._('$1 suggestions: ', JJJ.Constants.suggestions.length)
		));
		for (var i = 0; i < nSuggestions; i++) {
			var suggestion = JJJ.Constants.suggestions[i];
			var eA = JJJ.Functions.anchor(
					suggestion.name,
					'javascript:JJJ.Functions.showSuggestion(' + i + '); void(0);',
					suggestion.description
			);
			suggestion.element = eA;
			JJJ.Constants.SUGGESTION_BOX_DIV.append(eA);
			if (suggestion.replacement != null) 
			{
				var eSup = document.createElement('SUP');
				JJJ.Constants.SUGGESTION_BOX_DIV.append(eSup);
				var sup1 = suggestion.sup1 != null ? suggestion.sup1 : 'fix';
				
				eSup.appendChild (
					JJJ.Functions.anchor (
						JJJ.Functions._(sup1), 
						'javascript:JJJ.Functions.fixSuggestion(' + i + '); void(0);'
					)
				);
				
				//sometimes, suggestions may have more than one fix link
				if (suggestion.replacement2 != null)
				{
					var sup2 = suggestion.sup2 != null ? suggestion.sup2 : 'fix2';
					
					eSup.appendChild(document.createTextNode(' | '));
					eSup.appendChild(
						JJJ.Functions.anchor(
							JJJ.Functions._(sup2),
							'javascript:JJJ.Functions.fixSuggestion2(' + i + '); void(0);'
						)
					);
				}
				if (suggestion.replacement3 != null)
				{
					var sup3 = suggestion.sup3 != null ? suggestion.sup3 : 'fix3';
					
					eSup.appendChild(document.createTextNode(' | '));
					eSup.appendChild(
						JJJ.Functions.anchor(
							JJJ.Functions._(sup3),
							'javascript:JJJ.Functions.fixSuggestion3(' + i + '); void(0);'
						)
					);
				}
			}
			JJJ.Constants.SUGGESTION_BOX_DIV.append(document.createTextNode(' '));
		}
		if (JJJ.Constants.suggestions.length > JJJ.Constants.maxSuggestions) {
			JJJ.Constants.SUGGESTION_BOX_DIV.append(JJJ.Functions.anchor(
					'...', 'javascript: JJJ.Constants.maxSuggestions = 1000; JJJ.Functions.scan(true); void(0);',
					JJJ.Functions._('Show All')
			));
		}
	};
	
	// getSuggestions() returns the raw data used by scan().
	// It is convenient for unit testing.
	ARA_Functions.getSuggestions = function (s) {
		var suggestions = [];
		var missingRefGroupSuggestions = []; //we want to keep track of the ones we already have so we don't push the same message twice
		for (var i = 0; i < JJJ.Rules.length; i++)  //for each rule
		{
			var a = JJJ.Rules[i](s); //execute rule
			for (var j = 0; j < a.length; j++) //for each suggestion pushed by the rule
			{
				var returned_suggestion = a[j];
				
				//if the suggestion is not a missing reference groups suggestion, or it is and we didn't already push this one 
				if (!returned_suggestion.name.includes("missing reference groups") || !missingRefGroupSuggestions.includes(returned_suggestion.name)) 	
				{
					suggestions.push(returned_suggestion); //add suggestion to list of suggestions
					missingRefGroupSuggestions.push(returned_suggestion.name); //add suggestion to list of suggestions we already have
				}
			}
		}
		suggestions.sort(function (x, y) {
			return (x.start < y.start) ? -1 :
			       (x.start > y.start) ? 1 :
			       (x.end < y.end) ? -1 :
			       (x.end > y.end) ? 1 : 0;
		});
		return suggestions;
	};
	
	// === Internationalisation ===
	// ARA_Functions._() is a gettext-style internationalisation helper
	// (http://wiki.riteme.site/wiki/gettext)
	// If no translation is found for the parameter, it is returned as is.
	// Additionally, subsequent parameters are substituted for $1, $2, and so on.
	ARA_Functions._ = function (s) {
		if (JJJ.Constants.translation && JJJ.Constants.translation[s]) {
			s = JJJ.Constants.translation[s];
		}
		var index = 1;
		while (arguments[index]) {
			s = s.replace('$' + index, arguments[index]); // todo: replace all?
			index++;
		}
		return s;
	};
	
	// showSuggestion() handles clicks on the suggestions above the edit area
	// This does one of two things:
	// * on first click---highlight the corresponding text in the textarea
	// * on a second click, no later than a fixed number milliseconds after the
	// 		first one---show the help popup
	ARA_Functions.showSuggestion = function (k) {
		if (JJJ.Functions.getWikiText() != JJJ.Constants.scannedText) {
			// The text has changed - just do another scan and don't change selection
			JJJ.Functions.scan();
			return;
		}
		var suggestion = JJJ.Constants.suggestions[k];
		var now = new Date().getTime();
		if ((suggestion.help != null) && (JJJ.Constants.lastShownSuggestionIndex === k) && (now - JJJ.Constants.lastShownSuggestionTime < 1000)) {
			// Show help
			var p = JJJ.Functions.getPosition(suggestion.element);
			var POPUP_WIDTH = 300;
			var eDiv = document.createElement('DIV');
			eDiv.innerHTML = suggestion.help;
			eDiv.style.position = 'absolute';
			eDiv.style.left = Math.max(0, Math.min(p.x, document.body.clientWidth - POPUP_WIDTH)) + 'px';
			eDiv.style.top = (p.y + suggestion.element.offsetHeight) + 'px';
			eDiv.style.border = 'solid ThreeDShadow 1px';
			eDiv.style.backgroundColor = 'InfoBackground';
			eDiv.style.fontSize = '12px';
			eDiv.style.color = 'InfoText';
			eDiv.style.width = POPUP_WIDTH + 'px';
			eDiv.style.padding = '0.3em';
			eDiv.style.zIndex = 10;
			document.body.appendChild(eDiv);
			JJJ.Functions.observe(document.body, 'click', function (event) {
				event = event || window.event;
				var target = event.target || event.srcElement;
				var e = target;
				while (e != null) {
					if (e == eDiv) {
						return;
					}
					e = e.parentNode;
				}
				document.body.removeChild(eDiv);
				JJJ.Functions.stopObserving(document.body, 'click', arguments.callee);
			});
			JJJ.Functions.focusWikiText();
			return;
		}
		JJJ.Constants.lastShownSuggestionIndex = k;
		JJJ.Constants.lastShownSuggestionTime = now;
		JJJ.Functions.selectWikiText(suggestion.start, suggestion.end);
	};
	
	// Usually, there is a ``fix'' link next to each suggestion.  It is handled by:
	ARA_Functions.fixSuggestion = function(k) 
	{
		var s = JJJ.Functions.getWikiText();
		if (s != JJJ.Constants.scannedText) {
			JJJ.Functions.scan();
			return;
		}
		var suggestion = JJJ.Constants.suggestions[k];
		// the issue is not automatically fixable, return
		if (suggestion.replacement == null) { 
			return;
		}
		JJJ.Functions.setWikiText(
				s.substring(0, suggestion.start)
				+ suggestion.replacement
				+ s.substring(suggestion.end)
		);
		JJJ.Functions.selectWikiText(
				suggestion.start,
				suggestion.start + suggestion.replacement.length
		);
		// Propose an edit summary unless it's a new section
		var editform = document.getElementById('editform');
		if (!editform['wpSection'] || (editform['wpSection'].value != 'new')) {
			if (JJJ.Constants.appliedSuggestions[suggestion.name] == null) {
				JJJ.Constants.appliedSuggestions[suggestion.name] = 1;
			} else {
				JJJ.Constants.appliedSuggestions[suggestion.name]++;
			}
			var a = [];
			for (var i in JJJ.Constants.appliedSuggestions) {
				a.push(i);
			}
			a.sort(function (x, y) {
				return (JJJ.Constants.appliedSuggestions[x] > JJJ.Constants.appliedSuggestions[y]) ? -1 :
					   (JJJ.Constants.appliedSuggestions[x] < JJJ.Constants.appliedSuggestions[y]) ? 1 :
					   (x < y) ? -1 : (x > y) ? 1 : 0;
			});
			var s = '';
			for (var i = 0; i < a.length; i++) {
				var count = JJJ.Constants.appliedSuggestions[a[i]];
				s += ', ' + ((count == 1) ? a[i] : (count + 'x ' + a[i]));
			}
			// Cut off the leading ``, '' and add ``using ARA''
	                s = JJJ.Functions._(
					'fixed [[Help:CS1 errors#apostrophe markup|CS1 \'\'markup\'\' error(s)]] likely via [[User:MJL/ARA-light|script]]',
					s.substring(2)
	                        );
	               
			// Render in DOM
			JJJ.Constants.ADD_TO_SUMMARY_DIV.empty();
			JJJ.Constants.ADD_TO_SUMMARY_DIV.show();
			JJJ.Constants.ADD_TO_SUMMARY_DIV.append(JJJ.Functions.anchor(
					JJJ.Functions._('Add to summary'),
					'javascript:JJJ.Functions.addToSummary(unescape("' + escape(s) + '"));',
					JJJ.Functions._('Append the proposed summary to the input field below')
			));
			JJJ.Constants.ADD_TO_SUMMARY_DIV.append(document.createTextNode(': "' + s + '"'));
		}
		// Re-scan immediately
		JJJ.Functions.scan();
	};
	
	// if a suggestion has two 'fix' options, the second option will be handled here
	ARA_Functions.fixSuggestion2 = function (k) 
	{
		var s = JJJ.Functions.getWikiText();
		if (s != JJJ.Constants.scannedText) {
			JJJ.Functions.scan();
			return;
		}
		var suggestion = JJJ.Constants.suggestions[k];

		// the issue is not automatically fixable, return
		if (suggestion.replacement2 == null) { 
			return;
		}
		//otherwise, if we are executing JS, do so
		else if (suggestion.replacement2.includes("javascript:"))
		{
			eval(suggestion.replacement2);
			return;
		}
		JJJ.Functions.setWikiText(
				s.substring(0, suggestion.start2)
				+ suggestion.replacement2
				+ s.substring(suggestion.end2)
		);
		JJJ.Functions.selectWikiText(
				suggestion.start2,
				suggestion.start2 + suggestion.replacement2.length
		);
		// Propose an edit summary unless it's a new section
		var editform = document.getElementById('editform');
		if (!editform['wpSection'] || (editform['wpSection'].value != 'new')) {
			if (JJJ.Constants.appliedSuggestions[suggestion.name] == null) {
				JJJ.Constants.appliedSuggestions[suggestion.name] = 1;
			} else {
				JJJ.Constants.appliedSuggestions[suggestion.name]++;
			}
			var a = [];
			for (var i in JJJ.Constants.appliedSuggestions) {
				a.push(i);
			}
			a.sort(function (x, y) {
				return (JJJ.Constants.appliedSuggestions[x] > JJJ.Constants.appliedSuggestions[y]) ? -1 :
					   (JJJ.Constants.appliedSuggestions[x] < JJJ.Constants.appliedSuggestions[y]) ? 1 :
					   (x < y) ? -1 : (x > y) ? 1 : 0;
			});
			var s = '';
			for (var i = 0; i < a.length; i++) {
				var count = JJJ.Constants.appliedSuggestions[a[i]];
				s += ', ' + ((count == 1) ? a[i] : (count + 'x ' + a[i]));
			}
			// Cut off the leading ``, '' and add ``using ARA''
	                s = JJJ.Functions._(
					'fixed [[Help:CS1 errors#apostrophe markup|CS1 \'\'markup\'\' error(s)]] likely via [[User:MJL/ARA-light|script]]',
					s.substring(2)
	                        );
	               
			// Render in DOM
			JJJ.Constants.ADD_TO_SUMMARY_DIV.empty();
			JJJ.Constants.ADD_TO_SUMMARY_DIV.show();
			JJJ.Constants.ADD_TO_SUMMARY_DIV.append(JJJ.Functions.anchor(
					JJJ.Functions._('Add to summary'),
					'javascript:JJJ.Functions.addToSummary(unescape("' + escape(s) + '"));',
					JJJ.Functions._('Append the proposed summary to the input field below')
			));
			JJJ.Constants.ADD_TO_SUMMARY_DIV.append(document.createTextNode(': "' + s + '"'));
		}
		// Re-scan immediately
		JJJ.Functions.scan();
	};
	ARA_Functions.fixSuggestion3 = function (k) 
	{
		var s = JJJ.Functions.getWikiText();
		if (s != JJJ.Constants.scannedText) {
			JJJ.Functions.scan();
			return;
		}
		var suggestion = JJJ.Constants.suggestions[k];
		if (suggestion.replacement3 == null) { // the issue is not automatically fixable
			return;
		}
		JJJ.Functions.setWikiText(
				s.substring(0, suggestion.start3)
				+ suggestion.replacement3
				+ s.substring(suggestion.end3)
		);
		JJJ.Functions.selectWikiText(
				suggestion.start3,
				suggestion.start3 + suggestion.replacement3.length
		);
		// Propose an edit summary unless it's a new section
		var editform = document.getElementById('editform');
		if (!editform['wpSection'] || (editform['wpSection'].value != 'new')) {
			if (JJJ.Constants.appliedSuggestions[suggestion.name] == null) {
				JJJ.Constants.appliedSuggestions[suggestion.name] = 1;
			} else {
				JJJ.Constants.appliedSuggestions[suggestion.name]++;
			}
			var a = [];
			for (var i in JJJ.Constants.appliedSuggestions) {
				a.push(i);
			}
			a.sort(function (x, y) {
				return (JJJ.Constants.appliedSuggestions[x] > JJJ.Constants.appliedSuggestions[y]) ? -1 :
					   (JJJ.Constants.appliedSuggestions[x] < JJJ.Constants.appliedSuggestions[y]) ? 1 :
					   (x < y) ? -1 : (x > y) ? 1 : 0;
			});
			var s = '';
			for (var i = 0; i < a.length; i++) {
				var count = JJJ.Constants.appliedSuggestions[a[i]];
				s += ', ' + ((count == 1) ? a[i] : (count + 'x ' + a[i]));
			}
			// Cut off the leading ``, '' and add ``using ARA''
	                s = JJJ.Functions._(
					'fixed [[Help:CS1 errors#apostrophe markup|CS1 \'\'markup\'\' error(s)]] likely via [[User:MJL/ARA-light|script]]',
					s.substring(2)
	                        );
	               
			// Render in DOM
			JJJ.Constants.ADD_TO_SUMMARY_DIV.empty();
			JJJ.Constants.ADD_TO_SUMMARY_DIV.show();
			JJJ.Constants.ADD_TO_SUMMARY_DIV.append(JJJ.Functions.anchor(
					JJJ.Functions._('Add to summary'),
					'javascript:JJJ.Functions.addToSummary(unescape("' + escape(s) + '"));',
					JJJ.Functions._('Append the proposed summary to the input field below')
			));
			JJJ.Constants.ADD_TO_SUMMARY_DIV.append(document.createTextNode(': "' + s + '"'));
		}
		// Re-scan immediately
		JJJ.Functions.scan();
	};
	
	// The mnemonics of the accepted suggestions are accumulated in JJJ.Constants.appliedSuggestions
	// and the user is presented with a sample edit summary.  If she accepts it,
	// addToSummary() gets called.
	ARA_Functions.addToSummary = function (summary) {
		var wpSummary = document.getElementById('wpSummary');
		if (wpSummary.value != '') {
			summary = wpSummary.value + '; ' + summary;
		}
		if ((wpSummary.maxLength > 0) && (summary.length > wpSummary.maxLength)) {
			alert(JJJ.Funtions._(
					'Error: If the proposed text is added to the summary, '
					+ 'its length will exceed the $1-character maximum by $2 characters.',
					/* $1 = */ wpSummary.maxLength,
					/* $2 = */ summary.length - wpSummary.maxLength
			));
			return;
		}
		wpSummary.value = summary;
		JJJ.Constants.ADD_TO_SUMMARY_DIV.hide();
	};
	
	// delayScan() postpones the invocation of scan() with a certain timeout.
	// If delayScan() is invoked once again during that time, the original
	// timeout is cancelled, and another, clean timeout is started from zero.
	//
	// delayScan() will normally be invoked when a key is pressed---this
	// prevents frequent re-scans while the user is typing.
	ARA_Functions.delayScan = function () {
		if (JJJ.Constants.scanTimeoutId != null) {
			clearTimeout(JJJ.Constants.scanTimeoutId);
			JJJ.Constants.scanTimeoutId = null;
		}
		JJJ.Constants.scanTimeoutId = setTimeout(JJJ.Functions.scan, 500);
	};
	
	return ARA_Functions;
}

function getARARules()
{
	var ARA_Rules = [];
	
	// == Rules ==
	
	// properties:
	// * start---the 0-based inclusive index of the first character to be replaced
	// * end---analogous to start, but exclusive
	// * replacement---the proposed wikitext
	// * name---this is what appears at the top of the page
	// * description---used as a tooltip for the name of the suggestion
	
	
	if (!mw.config.get('wgContentLanguage') || mw.config.get('wgContentLanguage') === 'en') { // from this line on, a level of indent is spared
	
	// The rules are stored in an array and are grouped into categories.
	//*****************************************************************************************
	//*****************************************************************************************
	// === Functions ===
	
	/* ITALIC MARKUP HERE */
	//***publisher parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|publisher='s
	    var startIndex = 0;
	    var searchStr = "|publisher=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |publisher=
		{
			var pubStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(pubStartIndex); //+searchStr.length to exclude "|publisher=\'\'"
			
			//get to the actual beginning of the Publisher if there are spaces or newlines after the "publisher=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++pubStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = pubStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire publisher parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var pub = indexOnward.substring(0, cutOffIndex).trim(); //the pub parameter
			
			//If |publisher= ends with italic markup
			if (pub.endsWith('\'\''))
			{
				b.push({
					start:        pubStartIndex + pub.length - 2,
					end:          pubStartIndex + pub.length,
					replacement:  '',
					name:         '|publisher= ends with italic markup',
					description:  '|publisher= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the publisher begins with italic markup
		var replaceableStrings = ["publisher=\'\'", " publisher = \'\'", " publisher= \'\'", "publisher =\'\'", "publisher= \'\'", " publisher=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|publisher= begins with italic markup',
					description:  '|publisher= begins with italic markup',
				});
			}
		}
		return b;
	});
	//***website parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|website='s
	    var startIndex = 0;
	    var searchStr = "|website=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |website=
		{
			var webStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(webStartIndex); //+searchStr.length to exclude "|website=\'\'"
			
			//get to the actual beginning of the website if there are spaces or newlines after the "website=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++webStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = webStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire website parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var web = indexOnward.substring(0, cutOffIndex).trim(); //the web parameter
			
			//If the website ends with italic markup
			if (web.endsWith('\'\''))
			{
				b.push({
					start:        webStartIndex + web.length - 2,
					end:          webStartIndex + web.length,
					replacement:  '',
					name:         '|website= ends with italic markup',
					description:  '|website= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the website begins with italic markup
		var replaceableStrings = ["website=\'\'", " website = \'\'", " website= \'\'", "website =\'\'", "website= \'\'", " website=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|website= begins with italic markup',
					description:  '|website= begins with italic markup',
				});
			}
		}
		return b;
	});
	
		//***magazine parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|magazine='s
	    var startIndex = 0;
	    var searchStr = "|magazine=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |magazine=
		{
			var magStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(magStartIndex); //+searchStr.length to exclude "|magazine=\'\'"
			
			//get to the actual beginning of the magazine if there are spaces or newlines after the "magazine=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++magStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = magStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire magazine parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var mag = indexOnward.substring(0, cutOffIndex).trim(); //the mag parameter
			
			//If |magazine= ends with italic markup
			if (mag.endsWith('\'\''))
			{
				b.push({
					start:        magStartIndex + mag.length - 2,
					end:          magStartIndex + mag.length,
					replacement:  '',
					name:         '|magazine= ends with italic markup',
					description:  '|magazine= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the magazine begins with italic markup
		var replaceableStrings = ["magazine=\'\'", " magazine = \'\'", " magazine= \'\'", "magazine =\'\'", "magazine= \'\'", " magazine=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|magazine= begins with italic markup',
					description:  '|magazine= begins with italic markup',
				});
			}
		}
		return b;
	});
	//***work parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|work='s
	    var startIndex = 0;
	    var searchStr = "|work=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |work=
		{
			var worStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(worStartIndex); //+searchStr.length to exclude "|work=\'\'"
			
			//get to the actual beginning of the work if there are spaces or newlines after the "work=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++worStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = worStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire work parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var wor = indexOnward.substring(0, cutOffIndex).trim(); //the wor parameter
			
			//If the work ends with italic markup
			if (wor.endsWith('\'\''))
			{
				b.push({
					start:        worStartIndex + wor.length - 2,
					end:          worStartIndex + wor.length,
					replacement:  '',
					name:         '|work= ends with italic markup',
					description:  '|work= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the work begins with italic markup
		var replaceableStrings = ["work=\'\'", " work = \'\'", " work= \'\'", "work =\'\'", "work= \'\'", " work=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|work= begins with italic markup',
					description:  '|work= begins with italic markup',
				});
			}
		}
		return b;
	});
	
		//***periodical parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|periodical='s
	    var startIndex = 0;
	    var searchStr = "|periodical=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |periodical=
		{
			var perStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(perStartIndex); //+searchStr.length to exclude "|periodical=\'\'"
			
			//get to the actual beginning of the periodical if there are spaces or newlines after the "periodical=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++perStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = perStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire periodical parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var per = indexOnward.substring(0, cutOffIndex).trim(); //the per parameter
			
			//If |periodical= ends with italic markup
			if (per.endsWith('\'\''))
			{
				b.push({
					start:        perStartIndex + per.length - 2,
					end:          perStartIndex + per.length,
					replacement:  '',
					name:         '|periodical= ends with italic markup',
					description:  '|periodical= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the periodical begins with italic markup
		var replaceableStrings = ["periodical=\'\'", " periodical = \'\'", " periodical= \'\'", "periodical =\'\'", "periodical= \'\'", " periodical=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|periodical= begins with italic markup',
					description:  '|periodical= begins with italic markup',
				});
			}
		}
		return b;
	});
	//***newspaper parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|newspaper='s
	    var startIndex = 0;
	    var searchStr = "|newspaper=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |newspaper=
		{
			var nwpStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(nwpStartIndex); //+searchStr.length to exclude "|newspaper=\'\'"
			
			//get to the actual beginning of the newspaper if there are spaces or newlines after the "newspaper=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++nwpStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = nwpStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire newspaper parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var nwp = indexOnward.substring(0, cutOffIndex).trim(); //the nwp parameter
			
			//If the newspaper ends with italic markup
			if (nwp.endsWith('\'\''))
			{
				b.push({
					start:        nwpStartIndex + nwp.length - 2,
					end:          nwpStartIndex + nwp.length,
					replacement:  '',
					name:         '|newspaper= ends with italic markup',
					description:  '|newspaper= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the newspaper begins with italic markup
		var replaceableStrings = ["newspaper=\'\'", " newspaper = \'\'", " newspaper= \'\'", "newspaper =\'\'", "newspaper= \'\'", " newspaper=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|newspaper= begins with italic markup',
					description:  '|newspaper= begins with italic markup',
				});
			}
		}
		return b;
	});
		//***journal parameter contains italic markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|journal='s
	    var startIndex = 0;
	    var searchStr = "|journal=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |journal=
		{
			var jorStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(jorStartIndex); //+searchStr.length to exclude "|journal=\'\'"
			
			//get to the actual beginning of the journal if there are spaces or newlines after the "journal=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++jorStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = jorStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire journal parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var jor = indexOnward.substring(0, cutOffIndex).trim(); //the jor parameter
			
			//If |journal= ends with italic markup
			if (jor.endsWith('\'\''))
			{
				b.push({
					start:        jorStartIndex + jor.length - 2,
					end:          jorStartIndex + jor.length,
					replacement:  '',
					name:         '|journal= ends with italic markup',
					description:  '|journal= ends with italic markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the journal begins with italic markup
		var replaceableStrings = ["journal=\'\'", " journal = \'\'", " journal= \'\'", "journal =\'\'", "journal= \'\'", " journal=\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'", ""), 
					name:         '|journal= begins with italic markup',
					description:  '|journal= begins with italic markup',
				});
			}
		}
		return b;
	});
	
	
	/* BOLD MARKUP HERE */
		//***publisher parameter contains bold markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|publisher='s
	    var startIndex = 0;
	    var searchStr = "|publisher=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |publisher=
		{
			var pubStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(pubStartIndex); //+searchStr.length to exclude "|publisher=\'\'\'"
			
			//get to the actual beginning of the Publisher if there are spaces or newlines after the "publisher=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++pubStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = pubStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire publisher parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var pub = indexOnward.substring(0, cutOffIndex).trim(); //the pub parameter
			
			//If |publisher= ends with bold markup
			if (pub.endsWith('\'\'\''))
			{
				b.push({
					start:        pubStartIndex + pub.length - 2,
					end:          pubStartIndex + pub.length,
					replacement:  '',
					name:         '|publisher= ends with bold markup',
					description:  '|publisher= ends with bold markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the publisher begins with bold markup
		var replaceableStrings = ["publisher=\'\'\'", " publisher = \'\'\'", " publisher= \'\'\'", "publisher =\'\'\'", "publisher= \'\'\'", " publisher=\'\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'\'", ""), 
					name:         '|publisher= begins with bold markup',
					description:  '|publisher= begins with bold markup',
				});
			}
		}
		return b;
	});
	//***website parameter contains bold markup***
	ARA_Rules.push(function (s) {
		var b = [];

	    //get indices of all '|website='s
	    var startIndex = 0;
	    var searchStr = "|website=";
	    var searchStrLen = searchStr.length;
		var index, indices = [];
		while ((index = s.indexOf(searchStr, startIndex)) > -1) 
		{
		    indices.push(index);
		    startIndex = index + searchStrLen;
		}
		
		var indicesLength = indices.length;
		for (i = 0; i < indicesLength; i++) //for each |website=
		{
			var webStartIndex = indices[i] + searchStr.length;
			var indexOnward   = s.substring(webStartIndex); //+searchStr.length to exclude "|website=\'\'\'"
			
			//get to the actual beginning of the website if there are spaces or newlines after the "website=" and before the start of the text
			while ((indexOnward[0] == ' ' || indexOnward[0] == '\n'))
			{
				indexOnward = indexOnward.substring(1); //cut off the first character
				++webStartIndex;
			}
				
			var fullRef          = indexOnward;
			var fullRefPrevIndex = webStartIndex - 1;
				
		    //indices of various characters in the citation
			var firstBracketAfterIndex = indexOnward.includes("}") ? indexOnward.indexOf("}") : s.length;
			var firstBarAfterIndex     = indexOnward.includes("|") ? indexOnward.indexOf("|") : s.length;
			
			//get to the end of the citation
			fullRef = fullRef.substring(0, firstBracketAfterIndex);
			
			//get to the beginning of the citation
			while (fullRefPrevIndex >= 0 && s.charAt(fullRefPrevIndex) != '{')
	    	{
	              fullRef = s.charAt(fullRefPrevIndex) + fullRef; //prepend the character
	              --fullRefPrevIndex; //decrement index
	    	}
	    	//now we have the full ref.
	    	
			//get the entire website parameter. The parameter should either end with another | or }} for the end of the ref (if not malformed)
			//find the nearest delimeter
			var cutOffIndex = firstBarAfterIndex;
			
			if (firstBracketAfterIndex < cutOffIndex) 
				cutOffIndex = firstBracketAfterIndex;
			
			var web = indexOnward.substring(0, cutOffIndex).trim(); //the web parameter
			
			//If the website ends with bold markup
			if (web.endsWith('\'\'\''))
			{
				b.push({
					start:        webStartIndex + web.length - 2,
					end:          webStartIndex + web.length,
					replacement:  '',
					name:         '|website= ends with bold markup',
					description:  '|website= ends with bold markup',
				});
			}
		}
		
		return b;
	});
	
	ARA_Rules.push(function (s) {
		var b = [];
			//If the website begins with bold markup
		var replaceableStrings = ["website=\'\'\'", " website = \'\'\'", " website= \'\'\'", "website =\'\'\'", "website= \'\'\'", " website=\'\'\'"];
		for (i = 0; i < replaceableStrings.length; i++)
		{
			var replaceableString = replaceableStrings[i];
			if (s.includes(replaceableString))
			{
				b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace("\'\'\'", ""), 
					name:         '|website= begins with bold markup',
					description:  '|website= begins with bold markup',
				});
			}
		}
		return b;
	});
	
	/* TECHNICAL - HELPS SCRIPT FUNCTION */
	//***unnecessary whitespace in citation***
	ARA_Rules.push(function (s) {
		var b = [];
	   
	    var replaceableStrings = [" publisher=","publisher ="," publisher =", " website=","website ="," website =", " magazine=","magazine ="," magazine =", " work=","work ="," work =", " periodical=","periodical ="," periodical =", " newspaper=","newspaper ="," newspaper =", " journal=", "journal ="," journal= "];
	    for (i = 0; i < replaceableStrings.length; i++)
	    {
	    	var replaceableString = replaceableStrings[i];
	    	
	    	if (s.includes(replaceableString))
	    	{
	    		b.push({
					start: s.indexOf(replaceableString),
					end:   s.indexOf(replaceableString) + replaceableString.length,
					replacement: replaceableString.replace(" ", ""),
					name: 'extra whitespace in citation (' + replaceableString + ')',
					description: 'extra whitespace in citation (' + replaceableString + ')'
				});
	    	}
	    }
		
		return b;
	});
	
	} // end if mw.config.get('wgContentLanguage') === 'en'
	
	return ARA_Rules;
}
// </nowiki>