length must be 19 characters
characters in position
1–4 must be digits and must represent a year in the range of 1000 – next year
5 must be a letter
6–8 must be letter, digit, ampersand, or dot (ampersand cannot directly precede a dot; &. )
9–18 must be letter, digit, or dot
19 must be a letter or dot  
local function bibcode (options)
local id = options.id;
local access = options.access;
local handler = options.handler;
local err_type;
local err_msg = '';
local year;  
local text = external_link_id ({link = handler.link, label = handler.label, q = handler.q, redirect = handler.redirect,
prefix = handler.prefix, id = id, separator = handler.separator, encode = handler.encode,
access = access});  
if 19 ~= id:len then
err_type = cfg.err_msg_supl.length;
else
year = id:match ("^(%d%d%d%d)[%a][%w&%.][%w&%.][%w&%.][%w.]+[%a%.]$");
if not year then -- if nil then no pattern match
err_type = cfg.err_msg_supl.value; -- so value error
else
local next_year = tonumber (os.date ('%Y')) + 1; -- get the current year as a number and add one for next year
year = tonumber (year); -- convert year portion of bibcode to a number
if (1000 > year) or (year > next_year) then
err_type = cfg.err_msg_supl.year; -- year out of bounds
end
if id:find('&%.') then
err_type = cfg.err_msg_supl.journal; -- journal abbreviation must not have '&.' (if it does it's missing a letter)
end
end
end  
if is_set (err_type) then -- if there was an error detected
set_message ('err_bad_bibcode', {err_type});
options.coins_list_t['BIBCODE'] = nil; -- when error, unset so not included in COinS  
end  
return text;
end  
----------------------------< B I O R X I V >-----------------------------------------------------------------  
Format bioRxiv ID and do simple error checking. Before 2019-12-11, biorXiv IDs were 10.1101/ followed by exactly
6 digits. After 2019-12-11, biorXiv IDs retained the six-digit identifier but prefixed that with a yyyy.mm.dd.
date and suffixed with an optional version identifier.  
The bioRxiv ID is the string of characters:
https://doi.org/10.1101/078733 -> 10.1101/078733
or a date followed by a six-digit number followed by an optional version indicator 'v' and one or more digits:
https://www.biorxiv.org/content/10.1101/2019.12.11.123456v2 -> 10.1101/2019.12.11.123456v2  
see https://www.biorxiv.org/about-biorxiv  
local function biorxiv (options)
local id = options.id;
local handler = options.handler;
local err_msg = true; -- flag; assume that there will be an error  
local patterns = {
'^10.1101/%d%d%d%d%d%d$', -- simple 6-digit identifier (before 2019-12-11)
'^10.1101/(20[1-9]%d)%.([01]%d)%.([0-3]%d)%.%d%d%d%d%d%dv%d+$', -- y.m.d. date + 6-digit identifier + version (after 2019-12-11)
'^10.1101/(20[1-9]%d)%.([01]%d)%.([0-3]%d)%.%d%d%d%d%d%d$', -- y.m.d. date + 6-digit identifier (after 2019-12-11)
}  
for _, pattern in ipairs (patterns) do -- spin through the patterns looking for a match
if id:match (pattern) then
local y, m, d = id:match (pattern); -- found a match, attempt to get year, month and date from the identifier  
if m then -- m is nil when id is the six-digit form
if not is_valid_biorxiv_date (y, m, d) then -- validate the encoded date; TODO: don't ignore leap-year and actual month lengths ( is a poor date validator)
break; -- date fail; break out early so we don't unset the error message
end
end
err_msg = nil; -- we found a match so unset the error message
break; -- and done
end
end -- err_cat remains set here when no match  
if err_msg then
options.coins_list_t['BIORXIV'] = nil; -- when error, unset so not included in COinS
set_message ('err_bad_biorxiv'); -- and set the error message
end  
return external_link_id ({link = handler.link, label = handler.label, q = handler.q, redirect = handler.redirect,
prefix = handler.prefix, id = id, separator = handler.separator,
encode = handler.encode, access = handler.access});
end  
----------------------------< C I T E S E E R X >------------------------------------------------------------  
CiteSeerX use their own notion of "doi" (not to be confused with the identifiers resolved via doi.org).  
The description of the structure of this identifier can be found at Help_talk:Citation_Style_1/Archive_26#CiteSeerX_id_structure  
local function citeseerx (options)
local id = options.id;
local handler = options.handler;
local matched;  
local text = external_link_id ({link = handler.link, label = handler.label, q = handler.q, redirect = handler.redirect,
prefix = handler.prefix, id = id, separator = handler.separator, encode = handler.encode,
access = handler.access});  
matched = id:match ("^10%.1%.1%.[1-9]%d?%d?%d?%.[1-9]%d?%d?%d?$");
if not matched then
set_message ('err_bad_citeseerx' );
options.coins_list_t['CITESEERX'] = nil; -- when error, unset so not included in COinS
end  
return text;
end  
----------------------------< D O I >------------------------------------------------------------------------  
Formats a DOI and checks for DOI errors.  
DOI names contain two parts: prefix and suffix separated by a forward slash.
Prefix: directory indicator '10.' followed by a registrant code
Suffix: character string of any length chosen by the registrant  
This function checks a DOI name for: prefix/suffix. If the DOI name contains spaces or endashes, or, if it ends
with a period or a comma, this function will emit a bad_doi error message.  
DOI names are case-insensitive and can incorporate any printable Unicode characters so the test for spaces, endash,
and terminal punctuation may not be technically correct but it appears, that in practice these characters are rarely
if ever used in DOI names.  
local function doi (options)
local id = options.id;
local inactive = options.DoiBroken
local access = options.access;
local ignore_invalid = options.accept;
local handler = options.handler;
local err_flag;  
local text;
if is_set (inactive) then
local inactive_year = inactive:match("%d%d%d%d") or ''; -- try to get the year portion from the inactive date
local inactive_month, good;  
if is_set (inactive_year) then
if 4 < inactive:len then -- inactive date has more than just a year (could be anything)
local lang_obj = mw.getContentLanguage; -- get a language object for this wiki
good, inactive_month = pcall (lang_obj.formatDate, lang_obj, 'F', inactive); -- try to get the month name from the inactive date
if not good then
inactive_month = nil; -- something went wrong so make sure this is unset
end
end
else
inactive_year = nil; -- |doi-broken-date= has something but it isn't a date
end  
if is_set (inactive_year) and is_set (inactive_month) then
set_message ('maint_doi_inactive_dated', {inactive_year, inactive_month, ' '});
elseif is_set (inactive_year) then
set_message ('maint_doi_inactive_dated', {inactive_year, *, *});
else
set_message ('maint_doi_inactive');
end
inactive = " (" .. cfg.messages['inactive'] .. ' ' .. inactive .. ')';
end  
local registrant = mw.ustring.match (id, '^10%.([^/]+)/[^%s–]-[^%.,]$'); -- registrant set when DOI has the proper basic form  
local registrant_err_patterns = { -- these patterns are for code ranges that are not supported
'^[^1-3]%d%d%d%d%.%d%d*$', -- 5 digits with subcode (0xxxx, 40000+); accepts: 10000–39999
'^[^1-5]%d%d%d%d$', -- 5 digits without subcode (0xxxx, 60000+); accepts: 10000–59999
'^[^1-9]%d%d%d%.%d%d*$', -- 4 digits with subcode (0xxx); accepts: 1000–9999
'^[^1-9]%d%d%d$', -- 4 digits without subcode (0xxx); accepts: 1000–9999
'^%d%d%d%d%d%d+', -- 6 or more digits
'^%d%d?%d?$', -- less than 4 digits without subcode (with subcode is legitimate)
'^5555$', -- test registrant will never resolve
'[^%d%.]', -- any character that isn't a digit or a dot
}  
if not ignore_invalid then
if registrant then -- when DOI has proper form
for i, pattern in ipairs (registrant_err_patterns) do -- spin through error patterns
if registrant:match (pattern) then -- to validate registrant codes
err_flag = set_message ('err_bad_doi'); -- when found, mark this DOI as bad
break; -- and done
end
end