User:Monkbot/Task 7: CS1 et al. repair

Monkbot task 7 was created to modify cs1|2 templates that include 'et al.' in the assigned value of author and editor parameters.

A recent change to Module:Citation/CS1 (the engine underlying the Citation Style 1 and Citation Style 2 templates) detects 'et al.' in a variety of forms when they are part of the value assigned to author and editor parameters. Examples are:

|author2=et al.
|author2=''et al.''
|author=Smith, John, Et Al
|editor-last=Smith |editor-first=John ''et al.''

When these kinds of parameter values are detected, Module:Citation/CS1 adds the page to Category:CS1 maint: Explicit use of et al. and also adds a maintenance message. Task 7 operates on the content of this category.

All of the currently supported author and editor parameters and most of their various aliases are supported. |authors= and |editors= are not modified because of a bug in Module:Citation/CS1 – see this discussion. |authors= does not contribute to the CITEREF anchor so this parameter is now always 'fixed'.

Task 7 begins by hiding all templates that are not cs1|2. This allows the task to operate on cs1|2 templates that include templates.

Because the task needs to be able to add |display-authors=etal, task 7 finds and removes empty |display-authors= and |display-editors= parameters. It also removes empty author and editor name parameters.

When this has been done, task 7 protect certain CS1|2 templates:

  • cs1 with |display-authors=<value> and/or |display-editors=<value> – a later version of task 7 may address this kind of template
  • cs1 with |ref=harv or |mode=cs2, when any of the |last= aliases contain the et al text because |last= aliases are made part of the CITEREF anchor whereas the content of |first= aliases are not
  • cs2 ({{citation}} which automatically sets |ref=harv) when any of the |last= aliases contain the et al text because |last= aliases are made part of the CITEREF anchor whereas the content of |first= aliases are not
  • cs1|2 where there are author/editor parameters that numerically follow the parameter that contains the et al. text

Unprotected cs1|2 templates are then inspected for author and editor parameters that contain text that matches this pattern: [,;]?\s*'{0,2}\b[Ee][Tt]\s*[Aa][Ll]\b\.?'{0,2}\.? The pattern does not recognize 'etal' in the name 'Detal', for example.

If the |author= parameter value is Vancouver style, the code will change the parameter name to |vauthors= and the et al. text to the keyword etal preceded by a comma and a space.

For all other cases, when the pattern is found, the etal text is removed and |display-authors=etal or |display-editors=etal is added to the end of the template. Following this step, task 7 removes the protection from protected CS1 templates.

Because this replacement can result in leftover empty parameters (the first example above), task 7 then searches the article for empty author and editor parameters which it then removes.

The last step is to unhide the templates that were hidden at the beginning.

Article pages that contain {{bots|deny=Monkbot 7}} will not be edited by this task.

Script

edit
//2015-05-01: rev a: protect author lists where et al. is not in the numerically last author or editor parameter;
//2015-05-05: rev b: skip unless |display-xxxxors=etal is added to at least one template;
//2015-08-30: rev c: expand IS_CS1; convert |authors?= to |vauthors= when appropriate; Allow fixes to templates that have |ref=harv or |mode=cs2
//				when etal is in a |first= or alias parameter;

