require('strict'); local data = mw.loadData ('Module:Footnotes/anchor id list/data'); local whitelist = mw.loadData ('Module:Footnotes/whitelist'); local Lang_obj = mw.language.getContentLanguage();								-- used by template_list_add() to uppercase first letter of template name TODO: better way to do that?  local redirects_date = { 	['date'] = true, 	['datetomos'] = true, 	['formatdate'] = true, 	['isotodmymdy'] = true, 	['isotomos'] = true, 	} local redirects_patent = {														-- special case cs1-like templates because uses different parameters for name and date in anchor ID 	['Cite patent'] = true, 	['Citeref patent'] = true, 	['Ref patent'] = true, 	} local redirects_sfnref = { 	['sfnref'] = true, 	['harvid'] = true, 	} local aliases_author = {														-- these use pseudo-patterns in the same way as cs1|2; '#' represents 1 or more enumerator digits 	'last#', 	'author#', 	'শেষাংশ#', 	'লেখক#', 	'surname#', 	'author-last#', 	'author#-last', 	'subject#', 	'host#', 	} local aliases_contributor = { 	'contributor#', 	'contributor-last#', 	'contributor#-last', 	'contributor-surname#', 	'contributor#-surname', 	} local aliases_editor = { 	'editor#', 	'editor-last#', 	'editor#-last', 	'editor-surname#', 	'editor#-surname', 	} local aliases_harvc_author = { 	'last#', 	'author#', 	'শেষাংশ#', 	'লেখক#', 	} local aliases_inventor = {														-- cite patent 	'inventor#', 	'inventor-last#', 	'inventor#-last', 	'inventor-surname#', 	'inventor#-surname', 	'invent#', 	'invent-#', 	} local alias_patterns_date = {													-- normal lua patterns for most cs1|2-like templates 	'|%s*year%s*=%s*', 	'|%s*date%s*=%s*', 	'|%s*বছর%s*=%s*', 	'|%s*তারিখ%s*=%s*', 	'|%s*publication%-?date%s*=%s*', 	'|%s*air%-?date%s*=%s*', 	} local alias_patterns_harvc_date = {												-- normal lua patterns for harvc template 	'|%s*anchor%-year%s*=%s*', 	'|%s*year%s*=%s*', 	'|%s*বছর%s*=%s*', 	} local alias_patterns_patent_date = {											-- normal lua patterns for cite patent templates 	'|%s*issue%-date%s*=%s*', 	'|%s*gdate%s*=%s*', 	'|%s*publication%-date%s*=%s*', 	'|%s*pubdate%s*=%s*', 	} local patterns_date = {															-- normal lua patterns 	'(%d%d%d%d–%d%d%d%d%l?)$',													-- YYYY–YYYY four-digit year range at end (Season YYYY–YYYY); with or without dab 	'^(%d%d%d%d–%d%d%l?)$',														-- YYYY–YY two-digit year range; with or without dab 	'^(c%. %d%d%d%d?%l?)$',														-- three- or four-digit circa year; with or without dab 	'(%d%d%d%d?%l?)$',															-- three- or four-digit year at end of date (dmy or mdy); with or without dab 	'^(%d%d%d%d?%l?)',															-- three- or four-digit year at beginning of date (ymd or YYYY); with or without dab 	'^(n%.d%.%l?)$',															-- 'no date' with dots; with or without dab 	'^(nd%l?)$',																-- 'no date' without dots; with or without dab 	} local patterns_tags = { 	'.-', 	'<!%-%-.-%-%->', 	'.-', 	'<syntaxhighlight.->.-</syntaxhighlight>', 	'<source.->.-</source>',													-- deprecated alias of syntaxhighlight tag 	} local template_skip = {															-- templates to be skipped for whatever reason; mostly because they resemble cs1-like templates 	['Citation-attribution'] = true, 	} local Article_content;  local anchor_id_list = {};														-- exported tables local template_list = {}; local article_whitelist = {};  --[[--------------------------< A R T I C L E _ C O N T E N T _ G E T >----------------------------------------  get article content, remove certain html-like tags and their content so that this code doesn't include any citation templates inside the tags as valid tagets; they are not.  ]]  local function article_content_get () 	if not Article_content then 		Article_content = mw.title.getCurrentTitle():getContent() or ;		-- get the content of the article or ; new pages edited w/ve do not have 'content' until saved; ve does not preview; phab:T221625 		for _, tag in ipairs (patterns_tags) do 			Article_content = Article_content:gsub (tag, );					-- remove certain html-like tags and their content 		end 	end end  --[[--------------------------< S F N R E F _ G E T >----------------------------------------------------------  make an anchor id from the contents of  or .  this function assumes that  and  are correctly formed.  ]]  local function sfnref_get (template) 	template = template:gsub ('', '%1');							-- strip bounding template markup and trim 	local parts = mw.text.split (template, '%s*|%s*');							-- split at the pipe and remove extraneous space characters 	local anchor_id = {};  	if redirects_sfnref[parts[1]:lower()] then 		anchor_id[1] = 'CITEREF'; 	else 		return nil;																-- not an sfnref or harvid template 	end 	 	local i = 2;																-- indexer into parts{} table 	local j = 2;																-- indexer into anchor_id{} table which already has 'CITEREF' at [1] 	while parts[i] and 7 > j do													-- loop through what should be just positional parameters for names and year (2-6 four names and a date) 		if not parts[i]:find ('=') then											-- look for equal sign (named paraneter in a template that doesn't support named parameters) 			anchor_id[j] = parts[i];											-- positional parameters are saved 			j = j+1;															-- bump the anchor_id{} indexer 		end 		i = i+ 1;																-- bump the parts{} indexer 	end  	return table.concat (anchor_id, ); end  --[[--------------------------< D A T E _ G E T >--------------------------------------------------------------  extract year from one of |year=, |date=, |publicationdate=, or |publication-date in that order.  Does not error check (that is left to the cs1|2 templates to do)  also gets date from |<date alias>=  ]]  local function date_get (template, aliases) 	local date; 	local rvalue;  	for _, pattern in ipairs (aliases) do										-- spin through the date alias patterns 		rvalue = tostring(template):match (pattern);							-- is this |<date alias>= used (tostring() because something makes match() think template is a table) 		if rvalue then 			rvalue = tostring(template):match (pattern .. '(%b{})');			-- is rvalue a template? 			if rvalue then 				rvalue = rvalue:gsub ('', '%1');					-- strip bounding template markup and trim 				local parts = mw.text.split (rvalue, '%s*|%s*');				-- split at the pipe and remove extraneous space characters  				if redirects_date[parts[1]:lower()] then						-- if parts[1] names  or redirect 					rvalue = parts[2];											-- assume that date template is properly formed, first positional parameter is the date 				else 					return ;													-- |date= holds some other template than  or redirect 				end 			else 				rvalue = template:match (pattern .. '([^|}]+)'); 				if rvalue then													-- if rvalue is something 					rvalue = mw.text.trim (rvalue);								-- trim it 				end 				 				if not rvalue or  == rvalue then								-- if rvale was nothing or trimed to nothing 					rvalue = nil;												-- ensure that it is unset so we can try the next parameter in the list 				end 			end  			if rvalue then 				for _, pattern in ipairs (patterns_date) do						-- spin through the recognized date formats 					date = mw.ustring.match (rvalue, pattern);								-- attempt to extract year portion according to the pattern 					if date then 						return date;											-- matched so return; 					end 				end 				break;															-- found a date but it was malformed so abandon 			end 		end 	end  	return ;																	-- no date param or date param doesn't hold a recognized date; empty string for concatenation end    --[[--------------------------< V N A M E S _ G E T >----------------------------------------------------------  extract names from |vauthors= or |veditors=; there is no |vcontributors= parameter.  splits the v parameter value at the comma; correctly handles accept-as-witten markup when used to wrap a comma- separated names (corporate)  ]]  local function vnames_get (params, vparam) 	local vnames = {};															-- first four author or editor names go here 	local split = {};															-- temp table to assist in decoding accept-as-witten-markup  	if params[vparam] then														-- test for |vauthors= or |veditor= 		split = mw.text.split (params[vparam], '%s*,%s*');						-- this will separate portions of ((Black, Brown, White, an Co.)) 	 		local i = 1;															-- an indexer 		 		while split[i] do 			if split[i]:match ('^%(%(.*[^%)][^%)]$') then						-- first segment of comma-separated accept-as-witten; this segment has the opening doubled parens 				local name = split[i]; 				i=i+1;															-- bump indexer to next segment 				while split[i] do 					name = name .. ', ' .. split[i];							-- concatenate with previous segments 					if split[i]:match ('^.*%)%)$') then							-- if this table member has the closing doubled parens 						break;													-- and done reassembling so 					end 					i=i+1;														-- bump indexer 				end 				table.insert (vnames, name);									-- and add accept-as-witten name to the vnames table 	 			else 				table.insert (vnames, split[i]);								-- and add name to the vnames table 			end 		i=i+1;																	-- bump indexer 		if 5 == i then break; end												-- limit to four names 		end  		for i, vname in ipairs (vnames) do 			if not vname:match ('%(%(.-%)%)') then								-- without accept-this-value-as-written markup 				vnames[i] = vname:gsub ('(.-)%s+%u+$', '%1');					-- extract and save surname(s) 			end 		end 		for i, vname in ipairs (vnames) do										-- repeat, this time for accept-this-value-as-written markup 			vnames[i] = vname:gsub ('%(%((.-)%)%)', '%1');						-- remove markup if present and save the whole name 		end 	end  	return 0 ~= #vnames and table.concat (vnames) or nil						-- return a concatenation of the vnames; nil else end  --[[--------------------------< N A M E S _ G E T >------------------------------------------------------------  cs1|2 makes anchor id from contributor, author, or editor name-lists in that order  get the names from the cs1|2 template;  if there are no contributor names, try author names, then try editor names.  returns concatenated names in enumeration order when successful; nil else  empty name (nameholding parameter n is present without value) and missing name (nameholding parameter n is not present) are included as empty string with all other names  ]]  local function names_get (params, aliases_list) 	local names = {};															-- first four author or editor names go here 	local enum_alias;															-- alias with '#' replaced with a digit  	for enum=1, 4 do															-- four names only 		for i, alias in ipairs (aliases_list) do 			if not names[enum] then												-- hanven't found a previous alias with this [enum]? see if we can find this alias with this enum 				enum_alias = alias:gsub ('#', enum);							-- replace '#' to make 'lastn' 	 				if 1 == enum then												-- because |last= and last1= are exact aliases 					if params[enum_alias] then									-- test |last1= first 						names[enum] = params[enum_alias];						-- found so save the value assigned to |last1= 						break;													-- next enum 					else 						enum_alias = alias:gsub ('#', );						-- replace '#' to make 'last' 						if params[enum_alias] then 							names[enum] = params[enum_alias];					-- found so save the value assigned to |last= 							break;												-- next enum 						end 					end 				else															-- here for enum 2, 3, 4 					if params[enum_alias] then 						names[enum] = params[enum_alias];						-- found so save the value assigned to |lastn= 						break;													-- next enum 					end 				end 			end 		end 	end  	for enum=1, 4 do															-- spin through the names table and 		local name = names[enum]; 		if not name then														-- when nameholding parameter n is not present (nil) 			name = ;															-- convert to empty string for concatenation 		end 		name = name:gsub('%(%((.-)%)%)', '%1');									-- remove accept-as-written markup if present 		names[enum] = name;														-- save the modified name 	end  	local name_str = table.concat (names);										-- concatenate the names 	return  ~= name_str and name_str or nil;									-- return the concatenation if not empty string; nil else end  --[[--------------------------< T E M P L A T E _ S T R I P >--------------------------------------------------  removes the citation or havrc template's  markup then removes, in whole, any templates found inside the citation or harvc template.  Templates are not allowed in parameters that are made part of COinS metadata; yet, they will appear.  cs1|2 does not see the template markup but instead sees the result of the template as html.  cs1|2 strips the html which leaves the displayed value for the anchor id.  We can't do that here so, because templates aren't allowed in parameters, we simply discard any templates found in the cs1|2 template.  this may leave a |lastn= parameter empty which will be treated as if it were really empty as cs1|2 do (three authors, |last2= empty -> CITEREFLast1Last3YYYY (the harv and sfn render: 'Last1, & Last3 YYYY' with CITEREFLast1Last3YYYY).  ]]  local function template_strip (template) 	template = template:gsub ('^ for example) so declare ourselves done 		end  		template_name = template_name_get (template);							-- get first char uppercase trimmed template name; ignore subpages ~/new, ~/sandbox 		template_list_add (template);											-- add this template's name to the list  		if data.known_templates_cs12 [template_name] then 			anchor_id = anchor_id_make_cs12 (template);							-- extract an anchor id from this template 			list_add (anchor_id, anchor_id_list, true)  		elseif data.known_templates_vcite [template_name] then 			local     ref = template:match ('|%s*ref%s*=%s*(%b{})') or mw.ustring.match (template, '|%s*সূত্র%s*=%s*(%b{})');				-- first look for |ref= or |ref= because we will strip templates from the vcite template 			if ref then															-- |ref= 				anchor_id = sfnref_get (ref);									-- returns content of  or ; nil else 				list_add (anchor_id, anchor_id_list, true); 			else 				local params = {};  				template_params_get (template, params);							-- build a table of template parameters and their values  				anchor_id = params['ref'];										-- when both set, vcite uses value from |ref= 				if not anchor_id and params['harvid'] then 					anchor_id = 'CITEREF' .. params['harvid'];					-- in vcite, |harvid= auto-adds 'CITEREF' prefix to the value in |harvid= 				end 				list_add (anchor_id, anchor_id_list, true); 			end  		elseif data.known_templates_harvc [template_name] then 			anchor_id = anchor_id_make_harvc (template);						-- extract an anchor id from this template 			list_add (anchor_id, anchor_id_list, true); 			 		elseif data.known_templates_wikicite [template_name] then 			local     ref = template:match ('|%s*ref%s*=%s*(%b{})') or mw.ustring.match (template, '|%s*সূত্র%s*=%s*(%b{})');				-- first look for |ref= or |ref= 			 			if ref then 				anchor_id = sfnref_get (ref); 	 			elseif template:match ('|%s*ref%s*=([^|}]+)') or mw.ustring.match (template, '|%s*সূত্র%s*=([^|}]+)') then 				anchor_id = template:match ('|%s*ref%s*=([^|}]+)') or mw.ustring.match (template, '|%s*সূত্র%s*=([^|}]+)');				-- plain-text 	 			elseif template:match ('|%s*id%s*=%s*(%b{})') then 				ref = template:match ('|%s*id%s*=%s*(%b{})'); 				anchor_id = 'Reference-' .. sfnref_get (ref);  			elseif template:match ('|%s*id%s*=([^|}]+)') then 				anchor_id = 'Reference-' .. template:match ('|%s*id%s*=([^|}]+)');	-- plain-text  			else 				anchor_id = nil;												-- no matches, ensure that anchor_id has no value 			end 			 			if anchor_id then 				list_add (anchor_id, anchor_id_list, true); 			end  		elseif data.known_templates_anchor [template_name] then 			anchor_id_make_anchor (template, anchor_id_list);					-- extract anchor ids from this template if any 		 		elseif data.known_templates_sfn_whitelist [template_name] then 			template = template:gsub ('^{{[^|]+|', ):gsub ('}}$', , 1);		-- remove outer  and template name 			template = mw.text.trim (template, '%s|');							-- trim leading trailing white space and pipes 			template = mw.text.split (template, '%s*|%s*');						-- make a table of the template's parameters  			for _, anchor_id in ipairs (template) do							-- spin through this template's parameter 				if '' ~= anchor_id and not article_whitelist[anchor_id] then 					anchor_id = mw.uri.anchorEncode (anchor_id) 					article_whitelist[anchor_id] = 1;							-- add to the whitelist 				end 			end 		 		elseif template_name and whitelist.wrapper_templates[template_name] then 			anchor_id = anchor_id_make_wrapper (template);						-- extract an anchor id from this template if possible 			list_add (anchor_id, anchor_id_list, true);  		elseif template_name and template_name:match ('^Cit[ea]') then			-- not known, not known wrapper; last gasp, try as cs1-like 			anchor_id = anchor_id_make_cs12 (template);							-- extract an anchor id from this template if possible 			list_add (anchor_id, anchor_id_list, true); 		end  		tstart, tend = Article_content:find (find_pattern, tend);				-- search for another template; begin at end of last search 	end  mw.logObject (anchor_id_list, 'anchor_id_list'); mw.logObject (template_list, 'template_list'); mw.logObject (article_whitelist, 'article_whitelist');  	return anchor_id_list; end  --[[--------------------------< E X P O R T E D _ T A B L E S >------------------------------------------------ ]]  return { 	anchor_id_list = anchor_id_list_make(),										-- table of anchor ids available in this article 	article_whitelist = article_whitelist,										-- table of anchor ids with false-positive error message to be suppressed 	template_list = template_list,												-- table of templates used in this article 	}