local z = {
error_cats_t = {}; -- for categorizing citations that contain errors
error_ids_t = {}; -- list of error identifiers; used to prevent duplication of certain errors; local to this module
error_msgs_t = {}; -- sequence table of error messages
maint_cats_t = {}; -- for categorizing citations that aren't erroneous per se, but could use a little work
prop_cats_t = {}; -- for categorizing citations based on certain properties, language of source for instance
prop_keys_t = {}; -- for adding classes to the citation's <cite> tag
};  
----------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------  
local cfg; -- table of tables imported from selected Module:Citation/CS1/Configuration  
----------------------------< I S _ S E T >------------------------------------------------------------------  
Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string.  
local function is_set (var)
return not (var == nil or var == '');
end  
----------------------------< I N _ A R R A Y >--------------------------------------------------------------  
Whether needle is in haystack  
local function in_array (needle, haystack)
if needle == nil then
return false;
end
for n, v in ipairs (haystack) do
if v == needle then
return n;
end
end
return false;
end  
----------------------------< H A S _ A C C E P T _ A S _ W R I T T E N >------------------------------------  
When <str> is wholly wrapped in accept-as-written markup, return <str> without markup and true; return <str> and false else  
with allow_empty = false, <str> must have at least one character inside the markup
with allow_empty = true, <str> the markup can be empty like () to distinguish an empty template parameter from the specific condition "has no applicable value" in citation-context.  
After further evaluation the two cases might be merged at a later stage, but should be kept separated for now.  
local function has_accept_as_written (str, allow_empty)
if not is_set (str) then
return str, false;
end  
local count;  
if true == allow_empty then
str, count = str:gsub ('^%(%((.*)%)%)$', '%1'); -- allows () to be an empty set
else
str, count = str:gsub ('^%(%((.+)%)%)$', '%1');
end
return str, 0 ~= count;
end  
----------------------------< S U B S T I T U T E >----------------------------------------------------------  
Populates numbered arguments in a message string using an argument table. <args> may be a single string or a
sequence table of multiple strings.  
local function substitute (msg, args)
return args and mw.message.newRawMessage (msg, args):plain or msg;
end  
----------------------------< E R R O R _ C O M M E N T >----------------------------------------------------  
Wraps error messages with CSS markup according to the state of hidden. <content> may be a single string or a
sequence table of multiple strings.  
local function error_comment (content, hidden)
return substitute (hidden and cfg.presentation['hidden-error'] or cfg.presentation['visible-error'], content);
end  
----------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------  
Converts a hyphen, endash, emdash to endash under certain conditions. The hyphen/en/em must separate
like items; unlike items are returned unmodified. These forms are modified:
letter - letter (A-B)
digit - digit (4-5)
digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5)
letterdigit - letterdigit (A1-A5) (an optional separator between letter and
digit is supported – a.1-a.5 or a-1-a-5)
digitletter - digitletter (5a-5d) (an optional separator between letter and
digit is supported – 5.a-5.d or 5-a-5-d)  
any other forms are returned unmodified.  
str may be a comma- or semicolon-separated list of page ranges with/without single pages  
local function hyphen_to_dash (str)
if not is_set (str) then
return str;
end  
str = str:gsub ("(%(%(.-%)%))", function(m) return m:gsub(",", "，"):gsub(";", "；") end) -- replace commas and semicolons in accept-as-written markup with similar unicode characters so they'll be ignored during the split
str = str:gsub ('&[nm]dash;', {['&ndash;'] = '–', ['&mdash;'] = '—'}); -- replace &mdash; and &ndash; entities with their characters; semicolon mucks up the text.split
str = str:gsub ('&#45;', '-'); -- replace HTML numeric entity with hyphen character
str = str:gsub ('&nbsp;', ' '); -- replace &nbsp; entity with generic keyboard space character  
local out = {};
local list = mw.text.split (str, '%s*[,;]%s*'); -- split str at comma or semicolon separators if there are any  
local accept; -- boolean  
for _, item in ipairs (list) do -- for each item in the list
item, accept = has_accept_as_written (item); -- remove accept-this-as-written markup when it wraps all of item
if not accept and mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[—–-]%s*%w*[%.%-]?%w+$') then -- if a hyphenated range or has endash or emdash separators
if mw.ustring.match (item, '^%a+[%.%-]?%d+%s*[—–-]%s*%a+[%.%-]?%d+$') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)
mw.ustring.match (item, '^%d+[%.%-]?%a+%s*[—–-]%s*%d+[%.%-]?%a+$') or -- digitletter hyphen digitletter (optional separator between digit and letter)
mw.ustring.match (item, '^%d+[%.%-]%d+%s*[—–-]%s*%d+[%.%-]%d+$') then -- digit separator digit hyphen digit separator digit
item = mw.ustring.gsub (item, '(%w*[%.%-]?%w+)%s*[—–-]%s*(%w*[%.%-]?%w+)', '<span>%1 –</span> <span>%2</span>'); -- replace hyphen/dash, with spaced endash  
elseif mw.ustring.match (item, '^%d+%s*[—–-]%s*%d+$') or -- digit hyphen digit
mw.ustring.match (item, '^%a+%s*[—–-]%s*%a+$') then -- letter hyphen letter
item = mw.ustring.gsub (item, '(%w+)%s*[—–-]%s*(%w+)', '<span>%1–</span>%2'); -- replace hyphen/emdash with endash, remove extraneous space characters  
else
-- item = mw.ustring.gsub (item, '%s*[—–-]%s*', '–'); -- disabled; here when 'unlike' items so return <item> as is
end
end
table.insert (out, item); -- add the (possibly modified) item to the output table
end  
local temp_str = ''; -- concatenate the output table into a comma separated string
temp_str, accept = has_accept_as_written (table.concat (out, ', ')); -- remove accept-this-as-written markup when it wraps all of concatenated out
if accept then
temp_str = has_accept_as_written (str); -- when global markup removed, return original str; do it this way to suppress boolean second return value
return temp_str:gsub("，", ","):gsub("；", ";");
else
return temp_str:gsub("，", ","):gsub("；", ";"); -- else, return assembled temp_str
end
end  
--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------  
Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided (or link and display are the same), returns a wikilink in the form L; if neither are
provided or link is omitted, returns an empty string.  
]=]  
local function make_wikilink (link, display)
if not is_set (link) then return '' end  
if is_set (display) and link ~= display then
return table.concat ({*, display, *});
else
return table.concat ({*, link, *});
end
end  
--[[--------------------------< S E T _ M E S S A G E >----------------------------------------------------------  
Sets an error message using the ~/Configuration error_conditions{} table along with arguments supplied in the function
call, inserts the resulting message in z.error_msgs_t{} sequence table, and returns the error message.  
<error_id> – key value for appropriate error handler in ~/Configuration error_conditions{} table
<arguments> – may be a single string or a sequence table of multiple strings to be subsititued into error_conditions[error_id].message
<raw> – boolean
true – causes this function to return the error message not wrapped in visible-error, hidden-error span tag;