public string ProcessArticle(string ArticleText, string ArticleTitle, int wikiNamespace, out string Summary, out bool Skip)
	{
	Skip = true;
	Summary = "[[User:Monkbot/Task 7: CS1 et al. repair|Task 7c]]: repair/replace et al. in cs1 author/editor parameters;";

	string pattern;		// local variable to hold regex pattern for reuse

// this is CS1 only; CS2 ({{citation}} is ignored for the time being – see protection |ref=harv for reasons)
	string IS_CS1 = @"(?:[Cc]ite[_ ](?=(?:(?:AV|av) [Mm]edia(?: notes)?)|article|ar[Xx]iv|blog|book|conference|document|(?:DVD|dvd)(?: notes)?|encyclopa?edia|episode|interview|journal|letter|[Mm]agazine|mailing ?list|manual|map|news|news(?:group|paper)|paper|podcast|press release|report|serial|sign|speech|techreport|thesis|video|web)|[Cc]itation|[Cc]ite(?=\s*\|))";

	string IS_CS2 = @"[Cc]itation";
	
	string AUTH_PARAMS = @"(?:author|author\-first|author\-last|first|last|given|surname)";
	string LAST_AUTH_PARAMS = @"(?:author|author\-last|last|surname)";	// used for |ref=harv protection

	string IS_ETAL = @"[,;]?\s*""?'{0,2}\b[Ee][Tt]\s*[Aa][Ll]\b\.?'{0,2}\.?""?";
	
	string IS_VANC_NAMES = @"\s*([\p{L}\-‐'’\s]+\s+\p{Lu}+)";
	string IS_AUTH_SEP = @"\s*,\s*";
	string IS_ETAL_SEP = @"\s*[,\.]?\s*";		// etal may or may not be separated from the last author name by a comma or author list may be terminated with a period
	string IS_PARAM_END = @"\s*([\|\}])";		// for vancouver, only white space between etal next pipe or template close


//---------------------------< H I D E >----------------------------------------------------------------------
// HIDE TEMPLATES: find templates that are not CS1; replace the opening {{ with __0P3N__ and the closing }} with __CL0S3__

	while (Regex.Match (ArticleText, @"\{\{(?!\s*" + IS_CS1 + @")([^\{\}]*)\}\}").Success)
		{
		ArticleText = Regex.Replace(ArticleText, @"\{\{(?!\s*" + IS_CS1 + @")([^\{\}]*)\}\}", "__0P3N__$1__CL0S3__");
		}


//---------------------------< E M P T Y   D I S P L A Y - x x x x O R S >------------------------------------

// DISPLAY-AUTHORS: Remove empty |displayauthors= and |display-authors= parameters.
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*display\-?authors\s*=\s*([\|\}])", "$1$2");

// DISPLAY-EDITORS: Remove empty |displayeditors= and |display-editors= parameters.
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*display\-?editors\s*=\s*([\|\}])", "$1$2");


//---------------------------< E M P T Y   P A R A M E T E R S >----------------------------------------------
// remove empty author and editor parameter so that protection is applied appropriately

	ArticleText = remove_empty_authors (ArticleText, IS_CS1);
	ArticleText = remove_empty_editors (ArticleText, IS_CS1);
	
	
//---------------------------< P R O T E C T I O N >----------------------------------------------------------

// we have removed empty |display-authors= and empty |display-editors= so those that remain have an assigned value;
// protect those citations from further editing

	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*display\-?authors\s*=)", "$1__PROTECTED__$2");
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*display\-?editors\s*=)", "$1__PROTECTED__$2");
	
// protect CS1 citations that use |ref=harv or |mode=cs2
// links between CS1 citations and sfn or harv templates may already be broken because Module:Citation/CS1 does not include the
// et al. text in the CITEREF anchor

// if |ref=harv, et al cannot be removed from a |last= (or alias) parameter without breaking the harv link; if et al is in
// a |first= (or alias) parameter, it can be safely removed.  Protect only cs1 templates with |ref=harv or |mode=cs2 only when a
// |last= alias holds etal

//TODO: do similar protection for cs2 templates (requires modification of 

// |last= aliases precede |ref=harv
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*" + LAST_AUTH_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*\|\s*ref\s*=\s*harv)", "$1__PROTECTED__$2");
// |last= aliases precede |mode=cs2
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*" + LAST_AUTH_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*\|\s*mode\s*=\s*cs2)", "$1__PROTECTED__$2");

// |ref=harv precedes |last= aliases
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*ref\s*=\s*harv[^\}]*" + LAST_AUTH_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)", "$1__PROTECTED__$2");
// |mode=cs2 precedes |last= aliases
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*mode\s*=\s*cs2[^\}]*" + LAST_AUTH_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)", "$1__PROTECTED__$2");

// |last= aliases with etal in cs2
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS2 + @"[^\}]*" + LAST_AUTH_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)", "$1__PROTECTED__$2");


//---------------------------< P R O T E C T I O N n >---------------------------------------------------------
// protect CS1 citations that have at least one author or editor parameter numerically following one with et al.
// protect: |last=Alpha et al. |last2=Beta |last3=Gamma
// don't protect: |last=Alpha |last2=Beta |last3=Gamma et al.
//

