local function format_volume_issue (volume, issue, cite_class, origin, sepc, lower)
if not utilities.is_set (volume) and not utilities.is_set (issue) then
return '';
end  
-- same condition as in format_pages_sheets
local is_journal = 'journal' == cite_class or (utilities.in_array (cite_class, {'citation', 'map', 'interview'}) and 'journal' == origin);  
local is_numeric_vol = volume and (volume:match ('^[MDCLXVI]+$') or volume:match ('^%d+$')); -- is only uppercase roman numerals or only digits?
local is_long_vol = volume and (4 < mw.ustring.len(volume)); -- is |volume= value longer than 4 characters?  
if volume and (not is_numeric_vol and is_long_vol) then -- when not all digits or Roman numerals, is |volume= longer than 4 characters?
utilities.add_prop_cat ('long-vol'); -- yes, add properties cat
end  
if is_journal then -- journal-style formatting
local vol = '';  
if utilities.is_set (volume) then
if is_numeric_vol then -- |volume= value all digits or all uppercase Roman numerals?
vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, volume}); -- render in bold face
elseif is_long_vol then -- not all digits or Roman numerals; longer than 4 characters?
vol = utilities.substitute (cfg.messages['j-vol'], {sepc, utilities.hyphen_to_dash (volume)}); -- not bold
else -- four or fewer characters
vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, utilities.hyphen_to_dash (volume)}); -- bold
end
end
if utilities.is_set (issue) then
return vol .. utilities.substitute (cfg.messages['j-issue'], issue);
end
return vol;
end  
if 'podcast' == cite_class and utilities.is_set (issue) then
return wrap_msg ('issue', {sepc, issue}, lower);
end  
-- all other types of citation
if utilities.is_set (volume) and utilities.is_set (issue) then
return wrap_msg ('vol-no', {sepc, utilities.hyphen_to_dash (volume), issue}, lower);
elseif utilities.is_set (volume) then
return wrap_msg ('vol', {sepc, utilities.hyphen_to_dash (volume)}, lower);
else
return wrap_msg ('issue', {sepc, issue}, lower);
end
end  
--page(s)= or |sheet(s)= values and returns it with all of the others set to empty strings.
The return order is:
page, pages, sheet, sheets  
Singular has priority over plural when both are provided.  
local function format_pages_sheets (page, pages, sheet, sheets, cite_class, origin, sepc, nopp, lower)
if 'map' == cite_class then -- only cite map supports sheet(s) as in-source locators
if utilities.is_set (sheet) then
if 'journal' == origin then
return *, *, wrap_msg ('j-sheet', sheet, lower), '';
else
return *, *, wrap_msg ('sheet', {sepc, sheet}, lower), '';
end
elseif utilities.is_set (sheets) then
if 'journal' == origin then
return *, *, '', wrap_msg ('j-sheets', sheets, lower);
else
return *, *, '', wrap_msg ('sheets', {sepc, sheets}, lower);
end
end
end  
local is_journal = 'journal' == cite_class or (utilities.in_array (cite_class, {'citation', 'map', 'interview'}) and 'journal' == origin);  
if utilities.is_set (page) then
if is_journal then
return utilities.substitute (cfg.messages['j-page(s)'], page), *, *, '';
elseif not nopp then
return utilities.substitute (cfg.messages['p-prefix'], {sepc, page}), *, *, '';
else
return utilities.substitute (cfg.messages['nopp'], {sepc, page}), *, *, '';
end
elseif utilities.is_set (pages) then
if is_journal then
return utilities.substitute (cfg.messages['j-page(s)'], pages), *, *, '';
elseif tonumber(pages) ~= nil and not nopp then -- if pages is only digits, assume a single page number
return *, utilities.substitute (cfg.messages['p-prefix'], {sepc, pages}), *, '';
elseif not nopp then
return *, utilities.substitute (cfg.messages['pp-prefix'], {sepc, pages}), *, '';
else
return *, utilities.substitute (cfg.messages['nopp'], {sepc, pages}), *, '';
end
end  
return *, *, *, *; -- return empty strings
end  
----------------------------< I N S O U R C E _ L O C _ G E T >----------------------------------------------  
returns one of the in-source locators: page, pages, or at.  
If any of these are interwiki links to Wikisource, returns the label portion of the interwiki-link as plain text
for use in COinS. This COinS thing is done because here we convert an interwiki-link to an external link and
add an icon span around that; get_coins_pages doesn't know about the span. TODO: should it?  
TODO: add support for sheet and sheets?; streamline;  
TODO: make it so that this function returns only one of the three as the single in-source (the return value assigned
to a new name)?  
local function insource_loc_get (page, page_orig, pages, pages_orig, at)
local ws_url, ws_label, coins_pages, L; -- for Wikisource interwiki-links; TODO: this corrupts page metadata (span remains in place after cleanup; fix there?)  
if utilities.is_set (page) then
if utilities.is_set (pages) or utilities.is_set (at) then
pages = ''; -- unset the others
at = '';
end
extra_text_in_page_check (page, page_orig); -- emit error message when |page= value begins with what looks like p., pp., etc.  
ws_url, ws_label, L = wikisource_url_make (page); -- make ws URL from |page= interwiki link; link portion L becomes tooltip label
if ws_url then
page = external_link (ws_url, ws_label .. '&nbsp;', 'ws link in page'); -- space char after label to move icon away from in-source text; TODO: a better way to do this?
page = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], L, page});
coins_pages = ws_label;
end
elseif utilities.is_set (pages) then
if utilities.is_set (at) then
at = ''; -- unset
end
extra_text_in_page_check (pages, pages_orig); -- emit error message when |page= value begins with what looks like p., pp., etc.  
ws_url, ws_label, L = wikisource_url_make (pages); -- make ws URL from |pages= interwiki link; link portion L becomes tooltip label
if ws_url then
pages = external_link (ws_url, ws_label .. '&nbsp;', 'ws link in pages'); -- space char after label to move icon away from in-source text; TODO: a better way to do this?
pages = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], L, pages});
coins_pages = ws_label;
end
elseif utilities.is_set (at) then
ws_url, ws_label, L = wikisource_url_make (at); -- make ws URL from |at= interwiki link; link portion L becomes tooltip label
if ws_url then
at = external_link (ws_url, ws_label .. '&nbsp;', 'ws link in at'); -- space char after label to move icon away from in-source text; TODO: a better way to do this?
at = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], L, at});
coins_pages = ws_label;
end
end  
return page, pages, at, coins_pages;
end  
--archive-url= value is same as |url= or chapter-url= (or alias...) value  
local function is_unique_archive_url (archive, url, c_url, source, date)
if utilities.is_set (archive) then
if archive == url or archive == c_url then
utilities.set_message ('err_bad_url', {utilities.wrap_style ('parameter', source)}); -- add error message
return *, *; -- unset |archive-url= and |archive-date= because same as |url= or |chapter-url=
end
end  
return archive, date;
end  
--[=[-------------------------< A R C H I V E _ U R L _ C H E C K >--------------------------------------------  
Check archive.org URLs to make sure they at least look like they are pointing at valid archives and not to the
save snapshot URL or to calendar pages. When the archive URL is 'https://web.archive.org/save/' (or http://...)
archive.org saves a snapshot of the target page in the URL. That is something that Wikipedia should not allow
unwitting readers to do.  
When the archive.org URL does not have a complete timestamp, archive.org chooses a snapshot according to its own
algorithm or provides a calendar 'search' result. WP:ELNO discourages links to search results.  
This function looks at the value assigned to |archive-url= and returns empty strings for |archive-url= and
|archive-date= and an error message when: