Module:Citation/CS1: Difference between revisions

    m>Waldir
    m (fix link to redirect)
    m>Trappist the monk
    (Synch from sandbox;)
    Line 13: Line 13:


    local cfg = {}; -- table of configuration tables that are defined in Module:Citation/CS1/Configuration
    local cfg = {}; -- table of configuration tables that are defined in Module:Citation/CS1/Configuration
    local whitelist = {}; -- talbe 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


    --[[--------------------------< I S _ S E T >------------------------------------------------------------------
    --[[--------------------------< I S _ S E T >------------------------------------------------------------------
    Line 119: Line 119:


    Adds a category to z.maintenance_cats using names from the configuration file with additional text if any.
    Adds a category to z.maintenance_cats using names from the configuration file with additional text if any.
    To prevent duplication, the added_maint_cats table lists the categories by key that have been added to z.maintenance_cats.


    ]]
    ]]


    local added_maint_cats = {} -- list of maintenance categories that have been added to z.maintenance_cats
    local function add_maint_cat (key, arguments)
    local function add_maint_cat (key, arguments)
    table.insert( z.maintenance_cats, substitute (cfg.maint_cats [key], arguments)); -- make name then add to table
    if not added_maint_cats [key] then
    added_maint_cats [key] = true; -- note that we've added this category
    table.insert( z.maintenance_cats, substitute (cfg.maint_cats [key], arguments)); -- make name then add to table
    end
    end
    end


    Line 132: Line 137:
    ]]
    ]]


    local added_prop_cats = {} -- list of property categories that have been added to z.properties_cats
    local function add_prop_cat (key, arguments)
    local function add_prop_cat (key, arguments)
    table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table
    if not added_prop_cats [key] then
    added_prop_cats [key] = true; -- note that we've added this category
    table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table
    end
    end
     
    --[[--------------------------< A D D _ V A N C _ E R R O R >----------------------------------------------------
     
    Adds a single Vancouver system error message to the template's output regardless of how many error actually exist.
    To prevent duplication, added_vanc_errs is nil until an error message is emitted.
     
    ]]
     
    local added_vanc_errs; -- flag so we only emit one Vancouver error / category
    local function add_vanc_error ()
    if not added_vanc_errs then
    added_vanc_errs = true; -- note that we've added this category
    table.insert( z.message_tail, { set_error( 'vancouver', {}, true ) } );
    end
    end
    end


    Line 246: Line 270:
    end
    end


    --[[
    --[[--------------------------< D E P R E C A T E D _ P A R A M E T E R >--------------------------------------
    Categorize and emit an error message when the citation contains one or more deprecated parameters.  Because deprecated parameters (currently |month=,
     
    |coauthor=, and |coauthors=) aren't related to each other and because these parameters may be concatenated into the variables used by |date= and |author#= (and aliases)
    Categorize and emit an error message when the citation contains one or more deprecated parameters.  The function includes the
    details of which parameter caused the error message are not provided.  Only one error message is emitted regardless of the number of deprecated parameters in the citation.
    offending parameter name to the error message.  Only one error message is emitted regardless of the number of deprecated
    parameters in the citation.
     
    ]]
    ]]
    local page_in_deprecated_cat; -- sticky flag so that the category is added only once
    local function deprecated_parameter(name)
    local function deprecated_parameter(name)
    if true ~= Page_in_deprecated_cat then -- if we haven't been here before then set a
    if not page_in_deprecated_cat then
    Page_in_deprecated_cat=true; -- sticky flag so that if there are more than one deprecated parameter the category is added only once
    page_in_deprecated_cat = true; -- note that we've added this category
    table.insert( z.message_tail, { set_error( 'deprecated_params', {name}, true ) } ); -- add error message
    table.insert( z.message_tail, { set_error( 'deprecated_params', {name}, true ) } ); -- add error message
    end
    end
    Line 610: Line 638:
    end
    end


    --[[--------------------------< IS _ V A L I D _ I S X N  _ 1 3 >----------------------------------------------
    ISBN-13 and ISMN validator code calculates checksum across all 13 isbn/ismn digits including the check digit.
    If the number is valid, the result will be 0. Before calling this function, isbn-13/ismn must be checked for length
    and stripped of dashes, spaces and other non-isxn-13 characters.
    ]]
    local function is_valid_isxn_13 (isxn_str)
    local temp=0;
    isxn_str = { isxn_str:byte(1, 13) }; -- make a table of byte values '0' → 0x30 .. '9'  → 0x39
    for i, v in ipairs( isxn_str ) do
    temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) ); -- multiply odd index digits by 1, even index digits by 3 and sum; includes check digit
    end
    return temp % 10 == 0; -- sum modulo 10 is zero when isbn-13/ismn is correct
    end


    --[[--------------------------< C H E C K _ I S B N >------------------------------------------------------------
    --[[--------------------------< C H E C K _ I S B N >------------------------------------------------------------
    Line 631: Line 677:
    else
    else
    local temp = 0;
    local temp = 0;
    if isbn_str:match( "^97[89]%d*$" ) == nil then return false; end -- isbn13 begins with 978 or 979
    if isbn_str:match( "^97[89]%d*$" ) == nil then return false; end -- isbn13 begins with 978 or 979; ismn begins with 979
    isbn_str = { isbn_str:byte(1, len) };
    return is_valid_isxn_13 (isbn_str);
    for i, v in ipairs( isbn_str ) do
    temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) );
    end
    return temp % 10 == 0;
    end
    end
    end
    end


    --[[--------------------------< I S S N >----------------------------------------------------------------------
    --[[--------------------------< C H E C K _ I S M N >------------------------------------------------------------


    Validate and format an issnThis code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four
    Determines whether an ISMN string is validSimilar to isbn-13, ismn is 13 digits begining 979-0-... and uses the
    digits with a spaceWhen that condition occurred, the resulting link looked like this:
    same check digit calculationsSee http://www.ismn-international.org/download/Web_ISMN_Users_Manual_2008-6.pdf
     
    section 2, pages 9–12.
    |issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327]  -- can't have spaces in an external link
    This code now prevents that by inserting a hyphen at the issn midpoint. It also validates the issn for length and makes sure that the checkdigit agrees
    with the calculated value.  Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn
    error message.  The issn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits.


    ]]
    ]]


    local function issn(id)
    local function ismn (id)
    local issn_copy = id; -- save a copy of unadulterated issn; use this version for display if issn does not validate
    local handler = cfg.id_handlers['ISMN'];
    local handler = cfg.id_handlers['ISSN'];
    local text;
    local text;
    local valid_issn = true;
    local valid_ismn = true;


    id=id:gsub( "[%s-–]", "" ); -- strip spaces, hyphens, and endashes from the issn
    id=id:gsub( "[%s-–]", "" ); -- strip spaces, hyphens, and endashes from the ismn
     
    if 13 ~= id:len() or id:match( "^9790%d*$" ) == nil then -- ismn must be 13 digits and begin 9790
    valid_ismn = false;
    else
    valid_ismn=is_valid_isxn_13 (id); -- validate ismn
    end
     
    -- text = internal_link_id({link = handler.link, label = handler.label, -- use this (or external version) when there is some place to link to
    -- prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})
    text="[[" .. handler.link .. "|" .. handler.label .. "]]:" .. handler.separator .. id; -- because no place to link to yet
     
    if false == valid_ismn then
    text = text .. ' ' .. set_error( 'bad_ismn' ) -- add an error message if the issn is invalid
    end
    return text;
    end
     
    --[[--------------------------< I S S N >----------------------------------------------------------------------
     
    Validate and format an issn.  This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four
    digits with a space.  When that condition occurred, the resulting link looked like this:
     
    |issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327]  -- can't have spaces in an external link
    This code now prevents that by inserting a hyphen at the issn midpoint.  It also validates the issn for length and makes sure that the checkdigit agrees
    with the calculated value.  Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn
    error message.  The issn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits.
     
    ]]
     
    local function issn(id)
    local issn_copy = id; -- save a copy of unadulterated issn; use this version for display if issn does not validate
    local handler = cfg.id_handlers['ISSN'];
    local text;
    local valid_issn = true;
     
    id=id:gsub( "[%s-–]", "" ); -- strip spaces, hyphens, and endashes from the issn


    if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then -- validate the issn: 8 digits long, containing only 0-9 or X in the last position
    if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then -- validate the issn: 8 digits long, containing only 0-9 or X in the last position
    Line 1,281: Line 1,356:
    |firstn= also allowed to contain hyphens, spaces, apostrophes, and periods
    |firstn= also allowed to contain hyphens, spaces, apostrophes, and periods


    At the time of this writing, I had to write the 'if nil == mw.ustring.find ...' test ouside of the code editor and past it here
    At the time of this writing, I had to write the 'if nil == mw.ustring.find ...' test ouside of the code editor and paste it here
    because the code editor gets confused between character insertion point and cursor position.
    because the code editor gets confused between character insertion point and cursor position.


    Line 1,288: Line 1,363:
    local function is_good_vanc_name (last, first)
    local function is_good_vanc_name (last, first)
    if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]*$") then
    if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]*$") then
    if true ~= Page_in_vanc_error_cat then -- if we haven't been here before then set a sticky flag
    add_vanc_error ();
    Page_in_vanc_error_cat=true; -- so that if there are more than one error the category is added only once
    table.insert( z.message_tail, { set_error( 'vancouver', {}, true ) } );
    end
    return false; -- not a string of latin characters; Vancouver required Romanization
    return false; -- not a string of latin characters; Vancouver required Romanization
    end;
    end;
    Line 1,369: Line 1,441:
    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 is_good_vanc_name (one, first) then -- and name is all Latin characters
    if not person.corporate and is_good_vanc_name (one, first) 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) -- attempt to convert first name(s) to initials
    end
    end
    end
    end
    Line 1,376: Line 1,448:
    end
    end
    if is_set(person.link) and person.link ~= control.page_name then
    if is_set(person.link) and person.link ~= control.page_name then
    one = "[[" .. person.link .. "|" .. one .. "]]" -- link author/editor if this page is not the author's/editor's page
    one = "[[" .. person.link .. "|" .. one .. "]]" -- link author/editor if this page is not the author's/editor's page
    end
    end


    if is_set(person.link) and ((nil ~= person.link:find("//")) or (nil ~= person.link:find("[%[%]]"))) then
    if is_set(person.link) and ((nil ~= person.link:find("//")) or (nil ~= person.link:find("[%[%]]"))) then
    one = one .. " " .. set_error( 'bad_authorlink' ) end -- url or wikilink in author link;
    one = one .. " " .. set_error( 'bad_authorlink' ) end -- url or wikilink in author link;
    end
    end
    table.insert( text, one )
    table.insert( text, one )
    Line 1,387: Line 1,459:
    end
    end


    local count = #text / 2;
    local count = #text / 2; -- (number of names + number of separators) divided by 2
    if count > 0 then  
    if count > 0 then  
    if count > 1 and is_set(lastauthoramp) and not etal then
    if count > 1 and is_set(lastauthoramp) and not etal then
    text[#text-2] = " & ";
    text[#text-2] = " & "; -- replace last separator with ampersand text
    end
    end
    text[#text] = nil;  
    text[#text] = nil; -- erase the last separator
    end
    end
    local result = table.concat(text) -- construct list
    local result = table.concat(text) -- construct list
    if etal then  
    if etal and is_set (result) then -- etal may be set by |display-authors=etal but we might not have a last-first list
    local etal_text = cfg.messages['et al'];
    result = result .. ' ' .. cfg.messages['et al']; -- we've go a last-first list and etal so add et al.
    result = result .. " " .. etal_text;
    end
    end
    Line 1,419: Line 1,490:
    end
    end


    --[[--------------------------< E X T R A C T _ N A M E S >----------------------------------------------------
    --[[--------------------------< N A M E _ H A S _ E T A L >----------------------------------------------------
    Gets name list from the input arguments
     
    Evaluates the content of author and editor name parameters for variations on the theme eof et al.  If found,
    the et al. is removed, a flag is set to true and the function returns the modified name and the flag.


    Searches through args in sequential order to find |lastn= and |firstn= parameters (or their aliases), and their matching link and mask parameters.
    This function never sets the flag to false but returns it's previous state because it may have been set by
    previous passes through this function or by the parameters |display-authors=etal or |displayeditors=etal
     
    ]]
     
    local function name_has_etal (name, etal, nocat)
     
    if is_set (name) then -- name can be nil in which case just return
    local pattern = "[;,]? *[\"']*%f[Ee][Ee][Tt] *[Aa][Ll][%.\"']*$" -- variations on the 'et al' theme
    if name:match (pattern) then -- variants on et al.
    name = name:gsub (pattern, ''); -- if found, remove
    etal = true; -- set flag (may have been set previously here or by |display-authors=etal)
    if not nocat then -- no categorization for |vauthors=
    add_maint_cat ('etal'); -- and add a category if not already added
    end
    end
    end
    return name, etal; --
    end
     
    --[[--------------------------< E X T R A C T _ N A M E S >----------------------------------------------------
    Gets name list from the input arguments
     
    Searches through args in sequential order to find |lastn= and |firstn= parameters (or their aliases), and their matching link and mask parameters.
    Stops searching when both |lastn= and |firstn= are not found in args after two sequential attempts: found |last1=, |last2=, and |last3= but doesn't
    Stops searching when both |lastn= and |firstn= are not found in args after two sequential attempts: found |last1=, |last2=, and |last3= but doesn't
    find |last4= and |last5= then the search is done.
    find |last4= and |last5= then the search is done.
    Line 1,445: Line 1,542:
    local count = 0; -- used to count the number of times we haven't found a |last= (or alias for authors, |editor-last or alias for editors)
    local count = 0; -- used to count the number of times we haven't found a |last= (or alias for authors, |editor-last or alias for editors)
    local etal=false; -- return value set to true when we find some form of et al. in an author parameter
    local etal=false; -- return value set to true when we find some form of et al. in an author parameter
    local pattern = ",? *'*[Ee][Tt] *[Aa][Ll][%.']*$" -- variations on the 'et al' theme
     
    local err_msg_list_name = list_name:match ("(%w+)List") .. 's list'; -- modify AuthorList or EditorList for use in error messages if necessary
    local err_msg_list_name = list_name:match ("(%w+)List") .. 's list'; -- modify AuthorList or EditorList for use in error messages if necessary


    Line 1,455: Line 1,551:
    mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
    mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );


    local name = tostring(last);
    last, etal = name_has_etal (last, etal, false); -- find and remove variations on et al.
    if name:match (pattern) then -- varients on et al.
    first, etal = name_has_etal (first, etal, false); -- find and remove variations on et al.
    last = name:gsub (pattern, ''); -- if found, remove
    etal = true;
    end
    name = tostring(first);
    if name:match (pattern) then -- varients on et al.
    first = name:gsub (pattern, ''); -- if found, remove
    etal = true;
    end


    if first and not last then -- if there is a firstn without a matching lastn
    if first and not last then -- if there is a firstn without a matching lastn
    Line 1,470: Line 1,558:
    elseif not first and not last then -- if both firstn and lastn aren't found, are we done?
    elseif not first and not last then -- if both firstn and lastn aren't found, are we done?
    count = count + 1; -- number of times we haven't found last and first
    count = count + 1; -- number of times we haven't found last and first
    if 2 == count then -- two missing names and we give up
    if 2 <= count then -- two missing names and we give up
    break; -- normal exit or there is a two-name hole in the list; can't tell which
    break; -- normal exit or there is a two-name hole in the list; can't tell which
    end
    end
    else -- we have last with or without a first
    else -- we have last with or without a first
    names[n] = {last = last, first = first, link = link, mask = mask}; -- add this name to our names list
    names[n] = {last = last, first = first, link = link, mask = mask, corporate=false}; -- add this name to our names list (corporate for |vauthors= only)
    n = n + 1; -- point to next location in the names table
    n = n + 1; -- point to next location in the names table
    if 1 == count then -- if the previous name was missing
    if 1 == count then -- if the previous name was missing
    Line 1,484: Line 1,572:
    end
    end
    if true == etal then
    add_maint_cat ('etal');
    end
    return names, etal; -- all done, return our list of names
    return names, etal; -- all done, return our list of names
    end
    end
    Line 1,535: Line 1,620:
    elseif k == 'PMID' then
    elseif k == 'PMID' then
    table.insert( new_list, {handler.label, pmid( v ) } );
    table.insert( new_list, {handler.label, pmid( v ) } );
    elseif k == 'ISMN' then
    table.insert( new_list, {handler.label, ismn( v ) } );
    elseif k == 'ISSN' then
    elseif k == 'ISSN' then
    table.insert( new_list, {handler.label, issn( v ) } );
    table.insert( new_list, {handler.label, issn( v ) } );
    Line 1,707: Line 1,794:
    ]]
    ]]


    local function language_parameter (lang, namespace)
    local function language_parameter (lang)
    local code; -- the ISO639-1 two character code
    local code; -- the ISO639-1 two character code
    local name; -- the language name
    local name; -- the language name
    local language_list = {}; -- table of language names to be rendered
    local language_list = {}; -- table of language names to be rendered
    local names_table = {}; -- table made from the value assigned to |language=
    local names_table = {}; -- table made from the value assigned to |language=
    local unrec_cat = false -- flag so that we only add unrecognized category once
     
    names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list
    names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list
     
    for _, lang in ipairs (names_table) do -- reuse lang
    for _, lang in ipairs (names_table) do -- reuse lang
    if 0 == namespace and (('en' == lang:lower()) or ('english' == lang:lower())) then
     
    add_maint_cat ('english');
    end
    if 2 == lang:len() then -- ISO639-1 language code are 2 characters (fetchLanguageName also supports 3 character codes)
    if 2 == lang:len() then -- ISO639-1 language code are 2 characters (fetchLanguageName also supports 3 character codes)
    name = mw.language.fetchLanguageName( lang:lower(), "en" ); -- get ISO 639-1 language name if Language is a proper code
    name = mw.language.fetchLanguageName( lang:lower(), "en" ); -- get ISO 639-1 language name if Language is a proper code
    Line 1,733: Line 1,816:
    if is_set (code) then
    if is_set (code) then
    if 'no' == code then name = 'Norwegian' end; -- override wikimedia when code is 'no'
    if 'no' == code then name = 'Norwegian' end; -- override wikimedia when code is 'no'
    if 0 == namespace and 'en' ~= code then -- is this page main / article space and English not the language?
    if 'en' ~= code then -- English not the language
    add_prop_cat ('foreign_lang_source', {name, code})
    add_prop_cat ('foreign_lang_source', {name, code})
    end
    end
    elseif false == unrec_cat then
    else
    unrec_cat = true; -- only add this category once
    add_maint_cat ('unknown_lang'); -- add maint category if not already added
    add_maint_cat (unknown_lang);
    end
    end
    Line 1,752: Line 1,834:
    name = table.concat (language_list, ', ') -- and concatenate with '<comma><space>' separators
    name = table.concat (language_list, ', ') -- and concatenate with '<comma><space>' separators
    end
    end
    return (" " .. wrap_msg ('language', name)); -- wrap with '(in ...)'
    if 'English' == name then
    return ''; -- if one language and that language is English return an enpty string (no annotation)
    end
    return (" " .. wrap_msg ('language', name)); -- otherwise wrap with '(in ...)'
    end
    end


    Line 1,852: Line 1,937:
    local function style_format (format, url, fmt_param, url_param)
    local function style_format (format, url, fmt_param, url_param)
    if is_set (format) then
    if is_set (format) then
    format = wrap_style ('format', format:upper()); -- force upper case, add leading space, parenthases, resize
    format = wrap_style ('format', format); -- add leading space, parenthases, resize
    if not is_set (url) then
    if not is_set (url) then
    format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message
    format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message
    Line 1,901: Line 1,986:
    end
    end


    --[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
    --[[--------------------------< E X T R A _ T E X T _ I N _ P A G E _ C H E C K >------------------------------
     
    Adds page to Category:CS1 maint: extra text if |page= or |pages= has what appears to be some form of p. or pp.
    abbreviation in the first characters of the parameter content.


    This is the main function doing the majority of the citation
    check Page and Pages for extraneous p, p., pp, and pp. at start of parameter value:
    formatting.
    good pattern: '^P[^%.P%l]' matches when |page(s)= begins PX or P# but not Px where x and X are letters and # is a dgiit
    bad pattern: '^[Pp][Pp]' matches matches when |page(s)= begins pp or pP or Pp or PP


    ]]
    ]]


    local function citation0( config, args)
    local function extra_text_in_page_check (page, nopp)
    --[[
    -- local good_pattern = '^P[^%.P%l]';
    Load Input Parameters
    local good_pattern = '^P[^%.Pp]'; -- ok to begin with uppercase P: P7 (pg 7 of section P) but not p123 (page 123) TODO: add Gg for PG or Pg?
    The argument_wrapper facilitates the mapping of multiple
    -- local bad_pattern = '^[Pp][Pp]';
    aliases to single internal variable.
    local bad_pattern = '^[Pp]?[Pp]%.?[ %d]';
    ]]
    local A = argument_wrapper( args );


    local i  
    if is_set (nopp) then -- don't bother checking if |nopp= is set
    local PPrefix = A['PPrefix']
    return;
    local PPPrefix = A['PPPrefix']
    end
    if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end
     
    if not page:match (good_pattern) and (page:match (bad_pattern) or  page:match ('^[Pp]ages?')) then
    -- Pick out the relevant fields from the arguments.  Different citation templates
    add_maint_cat ('extra_text');
    -- define different field names for the same underlying things.
    end
    local Authors = A['Authors'];
    -- if Page:match ('^[Pp]?[Pp]%.?[ %d]') or  Page:match ('^[Pp]ages?[ %d]') or
    local author_etal;
    -- Pages:match ('^[Pp]?[Pp]%.?[ %d]') or  Pages:match ('^[Pp]ages?[ %d]') then
    local a, author_etal = extract_names( args, 'AuthorList' );
    -- add_maint_cat ('extra_text');
     
    -- end
    local Coauthors = A['Coauthors'];
    end
    local Others = A['Others'];
     
    local Editors = A['Editors'];
     
    local editor_etal;
    --[[--------------------------< P A R S E _ V A U T H O R S _ V E D I T O R S >--------------------------------
    local e, editor_etal = extract_names( args, 'EditorList' );
     
     
    This function extracts author / editor names from |vauthors= or |veditors= and finds matching |xxxxor-maskn= and
    local NameListFormat = A['NameListFormat']; -- replaces |author-format= and |editor-format=
    |xxxxor-linkn= in args.  It then returns a table of assembled names just as extract_names() does.
    if is_set (NameListFormat) and ('vanc' ~= NameListFormat) then -- only accepted value for this parameter is 'vanc'
     
    table.insert( z.message_tail, { set_error( 'invalid_param_val', {'name-list-format', NameListFormat}, true ) } ); -- not vanc so add error message
    Author / editor names in |vauthors= or |veditors= must be in Vancouver system style. Corporate or institutional names
    NameListFormat = ''; -- set to empty string
    may sometimes be required and because such names will often fail the is_good_vanc_name() and other format compliance
    end
    tests, are wrapped in doubled paranethese ((corporate name)) to suppress the format tests.
     
     
    local Year = A['Year'];
    ]]
    local PublicationDate = A['PublicationDate'];
     
    local OrigYear = A['OrigYear'];
    local function parse_vauthors_veditors (args, vparam, list_name)
    local Date = A['Date'];
    local names = {}; -- table of names assembled from |vauthors=, |author-maskn=, |author-linkn=
    local LayDate = A['LayDate'];
    local v_name_table = {};
    ------------------------------------------------- Get title data
    local etal = false; -- return value set to true when we find some form of et al. vauthors parameter
    local Title = A['Title'];
    local last, first, link, mask;
    local ScriptTitle = A['ScriptTitle'];
    local corporate = false;
    local BookTitle = A['BookTitle'];
     
    local Conference = A['Conference'];
    vparam, etal = name_has_etal (vparam, etal, true); -- find and remove variations on et al. do not categorize (do it here because et al. might have a period)
    local TransTitle = A['TransTitle'];
    if vparam:find ('%[%[') or vparam:find ('%]%]') then -- no wikilinking vauthors names
    local TitleNote = A['TitleNote'];
    add_vanc_error ();
    local TitleLink = A['TitleLink'];
    end
    local Chapter = A['Chapter'];
    v_name_table = mw.text.split(vparam, "%s*,%s*") -- names are separated by commas
    local ChapterLink = A['ChapterLink']; -- deprecated
     
    local TransChapter = A['TransChapter'];
    for i, v_name in ipairs(v_name_table) do
    local TitleType = A['TitleType'];
    if v_name:match ('^%(%(.+%)%)$') then -- corporate authors are wrapped in doubled parenthese to supress vanc formatting and error detection
    local Degree = A['Degree'];
    first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
    local Docket = A['Docket'];
    last = v_name:match ('^%(%((.+)%)%)$')
    local ArchiveFormat = A['ArchiveFormat'];
    corporate = true;
    local ArchiveURL = A['ArchiveURL'];
    elseif string.find(v_name, "%s") then
    local URL = A['URL']
        lastfirstTable = {}
    local URLorigin = A:ORIGIN('URL'); -- get name of parameter that holds URL
        lastfirstTable = mw.text.split(v_name, "%s")
    local ChapterURL = A['ChapterURL'];
        first = table.remove(lastfirstTable); -- removes and returns value of last element in table which should be author intials
    local ChapterURLorigin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
        last  = table.concat(lastfirstTable, " ") -- returns a string that is the concatenation of all other names that are not initials
    local ConferenceFormat = A['ConferenceFormat'];
    else
    local ConferenceURL = A['ConferenceURL'];
    first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
    local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
    last = v_name; -- last name or single corporate name?  Doesn't support multiword corporate names? do we need this?
    local Periodical = A['Periodical'];
    end
    if is_set (first) and not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
    add_vanc_error ();
    end
    -- this from extract_names ()
    link = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i );
    mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
    names[i] = {last = last, first = first, link = link, mask = mask, corporate=corporate}; -- add this assembled name to our names list
    end
    return names, etal; -- all done, return our list of names
    end
     
    --[[--------------------------< S E L E C T _ A U T H O R _ E D I T O R _ S O U R C E >------------------------
     
    Select one of |authors=, |authorn= / |lastn / firstn=, or |vauthors= as the source of the author name list or
    select one of |editors=, |editorn= / editor-lastn= / |editor-firstn= or |veditors= as the source of the editor name list.
     
    Only one of these appropriate three will be used.  The hierarchy is: |authorn= (and aliases) highest and |authors= lowest and
    similarly, |editorn= (and aliases) highest and |editors= lowest
     
    When looking for |authorn= / |editorn= parameters, test |xxxxor1= and |xxxxor2= (and all of their aliases); stops after the second
    test which mimicks the test used in extract_names() when looking for a hole in the author name list.  There may be a better
    way to do this, I just haven't discovered what that way is.
     
    Emits an error message when more than one xxxxor name source is provided.
     
    In this function, vxxxxors = vauthors or veditors; xxxxors = authors or editors as appropriate.
     
    ]]
     
    local function select_author_editor_source (vxxxxors, xxxxors, args, list_name)
    local lastfirst = false;
    if select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', 1 ) or -- do this twice incase we have a first 1 without a last1
    select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', 2 ) then
    lastfirst=true;
    end
     
    if (is_set (vxxxxors) and true == lastfirst) or -- these are the three error conditions
    (is_set (vxxxxors) and is_set (xxxxors)) or
    (true == lastfirst and is_set (xxxxors)) then
    local err_name;
    if 'AuthorList' == list_name then -- figure out which name should be used in error message
    err_name = 'author';
    else
    err_name = 'editor';
    end
    table.insert( z.message_tail, { set_error( 'redundant_parameters',
    {err_name .. '-name-list parameters'}, true ) } ); -- add error message
    end
     
    if true == lastfirst then return 1 end; -- return a number indicating which author name source to use
    if is_set (vxxxxors) then return 2 end;
    if is_set (xxxxors) then return 3 end;
    return 0; -- no authors so return 0
    end
     
    --[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
     
    This is the main function doing the majority of the citation formatting.
     
    ]]
     
    local function citation0( config, args)
    --[[
    Load Input Parameters
    The argument_wrapper facilitates the mapping of multiple aliases to single internal variable.
    ]]
    local A = argument_wrapper( args );
     
    local i  
    local PPrefix = A['PPrefix']
    local PPPrefix = A['PPPrefix']
    local NoPP = A['NoPP']  
    if in_array(NoPP:lower(), {'yes', 'true', 'y'}) then
    PPPrefix = ''; -- unset these, prefix if used is in |page= or |pages=
    PPrefix = '';
    else
    NoPP = nil; -- unset, used as a flag later
    end
     
    -- Pick out the relevant fields from the arguments.  Different citation templates
    -- define different field names for the same underlying things.
    local author_etal;
    local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors=
    local Authors;
    local NameListFormat = A['NameListFormat']; -- replaces |author-format= and |editor-format=
     
    do -- to limit scope of selected
    local selected = select_author_editor_source (A['Vauthors'], A['Authors'], args, 'AuthorList');
    if 1 == selected then
    a, author_etal = extract_names (args, 'AuthorList'); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn=
    elseif 2 == selected then
    NameListFormat = 'vanc'; -- override whatever |name-list-format= might be
    a, author_etal = parse_vauthors_veditors (args, args.vauthors, 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn=
    elseif 3 == selected then
    Authors = A['Authors']; -- use content of |authors=
    end
    end
     
    local Coauthors = A['Coauthors'];
    local Others = A['Others'];
     
    local editor_etal;
    local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
    local Editors;
     
    do -- to limit scope of selected
    local selected = select_author_editor_source (A['Veditors'], A['Editors'], args, 'EditorList');
    if 1 == selected then
    e, editor_etal = extract_names (args, 'EditorList'); -- fetch editor list from |editorn= / |editor-lastn= / |editor-firstn=, |editor-linkn=, and |editor-maskn=
    elseif 2 == selected then
    NameListFormat = 'vanc'; -- override whatever |name-list-format= might be
    e, editor_etal = parse_vauthors_veditors (args, args.veditors, 'EditorList'); -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn=
    elseif 3 == selected then
    Editors = A['Editors']; -- use content of |editors=
    end
    end
     
    if is_set (NameListFormat) and ('vanc' ~= NameListFormat) then -- only accepted value for this parameter is 'vanc'
    table.insert( z.message_tail, { set_error( 'invalid_param_val', {'name-list-format', NameListFormat}, true ) } ); -- not vanc so add error message
    NameListFormat = ''; -- set to empty string
    end
     
    local Year = A['Year'];
    local PublicationDate = A['PublicationDate'];
    local OrigYear = A['OrigYear'];
    local Date = A['Date'];
    local LayDate = A['LayDate'];
    ------------------------------------------------- Get title data
    local Title = A['Title'];
    local ScriptTitle = A['ScriptTitle'];
    local BookTitle = A['BookTitle'];
    local Conference = A['Conference'];
    local TransTitle = A['TransTitle'];
    local TitleNote = A['TitleNote'];
    local TitleLink = A['TitleLink'];
    local Chapter = A['Chapter'];
    local ChapterLink = A['ChapterLink']; -- deprecated but used internally by cite episode
    local TransChapter = A['TransChapter'];
    local TitleType = A['TitleType'];
    local Degree = A['Degree'];
    local Docket = A['Docket'];
    local ArchiveFormat = A['ArchiveFormat'];
    local ArchiveURL = A['ArchiveURL'];
    local URL = A['URL']
    local URLorigin = A:ORIGIN('URL'); -- get name of parameter that holds URL
    local ChapterURL = A['ChapterURL'];
    local ChapterURLorigin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
    local ConferenceFormat = A['ConferenceFormat'];
    local ConferenceURL = A['ConferenceURL'];
    local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
    local Periodical = A['Periodical'];


    local Series = A['Series'];
    local Series = A['Series'];
    Line 2,048: Line 2,287:
    if is_set(Page) then
    if is_set(Page) then
    if is_set(Pages) or is_set(At) then
    if is_set(Pages) or is_set(At) then
    Page = Page .. " " .. set_error('extra_pages'); -- add error message
    Page = Page .. " " .. set_error('extra_pages'); -- add error message
    Pages = ''; -- unset the others
    Pages = ''; -- unset the others
    At = '';
    At = '';
    end
    end
    extra_text_in_page_check (Page, NoPP); -- add this page to maint cat if |page= value begins with what looks like p. or pp.
    elseif is_set(Pages) then
    elseif is_set(Pages) then
    if is_set(At) then
    if is_set(At) then
    Pages = Pages .. " " .. set_error('extra_pages'); -- add error messages
    Pages = Pages .. " " .. set_error('extra_pages'); -- add error messages
    At = ''; -- unset
    At = ''; -- unset
    end
    end
    extra_text_in_page_check (Pages, NoPP); -- add this page to maint cat if |page= value begins with what looks like p. or pp.
    end
    end


    Line 2,089: Line 2,330:
    TransChapter = TransTitle;
    TransChapter = TransTitle;
    ChapterURL = URL;
    ChapterURL = URL;
    if not is_set (ChapterURL) and is_set (TitleLink) then
    Chapter= '[[' .. TitleLink .. '|' .. Chapter .. ']]';
    end
    Title = Periodical;
    Title = Periodical;
    ChapterFormat = Format;
    ChapterFormat = Format;
    Line 2,095: Line 2,339:
    URL = ''; -- redundant so unset
    URL = ''; -- redundant so unset
    Format = ''; -- redundant so unset
    Format = ''; -- redundant so unset
    TitleLink = ''; -- redundant so unset
    end
    end
    else -- |title not set
    else -- |title not set
    Line 2,268: Line 2,513:
    if not is_set (ID_list['ARXIV']) then -- |arxiv= or |eprint= required for cite arxiv
    if not is_set (ID_list['ARXIV']) then -- |arxiv= or |eprint= required for cite arxiv
    table.insert( z.message_tail, { set_error( 'arxiv_missing', {}, true ) } ); -- add error message
    table.insert( z.message_tail, { set_error( 'arxiv_missing', {}, true ) } ); -- add error message
    elseif is_set (Series) then -- series is an alias of version
    ID_list['ARXIV'] = ID_list['ARXIV'] .. Series; -- concatenate version onto the end of the arxiv identifier
    Series = ''; -- unset
    deprecated_parameter ('version'); -- deprecated parameter but only for cite arxiv
    end
    end
    if first_set (AccessDate, At, Chapter, Format, Page, Pages, Periodical, PublisherName, URL, -- a crude list of parameters that are not supported by cite arxiv
    if first_set (AccessDate, At, Chapter, Format, Page, Pages, Periodical, PublisherName, URL, -- a crude list of parameters that are not supported by cite arxiv
    ID_list['ASIN'], ID_list['BIBCODE'], ID_list['DOI'], ID_list['ISBN'], ID_list['ISSN'],
    ID_list['ASIN'], ID_list['BIBCODE'], ID_list['DOI'], ID_list['ISBN'], ID_list['ISSN'],
    Line 2,418: Line 2,668:
    -- 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.
    do -- do-block to limit scope of LastFirstAuthors
    do -- do-block to limit scope of last_first_list
    local LastFirstAuthors;
    local last_first_list;
    local Maximum = A['DisplayAuthors'];
    local maximum = A['DisplayAuthors'];


    Maximum , author_etal = get_display_authors_editors (Maximum, #a, 'authors', author_etal);
    maximum , author_etal = get_display_authors_editors (maximum, #a, 'authors', author_etal);


    local control = {  
    local control = {  
    format = NameListFormat, -- empty string or 'vanc'
    format = NameListFormat, -- empty string or 'vanc'
    maximum = Maximum,
    maximum = maximum,
    lastauthoramp = LastAuthorAmp,
    lastauthoramp = LastAuthorAmp,
    page_name = this_page.text -- get current page name so that we don't wikilink to it via authorlinkn
    page_name = this_page.text -- get current page name so that we don't wikilink to it via authorlinkn
    };
    };
    -- If the coauthor field is also used, prevent ampersand and et al. formatting.
    if is_set(Coauthors) then -- if the coauthor field is also used, prevent ampersand and et al. formatting.
    if is_set(Coauthors) then
    control.lastauthoramp = nil;
    control.lastauthoramp = nil;
    control.maximum = #a + 1;
    control.maximum = #a + 1;
    end
    end
    LastFirstAuthors = list_people(control, a, author_etal);
    last_first_list = list_people(control, a, author_etal);


    if is_set (Authors) then
    if is_set (Authors) then
    if is_set (LastFirstAuthors) then -- if both author name styles
    Authors, author_etal = name_has_etal (Authors, author_etal, false); -- find and remove variations on et al.
    table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'authors') .. ' and ' .. wrap_style ('parameter', 'last')}, true ) } ); -- add error message
    if author_etal then
    Authors = LastFirstAuthors; -- TODO: is this correct or should we use |authors= instead?
    Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
    end
    end
    else
    else
    Authors = LastFirstAuthors; -- either an author name list or an empty string
    Authors = last_first_list; -- either an author name list or an empty string
    end
    end
    end -- end of do
    end -- end of do


    Line 2,454: Line 2,704:


    local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
    local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
    if not is_set(Editors) then
    do
    local Maximum = A['DisplayEditors'];
    local last_first_list;
    local maximum = A['DisplayEditors'];


    Maximum , editor_etal = get_display_authors_editors (Maximum, #e, 'editors', editor_etal);
    maximum , editor_etal = get_display_authors_editors (maximum, #e, 'editors', editor_etal);
    -- Preserve old-style implicit et al.
    -- Preserve old-style implicit et al.
    if not is_set(Maximum) and #e == 4 then  
    if not is_set(maximum) and #e == 4 then  
    Maximum = 3;
    maximum = 3;
    table.insert( z.message_tail, { set_error('implict_etal_editor', {}, true) } );
    table.insert( z.message_tail, { set_error('implict_etal_editor', {}, true) } );
    end
    end
    Line 2,466: Line 2,717:
    local control = {  
    local control = {  
    format = NameListFormat, -- empty string or 'vanc'
    format = NameListFormat, -- empty string or 'vanc'
    maximum = Maximum,
    maximum = maximum,
    lastauthoramp = LastAuthorAmp,
    lastauthoramp = LastAuthorAmp,
    page_name = this_page.text -- get current page name so that we don't wikilink to it via editorlinkn
    page_name = this_page.text -- get current page name so that we don't wikilink to it via editorlinkn
    };
    };


    Editors, EditorCount = list_people(control, e, editor_etal);
    last_first_list, EditorCount = list_people(control, e, editor_etal);
     
    if is_set (Editors) then
    if editor_etal then
    Editors = Editors .. ' ' .. cfg.messages['et al']; -- add et al. to editors parameter beause |display-editors=etal
    EditorCount = 2; -- with et al., |editors= is multiple names; spoof to display (eds.) annotation
    else
    EditorCount = 2; -- we don't know but assume |editors= is multiple names; spoof to display (eds.) annotation
    end
    else
    Editors = last_first_list; -- either an author name list or an empty string
    end
     
    if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then  
    if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then  
    EditorCount = 2; -- spoof to display (eds.) annotation
    EditorCount = 2; -- spoof to display (eds.) annotation
    end
    end
    else
    EditorCount = 1;
    end
    end


    -- apply |[xx-]format= styling; at the end, these parameters hold correctly styled format annotation,
    -- apply |[xx-]format= styling; at the end, these parameters hold correctly styled format annotation,
    Line 2,695: Line 2,957:


    if is_set (Language) then
    if is_set (Language) then
    Language = language_parameter (Language, this_page.namespace); -- format, categories (article namespace only), name from ISO639-1, etc
    Language = language_parameter (Language); -- format, categories, name from ISO639-1, etc
    else
    else
    Language=""; -- language not specified so make sure this is an empty string;
    Language=""; -- language not specified so make sure this is an empty string;
    Line 2,703: Line 2,965:


    TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
    TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
    Edition = is_set(Edition) and (" " .. wrap_msg ('edition', Edition)) or "";
    if is_set (Edition) then
    if Edition:match ('[Ee]d%.?$') or Edition:match ('[Ee]dition$') then
    add_maint_cat ('extra_text', 'edition');
    end
    Edition = " " .. wrap_msg ('edition', Edition);
    else
    Edition = '';
    end
    Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";
    Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";
    Series = is_set(Series) and (sepc .. " " .. Series) or "";
    Series = is_set(Series) and (sepc .. " " .. Series) or "";
    Line 3,060: Line 3,329:


    if #z.maintenance_cats ~= 0 then
    if #z.maintenance_cats ~= 0 then
    text = text .. ' <span class="citation-comment" style="display:none; color:#33aa33">';
    text = text .. '<span class="citation-comment" style="display:none; color:#33aa33">';
    for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
    for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
    text = text .. v .. ' ([[:Category:' .. v ..'|link]])';
    text = text .. ' ' .. v .. ' ([[:Category:' .. v ..'|link]])';
    end
    end
    text = text .. '</span>'; -- maintenance mesages (realy just the names of the categories for now)
    text = text .. '</span>'; -- maintenance mesages (realy just the names of the categories for now)