// AUTHORS: with enumerator at the end of the parameter name
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*" + AUTH_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)",
		delegate(Match match)
			{
			string raw_capture = match.Groups[0].Value;						// 0 - the captured citation
			string protect_lead = match.Groups[1].Value;					// 1 - opening double curly braces
			string protect_tail = match.Groups[2].Value;					// 2 - from opening double curly braces through closing curly braces
			int parameter_number;											// 3 - author / editor enumerator; may be an empty string for |last= etc

			if ("" == match.Groups[3].Value)								// author parameter was not enumerated |last= not last1=
				parameter_number = 2;										// if not enumerated then |<parameter>n+1= where n=1 so point to |parameter2=
			else
				parameter_number = Convert.ToInt32(match.Groups[3].Value) + 1;	// is enumerated so point to |<parameter>n+1=
			
																			// attempt to find any of |authorn+1=, |lastn+1=, or |firstn+1
			if (Regex.Match (raw_capture, @"\|\s*" + AUTH_PARAMS + parameter_number.ToString()).Success)	// include first in case cite is missing a |lastn= parameter
				return protect_lead + @"__PROTECTED" + parameter_number.ToString() + @"__" + protect_tail;	// found |<parameter>n+1= so protect this citation
			return raw_capture;
			});

// AUTHORS: with enumerator in the middle of the parameter name
	AUTH_PARAMS = @"(?:\-first|-last)";
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*author(\d*)" + AUTH_PARAMS + @"\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)",
		delegate(Match match)
			{
			string raw_capture = match.Groups[0].Value;						// 0 - the captured citation
			string protect_lead = match.Groups[1].Value;					// 1 - opening double curly braces
			string protect_tail = match.Groups[2].Value;					// 2 - from opening double curly braces through closing curly braces
			int parameter_number;											// 3 - author / editor enumerator; may be an empty string for |last= etc

			if ("" == match.Groups[3].Value)								// author parameter was not enumerated |last= not last1=
				parameter_number = 2;										// if not enumerated then |<parameter>n+1= where n=1 so point to |parameter2=
			else
				parameter_number = Convert.ToInt32(match.Groups[3].Value) + 1;	// is enumerated so point to |<parameter>n+1=
			
																			// attempt to find any of |authorn+1=, |lastn+1=, or |firstn+1
			if (Regex.Match (raw_capture, @"\|\s*author" + parameter_number.ToString() + AUTH_PARAMS).Success)	// include first in case cite is missing a |lastn= parameter
				return protect_lead + @"__PROTECTED" + parameter_number.ToString() + @"__" + protect_tail;	// found |<parameter>n+1= so protect this citation
			return raw_capture;
			});

// EDITORS: with enumerator at the end of the parameter name
	string EDIT_PARAMS = @"(?:editor|editor\-first|editor\-last|editor\-given|editor\-surname)";
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*" + EDIT_PARAMS + @"(\d*)\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)",
		delegate(Match match)
			{
			string raw_capture = match.Groups[0].Value;						// 0 - the captured citation
			string protect_lead = match.Groups[1].Value;					// 1 - opening double curly braces
			string protect_tail = match.Groups[2].Value;					// 2 - from opening double curly braces through closing curly braces
			int parameter_number;											// 3 - author / editor enumerator; may be an empty string for |last= etc

			if ("" == match.Groups[3].Value)								// author parameter was not enumerated |last= not last1=
				parameter_number = 2;										// if not enumerated then |<parameter>n+1= where n=1 so point to |parameter2=
			else
				parameter_number = Convert.ToInt32(match.Groups[3].Value) + 1;	// is enumerated so point to |<parameter>n+1=
			
																			// attempt to find any of |authorn+1=, |lastn+1=, or |firstn+1
			if (Regex.Match (raw_capture, @"\|\s*" + EDIT_PARAMS + parameter_number.ToString()).Success)	// include first in case cite is missing a |lastn= parameter
				return protect_lead + @"__PROTECTED" + parameter_number.ToString() + @"__" + protect_tail;	// found |<parameter>n+1= so protect this citation
			return raw_capture;
			});


// EDITORS: with enumerator in the middle of the parameter name
	EDIT_PARAMS = @"(?:\-first|\-last|\-given|\-surname)";
	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*)(" + IS_CS1 + @"[^\}]*\|\s*editor(\d*)" + EDIT_PARAMS + @"\s*=\s*[^\|\}]*?" + IS_ETAL + @"[^\}]*)",
		delegate(Match match)
			{
			string raw_capture = match.Groups[0].Value;						// 0 - the captured citation
			string protect_lead = match.Groups[1].Value;					// 1 - opening double curly braces
			string protect_tail = match.Groups[2].Value;					// 2 - from opening double curly braces through closing curly braces
			int parameter_number;											// 3 - author / editor enumerator; may be an empty string for |last= etc

			if ("" == match.Groups[3].Value)								// author parameter was not enumerated |last= not last1=
				parameter_number = 2;										// if not enumerated then |<parameter>n+1= where n=1 so point to |parameter2=
			else
				parameter_number = Convert.ToInt32(match.Groups[3].Value) + 1;	// is enumerated so point to |<parameter>n+1=
			
																			// attempt to find any of |authorn+1=, |lastn+1=, or |firstn+1
			if (Regex.Match (raw_capture, @"\|\s*editor" + parameter_number.ToString() + EDIT_PARAMS).Success)	// include first in case cite is missing a |lastn= parameter
				return protect_lead + @"__PROTECTED" + parameter_number.ToString() + @"__" + protect_tail;	// found |<parameter>n+1= so protect this citation
			return raw_capture;
			});


//---------------------------< V A N C O U V E R   S T Y L E >------------------------------------------------
//
// When the content of |author= or |authors= has Vancouver-style names, change parameter name to |vauthors=

// one name in Vancouver style:
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors?\s*=" + IS_VANC_NAMES + IS_ETAL_SEP + IS_ETAL + IS_PARAM_END;
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1 |vauthors=$2, etal $3");
		Skip = false;
		}

// two names in Vancouver style:
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors?\s*=" + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_ETAL_SEP + IS_ETAL + IS_PARAM_END;
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1 |vauthors=$2, $3, etal $4");
		Skip = false;
		}

// three names in Vancouver style:
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors?\s*=" + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_ETAL_SEP + IS_ETAL + IS_PARAM_END;
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1 |vauthors=$2, $3, $4, etal $5");
		Skip = false;
		}

// four names in Vancouver style:
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors?\s*=" + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_ETAL_SEP + IS_ETAL + IS_PARAM_END;
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1 |vauthors=$2, $3, $4, $5, etal $6");
		Skip = false;
		}

// five names in Vancouver style:
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors?\s*=" + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_ETAL_SEP + IS_ETAL + IS_PARAM_END;
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1 |vauthors=$2, $3, $4, $5, $6, etal $7");
		Skip = false;
		}

// six names in Vancouver style:
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors?\s*=" + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_AUTH_SEP + IS_VANC_NAMES + IS_ETAL_SEP + IS_ETAL + IS_PARAM_END;
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1 |vauthors=$2, $3, $4, $5, $6, $7, etal $8");
		Skip = false;
		}




//---------------------------< R E M O V E   E T   A L   ( A U T H O R S ) >----------------------------------

// AUTHORn: remove et al when it occurs in |authorn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*author\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// AUTHORS: remove et al when it occurs in |authors= parameters
//DISABLED because not properly supported in Module:Citation/CS1
//	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*authors\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)", "$1$2|display-authors=etal");

// AUTHOR-FIRSTn: remove et al when it occurs in |author-firstn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*author-first\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// AUTHOR-LASTn: remove et al when it occurs in |author-lastn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*author-last\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// AUTHORn-FIRST: remove et al when it occurs in |authorn-first= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*author\d+-first\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// AUTHORn-LAST: remove et al when it occurs in |authorn-last= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*author\d+-last\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// FIRSTn: remove et al when it occurs in |firstn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*first\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// GIVENn: remove et al when it occurs in |givenn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*given\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// LASTn: remove et al when it occurs in |lastn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*last\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

// SURNAMEn: remove et al when it occurs in |surnamen= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*surname\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}

	
//---------------------------< R E M O V E   E T   A L   ( C O A U T H O R S ) >------------------------------

// COAUTHORS: remove et al when it occurs in |coauthor= or |coauthors= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*coauthors?\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-authors=etal");
		Skip = false;
		}


//---------------------------< R E M O V E   E T   A L   ( E D I T O R S ) >----------------------------------

// EDITORn: remove et al when it occurs in |editorn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITORS: remove et al when it occurs in |editors= parameters
//DISABLED because not properly supported in Module:Citation/CS1
//	ArticleText = Regex.Replace(ArticleText, @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editors\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)", "$1$2|display-editors=etal");

// EDITOR-FIRSTn: remove et al when it occurs in |editor-firstn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor-first\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITOR-LASTn: remove et al when it occurs in |editor-lastn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor-last\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITORn-FIRST: remove et al when it occurs in |editorn-first= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\d+-first\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITORn-LAST: remove et al when it occurs in |editorn-last= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\d+-last\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITOR-GIVENn: remove et al when it occurs in |editor-givenn= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\-given\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITORn-GIVEN: remove et al when it occurs in |editorn-given= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\d+\-given\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITOR-SURNAMEn: remove et al when it occurs in |editor-surnamen= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\-surname\d*\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

