Module:Citation/CS1: Difference between revisions

    (sync from sandbox;)
    (sync from sandbox;)
    Line 3: Line 3:


    --[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
    --[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
    each of these counts against the Lua upvalue limit
    each of these counts against the Lua upvalue limit
    ]]
    ]]


    Line 18: Line 16:
    local whitelist = {}; -- table of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist
    local whitelist = {}; -- table of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist


     
    --[[------------------< P A G E  S C O P E  V A R I A B L E S >---------------
    --[[--------------------------< P A G E  S C O P E  V A R I A B L E S >--------------------------------------
    declare variables here that have page-wide scope that are not brought in from
     
    other modules; that are created here and used here
    declare variables here that have page-wide scope that are not brought in from other modules; that are created here and used here
     
    ]]
    ]]
    local added_deprecated_cat; -- Boolean flag so that the category is added only once
    local added_deprecated_cat; -- Boolean flag so that the category is added only once
    local added_discouraged_cat; -- Boolean flag so that the category is added only once
    local added_vanc_errs; -- Boolean flag so we only emit one Vancouver error / category
    local added_vanc_errs; -- Boolean flag so we only emit one Vancouver error / category
    local Frame; -- holds the module's frame table
    local Frame; -- holds the module's frame table


    --[[--------------------------< F I R S T _ S E T >------------------------------------------------------------
    --[[--------------------------< F I R S T _ S E T >------------------------------------------------------------
    Line 62: Line 56:
    ]]
    ]]


    local function add_vanc_error (source)
    local function add_vanc_error (source, position)
    if not added_vanc_errs then
    if added_vanc_errs then return end
    added_vanc_errs = true; -- note that we've added this category
    table.insert( z.message_tail, { utilities.set_message ( 'err_vancouver', {source}, true ) } );
    added_vanc_errs = true; -- note that we've added this category
    end
    table.insert( z.message_tail, { utilities.set_message ( 'err_vancouver', {source, position}, true ) } );
    end
    end


    Line 415: Line 409:
    domain, path = URL:match ('^([/%.%-%+:%a%d]+)([/%?#].*)$'); -- split the URL into scheme plus domain and path
    domain, path = URL:match ('^([/%.%-%+:%a%d]+)([/%?#].*)$'); -- split the URL into scheme plus domain and path
    if path then -- if there is a path portion
    if path then -- if there is a path portion
    path = path:gsub ('[%[%]]', {['['] = '%5b', [']'] = '%5d'}); -- replace '[' and ']' with their percent-encoded values
    path = path:gsub ('[%[%]]', {['['] = '%5b', [']'] = '%5d'}); -- replace '[' and ']' with their percent-encoded values
    URL = table.concat ({domain, path}); -- and reassemble
    URL = table.concat ({domain, path}); -- and reassemble
    end
    end
    Line 443: Line 437:
    added_deprecated_cat = true; -- note that we've added this category
    added_deprecated_cat = true; -- note that we've added this category
    table.insert( z.message_tail, { utilities.set_message ( 'err_deprecated_params', {name}, true ) } ); -- add error message
    table.insert( z.message_tail, { utilities.set_message ( 'err_deprecated_params', {name}, true ) } ); -- add error message
    end
    end
    --[[--------------------------< D I S C O U R A G E D _ P A R A M E T E R >------------------------------------
    Categorize and emit an maintenance message when the citation contains one or more discouraged parameters.  Only
    one error message is emitted regardless of the number of discouraged parameters in the citation.
    added_discouraged_cat is a Boolean declared in page scope variables above
    ]]
    local function discouraged_parameter(name)
    if not added_discouraged_cat then
    added_discouraged_cat = true; -- note that we've added this category
    table.insert( z.message_tail, { utilities.set_message ( 'maint_discouraged', {name}, true ) } ); -- add maint message
    end
    end
    end
    end
    Line 539: Line 550:
    -- if we get this far we have prefix and script
    -- if we get this far we have prefix and script
    name = cfg.lang_code_remap[lang] or mw.language.fetchLanguageName( lang, cfg.this_wiki_code ); -- get language name so that we can use it to categorize
    name = cfg.lang_code_remap[lang] or mw.language.fetchLanguageName( lang, cfg.this_wiki_code ); -- get language name so that we can use it to categorize
    if utilities.is_set (name) then -- is prefix a proper ISO 639-1 language code?
    if utilities.is_set (name) then -- is prefix a proper ISO 639-1 language code?
    script_value = script_value:gsub ('^%l+%s*:%s*', ''); -- strip prefix from script
    script_value = script_value:gsub ('^%l+%s*:%s*', ''); -- strip prefix from script
    -- is prefix one of these language codes?
    -- is prefix one of these language codes?
    Line 755: Line 766:
    local function has_invisible_chars (param, v)
    local function has_invisible_chars (param, v)
    local position = ''; -- position of invisible char or starting position of stripmarker
    local position = ''; -- position of invisible char or starting position of stripmarker
    local dummy; -- end of matching string; not used but required to hold end position when a capture is returned
    local capture; -- used by stripmarker detection to hold name of the stripmarker
    local capture; -- used by stripmarker detection to hold name of the stripmarker
    local i = 1;
    local stripmarker; -- boolean set true when a stripmarker is found
    local stripmarker, apostrophe;
     
    capture = string.match (v, '[%w%p ]*'); -- test for values that are simple ASCII text and bypass other tests if true
    capture = string.match (v, '[%w%p ]*'); -- test for values that are simple ASCII text and bypass other tests if true
    if capture == v then -- if same there are no Unicode characters
    if capture == v then -- if same there are no Unicode characters
    Line 765: Line 774:
    end
    end


    while cfg.invisible_chars[i] do
    for _, invisible_char in ipairs (cfg.invisible_chars) do
    local char = cfg.invisible_chars[i][1] -- the character or group name
    local char_name = invisible_char[1]; -- the character or group name
    local pattern = cfg.invisible_chars[i][2] -- the pattern used to find it
    local pattern = invisible_char[2]; -- the pattern used to find it
    position, dummy, capture = mw.ustring.find (v, pattern) -- see if the parameter value contains characters that match the pattern
    position, _, capture = mw.ustring.find (v, pattern); -- see if the parameter value contains characters that match the pattern
    if position and (char == 'zero width joiner') then -- if we found a zero-width joiner character
    if position and (cfg.invisible_defs.zwj == capture) then -- if we found a zero-width joiner character
    if mw.ustring.find (v, cfg.indic_script) then -- it's ok if one of the Indic scripts
    if mw.ustring.find (v, cfg.indic_script) then -- it's ok if one of the Indic scripts
    position = nil; -- unset position
    elseif cfg.emoji[mw.ustring.codepoint (v, position+1)] then -- is zwj followed by a character listed in emoji{}?
    position = nil; -- unset position
    position = nil; -- unset position
    end
    end
    Line 780: Line 791:
    ('templatestyles' == capture and utilities.in_array (param, {'id', 'quote'})) then -- templatestyles stripmarker allowed in these parameters
    ('templatestyles' == capture and utilities.in_array (param, {'id', 'quote'})) then -- templatestyles stripmarker allowed in these parameters
    stripmarker = true; -- set a flag
    stripmarker = true; -- set a flag
    elseif true == stripmarker and 'delete' == char then -- because stripmakers begin and end with the delete char, assume that we've found one end of a stripmarker
    elseif true == stripmarker and cfg.invisible_defs.del == capture then -- because stripmakers begin and end with the delete char, assume that we've found one end of a stripmarker
    position = nil; -- unset
    position = nil; -- unset
    else
    else
    local err_msg;
    local err_msg;
    if capture then
    if capture and not (cfg.invisible_defs.del == capture or cfg.invisible_defs.zwj == capture) then
    err_msg = capture .. ' ' .. char;
    err_msg = capture .. ' ' .. char_name;
    else
    else
    err_msg = char .. ' ' .. 'character';
    err_msg = char_name .. ' ' .. 'character';
    end
    end


    table.insert( z.message_tail, { utilities.set_message ( 'err_invisible_char', {err_msg, utilities.wrap_style ('parameter', param), position}, true ) } ); -- add error message
    table.insert (z.message_tail, {utilities.set_message ('err_invisible_char', {err_msg, utilities.wrap_style ('parameter', param), position}, true)}); -- add error message
    return; -- and done with this parameter
    return; -- and done with this parameter
    end
    end
    end
    end
    i = i + 1; -- bump our index
    end
    end
    end
    end
    Line 812: Line 822:
    return setmetatable({
    return setmetatable({
    ORIGIN = function ( self, k )
    ORIGIN = function ( self, k )
    local dummy = self[k]; -- force the variable to be loaded.
    local dummy = self[k]; -- force the variable to be loaded.
    return origin[k];
    return origin[k];
    end
    end
    Line 827: Line 837:
    v, origin[k] = utilities.select_one ( args, list, 'err_redundant_parameters' );
    v, origin[k] = utilities.select_one ( args, list, 'err_redundant_parameters' );
    if origin[k] == nil then
    if origin[k] == nil then
    origin[k] = ''; -- Empty string, not nil
    origin[k] = ''; -- Empty string, not nil
    end
    end
    elseif list ~= nil then
    elseif list ~= nil then
    Line 924: Line 934:
    str = str:gsub ('&[nm]dash;', {['&ndash;'] = '–', ['&mdash;'] = '—'}); -- replace &mdash; and &ndash; entities with their characters; semicolon mucks up the text.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 ('&#45;', '-'); -- replace HTML numeric entity with hyphen character
    str = str:gsub ('[^%-]%-%-%-[^%-]', '—'); -- replace triple-hyphen with emdash
    str = str:gsub ('[^%-]%-%-[^%-]', '–'); -- replace double-hyphen (as found in BibTeX entries) with endash


    str = str:gsub ('&nbsp;', ' '); -- replace &nbsp; entity with generic keyboard space character
    str = str:gsub ('&nbsp;', ' '); -- replace &nbsp; entity with generic keyboard space character
    Line 933: Line 941:


    for _, item in ipairs (list) do -- for each item in the list
    for _, item in ipairs (list) do -- for each item in the list
    item, accept = utilities.has_accept_as_written (item); -- remove accept-this-as-written markup when it wraps all of item
    item, accept = utilities.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 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 item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)
    if item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)
    Line 951: Line 959:
    temp_str, accept = utilities.has_accept_as_written (table.concat (out, ', ')); -- remove accept-this-as-written markup when it wraps all of concatenated out
    temp_str, accept = utilities.has_accept_as_written (table.concat (out, ', ')); -- remove accept-this-as-written markup when it wraps all of concatenated out
    if accept then
    if accept then
    return utilities.has_accept_as_written (str); -- when global markup removed, return original str
    return utilities.has_accept_as_written (str); -- when global markup removed, return original str
    else
    else
    return temp_str; -- else, return assembled temp_str
    return temp_str; -- else, return assembled temp_str
    Line 996: Line 1,004:
    trim = false;
    trim = false;
    end_chr = f.sub(str, -1, -1); -- get the last character of the output string
    end_chr = f.sub(str, -1, -1); -- get the last character of the output string
    -- str = str .. "<HERE(enchr=" .. end_chr .. ")" -- debug stuff?
    -- str = str .. "<HERE(enchr=" .. end_chr .. ")" -- debug stuff?
    if end_chr == duplicate_char then -- if same as separator
    if end_chr == duplicate_char then -- if same as separator
    str = f.sub(str, 1, -2); -- remove it
    str = f.sub(str, 1, -2); -- remove it
    elseif end_chr == "'" then -- if it might be wiki-markup
    elseif end_chr == "'" then -- if it might be wiki-markup
    if f.sub(str, -3, -1) == duplicate_char .. "''" then -- if last three chars of str are sepc''  
    if f.sub(str, -3, -1) == duplicate_char .. "''" then -- if last three chars of str are sepc''  
    str = f.sub(str, 1, -4) .. "''"; -- remove them and add back ''
    str = f.sub(str, 1, -4) .. "''"; -- remove them and add back ''
    elseif  f.sub(str, -5, -1) == duplicate_char .. "]]''" then -- if last five chars of str are sepc]]''  
    elseif  f.sub(str, -5, -1) == duplicate_char .. "]]''" then -- if last five chars of str are sepc]]''  
    Line 1,008: Line 1,016:
    end
    end
    elseif end_chr == "]" then -- if it might be wiki-markup
    elseif end_chr == "]" then -- if it might be wiki-markup
    if f.sub(str, -3, -1) == duplicate_char .. "]]" then -- if last three chars of str are sepc]] wikilink  
    if f.sub(str, -3, -1) == duplicate_char .. "]]" then -- if last three chars of str are sepc]] wikilink  
    trim = true;
    trim = true;
    elseif f.sub(str, -3, -1) == duplicate_char .. '"]' then -- if last three chars of str are sepc"] quoted external link  
    elseif f.sub(str, -3, -1) == duplicate_char .. '"]' then -- if last three chars of str are sepc"] quoted external link  
    trim = true;
    trim = true;
    elseif  f.sub(str, -2, -1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link
    elseif  f.sub(str, -2, -1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link
    trim = true;
    trim = true;
    elseif f.sub(str, -4, -1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title.
    elseif f.sub(str, -4, -1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title.
    Line 1,019: Line 1,027:
    elseif end_chr == " " then -- if last char of output string is a space
    elseif end_chr == " " then -- if last char of output string is a space
    if f.sub(str, -2, -1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space>
    if f.sub(str, -2, -1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space>
    str = f.sub(str, 1, -3); -- remove them both
    str = f.sub(str, 1, -3); -- remove them both
    end
    end
    end
    end
    Line 1,034: Line 1,042:
    end
    end
    end
    end
    str = str .. value; -- add it to the output string
    str = str .. value; -- add it to the output string
    end
    end
    end
    end
    Line 1,059: Line 1,067:


    For Vancouver style, author/editor names are supposed to be rendered in Latin
    For Vancouver style, author/editor names are supposed to be rendered in Latin
    (read ASCII) characters.  When a name uses characters that contain diacritical marks,
    (read ASCII) characters.  When a name uses characters that contain diacritical
    those characters are to converted to the corresponding Latin character. When a name
    marks, those characters are to be converted to the corresponding Latin
    is written using a non-Latin alphabet or logogram, that name is to be transliterated
    character. When a name is written using a non-Latin alphabet or logogram, that
    into Latin characters. The module doesn't do this so editors may/must.
    name is to be transliterated into Latin characters. The module doesn't do this
    so editors may/must.


    This test allows |first= and |last= names to contain any of the letters defined
    This test allows |first= and |last= names to contain any of the letters defined
    Line 1,091: Line 1,100:
    ]]
    ]]


    local function is_good_vanc_name (last, first, suffix)
    local function is_good_vanc_name (last, first, suffix, position)
    if not suffix then
    if not suffix then
    if first:find ('[,%s]') then -- when there is a space or comma, might be first name/initials + generational suffix
    if first:find ('[,%s]') then -- when there is a space or comma, might be first name/initials + generational suffix
    Line 1,100: Line 1,109:
    if utilities.is_set (suffix) then
    if utilities.is_set (suffix) then
    if not is_suffix (suffix) then
    if not is_suffix (suffix) then
    add_vanc_error (cfg.err_msg_supl.suffix);
    add_vanc_error (cfg.err_msg_supl.suffix, position);
    return false; -- not a name with an appropriate suffix
    return false; -- not a name with an appropriate suffix
    end
    end
    Line 1,106: Line 1,115:
    if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
    if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
    nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
    nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
    add_vanc_error (cfg.err_msg_supl['non-Latin char']);
    add_vanc_error (cfg.err_msg_supl['non-Latin char'], position);
    return false; -- not a string of Latin characters; Vancouver requires Romanization
    return false; -- not a string of Latin characters; Vancouver requires Romanization
    end;
    end;
    Line 1,130: Line 1,139:
    ]]
    ]]


    local function reduce_to_initials(first)
    local function reduce_to_initials(first, position)
    local name, suffix = mw.ustring.match(first, "^(%u+) ([%dJS][%drndth]+)$");
    local name, suffix = mw.ustring.match(first, "^(%u+) ([%dJS][%drndth]+)$");


    Line 1,143: Line 1,152:
    return first; -- one or two initials and a valid suffix so nothing to do
    return first; -- one or two initials and a valid suffix so nothing to do
    else
    else
    add_vanc_error (cfg.err_msg_supl.suffix); -- one or two initials with invalid suffix so error message
    add_vanc_error (cfg.err_msg_supl.suffix, position); -- one or two initials with invalid suffix so error message
    return first; -- and return first unmolested
    return first; -- and return first unmolested
    end
    end
    Line 1,166: Line 1,175:
    end
    end
    if 3 > i then
    if 3 > i then
    table.insert (initials, mw.ustring.sub(names[i], 1, 1)); -- insert the initial at end of initials table
    table.insert (initials, mw.ustring.sub(names[i], 1, 1)); -- insert the initial at end of initials table
    end
    end
    i = i + 1; -- bump the counter
    i = i + 1; -- bump the counter
    Line 1,232: Line 1,241:
    if ("vanc" == format) then -- if Vancouver format
    if ("vanc" == format) then -- if Vancouver format
    one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
    one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
    if not person.corporate and is_good_vanc_name (one, first) then -- and name is all Latin characters; corporate authors not tested
    if not person.corporate and is_good_vanc_name (one, first, nil, i) then -- and name is all Latin characters; corporate authors not tested
    first = reduce_to_initials (first); -- attempt to convert first name(s) to initials
    first = reduce_to_initials (first, i); -- attempt to convert first name(s) to initials
    end
    end
    end
    end
    Line 1,273: Line 1,282:
    end
    end


     
    --[[--------------------< M A K E _ C I T E R E F _ I D >-----------------------
    --[[--------------------------< A N C H O R _ I D >-----------------------------


    Generates a CITEREF anchor ID if we have at least one name or a date.  Otherwise
    Generates a CITEREF anchor ID if we have at least one name or a date.  Otherwise
    Line 1,284: Line 1,292:
    ]]
    ]]


    local function anchor_id (namelist, year)
    local function make_citeref_id (namelist, year)
    local names={}; -- a table for the one to four names and year
    local names={}; -- a table for the one to four names and year
    for i,v in ipairs (namelist) do -- loop through the list and take up to the first four last names
    for i,v in ipairs (namelist) do -- loop through the list and take up to the first four last names
    names[i] = v.last  
    names[i] = v.last
    if i == 4 then break end -- if four then done
    if i == 4 then break end -- if four then done
    end
    end
    table.insert (names, year); -- add the year at the end
    table.insert (names, year); -- add the year at the end
    local id = table.concat(names); -- concatenate names and year for CITEREF id
    local id = table.concat(names); -- concatenate names and year for CITEREF id
    if utilities.is_set (id) then -- if concatenation is not an empty string
    if utilities.is_set (id) then -- if concatenation is not an empty string
    return "CITEREF" .. id; -- add the CITEREF portion
    return "CITEREF" .. id; -- add the CITEREF portion
    else
    else
    return ''; -- return an empty string; no reason to include CITEREF id in this citation
    return ''; -- return an empty string; no reason to include CITEREF id in this citation
    end
    end
    end
    end
    Line 1,432: Line 1,440:
    first, accept_name = utilities.has_accept_as_written (first); -- remove accept-this-as-written markup when it wraps all of <first>
    first, accept_name = utilities.has_accept_as_written (first); -- remove accept-this-as-written markup when it wraps all of <first>


    if not accept_name then -- <first> not wrapped in accept-as-written markup
    if not accept_name then -- <first> not wrapped in accept-as-written markup
    name_has_ed_markup (first, list_name); -- check for extraneous 'editor' annotation
    name_has_ed_markup (first, list_name); -- check for extraneous 'editor' annotation
    name_is_numeric (first, list_name); -- check for names that are composed of digits and punctuation
    name_is_numeric (first, list_name); -- check for names that are composed of digits and punctuation
    Line 1,517: Line 1,525:




    --[[--------------------------< G E T _ I S O 6 3 9 _ C O D E >------------------------------------------------
    --[[---------------------< G E T _ I S O 6 3 9 _ C O D E >----------------------


    Validates language names provided in |language= parameter if not an ISO639-1 or 639-2 code.
    Validates language names provided in |language= parameter if not an ISO639-1 or 639-2 code.
    Line 1,655: Line 1,663:
    end
    end


     
    --[[-----------------------< S E T _ C S _ S T Y L E >--------------------------
    --[[----------------------< S E T _ C S 1 _ S T Y L E >-------------------------
    Gets the default CS style configuration for the given mode.
     
    Returns default separator and either postscript as passed in or the default.
    Set style settings for CS1 citation templates. Returns separator and postscript settings
    In CS1, the default postscript and separator are '.'.
    At en.wiki, for cs1:
    In CS2, the default postscript is the empty string and the default separator is ','.
    ps gets: '.'
    sep gets: '.'
     
    ]]
    ]]
     
    local function set_cs_style (postscript, mode)
    local function set_cs1_style (ps)
    if utilities.is_set(postscript) then
    if not utilities.is_set (ps) then -- unless explicitly set to something
    -- emit a maintenance message if user postscript is the default cs1 postscript
    ps = cfg.presentation['ps_cs1']; -- terminate the rendered citation
    -- we catch the opposite case for cs2 in set_style
    if mode == 'cs1' and postscript == cfg.presentation['ps_' .. mode] then
    utilities.set_message ('maint_postscript');
    end
    else
    postscript = cfg.presentation['ps_' .. mode];
    end
    end
    return cfg.presentation['sep_cs1'], ps; -- element separator
    return cfg.presentation['sep_' .. mode], postscript;
    end
    end


     
    --[[--------------------------< S E T _ S T Y L E >-----------------------------
    --[[-----------------------< S E T _ C S 2 _ S T Y L E >------------------------
    Sets the separator and postscript styles. Checks the |mode= first and the
     
    #invoke CitationClass second. Removes the postscript if postscript == none.
    Set style settings for CS2 citation templates. Returns separator, postscript, ref settings
    At en.wiki, for cs2:
    ps gets: '' (empty string - no terminal punctuation)
    sep gets: ','
     
    ]]
    ]]
    local function set_style (mode, postscript, cite_class)
    local sep;
    if 'cs2' == mode then
    sep, postscript = set_cs_style (postscript, 'cs2');
    elseif 'cs1' == mode then
    sep, postscript = set_cs_style (postscript, 'cs1');
    elseif 'citation' == cite_class then
    sep, postscript = set_cs_style (postscript, 'cs2');
    else
    sep, postscript = set_cs_style (postscript, 'cs1');
    end


    local function set_cs2_style (ps, ref)
    if cfg.keywords_xlate[postscript:lower()] == 'none' then
    if not utilities.is_set (ps) then -- if |postscript= has not been set, set cs2 default
    -- emit a maintenance message if user postscript is the default cs2 postscript
    ps = cfg.presentation['ps_cs2']; -- terminate the rendered citation
    -- we catch the opposite case for cs1 in set_cs_style
    if 'cs2' == mode or 'citation' == cite_class then
    utilities.set_message ('maint_postscript');
    end
    postscript = '';
    end
    end
    if not utilities.is_set (ref) then -- if |ref= is not set
    ref = "harv"; -- set default |ref=harv
    return sep, postscript
    end
    return cfg.presentation['sep_cs2'], ps, ref; -- element separator
    end
    end


    --[=[-------------------------< I S _ P D F >-----------------------------------


    --[[---------< G E T _ S E T T I N G S _ F R O M _ C I T E _ C L A S S >--------
    Determines if a URL has the file extension that is one of the PDF file extensions
     
    When |mode= is not set or when its value is invalid, use config.CitationClass and
    parameter values to establish rendered style.
     
    ]]
     
    local function get_settings_from_cite_class (ps, ref, cite_class)
    local sep;
    if (cite_class == "citation") then -- for citation templates (CS2)
    sep, ps, ref = set_cs2_style (ps, ref);
    else -- not a citation template so CS1
    sep, ps = set_cs1_style (ps);
    end
     
    return sep, ps, ref -- return them all
    end
     
     
    --[[--------------------------< S E T _ S T Y L E >------------------------------------------------------------
     
    Establish basic style settings to be used when rendering the citation. Uses |mode=
    if set and valid or uses config.CitationClass from the template's #invoke: to establish style.
     
    ]]
     
    local function set_style (mode, ps, ref, cite_class)
    local sep;
    if 'cs2' == mode then -- if this template is to be rendered in CS2 (citation) style
    sep, ps, ref = set_cs2_style (ps, ref);
    elseif 'cs1' == mode then -- if this template is to be rendered in CS1 (cite xxx) style
    sep, ps = set_cs1_style (ps);
    else -- anything but cs1 or cs2
    sep, ps, ref = get_settings_from_cite_class (ps, ref, cite_class); -- get settings based on the template's CitationClass
    end
     
    if cfg.keywords_xlate[ps:lower()] == 'none' then -- if assigned value is 'none' then
    ps = ''; -- set to empty string
    end
    return sep, ps, ref
    end
     
     
    --[=[-------------------------< I S _ P D F >-----------------------------------
     
    Determines if a URL has the file extension that is one of the PDF file extensions
    used by [[MediaWiki:Common.css]] when applying the PDF icon to external links.
    used by [[MediaWiki:Common.css]] when applying the PDF icon to external links.


    Line 1,801: Line 1,774:
    ]]
    ]]


    local function get_display_names (max, count, list_name, etal)
    local function get_display_names (max, count, list_name, etal, param)
    if utilities.is_set (max) then
    if utilities.is_set (max) then
    if 'etal' == max:lower():gsub("[ '%.]", '') then -- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
    if 'etal' == max:lower():gsub("[ '%.]", '') then -- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
    Line 1,809: Line 1,782:
    max = tonumber (max); -- make it a number
    max = tonumber (max); -- make it a number
    if max >= count then -- if |display-xxxxors= value greater than or equal to number of authors/editors
    if max >= count then -- if |display-xxxxors= value greater than or equal to number of authors/editors
    table.insert( z.message_tail, {utilities.set_message ('err_disp_name', {cfg.special_case_translation [list_name], max}, true)}); -- add error message
    table.insert( z.message_tail, {utilities.set_message ('err_disp_name', {param, max}, true)}); -- add error message
    max = nil;
    max = nil;
    end
    end
    else -- not a valid keyword or number
    else -- not a valid keyword or number
    table.insert( z.message_tail, {utilities.set_message ('err_disp_name', {cfg.special_case_translation [list_name], max}, true)}); -- add error message
    table.insert( z.message_tail, {utilities.set_message ('err_disp_name', {param, max}, true)}); -- add error message
    max = nil; -- unset; as if |display-xxxxors= had not been set
    max = nil; -- unset; as if |display-xxxxors= had not been set
    end
    end
    Line 1,834: Line 1,807:
    ]]
    ]]


    local function extra_text_in_page_check (page)
    local function extra_text_in_page_check (val, name)
    local good_pattern = '^P[^%.PpGg]'; -- OK to begin with uppercase P: P7 (page 7 of section P), but not p123 (page 123)
    if not val:match (cfg.vol_iss_pg_patterns.good_ppattern) then
    local bad_pattern = '^[Pp][PpGg]?%.?[ %d]';
    for _, pattern in ipairs (cfg.vol_iss_pg_patterns.bad_ppatterns) do -- spin through the selected sequence table of patterns
     
    if val:match (pattern) then -- when a match, error so
    if not page:match (good_pattern) and (page:match (bad_pattern) or page:match ('^[Pp]ages?') or page:match ('^[Pp]gs.?')) then
    table.insert (z.message_tail, {utilities.set_message ('err_extra_text_pages', {name}, true)}); -- add error message
    table.insert( z.message_tail, { utilities.set_message ( 'err_extra_text_pages')}); -- add error
    return; -- and done
    end
    end
    end
    end
    end
    end




    --[=[-------------------------< G E T _ V _ N A M E _ T A B L E >----------------------------------------------
    --[[--------------------------< E X T R A _ T E X T _ I N _ V O L _ I S S _ C H E C K >------------------------
     
    split apart a |vauthors= or |veditors= parameter.  This function allows for corporate names, wrapped in doubled
    parentheses to also have commas; in the old version of the code, the doubled parentheses were included in the
    rendered citation and in the metadata.  Individual author names may be wikilinked


    |vauthors=Jones AB, [[E. B. White|White EB]], ((Black, Brown, and Co.))
    Adds error if |volume= or |issue= has what appears to be some form of redundant 'type' indicator.


    ]=]
    For |volume=:
    'V.', or 'Vol.' (with or without the dot) abbreviations or 'Volume' in the first characters of the parameter
    content (all case insensitive). 'V' and 'v' (without the dot) are presumed to be roman numerals so
    are allowed.


    local function get_v_name_table (vparam, output_table, output_link_table)
    For |issue=:
    local name_table = mw.text.split(vparam, "%s*,%s*"); -- names are separated by commas
    'No.', 'I.', 'Iss.' (with or without the dot) abbreviations, or 'Issue' in the first characters of the
    local wl_type, label, link; -- wl_type not used here; just a placeholder
    parameter content (all case insensitive).
    local i = 1;
    Single character values ('v', 'i', 'n') allowed when not followed by separator character ('.', ':', '=', or
    whitespace character) – param values are trimmed of whitespace by MediaWiki before delivered to the module.
    <val> is |volume= or |issue= parameter value
    <name> is |volume= or |issue= parameter name for error message
    <selector> is 'v' for |volume=, 'i' for |issue=
     
    sets error message on failure; returns nothing
     
    ]]
     
    local function extra_text_in_vol_iss_check (val, name, selector)
    if not utilities.is_set (val) then
    return;
    end
    local patterns = 'v' == selector and cfg.vol_iss_pg_patterns.vpatterns or cfg.vol_iss_pg_patterns.ipatterns;
     
    local handler = 'v' == selector and 'err_extra_text_volume' or 'err_extra_text_issue';
    val = val:lower(); -- force parameter value to lower case
    for _, pattern in ipairs (patterns) do -- spin through the selected sequence table of patterns
    if val:match (pattern) then -- when a match, error so
    table.insert (z.message_tail, {utilities.set_message (handler, {name}, true)}); -- add error message
    return; -- and done
    end
    end
    end
     
     
    --[=[-------------------------< G E T _ V _ N A M E _ T A B L E >----------------------------------------------
     
    split apart a |vauthors= or |veditors= parameter.  This function allows for corporate names, wrapped in doubled
    parentheses to also have commas; in the old version of the code, the doubled parentheses were included in the
    rendered citation and in the metadata.  Individual author names may be wikilinked
     
    |vauthors=Jones AB, [[E. B. White|White EB]], ((Black, Brown, and Co.))
     
    ]=]
     
    local function get_v_name_table (vparam, output_table, output_link_table)
    local name_table = mw.text.split(vparam, "%s*,%s*"); -- names are separated by commas
    local wl_type, label, link; -- wl_type not used here; just a placeholder
    local i = 1;
    while name_table[i] do
    while name_table[i] do
    if name_table[i]:match ('^%(%(.*[^%)][^%)]$') then -- first segment of corporate with one or more commas; this segment has the opening doubled parentheses
    if name_table[i]:match ('^%(%(.*[^%)][^%)]$') then -- first segment of corporate with one or more commas; this segment has the opening doubled parentheses
    local name = name_table[i];
    local name = name_table[i];
    i = i + 1; -- bump indexer to next segment
    i = i + 1; -- bump indexer to next segment
    while name_table[i] do
    while name_table[i] do
    name = name .. ', ' .. name_table[i]; -- concatenate with previous segments
    name = name .. ', ' .. name_table[i]; -- concatenate with previous segments
    Line 1,919: Line 1,936:
    v_name, accept_name = utilities.has_accept_as_written (v_name); -- remove accept-this-as-written markup when it wraps all of <v_name>
    v_name, accept_name = utilities.has_accept_as_written (v_name); -- remove accept-this-as-written markup when it wraps all of <v_name>


    -- if v_name:match ('^%(%(.+%)%)$') then -- corporate authors are wrapped in doubled parentheses to suppress vanc formatting and error detection
    -- last = v_name:match ('^%(%((.+)%)%)$') -- remove doubled parentheses
    if accept_name then
    if accept_name then
    last = v_name;
    last = v_name;
    Line 1,926: Line 1,941:
    elseif string.find(v_name, "%s") then
    elseif string.find(v_name, "%s") then
    if v_name:find('[;%.]') then -- look for commonly occurring punctuation characters;  
    if v_name:find('[;%.]') then -- look for commonly occurring punctuation characters;  
    add_vanc_error (cfg.err_msg_supl.punctuation);
    add_vanc_error (cfg.err_msg_supl.punctuation, i);
    end
    end
    local lastfirstTable = {}
    local lastfirstTable = {}
    Line 1,940: Line 1,955:
    first = ''; -- unset
    first = ''; -- unset
    last = v_name; -- last empty because something wrong with first
    last = v_name; -- last empty because something wrong with first
    add_vanc_error (cfg.err_msg_supl.name);
    add_vanc_error (cfg.err_msg_supl.name, i);
    end
    end
    if mw.ustring.match (last, '%a+%s+%u+%s+%a+') then
    if mw.ustring.match (last, '%a+%s+%u+%s+%a+') then
    add_vanc_error (cfg.err_msg_supl['missing comma']); -- matches last II last; the case when a comma is missing
    add_vanc_error (cfg.err_msg_supl['missing comma'], i); -- matches last II last; the case when a comma is missing
    end
    end
    if mw.ustring.match (v_name, ' %u %u$') then -- this test is in the wrong place TODO: move or replace with a more appropriate test
    if mw.ustring.match (v_name, ' %u %u$') then -- this test is in the wrong place TODO: move or replace with a more appropriate test
    add_vanc_error (cfg.err_msg_supl.name); -- matches a space between two initials
    add_vanc_error (cfg.err_msg_supl.initials, i); -- matches a space between two initials
    end
    end
    else
    else
    Line 1,954: Line 1,969:
    if utilities.is_set (first) then
    if utilities.is_set (first) then
    if not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
    if not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
    add_vanc_error (cfg.err_msg_supl.initials); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials
    add_vanc_error (cfg.err_msg_supl.initials, i); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials
    end
    end
    is_good_vanc_name (last, first, suffix); -- check first and last before restoring the suffix which may have a non-Latin digit
    is_good_vanc_name (last, first, suffix, i); -- check first and last before restoring the suffix which may have a non-Latin digit
    if utilities.is_set (suffix) then
    if utilities.is_set (suffix) then
    first = first .. ' ' .. suffix; -- if there was a suffix concatenate with the initials
    first = first .. ' ' .. suffix; -- if there was a suffix concatenate with the initials
    Line 1,963: Line 1,978:
    else
    else
    if not corporate then
    if not corporate then
    is_good_vanc_name (last, '');
    is_good_vanc_name (last, '', nil, i);
    end
    end
    end
    end
    Line 2,177: Line 2,192:
    ]]
    ]]


    local function insource_loc_get (page, pages, at)
    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?)
    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?)


    Line 2,185: Line 2,200:
    at = '';
    at = '';
    end
    end
    extra_text_in_page_check (page); -- add this page to maint cat if |page= value begins with what looks like p., pp., etc.
    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
    ws_url, ws_label, L = wikisource_url_make (page); -- make ws URL from |page= interwiki link; link portion L becomes tooltip label
    Line 2,197: Line 2,212:
    at = ''; -- unset
    at = ''; -- unset
    end
    end
    extra_text_in_page_check (pages); -- add this page to maint cat if |pages= value begins with what looks like p., pp., etc.
    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
    ws_url, ws_label, L = wikisource_url_make (pages); -- make ws URL from |pages= interwiki link; link portion L becomes tooltip label
    Line 2,215: Line 2,230:
    return page, pages, at, coins_pages;
    return page, pages, at, coins_pages;
    end
    --[[--------------------------< I S _ U N I Q U E _ A R C H I V E _ U R L >------------------------------------
    add error message when |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
    table.insert (z.message_tail, {utilities.set_message ('err_bad_url', {utilities.wrap_style ('parameter', source)}, true)}); -- add error message
    return '', ''; -- unset |archive-url= and |archive-date= because same as |url= or |chapter-url=
    end
    end
    return archive, date;
    end
    end


    Line 2,364: Line 2,396:
    ]]
    ]]


    local function citation0( config, args)
    local function citation0( config, args )
    --[[  
    --[[  
    Load Input Parameters
    Load Input Parameters
    Line 2,374: Line 2,406:
    -- Pick out the relevant fields from the arguments.  Different citation templates
    -- Pick out the relevant fields from the arguments.  Different citation templates
    -- define different field names for the same underlying things.
    -- define different field names for the same underlying things.
    local Mode = is_valid_parameter_value (A['Mode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');


    local author_etal;
    local author_etal;
    local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors=
    local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors=
    local Authors;
    local Authors;
    local NameListStyle = is_valid_parameter_value (A['NameListStyle'], A:ORIGIN('NameListStyle'), cfg.keywords_lists['name-list-style'], '');
    local NameListStyle = is_valid_parameter_value (A['NameListStyle'], A:ORIGIN('NameListStyle'), cfg.keywords_lists['name-list-style'], '');
    local Collaboration = A['Collaboration'];
    local Collaboration = A['Collaboration'];
    Line 2,389: Line 2,418:
    a, author_etal = extract_names (args, 'AuthorList'); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn=
    a, author_etal = extract_names (args, 'AuthorList'); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn=
    elseif 2 == selected then
    elseif 2 == selected then
    NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
    NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
    a, author_etal = parse_vauthors_veditors (args, args.vauthors, 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn=
    a, author_etal = parse_vauthors_veditors (args, args.vauthors, 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn=
    elseif 3 == selected then
    elseif 3 == selected then
    Line 2,401: Line 2,430:
    end
    end
    end
    end
    local Others = A['Others'];


    local editor_etal;
    local editor_etal;
    local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
    local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
    local Editors;


    do -- to limit scope of selected
    do -- to limit scope of selected
    Line 2,417: Line 2,443:
    end
    end
    end
    end
     
    local translator_etal;
    local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
    local Translators; -- assembled translators name list
    t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
     
    local interviewer_etal;
    local interviewers_list = {};
    local Interviewers; -- used later
    interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
     
    local contributor_etal;
    local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
    local Contributors; -- assembled contributors name list
     
    local Chapter = A['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases
    local Chapter = A['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases
    local Chapter_origin = A:ORIGIN ('Chapter');
    local Chapter_origin = A:ORIGIN ('Chapter');
    local Contribution; -- because contribution is required for contributor(s)
    local Contribution; -- because contribution is required for contributor(s)
    if 'contribution' == A:ORIGIN ('Chapter') then
    if 'contribution' == Chapter_origin then
    Contribution = A['Chapter']; -- get the name of the contribution
    Contribution = Chapter; -- get the name of the contribution
    end
    end
     
    local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
    if utilities.in_array (config.CitationClass, {"book", "citation"}) and not utilities.is_set (A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
    if utilities.in_array (config.CitationClass, {"book", "citation"}) and not utilities.is_set (A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
    c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
    c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
    Line 2,459: Line 2,472:
    end
    end


    if utilities.is_set (Others) then
    if 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor=
    utilities.set_message ('maint_others');
    end
    end
    local Year = A['Year'];
    local PublicationDate = A['PublicationDate'];
    local OrigDate = A['OrigDate'];
    local Date = A['Date'];
    local LayDate = A['LayDate'];
    ------------------------------------------------- Get title data
    local Title = A['Title'];
    local Title = A['Title'];
    local ScriptTitle = A['ScriptTitle'];
    local BookTitle = A['BookTitle'];
    local Conference = A['Conference'];
    local TransTitle = A['TransTitle'];
    local TransTitle_origin = A:ORIGIN ('TransTitle');
    local TitleNote = A['TitleNote'];
    local TitleLink = A['TitleLink'];
    local TitleLink = A['TitleLink'];


    local auto_select = ''; -- default is auto
    local auto_select = ''; -- default is auto
    local accept_link;
    local accept_link;
    TitleLink, accept_link = utilities.has_accept_as_written(TitleLink, true); -- test for accept-this-as-written markup
    TitleLink, accept_link = utilities.has_accept_as_written(TitleLink, true); -- test for accept-this-as-written markup
    if (not accept_link) and utilities.in_array (TitleLink, {'none', 'pmc', 'doi'}) then -- check for special keywords
    if (not accept_link) and utilities.in_array (TitleLink, {'none', 'pmc', 'doi'}) then -- check for special keywords
    auto_select = TitleLink; -- remember selection for later
    auto_select = TitleLink; -- remember selection for later
    TitleLink = ''; -- treat as if |title-link= would have been empty
    TitleLink = ''; -- treat as if |title-link= would have been empty
    end
    end


    Line 2,491: Line 2,486:


    local Section = ''; -- {{cite map}} only; preset to empty string for concatenation if not used
    local Section = ''; -- {{cite map}} only; preset to empty string for concatenation if not used
    if 'map' == config.CitationClass and 'section' == A:ORIGIN ('Chapter') then
    if 'map' == config.CitationClass and 'section' == Chapter_origin then
    Section = A['Chapter']; -- get |section= from |chapter= alias list; |chapter= and the other aliases not supported in {{cite map}}
    Section = A['Chapter']; -- get |section= from |chapter= alias list; |chapter= and the other aliases not supported in {{cite map}}
    Chapter = ''; -- unset for now; will be reset later from |map= if present
    Chapter = ''; -- unset for now; will be reset later from |map= if present
    end
    end


    local ScriptChapter = A['ScriptChapter'];
    local Periodical = A['Periodical'];
    local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
    local Periodical_origin = '';
    local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
    local TransChapter = A['TransChapter'];
    local TransChapter_origin = A:ORIGIN ('TransChapter');
    local TitleType = A['TitleType'];
    local Degree = A['Degree'];
    local Docket = A['Docket'];
    local ArchiveFormat = A['ArchiveFormat'];
     
    local ArchiveDate;
    local ArchiveURL;
     
    ArchiveURL, ArchiveDate = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
    local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
     
    local URL = A['URL']
    local URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
    local ChapterURL = A['ChapterURL'];
    local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
    local ConferenceFormat = A['ConferenceFormat'];
    local ConferenceURL = A['ConferenceURL'];
    local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
     
    local Periodical = A['Periodical'];
    local Periodical_origin = '';
    if utilities.is_set (Periodical) then
    if utilities.is_set (Periodical) then
    Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter
    Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter
    Line 2,542: Line 2,512:


    local ScriptPeriodical = A['ScriptPeriodical'];
    local ScriptPeriodical = A['ScriptPeriodical'];
    local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');


    -- web and news not tested for now because of  
    -- web and news not tested for now because of  
    Line 2,553: Line 2,522:
    end
    end
    end
    end
    local TransPeriodical =  A['TransPeriodical'];
    local TransPeriodical_origin =  A:ORIGIN ('TransPeriodical');
    local Series = A['Series'];
    local Volume;
    local Volume;
    local Issue;
    local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
    local Page;
    local Pages;
    local At;
     
    if 'citation' == config.CitationClass then
    if 'citation' == config.CitationClass then
    if utilities.is_set (Periodical) then
    if utilities.is_set (Periodical) then
    Line 2,580: Line 2,540:
    Volume = A['Volume'];
    Volume = A['Volume'];
    end
    end
    extra_text_in_vol_iss_check (Volume, A:ORIGIN ('Volume'), 'v');


    local Issue;
    if 'citation' == config.CitationClass then
    if 'citation' == config.CitationClass then
    if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, {'journal', 'magazine', 'newspaper', 'periodical', 'work'}) or -- {{citation}} renders issue for these 'periodicals'
    if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, {'journal', 'magazine', 'newspaper', 'periodical', 'work'}) or -- {{citation}} renders issue for these 'periodicals'
    Line 2,591: Line 2,553:
    end
    end
    end
    end
    extra_text_in_vol_iss_check (Issue, A:ORIGIN ('Issue'), 'i');


    local Position = '';
    local Page;
    local Pages;
    local At;
    if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page) then
    if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page) then
    Page = A['Page'];
    Page = A['Page'];
    Line 2,598: Line 2,563:
    At = A['At'];
    At = A['At'];
    end
    end
    local QuotePage = A['QuotePage'];
    local QuotePages = hyphen_to_dash (A['QuotePages']);


    local Edition = A['Edition'];
    local Edition = A['Edition'];
    Line 2,629: Line 2,592:
    end
    end


    local URL = A['URL']
    local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
    local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
    if not utilities.is_set (URL) and utilities.is_set (UrlAccess) then
    UrlAccess = nil;
    if not utilities.is_set (URL) and utilities.is_set (UrlAccess) then
    table.insert( z.message_tail, { utilities.set_message ( 'err_param_access_requires_param', {'url'}, true ) } );
    UrlAccess = nil;
    end
    table.insert( z.message_tail, { utilities.set_message ( 'err_param_access_requires_param', {'url'}, true ) } );
    end
    local ChapterURL = A['ChapterURL'];
    local ChapterUrlAccess = is_valid_parameter_value (A['ChapterUrlAccess'], A:ORIGIN('ChapterUrlAccess'), cfg.keywords_lists['url-access'], nil);
    local ChapterUrlAccess = is_valid_parameter_value (A['ChapterUrlAccess'], A:ORIGIN('ChapterUrlAccess'), cfg.keywords_lists['url-access'], nil);
    if not utilities.is_set (ChapterURL) and utilities.is_set (ChapterUrlAccess) then
    if not utilities.is_set (ChapterURL) and utilities.is_set (ChapterUrlAccess) then
    Line 2,647: Line 2,613:
    end
    end


    local Via = A['Via'];
    local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
    local AccessDate = A['AccessDate'];
    local Agency = A['Agency'];
     
    local Language = A['Language'];
    local Format = A['Format'];
    local ChapterFormat = A['ChapterFormat'];
    local DoiBroken = A['DoiBroken'];
    local ID = A['ID'];
    local ASINTLD = A['ASINTLD'];
    local Embargo = A['Embargo'];
    local Class = A['Class']; -- arxiv class identifier
     
    local Quote = A['Quote'];
    local ScriptQuote = A['ScriptQuote'];
    local TransQuote = A['TransQuote'];
    local LayFormat = A['LayFormat'];
    local LayURL = A['LayURL'];
    local LaySource = A['LaySource'];
    local Transcript = A['Transcript'];
    local TranscriptFormat = A['TranscriptFormat'];
    local TranscriptURL = A['TranscriptURL']
    local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
     
    local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN('NoTracking'), cfg.keywords_lists['yes_true_y'], nil);
    local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN('NoTracking'), cfg.keywords_lists['yes_true_y'], nil);


    -- local variables that are not cs1 parameters
    -- check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
    local use_lowercase; -- controls capitalization of certain static text
    if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
    local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
    if utilities.in_array (this_page.nsText, cfg.uncategorized_namespaces) then
    local anchor_year; -- used in the CITEREF identifier
    no_tracking_cats = "true"; -- set no_tracking_cats
    local COinS_date = {}; -- holds date info extracted from |date= for the COinS metadata by Module:Date verification
    end
     
    for _, v in ipairs (cfg.uncategorized_subpages) do -- cycle through page name patterns
    local DF = is_valid_parameter_value (A['DF'], A:ORIGIN('DF'), cfg.keywords_lists['df'], '');
    if this_page.text:match (v) then -- test page name against each pattern
    if not utilities.is_set (DF) then
    DF = cfg.global_df; -- local |df= if present overrides global df set by {{use xxx date}} template
    end
     
    local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma
    local PostScript;
    local Ref = A['Ref'];
    if 'harv' == Ref then
    utilities.set_message ('maint_ref_harv'); -- add maint cat to identify templates that have this now-extraneous param value
    elseif not utilities.is_set (Ref) then
    Ref = 'harv'; -- set as default when not set externally
    end
    sepc, PostScript, Ref = set_style (Mode:lower(), A['PostScript'], Ref, config.CitationClass);
    use_lowercase = ( sepc == ',' ); -- used to control capitalization for certain static text
     
    -- check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
    if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
    if utilities.in_array (this_page.nsText, cfg.uncategorized_namespaces) then
    no_tracking_cats = "true"; -- set no_tracking_cats
    end
    for _, v in ipairs (cfg.uncategorized_subpages) do -- cycle through page name patterns
    if this_page.text:match (v) then -- test page name against each pattern
    no_tracking_cats = "true"; -- set no_tracking_cats
    no_tracking_cats = "true"; -- set no_tracking_cats
    break; -- bail out if one is found
    break; -- bail out if one is found
    Line 2,713: Line 2,633:
    local coins_pages;
    local coins_pages;
    Page, Pages, At, coins_pages = insource_loc_get (Page, Pages, At);
    Page, Pages, At, coins_pages = insource_loc_get (Page, A:ORIGIN('Page'), Pages, A:ORIGIN('Pages'), At);


    local NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
    local NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
    Line 2,727: Line 2,647:


    if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
    if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
    local URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
    local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
    local ScriptChapter = A['ScriptChapter'];
    local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
    local Format = A['Format'];
    local ChapterFormat = A['ChapterFormat'];
    local TransChapter = A['TransChapter'];
    local TransChapter_origin = A:ORIGIN ('TransChapter');
    local TransTitle = A['TransTitle'];
    local ScriptTitle = A['ScriptTitle'];
    --[[
    --[[
    Line 2,768: Line 2,699:
    TransChapter = TransTitle;
    TransChapter = TransTitle;
    ChapterURL = URL;
    ChapterURL = URL;
    ChapterURL_origin = A:ORIGIN('URL')
    ChapterURL_origin = URL_origin;


    ChapterUrlAccess = UrlAccess;
    ChapterUrlAccess = UrlAccess;
    Line 2,792: Line 2,723:


    -- special case for cite techreport.
    -- special case for cite techreport.
    local ID = A['ID'];
    if (config.CitationClass == "techreport") then -- special case for cite techreport
    if (config.CitationClass == "techreport") then -- special case for cite techreport
    if utilities.is_set (A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
    if utilities.is_set (A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
    Line 2,803: Line 2,735:


    -- Account for the oddity that is {{cite conference}}, before generation of COinS data.
    -- Account for the oddity that is {{cite conference}}, before generation of COinS data.
    local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
    local Conference = A['Conference'];
    local BookTitle = A['BookTitle'];
    local TransTitle_origin = A:ORIGIN ('TransTitle');
    if 'conference' == config.CitationClass then
    if 'conference' == config.CitationClass then
    if utilities.is_set (BookTitle) then
    if utilities.is_set (BookTitle) then
    Line 2,824: Line 2,760:
    Conference = ''; -- not cite conference or cite speech so make sure this is empty string
    Conference = ''; -- not cite conference or cite speech so make sure this is empty string
    end
    end
     
    -- CS1/2 mode
    local Mode = is_valid_parameter_value (A['Mode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');
    -- separator character and postscript
    local sepc, PostScript = set_style (Mode:lower(), A['PostScript'], config.CitationClass);
    -- controls capitalization of certain static text
    local use_lowercase = ( sepc == ',' );
    -- cite map oddities
    -- cite map oddities
    local Cartography = "";
    local Cartography = "";
    Line 2,856: Line 2,799:


    -- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
    -- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
    local Series = A['Series'];
    if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
    if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
    local SeriesLink = A['SeriesLink'];
    local SeriesLink = A['SeriesLink'];
    Line 2,890: Line 2,834:
    ChapterURL = URL;
    ChapterURL = URL;
    ChapterUrlAccess = UrlAccess;
    ChapterUrlAccess = UrlAccess;
    ChapterURL_origin = A:ORIGIN('URL');
    ChapterURL_origin = URL_origin;
    Title = Series; -- promote series to title
    Title = Series; -- promote series to title
    Line 2,917: Line 2,861:


    -- handle type parameter for those CS1 citations that have default values
    -- handle type parameter for those CS1 citations that have default values
    local TitleType = A['TitleType'];
    local Degree = A['Degree'];
    if utilities.in_array (config.CitationClass, {"AV-media-notes", "interview", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"}) then
    if utilities.in_array (config.CitationClass, {"AV-media-notes", "interview", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"}) then
    TitleType = set_titletype (config.CitationClass, TitleType);
    TitleType = set_titletype (config.CitationClass, TitleType);
    Line 2,930: Line 2,876:


    -- legacy: promote PublicationDate to Date if neither Date nor Year are set.
    -- legacy: promote PublicationDate to Date if neither Date nor Year are set.
    local Date = A['Date'];
      local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
      local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
    local PublicationDate = A['PublicationDate'];
    local Year = A['Year'];


    if not utilities.is_set (Date) then
    if not utilities.is_set (Date) then
    Line 2,954: Line 2,903:
    Date validation supporting code is in Module:Citation/CS1/Date_validation
    Date validation supporting code is in Module:Citation/CS1/Date_validation
    ]]
    ]]
    local DF = is_valid_parameter_value (A['DF'], A:ORIGIN('DF'), cfg.keywords_lists['df'], '');
    if not utilities.is_set (DF) then
    DF = cfg.global_df; -- local |df= if present overrides global df set by {{use xxx date}} template
    end
    local ArchiveURL;
    local ArchiveDate;
    local ArchiveFormat = A['ArchiveFormat'];
    ArchiveURL, ArchiveDate = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
    ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
    ArchiveURL, ArchiveDate = is_unique_archive_url (ArchiveURL, URL, ChapterURL, A:ORIGIN('ArchiveURL'), ArchiveDate); -- add error message when URL or ChapterURL == ArchiveURL
    local AccessDate = A['AccessDate'];
    local LayDate = A['LayDate'];
    local COinS_date = {}; -- holds date info extracted from |date= for the COinS metadata by Module:Date verification
    local DoiBroken = A['DoiBroken'];
    local Embargo = A['Embargo'];
    local anchor_year; -- used in the CITEREF identifier
    do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
    do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
    local error_message = '';
    local error_message = '';
    Line 2,978: Line 2,949:


    if utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;  
    if utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;  
    local mismatch = validation.year_date_check (Year, Date);
    validation.year_date_check (Year, A:ORIGIN ('Year'), Date, A:ORIGIN ('Date'), error_list);
    if 0 == mismatch then -- |year= does not match a year-value in |date=
    table.insert (error_list, '<code class="cs1-code">&#124;year= / &#124;date= mismatch</code>');
    elseif 1 == mismatch then -- |year= matches year-value in |date=
    utilities.set_message ('maint_date_year'); -- add a maint cat
    end
    end
    end
    Line 2,989: Line 2,955:
    local modified = false; -- flag
    local modified = false; -- flag
    if validation.edtf_transform (date_parameters_list) then -- edtf dates to MOS compliant format
    modified = true;
    end
    if utilities.is_set (DF) then -- if we need to reformat dates
    if utilities.is_set (DF) then -- if we need to reformat dates
    modified = validation.reformat_dates (date_parameters_list, DF); -- reformat to DF format, use long month names if appropriate
    modified = validation.reformat_dates (date_parameters_list, DF); -- reformat to DF format, use long month names if appropriate
    Line 3,020: Line 2,990:
    local ID_list = {}; -- sequence table of rendered identifiers
    local ID_list = {}; -- sequence table of rendered identifiers
    local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
    local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
    ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, Embargo = Embargo, Class = Class});
    local Class = A['Class']; -- arxiv class identifier
    local ID_support = {
    {A['ASINTLD'], 'ASIN', 'err_asintld_missing_asin', A:ORIGIN ('ASINTLD')},
    {DoiBroken, 'DOI', 'err_doibroken_missing_doi', A:ORIGIN ('DoiBroken')},
    {Embargo, 'PMC', 'err_embargo_missing_pmc', A:ORIGIN ('Embargo')},
    }


    if utilities.is_set (DoiBroken) and not ID_list_coins['DOI'] then
    ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {DoiBroken = DoiBroken, ASINTLD = A['ASINTLD'], Embargo = Embargo, Class = Class}, ID_support);
    table.insert (z.message_tail, {utilities.set_message ('err_doibroken_missing_doi', A:ORIGIN('DoiBroken'))});
    end


    -- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
    -- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
    Line 3,038: Line 3,012:


    if config.CitationClass == "journal" and not utilities.is_set (URL) and not utilities.is_set (TitleLink) and not utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) then -- TODO: remove 'none' once existing citations have been switched to 'off', so 'none' can be used as token for "no title" instead
    if config.CitationClass == "journal" and not utilities.is_set (URL) and not utilities.is_set (TitleLink) and not utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) then -- TODO: remove 'none' once existing citations have been switched to 'off', so 'none' can be used as token for "no title" instead
        if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
    if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
      if identifiers.auto_link_urls[auto_select] then -- manual selection
      if identifiers.auto_link_urls[auto_select] then -- manual selection
    URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
    URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
      URL_origin = cfg.id_handlers[auto_select:upper()].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
      URL_origin = cfg.id_handlers[auto_select:upper()].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
    elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
    elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
    URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
    URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
    URL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
    URL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
    elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
    elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
    URL = identifiers.auto_link_urls['doi'];
    URL = identifiers.auto_link_urls['doi'];
    URL_origin = cfg.id_handlers['DOI'].parameters[1];
    URL_origin = cfg.id_handlers['DOI'].parameters[1];
    Line 3,095: Line 3,069:
    coins_author = c; -- use that instead
    coins_author = c; -- use that instead
    end
    end
    local QuotePage = A['QuotePage'];
    local QuotePages = hyphen_to_dash (A['QuotePages']);


    -- this is the function call to COinS()
    -- this is the function call to COinS()
    Line 3,130: Line 3,107:
    end
    end


    local Editors;
    local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
    local Contributors; -- assembled contributors name list
    local contributor_etal;
    local Translators; -- assembled translators name list
    local translator_etal;
    local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
    t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
    local Interviewers;
    local interviewers_list = {};
    interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
    local interviewer_etal;
    -- Now perform various field substitutions.
    -- Now perform various field substitutions.
    -- We also add leading spaces and surrounding markup and punctuation to the
    -- We also add leading spaces and surrounding markup and punctuation to the
    -- various parts of the citation, but only when they are non-nil.
    -- various parts of the citation, but only when they are non-nil.
    local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
    do
    do
    local last_first_list;
    local last_first_list;
    Line 3,143: Line 3,132:


    do -- do editor name list first because the now unsupported coauthors used to modify control table
    do -- do editor name list first because the now unsupported coauthors used to modify control table
    control.maximum , editor_etal = get_display_names (A['DisplayEditors'], #e, 'editors', editor_etal);
    control.maximum , editor_etal = get_display_names (A['DisplayEditors'], #e, 'editors', editor_etal, A:ORIGIN ('DisplayEditors'));
    Editors, EditorCount = list_people (control, e, editor_etal);
    Editors, EditorCount = list_people (control, e, editor_etal);


    Line 3,151: Line 3,140:
    end
    end
    do -- now do interviewers
    do -- now do interviewers
    control.maximum , interviewer_etal = get_display_names (A['DisplayInterviewers'], #interviewers_list, 'interviewers', interviewer_etal);
    control.maximum, interviewer_etal = get_display_names (A['DisplayInterviewers'], #interviewers_list, 'interviewers', interviewer_etal, A:ORIGIN ('DisplayInterviewers'));
    Interviewers = list_people (control, interviewers_list, interviewer_etal);
    Interviewers = list_people (control, interviewers_list, interviewer_etal);
    end
    end
    do -- now do translators
    do -- now do translators
    control.maximum , translator_etal = get_display_names (A['DisplayTranslators'], #t, 'translators', translator_etal);
    control.maximum, translator_etal = get_display_names (A['DisplayTranslators'], #t, 'translators', translator_etal, A:ORIGIN ('DisplayTranslators'));
    Translators = list_people (control, t, translator_etal);
    Translators = list_people (control, t, translator_etal);
    end
    end
    do -- now do contributors
    do -- now do contributors
    control.maximum , contributor_etal = get_display_names (A['DisplayContributors'], #c, 'contributors', contributor_etal);
    control.maximum, contributor_etal = get_display_names (A['DisplayContributors'], #c, 'contributors', contributor_etal, A:ORIGIN ('DisplayContributors'));
    Contributors = list_people (control, c, contributor_etal);
    Contributors = list_people (control, c, contributor_etal);
    end
    end
    do -- now do authors
    do -- now do authors
    control.maximum , author_etal = get_display_names (A['DisplayAuthors'], #a, 'authors', author_etal);
    control.maximum, author_etal = get_display_names (A['DisplayAuthors'], #a, 'authors', author_etal, A:ORIGIN ('DisplayAuthors'));


    last_first_list = list_people(control, a, author_etal);
    last_first_list = list_people (control, a, author_etal);


    if utilities.is_set (Authors) then
    if utilities.is_set (Authors) then
    Line 3,183: Line 3,172:
    end
    end


    -- apply |[xx-]format= styling; at the end, these parameters hold correctly styled format annotation,
    local ConferenceFormat = A['ConferenceFormat'];
    -- an error message if the associated URL is not set, or an empty string for concatenation
    local ConferenceURL = A['ConferenceURL'];
    ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
    ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
    ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
    Format = style_format (Format, URL, 'format', 'url');
    Format = style_format (Format, URL, 'format', 'url');
    LayFormat = style_format (LayFormat, LayURL, 'lay-format', 'lay-url');
    TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');


    -- special case for chapter format so no error message or cat when chapter not supported
    -- special case for chapter format so no error message or cat when chapter not supported
    Line 3,210: Line 3,196:
    end
    end


    local OriginalURL, OriginalURL_origin, OriginalFormat, OriginalAccess;
    local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
    local OriginalURL
    local OriginalURL_origin
    local OriginalFormat
    local OriginalAccess;
    UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
    UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
    if utilities.is_set ( ArchiveURL ) then
    if utilities.is_set ( ArchiveURL ) then
    Line 3,377: Line 3,367:
    end
    end


    local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
    if utilities.is_set (Conference) then
    if utilities.is_set (Conference) then
    if utilities.is_set (ConferenceURL) then
    if utilities.is_set (ConferenceURL) then
    Line 3,386: Line 3,377:
    end
    end


    local Position = '';
    if not utilities.is_set (Position) then
    if not utilities.is_set (Position) then
    local Minutes = A['Minutes'];
    local Minutes = A['Minutes'];
    Line 3,432: Line 3,424:
    end
    end


    if utilities.is_set (Language) then
    local Others = A['Others'];
    Language = language_parameter (Language); -- format, categories, name from ISO639-1, etc.
    if utilities.is_set (Others) and 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor=
    else
    if config.CitationClass == "AV-media-notes"
    Language=""; -- language not specified so make sure this is an empty string;
    or config.CitationClass == "audio-visual" then -- special maint for AV/M which has a lot of 'false' positives right now
    --[[ TODO: need to extract the wrap_msg from language_parameter
    utilities.set_message ('maint_others_avm')
    so that we can solve parentheses bunching problem with Format/Language/TitleType
    else
    ]]
    utilities.set_message ('maint_others');
    end
    end
    end
    Others = utilities.is_set (Others) and (sepc .. " " .. Others) or "";
    Others = utilities.is_set (Others) and (sepc .. " " .. Others) or "";
    Line 3,450: Line 3,442:
    end
    end
    local TitleNote = A['TitleNote'];
    TitleNote = utilities.is_set (TitleNote) and (sepc .. " " .. TitleNote) or "";
    TitleNote = utilities.is_set (TitleNote) and (sepc .. " " .. TitleNote) or "";
    if utilities.is_set (Edition) then
    if utilities.is_set (Edition) then
    Line 3,461: Line 3,454:


    Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
    Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
    OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
    local Agency = A['Agency'];
    Agency = utilities.is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
    Agency = utilities.is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
    Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase);
    Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase);
    ------------------------------------ totally unrelated data
    Via = utilities.is_set (Via) and  wrap_msg ('via', Via) or '';


    if utilities.is_set (AccessDate) then
    if utilities.is_set (AccessDate) then
    Line 3,479: Line 3,469:
    if utilities.is_set (ID) then ID = sepc .. " " .. ID; end
    if utilities.is_set (ID) then ID = sepc .. " " .. ID; end
    local Docket = A['Docket'];
       if "thesis" == config.CitationClass and utilities.is_set (Docket) then
       if "thesis" == config.CitationClass and utilities.is_set (Docket) then
    ID = sepc .. " Docket " .. Docket .. ID;
    ID = sepc .. " Docket " .. Docket .. ID;
    Line 3,490: Line 3,482:
    end
    end


    local Quote = A['Quote'];
    local TransQuote = A['TransQuote'];
    local ScriptQuote = A['ScriptQuote'];
    if utilities.is_set (Quote) or utilities.is_set (TransQuote) or utilities.is_set (ScriptQuote) then
    if utilities.is_set (Quote) or utilities.is_set (TransQuote) or utilities.is_set (ScriptQuote) then


    Line 3,498: Line 3,493:
    end
    end


    Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
    Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
    if utilities.is_set (ScriptQuote) then
    if utilities.is_set (ScriptQuote) then
    Quote = script_concatenate (Quote, ScriptQuote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped
    Quote = script_concatenate (Quote, ScriptQuote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped
    end
    end


    Line 3,511: Line 3,506:
    end
    end


    -- if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page)
    if utilities.is_set (QuotePage) or utilities.is_set (QuotePages) then -- add page prefix
    if utilities.is_set (QuotePage) or utilities.is_set (QuotePages) then -- add page prefix
    local quote_prefix = '';
    local quote_prefix = '';
    if utilities.is_set (QuotePage) then
    if utilities.is_set (QuotePage) then
    extra_text_in_page_check (QuotePage); -- add to maint cat if |quote-page= value begins with what looks like p., pp., etc.
    extra_text_in_page_check (QuotePage, 'quote-page'); -- add to maint cat if |quote-page= value begins with what looks like p., pp., etc.
    if not NoPP then
    if not NoPP then
    quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePage}), '', '', '';
    quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePage}), '', '', '';
    Line 3,522: Line 3,516:
    end
    end
    elseif utilities.is_set (QuotePages) then
    elseif utilities.is_set (QuotePages) then
    extra_text_in_page_check (QuotePages); -- add to maint cat if |quote-pages= value begins with what looks like p., pp., etc.
    extra_text_in_page_check (QuotePages, 'quote-pages'); -- add to maint cat if |quote-pages= value begins with what looks like p., pp., etc.
    if tonumber(QuotePages) ~= nil and not NoPP then -- if only digits, assume single page
    if tonumber(QuotePages) ~= nil and not NoPP then -- if only digits, assume single page
    quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePages}), '', '';
    quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePages}), '', '';
    Line 3,538: Line 3,532:


    PostScript = ""; -- cs1|2 does not supply terminal punctuation when |quote= is set
    PostScript = ""; -- cs1|2 does not supply terminal punctuation when |quote= is set
    end
    -- We check length of PostScript here because it will have been nuked by
    -- the quote parameters. We'd otherwise emit a message even if there wasn't
    -- a displayed postscript.
    -- TODO: Should the max size (1) be configurable?
    -- TODO: Should we check a specific pattern?
    if utilities.is_set(PostScript) and mw.ustring.len(PostScript) > 1 then
    utilities.set_message('maint_postscript')
    end
    end
    Line 3,583: Line 3,586:
    local Lay = '';
    local Lay = '';
    local LaySource = A['LaySource'];
    local LayURL = A['LayURL'];
    local LayFormat = A['LayFormat'];
    LayFormat = style_format (LayFormat, LayURL, 'lay-format', 'lay-url');
    if utilities.is_set (LayURL) then
    if utilities.is_set (LayURL) then
    if utilities.is_set (LayDate) then LayDate = " (" .. LayDate .. ")" end
    if utilities.is_set (LayDate) then LayDate = " (" .. LayDate .. ")" end
    Line 3,599: Line 3,606:
    end
    end


    local TranscriptURL = A['TranscriptURL']
    local TranscriptFormat = A['TranscriptFormat'];
    TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
    local Transcript = A['Transcript'];
    local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
    if utilities.is_set (Transcript) then
    if utilities.is_set (Transcript) then
    if utilities.is_set (TranscriptURL) then
    if utilities.is_set (TranscriptURL) then
    Line 3,624: Line 3,636:
    end
    end
    local TransPeriodical =  A['TransPeriodical'];
    local TransPeriodical_origin =  A:ORIGIN ('TransPeriodical');
    -- Several of the above rely upon detecting this as nil, so do it last.
    -- Several of the above rely upon detecting this as nil, so do it last.
    if (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical) or utilities.is_set (TransPeriodical)) then
    if (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical) or utilities.is_set (TransPeriodical)) then
    Line 3,631: Line 3,645:
    Periodical = format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin);
    Periodical = format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin);
    end
    end
    end
    local Language = A['Language'];
    if utilities.is_set (Language) then
    Language = language_parameter (Language); -- format, categories, name from ISO639-1, etc.
    else
    Language=''; -- language not specified so make sure this is an empty string;
    --[[ TODO: need to extract the wrap_msg from language_parameter
    so that we can solve parentheses bunching problem with Format/Language/TitleType
    ]]
    end
    end


    Line 3,687: Line 3,711:
    end
    end
    local Via = A['Via'];
    Via = utilities.is_set (Via) and  wrap_msg ('via', Via) or '';
    local idcommon;
    local idcommon;
    if 'audio-visual' == config.CitationClass or 'episode' == config.CitationClass then -- special case for cite AV media & cite episode position transcript
    if 'audio-visual' == config.CitationClass or 'episode' == config.CitationClass then -- special case for cite AV media & cite episode position transcript
    Line 3,697: Line 3,723:
    local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;
    local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;


    local OrigDate = A['OrigDate'];
    OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
    if utilities.is_set (Date) then
    if utilities.is_set (Date) then
    if utilities.is_set (Authors) or utilities.is_set (Editors) then -- date follows authors or editors when authors not set
    if utilities.is_set (Authors) or utilities.is_set (Editors) then -- date follows authors or editors when authors not set
    Line 3,772: Line 3,800:
    text = safe_join( {text, PostScript}, sepc );
    text = safe_join( {text, PostScript}, sepc );


    -- Now enclose the whole thing in a <cite/> element
    -- Now enclose the whole thing in a <cite> element
    local options = {};
    local options = {};
    Line 3,780: Line 3,808:
    options.class = string.format ('%s %s', 'citation', utilities.is_set (Mode) and Mode or 'cs2');
    options.class = string.format ('%s %s', 'citation', utilities.is_set (Mode) and Mode or 'cs2');
    end
    end
     
    if utilities.is_set (Ref) and 'none' ~= cfg.keywords_xlate[Ref:lower()] then
    local Ref = A['Ref'];
    if 'harv' == Ref then -- need to check this before setting to default
    utilities.set_message ('maint_ref_harv'); -- add maint cat to identify templates that have this now-extraneous param value
    elseif not utilities.is_set (Ref) then
    Ref = 'harv'; -- set as default when not set externally
    end
    if 'none' ~= cfg.keywords_xlate[Ref:lower()] then
    local id = Ref
    local id = Ref
    if ('harv' == Ref ) then
    local namelist = {}; -- holds selected contributor, author, editor name list
    local namelist = {}; -- holds selected contributor, author, editor name list
    local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation
    local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation


    if #c > 0 then -- if there is a contributor list
    if #c > 0 then -- if there is a contributor list
    namelist = c; -- select it
    namelist = c; -- select it
    elseif #a > 0 then -- or an author list
    elseif #a > 0 then -- or an author list
    namelist = a;
    namelist = a;
    elseif #e > 0 then -- or an editor list
    elseif #e > 0 then -- or an editor list
    namelist = e;
    namelist = e;
    end
    end
    if #namelist > 0 then -- if there are names in namelist
    local citeref_id
    id = anchor_id (namelist, year); -- go make the CITEREF anchor
    if #namelist > 0 then -- if there are names in namelist
    else
    citeref_id = make_citeref_id (namelist, year); -- go make the CITEREF anchor
    id = ''; -- unset
    else
    end
    citeref_id = ''; -- unset
    end
    if citeref_id == Ref then
    utilities.set_message ('maint_ref_duplicates_default');
    end
    if 'harv' == Ref then
    id = citeref_id
    end
    end
    options.id = id;
    options.id = id;
    Line 3,879: Line 3,918:
    if true == state then return true; end -- valid actively supported parameter
    if true == state then return true; end -- valid actively supported parameter
    if false == state then
    if false == state then
    if empty then return nil; end -- deprecated empty parameters are treated as unknowns
    if empty then return nil; end -- empty deprecated parameters are treated as unknowns
    deprecated_parameter (name); -- parameter is deprecated but still supported
    deprecated_parameter (name); -- parameter is deprecated but still supported
    return true;
    end
    if 'discouraged' == state then
    discouraged_parameter (name); -- parameter is discouraged but still supported
    return true;
    return true;
    end
    end
    Line 4,071: Line 4,114:
    error_text, error_state = utilities.set_message ('err_parameter_ignored_suggest', {k, param}, true); -- set the suggestion error message
    error_text, error_state = utilities.set_message ('err_parameter_ignored_suggest', {k, param}, true); -- set the suggestion error message
    else
    else
    error_text, error_state = utilities.set_message ( 'err_parameter_ignored', {param}, true ); -- suggested param not supported by this template
    error_text, error_state = utilities.set_message ( 'err_parameter_ignored', {k}, true ); -- suggested param not supported by this template
    v = ''; -- unset
    v = ''; -- unset
    end
    end
    end
    end
    end
    end
    if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an explicit suggestion?
    if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an explicit suggestion?
    if suggestions.suggestions[ k:lower() ] ~= nil then
    if (suggestions.suggestions[ k:lower() ] ~= nil) and validate (suggestions.suggestions[ k:lower() ], config.CitationClass) then
    error_text, error_state = utilities.set_message ( 'err_parameter_ignored_suggest', {k, suggestions.suggestions[ k:lower() ]}, true );
    error_text, error_state = utilities.set_message ( 'err_parameter_ignored_suggest', {k, suggestions.suggestions[ k:lower() ]}, true );
    else
    else