// EDITORn-SURNAME: remove et al when it occurs in |editorn-surname= parameters
	pattern = @"(\{\{\s*" + IS_CS1 + @"[^\}]*\|\s*editor\d+\-surname\s*=\s*[^\|\}]*?)" + IS_ETAL + @"([^\}]*)";
	if (Regex.Match (ArticleText, pattern).Success)
		{
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2|display-editors=etal");
		Skip = false;
		}

	
//---------------------------< U N P R O T E C T >------------------------------------------------------------

// UNPROTECT: if we protected any citations by adding __PROTECTED__ to them, search for those strings and replace them with nothing.
	ArticleText = Regex.Replace(ArticleText, @"__PROTECTED__", "");	

// UNPROTECT: if we protected any citations by adding __PROTECTEDn__ to them
	ArticleText = Regex.Replace(ArticleText, @"__PROTECTED\d+__", "");	


//---------------------------< E M P T Y   P A R A M E T E R S   R E D U X >----------------------------------
// after we have done the replacements, we may have empty author or editor parmeters.  Remove them.

	ArticleText = remove_empty_authors (ArticleText, IS_CS1);
	ArticleText = remove_empty_editors (ArticleText, IS_CS1);
	

//---------------------------< U N H I D E >------------------------------------------------------------------

// UNHIDE: replace __0P3N__ with {{
	ArticleText = Regex.Replace(ArticleText, @"__0P3N__", "{{");

// UNHIDE: replace __CL0S3__ with }}
	ArticleText = Regex.Replace(ArticleText, @"__CL0S3__", "}}");


	return ArticleText;
	}


//===========================<< S U P P O R T   F U N C T I O N S >>==========================================

//---------------------------< D E L E T E _ E M P T Y _ P A R A M E T E R >----------------------------------
//
// Necessary for enumerated parameters where there are multiple empty parameters in a single citation
// The pattern is provided by another function according to what needs to be deleted

string delete_empty_parameter (string ArticleText, string pattern)
	{
	while (Regex.Match (ArticleText, pattern).Success)					// can we find the pattern?
		ArticleText = Regex.Replace(ArticleText, pattern, "$1$2");		// delete the parameter
	return ArticleText;													// and return
	}


//---------------------------< R E M O V E _ E M P T Y _ A U T H O R S >--------------------------------------
// remove empty author parameters

string remove_empty_authors (string ArticleText, string IS_CS1)
	{
	string pattern;
	
// AUTHORn: Remove empty |authorn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*author\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// AUTHORS: Remove empty |authors= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*authors\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// AUTHOR-FIRSTn: Remove empty |author-firstn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*author\-first\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// AUTHOR-LASTn: Remove empty |author-lastn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*author\-last\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// AUTHORn-FIRST: Remove empty |authorn-first= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*author\d+\-first\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// AUTHORn-LAST: Remove empty |authorn-last= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*author\d+\-last\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// COAUTHORS: Remove empty |coauthor= and |coauthors= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*coauthors?\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// FIRSTn: Remove empty |firstn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*first\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// GIVENn: Remove empty |givenn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*given\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// LASTn: Remove empty |lastn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*last\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// SURNAMEn: Remove empty |surnamen= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*surname\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

	return ArticleText;
	}



//---------------------------< R E M O V E _ E M P T Y _ E D I T O R S >--------------------------------------
// remove empty editor parameters
	
string remove_empty_editors (string ArticleText, string IS_CS1)
	{
	string pattern;

// EDITORn: Remove empty |editorn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITORS: Remove empty |editors= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editors\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITOR-FIRSTn: Remove empty |editor-firstn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\-first\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITOR-GIVENn: Remove empty |editor-givenn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\-given\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITORn-FIRST: Remove empty |editorn-first= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\d+\-first\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITORn-GIVEN: Remove empty |editorn-given= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\d+\-given\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITOR-LASTn: Remove empty |editor-lastn= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\-last\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITORn-LAST: Remove empty |editorn-last= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\d+\-last\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITOR-SURNAMEn: Remove empty |editor-surnamen= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\-surname\d*\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

// EDITORn-SURNAME: Remove empty |editorn-surname= parameters.
	pattern=@"(\{\{\s*" + IS_CS1 + @"[^\}]*)\|\s*editor\d+\-surname\s*=\s*([\|\}])";
	ArticleText = delete_empty_parameter (ArticleText, pattern);

	return ArticleText;
	}