Module:Citation/CS1: Difference between revisions

    m (deprecated enumerated parameter error message fix;)
    (sync from sandbox;)
    Line 600: Line 600:




    --[[--------------------------< W I K I S O U R C E _ U R L _ M A K E >----------------------------------------
    --[[----------------< W I K I S O U R C E _ U R L _ M A K E >-------------------


    Makes a Wikisource URL from Wikisource interwiki-link.  Returns the URL and appropriate label; nil else.
    Makes a Wikisource URL from Wikisource interwiki-link.  Returns the URL and appropriate
    label; nil else.


    str is the value assigned to |chapter= (or aliases) or |title= or |title-link=
    str is the value assigned to |chapter= (or aliases) or |title= or |title-link=
    Line 643: Line 644:
    end
    end
    end
    end
     
    if ws_url then
    if ws_url then
    ws_url = mw.uri.encode (ws_url, 'WIKI'); -- make a usable URL
    ws_url = mw.uri.encode (ws_url, 'WIKI'); -- make a usable URL
    ws_url = ws_url:gsub ('%%23', '#'); -- undo percent encoding of fragment marker
    ws_url = ws_url:gsub ('%%23', '#'); -- undo percent-encoding of fragment marker
    end
    end


    Line 653: Line 654:




    --[[--------------------------< F O R M A T _ P E R I O D I C A L >--------------------------------------------
    --[[----------------< F O R M A T _ P E R I O D I C A L >-----------------------


    Format the three periodical parameters: |script-<periodical>=, |<periodical>=, and |trans-<periodical>= into a single Periodical meta-
    Format the three periodical parameters: |script-<periodical>=, |<periodical>=,
    parameter.
    and |trans-<periodical>= into a single Periodical meta-parameter.


    ]]
    ]]
    Line 685: Line 686:




    --[[--------------------------< F O R M A T _ C H A P T E R _ T I T L E >--------------------------------------
    --[[------------------< F O R M A T _ C H A P T E R _ T I T L E >---------------


    Format the four chapter parameters: |script-chapter=, |chapter=, |trans-chapter=, and |chapter-url= into a single chapter meta-
    Format the four chapter parameters: |script-chapter=, |chapter=, |trans-chapter=,
    parameter (chapter_url_source used for error messages).
    and |chapter-url= into a single chapter meta- parameter (chapter_url_source used
    for error messages).


    ]]
    ]]
    Line 697: Line 699:
    local ws_url, ws_label, L = wikisource_url_make (chapter); -- make a wikisource URL and label from a wikisource interwiki link
    local ws_url, ws_label, L = wikisource_url_make (chapter); -- make a wikisource URL and label from a wikisource interwiki link
    if ws_url then
    if ws_url then
    ws_label = ws_label:gsub ('_', ''); -- replace underscore separaters with space characters
    ws_label = ws_label:gsub ('_', ' '); -- replace underscore separators with space characters
    chapter = ws_label;
    chapter = ws_label;
    end
    end
    Line 734: Line 736:




    --[[--------------------------< H A S _ I N V I S I B L E _ C H A R S >----------------------------------------
    --[[----------------< H A S _ I N V I S I B L E _ C H A R S >-------------------


    This function searches a parameter's value for non-printable or invisible characters. The search stops at the
    This function searches a parameter's value for non-printable or invisible characters.
    first match.
    The search stops at the first match.


    This function will detect the visible replacement character when it is part of the Wikisource.
    This function will detect the visible replacement character when it is part of the Wikisource.


    Detects but ignores nowiki and math stripmarkers.  Also detects other named stripmarkers (gallery, math, pre, ref)
    Detects but ignores nowiki and math stripmarkers.  Also detects other named stripmarkers
    and identifies them with a slightly different error message. See also coins_cleanup().
    (gallery, math, pre, ref) and identifies them with a slightly different error message.
    See also coins_cleanup().


    Output of this function is an error message that identifies the character or the Unicode group, or the stripmarker
    Output of this function is an error message that identifies the character or the
    that was detected along with its position (or, for multi-byte characters, the position of its first byte) in the
    Unicode group, or the stripmarker that was detected along with its position (or,
    parameter value.
    for multi-byte characters, the position of its first byte) in the parameter value.


    ]]
    ]]
    Line 791: Line 794:
    end
    end
    end
    end
    i = i+1; -- bump our index
    i = i + 1; -- bump our index
    end
    end
    end
    end




    --[[--------------------------< A R G U M E N T _ W R A P P E R >----------------------------------------------
    --[[-------------------< A R G U M E N T _ W R A P P E R >----------------------


    Argument wrapper.  This function provides support for argument mapping defined in the configuration file so that
    Argument wrapper.  This function provides support for argument mapping defined
    multiple names can be transparently aliased to single internal variable.
    in the configuration file so that multiple names can be transparently aliased to
    single internal variable.


    ]]
    ]]
    Line 808: Line 812:
    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 846: Line 850:




    --[[--------------------------< N O W R A P _ D A T E >--------------------------------------------------------
    --[[--------------------------< N O W R A P _ D A T E >-------------------------


    When date is YYYY-MM-DD format wrap in nowrap span: <span ...>YYYY-MM-DD</span>. When date is DD MMMM YYYY or is
    When date is YYYY-MM-DD format wrap in nowrap span: <span ...>YYYY-MM-DD</span>.
    MMMM DD, YYYY then wrap in nowrap span: <span ...>DD MMMM</span> YYYY or <span ...>MMMM DD,</span> YYYY
    When date is DD MMMM YYYY or is MMMM DD, YYYY then wrap in nowrap span:
    <span ...>DD MMMM</span> YYYY or <span ...>MMMM DD,</span> YYYY


    DOES NOT yet support MMMM YYYY or any of the date ranges.
    DOES NOT yet support MMMM YYYY or any of the date ranges.
    Line 871: Line 876:




    --[[--------------------------< S E T _ T I T L E T Y P E >----------------------------------------------------
    --[[--------------------------< S E T _ T I T L E T Y P E >---------------------


    This function sets default title types (equivalent to the citation including |type=<default value>) for those templates that have defaults.
    This function sets default title types (equivalent to the citation including
    Also handles the special case where it is desirable to omit the title type from the rendered citation (|type=none).
    |type=<default value>) for those templates that have defaults. Also handles the
    special case where it is desirable to omit the title type from the rendered citation
    (|type=none).


    ]]
    ]]
    Line 892: Line 899:
    --[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------
    --[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------


    Converts a hyphen to a dash under certain conditions.  The hyphen must separate like items; unlike items are
    Converts a hyphen to a dash under certain conditions.  The hyphen must separate
    returned unmodified.  These forms are modified:
    like items; unlike items are returned unmodified.  These forms are modified:
    letter - letter (A - B)
    letter - letter (A - B)
    digit - digit (4-5)
    digit - digit (4-5)
    digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5)
    digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5)
    letterdigit - letterdigit (A1-A5) (an optional separator between letter and digit is supported – a.1-a.5 or a-1-a-5)
    letterdigit - letterdigit (A1-A5) (an optional separator between letter and
    digitletter - digitletter (5a - 5d) (an optional separator between letter and digit is supported – 5.a-5.d or 5-a-5-d)
    digit is supported – a.1-a.5 or a-1-a-5)
    digitletter - digitletter (5a - 5d) (an optional separator between letter and
    digit is supported – 5.a-5.d or 5-a-5-d)


    any other forms are returned unmodified.
    any other forms are returned unmodified.
    Line 911: Line 920:
    end
    end


    local accept; -- Boolean
    local accept; -- Boolean
    str, accept = utilities.has_accept_as_written (str); -- remove accept-this-as-written markup when it wraps all of str
    if accept then
    return str; -- when markup removed, nothing to do, we're done
    end


    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 ('&nbsp;', ' '); -- replace &nbsp; entity with generic keyboard space 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
    local out = {};
    local out = {};
    Line 925: Line 933:


    for _, item in ipairs (list) do -- for each item in the list
    for _, item in ipairs (list) do -- for each item in the list
    if mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$') then -- if a hyphenated range or has endash or emdash separators
    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 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)
    item:match ('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$') or -- digitletter hyphen digitletter (optional separator between digit and letter)
    item:match ('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$') or -- digitletter hyphen digitletter (optional separator between digit and letter)
    Line 936: Line 945:
    end
    end
    end
    end
    item = utilities.has_accept_as_written (item); -- remove accept-this-as-written markup when it wraps all of str
    table.insert (out, item); -- add the (possibly modified) item to the output table
    table.insert (out, item); -- add the (possibly modified) item to the output table
    end
    end


    return table.concat (out, ', '); -- concatenate the output table into a comma separated string
            local temp_str = ''; -- concatenate the output table into a comma separated string
    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
    return utilities.has_accept_as_written (str); -- when global markup removed, return original str
    else
    return temp_str; -- else, return assembled temp_str
    end
    end
    end


     
    --[[--------------------------< S A F E _ J O I N >-----------------------------
    --[[--------------------------< S A F E _ J O I N >------------------------------------------------------------


    Joins a sequence of strings together while checking for duplicate separation characters.
    Joins a sequence of strings together while checking for duplicate separation characters.
    Line 1,021: Line 1,034:
    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,028: Line 1,041:




    --[[--------------------------< I S _ S U F F I X >------------------------------------------------------------
    --[[--------------------------< I S _ S U F F I X >-----------------------------


    returns true is suffix is properly formed Jr, Sr, or ordinal in the range 1–9. Puncutation not allowed.
    returns true is suffix is properly formed Jr, Sr, or ordinal in the range 1–9.
    Puncutation not allowed.


    ]]
    ]]
    Line 1,042: Line 1,056:




    --[[--------------------------< I S _ G O O D _ V A N C _ N A M E >--------------------------------------------
    --[[--------------------< I S _ G O O D _ V A N C _ N A M E >-------------------


    For Vancouver style, author/editor names are supposed to be rendered in Latin (read ASCII) characters.  When a name
    For Vancouver style, author/editor names are supposed to be rendered in Latin
    uses characters that contain diacritical marks, those characters are to converted to the corresponding Latin character.
    (read ASCII) characters.  When a name uses characters that contain diacritical marks,
    When a name is written using a non-Latin alphabet or logogram, that name is to be transliterated into Latin characters.
    those characters are to converted to the corresponding Latin character. When a name
    These things are not currently possible in this module so are left to the editor to do.
    is written using a non-Latin alphabet or logogram, that 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 in the four Unicode Latin character sets
    This test allows |first= and |last= names to contain any of the letters defined
    in the four Unicode Latin character sets
    [http://www.unicode.org/charts/PDF/U0000.pdf C0 Controls and Basic Latin] 0041–005A, 0061–007A
    [http://www.unicode.org/charts/PDF/U0000.pdf C0 Controls and Basic Latin] 0041–005A, 0061–007A
    [http://www.unicode.org/charts/PDF/U0080.pdf C1 Controls and Latin-1 Supplement] 00C0–00D6, 00D8–00F6, 00F8–00FF
    [http://www.unicode.org/charts/PDF/U0080.pdf C1 Controls and Latin-1 Supplement] 00C0–00D6, 00D8–00F6, 00F8–00FF
    Line 1,055: Line 1,071:
    [http://www.unicode.org/charts/PDF/U0180.pdf Latin Extended-B] 0180–01BF, 01C4–024F
    [http://www.unicode.org/charts/PDF/U0180.pdf Latin Extended-B] 0180–01BF, 01C4–024F


    |lastn= also allowed to contain hyphens, spaces, and apostrophes. (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
    |lastn= also allowed to contain hyphens, spaces, and apostrophes.
    (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
    |firstn= also allowed to contain hyphens, spaces, apostrophes, and periods
    |firstn= also allowed to contain hyphens, spaces, apostrophes, and periods


    This original test:
    This original test:
    if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]+[2-6%a]*$") then
    if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$")
    was written outside of the code editor and pasted here because the code editor gets confused between character insertion point and cursor position.
    or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]+[2-6%a]*$") then
    The test has been rewritten to use decimal character escape sequence for the individual bytes of the Unicode characters so that it is not necessary
    was written outside of the code editor and pasted here because the code editor
    to use an external editor to maintain this code.
    gets confused between character insertion point and cursor position. The test has
    been rewritten to use decimal character escape sequence for the individual bytes
    of the Unicode characters so that it is not necessary to use an external editor
    to maintain this code.


    \195\128-\195\150 – À-Ö (U+00C0–U+00D6 – C0 controls)
    \195\128-\195\150 – À-Ö (U+00C0–U+00D6 – C0 controls)
    Line 1,097: Line 1,117:
    Attempts to convert names to initials in support of |name-list-style=vanc.   
    Attempts to convert names to initials in support of |name-list-style=vanc.   


    Names in |firstn= may be separated by spaces or hyphens, or for initials, a period. See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35062/.
    Names in |firstn= may be separated by spaces or hyphens, or for initials, a period.
    See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35062/.


    Vancouver style requires family rank designations (Jr, II, III, etc.) to be rendered as Jr, 2nd, 3rd, etc.  See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35085/.
    Vancouver style requires family rank designations (Jr, II, III, etc.) to be rendered
    This code only accepts and understands generational suffix in the Vancouver format because Roman numerals look like, and can be mistaken for, initials.
    as Jr, 2nd, 3rd, etc.  See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35085/.
    This code only accepts and understands generational suffix in the Vancouver format
    because Roman numerals look like, and can be mistaken for, initials.


    This function uses ustring functions because firstname initials may be any of the Unicode Latin characters accepted by is_good_vanc_name ().
    This function uses ustring functions because firstname initials may be any of the
    Unicode Latin characters accepted by is_good_vanc_name ().


    ]]
    ]]
    Line 1,144: Line 1,168:
    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
    end
    end
    Line 1,151: Line 1,175:




    --[[--------------------------< L I S T _ P E O P L E >-------------------------------------------------------
    --[[--------------------------< L I S T _ P E O P L E >--------------------------


    Formats a list of people (e.g. authors, contributors, editors, interviewers, translators)  
    Formats a list of people (authors, contributors, editors, interviewers, translators)  


    names in the list will be linked when
    names in the list will be linked when
    |<name>-link= has a value
    |<name>-link= has a value
    |<name>-mask- does NOT have a value; masked names are presumed to have been rendered previously so should have been linked there
    |<name>-mask- does NOT have a value; masked names are presumed to have been
    rendered previously so should have been linked there


    when |<name>-mask=0, the associated name is not rendered
    when |<name>-mask=0, the associated name is not rendered
    Line 1,168: Line 1,193:
    local format = control.format;
    local format = control.format;
    local maximum = control.maximum;
    local maximum = control.maximum;
    local lastauthoramp = control.lastauthoramp; -- TODO: delete after deprecation
    local name_list = {};
    local name_list = {};


    Line 1,174: Line 1,198:
    sep = cfg.presentation['sep_nl_vanc']; -- name-list separator between names is a comma
    sep = cfg.presentation['sep_nl_vanc']; -- name-list separator between names is a comma
    namesep = cfg.presentation['sep_name_vanc']; -- last/first separator is a space
    namesep = cfg.presentation['sep_name_vanc']; -- last/first separator is a space
    lastauthoramp = nil; -- TODO: delete after deprecation -- unset because isn't used by Vancouver style
    else
    else
    sep = cfg.presentation['sep_nl']; -- name-list separator between names is a semicolon
    sep = cfg.presentation['sep_nl']; -- name-list separator between names is a semicolon
    Line 1,197: Line 1,220:
    local n = tonumber (mask); -- convert to a number if it can be converted; nil else
    local n = tonumber (mask); -- convert to a number if it can be converted; nil else
    if n then
    if n then
    one = 0 ~= n and string.rep("&mdash;",n) or nil; -- make a string of (n > 0) mdashes, nil else, to replace name
    one = 0 ~= n and string.rep("&mdash;", n) or nil; -- make a string of (n > 0) mdashes, nil else, to replace name
    person.link = nil; -- don't create link to name if name is replaces with mdash string or has been set nil
    person.link = nil; -- don't create link to name if name is replaces with mdash string or has been set nil
    else
    else
    Line 1,229: Line 1,252:
    if 0 < count then  
    if 0 < count then  
    if 1 < count and not etal then
    if 1 < count and not etal then
    if 'amp' == format or utilities.is_set (lastauthoramp) then -- TODO: delete lastauthoramp after deprecation
    if 'amp' == format then
    name_list[#name_list-2] = " & "; -- replace last separator with ampersand text
    name_list[#name_list-2] = " & "; -- replace last separator with ampersand text
    elseif 'and' == format then
    elseif 'and' == format then
    Line 1,251: Line 1,274:




    --[[--------------------------< A N C H O R _ 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 returns an empty string.
    Generates a CITEREF anchor ID if we have at least one name or a date.  Otherwise
    returns an empty string.


    namelist is one of the contributor-, author-, or editor-name lists chosen in that order.  year is Year or anchor_year.
    namelist is one of the contributor-, author-, or editor-name lists chosen in that
    order.  year is Year or anchor_year.


    ]]
    ]]
    Line 1,275: Line 1,300:




    --[[--------------------------< N A M E _ H A S _ E T A L >----------------------------------------------------
    --[[---------------------< N A M E _ H A S _ E T A L >--------------------------


    Evaluates the content of name parameters (author, editor, etc.) for variations on the theme of et al.  If found,
    Evaluates the content of name parameters (author, editor, etc.) for variations on
    the et al. is removed, a flag is set to true and the function returns the modified name and the flag.
    the theme of 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.


    This function never sets the flag to false but returns it's previous state because it may have been set by
    This function never sets the flag to false but returns its previous state because
    previous passes through this function or by the associated |display-<names>=etal parameter
    it may have been set by previous passes through this function or by the associated
    |display-<names>=etal parameter


    ]]
    ]]
    Line 1,288: Line 1,315:


    if utilities.is_set (name) then -- name can be nil in which case just return
    if utilities.is_set (name) then -- name can be nil in which case just return
    local patterns = cfg.et_al_patterns; --get patterns from configuration
    local patterns = cfg.et_al_patterns; -- get patterns from configuration
    for _, pattern in ipairs (patterns) do -- loop through all of the patterns
    for _, pattern in ipairs (patterns) do -- loop through all of the patterns
    Line 1,305: Line 1,332:




    --[[--------------------------< N A M E _ I S _ N U M E R I C >------------------------------------------------
    --[[---------------------< N A M E _ I S _ N U M E R I C >----------------------


    Add maint cat when name parameter value does not contain letters.  Does not catch mixed alphanumeric names so
    Add maint cat when name parameter value does not contain letters.  Does not catch
    |last=A. Green (1922-1987) does not get caught in the current version of this test but |first=(1888) is caught.
    mixed alphanumeric names so |last=A. Green (1922-1987) does not get caught in the
    current version of this test but |first=(1888) is caught.


    returns nothing
    returns nothing
    Line 1,323: Line 1,351:




    --[[--------------------------< N A M E _ H A S _ E D _ M A R K U P >------------------------------------------
    --[[-------------------< N A M E _ H A S _ E D _ M A R K U P >------------------


    Evaluates the content of author and editor parameters for extraneous editor annotations: ed, ed., eds, (Ed.), etc.
    Evaluates the content of author and editor parameters for extraneous editor annotations:
    These annotation do not belong in author parameters and are redundant in editor parameters.  If found, the function
    ed, ed., eds, (Ed.), etc. These annotations do not belong in author parameters and
    adds the editor markup maintenance category.
    are redundant in editor parameters.  If found, the function adds the editor markup
    maintenance category.


    returns nothing
    returns nothing
    Line 1,347: Line 1,376:




    --[[--------------------------< N A M E _ H A S _ M U L T _ N A M E S >----------------------------------------
    --[[-----------------< N A M E _ H A S _ M U L T _ N A M E S >------------------


    Evaluates the content of last/surname (authors etc.) parameters for multiple names. Multiple names are indicated
    Evaluates the content of last/surname (authors etc.) parameters for multiple names.
    if there is more than one comma or any semicolons. If found, the function adds the multiple name maintenance category.
    Multiple names are indicated if there is more than one comma or any "unescaped"
    semicolons. Escaped semicolons are ones used as part of selected HTML entities.
    If the condition is met, the function adds the multiple name maintenance category.


    returns nothing
    returns nothing
    Line 1,357: Line 1,388:


    local function name_has_mult_names (name, list_name)
    local function name_has_mult_names (name, list_name)
    local _, commas, semicolons;
    local _, commas, semicolons, nbsps;
    if utilities.is_set (name) then
    if utilities.is_set (name) then
    _, commas = name:gsub (',', ''); -- count the number of commas
    _, commas = name:gsub (',', ''); -- count the number of commas
    _, semicolons = name:gsub (';', ''); -- count the number of semicolons
    _, semicolons = name:gsub (';', ''); -- count the number of semicolons
    -- nbsps probably should be its own separate count rather than merged in
    -- some way with semicolons because Lua patterns do not support the
    -- grouping operator that regex does, which means there is no way to add
    -- more entities to escape except by adding more counts with the new
    -- entities
    _, nbsps = name:gsub ('&nbsp;',''); -- count nbsps
    if 1 < commas or 0 < semicolons then
    -- There is exactly 1 semicolon per &nbsp; entity, so subtract nbsps
    -- from semicolons to 'escape' them. If additional entities are added,
    -- they also can be subtracted.
    if 1 < commas or 0 < (semicolons - nbsps) then
    utilities.set_message ('maint_mult_names', cfg.special_case_translation [list_name]); -- add a maint message
    utilities.set_message ('maint_mult_names', cfg.special_case_translation [list_name]); -- add a maint message
    end
    end
    Line 1,369: Line 1,409:




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


    This function calls various name checking functions used to validate the content of the various name-holding
    This function calls various name checking functions used to validate the content
    parameters.
    of the various name-holding parameters.


    ]]
    ]]
    Line 1,385: Line 1,425:
    name_has_mult_names (last, list_name); -- check for multiple names in the parameter (last only)
    name_has_mult_names (last, list_name); -- check for multiple names in the parameter (last only)
    name_has_ed_markup (last, list_name); -- check for extraneous 'editor' annotation
    name_has_ed_markup (last, list_name); -- check for extraneous 'editor' annotation
    name_is_numeric (last, list_name); -- check for names that are compsed of digits and punctuation
    name_is_numeric (last, list_name); -- check for names that are composed of digits and punctuation
    end
    end
    end
    end
    Line 1,394: Line 1,434:
    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 compsed of digits and punctuation
    name_is_numeric (first, list_name); -- check for names that are composed of digits and punctuation
    end
    end
    end
    end
    Line 1,402: Line 1,442:




    --[[--------------------------< E X T R A C T _ N A M E S >----------------------------------------------------
    --[[----------------------< E X T R A C T _ N A M E S >-------------------------
    Gets name list from the input arguments
    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.
    Searches through args in sequential order to find |lastn= and |firstn= 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
    (or their aliases), and their matching link and mask parameters. Stops searching
    find |last4= and |last5= then the search is done.
    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.


    This function emits an error message when there is a |firstn= without a matching |lastn=.  When there are 'holes' in the list of last names, |last1= and |last3=
    This function emits an error message when there is a |firstn= without a matching
    are present but |last2= is missing, an error message is emitted. |lastn= is not required to have a matching |firstn=.
    |lastn=.  When there are 'holes' in the list of last names, |last1= and |last3=
    are present but |last2= is missing, an error message is emitted. |lastn= is not
    required to have a matching |firstn=.


    When an author or editor parameter contains some form of 'et al.', the 'et al.' is stripped from the parameter and a flag (etal) returned
    When an author or editor parameter contains some form of 'et al.', the 'et al.'
    that will cause list_people() to add the static 'et al.' text from Module:Citation/CS1/Configuration.  This keeps 'et al.' out of the  
    is stripped from the parameter and a flag (etal) returned that will cause list_people()
    template's metadata.  When this occurs, the page is added to a maintenance category.
    to add the static 'et al.' text from Module:Citation/CS1/Configuration.  This keeps
    'et al.' out of the template's metadata.  When this occurs, an error is emitted.


    ]]
    ]]
    Line 1,476: Line 1,521:
    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.


    Returns the language name and associated two- or three-character code.  Because case of the source may be incorrect
    Returns the language name and associated two- or three-character code.  Because
    or different from the case that WikiMedia uses, the name comparisons are done in lower case and when a match is
    case of the source may be incorrect or different from the case that WikiMedia uses,
    found, the Wikimedia version (assumed to be correct) is returned along with the code.  When there is no match, we
    the name comparisons are done in lower case and when a match is found, the Wikimedia
    return the original language name string.
    version (assumed to be correct) is returned along with the code.  When there is no
    match, we return the original language name string.


    mw.language.fetchLanguageNames(<local wiki language>, 'all') returns a list of languages that in some cases may include
    mw.language.fetchLanguageNames(<local wiki language>, 'all') returns a list of
    extensions. For example, code 'cbk-zam' and its associated name 'Chavacano de Zamboanga' (MediaWiki does not support
    languages that in some cases may include extensions. For example, code 'cbk-zam'
    code 'cbk' or name 'Chavacano'.  Most (all?) of these languages are not used a 'language' codes per se, rather they
    and its associated name 'Chavacano de Zamboanga' (MediaWiki does not support
    are used as sub-domain names: cbk-zam.wikipedia.org. A list of language names and codes supported by fetchLanguageNames()
    code 'cbk' or name 'Chavacano'.  Most (all?) of these languages are not used a
    can be found at Template:Citation Style documentation/language/doc
    'language' codes per se, rather they are used as sub-domain names: cbk-zam.wikipedia.org.
    A list of language names and codes supported by fetchLanguageNames() can be found
    at Template:Citation Style documentation/language/doc


    Names that are included in the list will be found if that name is provided in the |language= parameter.  For example,
    Names that are included in the list will be found if that name is provided in the
    if |language=Chavacano de Zamboanga, that name will be found with the associated code 'cbk-zam'.  When names are found
    |language= parameter.  For example, if |language=Chavacano de Zamboanga, that name
    and the associated code is not two or three characters, this function returns only the WikiMedia language name.
    will be found with the associated code 'cbk-zam'.  When names are found and the
    associated code is not two or three characters, this function returns only the
    WikiMedia language name.


    Some language names have multiple entries under different codes:
    Some language names have multiple entries under different codes:
    Line 1,523: Line 1,573:




    --[[--------------------------< L A N G U A G E _ P A R A M E T E R >------------------------------------------
    --[[-------------------< L A N G U A G E _ P A R A M E T E R >------------------


    Gets language name from a provided two- or three-character ISO 639 code.  If a code is recognized by MediaWiki,
    Gets language name from a provided two- or three-character ISO 639 code.  If a code
    use the returned name; if not, then use the value that was provided with the language parameter.
    is recognized by MediaWiki, use the returned name; if not, then use the value that
    was provided with the language parameter.


    When |language= contains a recognized language (either code or name), the page is assigned to the category for
    When |language= contains a recognized language (either code or name), the page is
    that code: Category:Norwegian-language sources (no). For valid three-character code languages, the page is assigned
    assigned to the category for that code: Category:Norwegian-language sources (no).
    to the single category for '639-2' codes: Category:CS1 ISO 639-2 language sources.
    For valid three-character code languages, the page is assigned to the single category
    for '639-2' codes: Category:CS1 ISO 639-2 language sources.


    Languages that are the same as the local wiki are not categorized.  MediaWiki does not recognize three-character
    Languages that are the same as the local wiki are not categorized.  MediaWiki does
    equivalents of two-character codes: code 'ar' is recognized but code 'ara' is not.
    not recognize three-character equivalents of two-character codes: code 'ar' is
    recognized but code 'ara' is not.


    This function supports multiple languages in the form |language=nb, French, th where the language names or codes are
    This function supports multiple languages in the form |language=nb, French, th
    separated from each other by commas with optional space characters.
    where the language names or codes are separated from each other by commas with
    optional space characters.


    ]]
    ]]
    Line 1,602: Line 1,656:




    --[[--------------------------< S E T _ C S 1 _ S T Y L E >----------------------------------------------------
    --[[----------------------< S E T _ C S 1 _ S T Y L E >-------------------------


    Set style settings for CS1 citation templates. Returns separator and postscript settings
    Set style settings for CS1 citation templates. Returns separator and postscript settings
    Line 1,619: Line 1,673:




    --[[--------------------------< S E T _ C S 2 _ S T Y L E >----------------------------------------------------
    --[[-----------------------< S E T _ C S 2 _ S T Y L E >------------------------


    Set style settings for CS2 citation templates. Returns separator, postscript, ref settings
    Set style settings for CS2 citation templates. Returns separator, postscript, ref settings
    Line 1,639: Line 1,693:




    --[[--------------------------< G E T _ S E T T I N G S _ F R O M _ C I T E _ C L A S S >----------------------
    --[[---------< G E T _ S E T T I N G S _ F R O M _ C I T E _ C L A S S >--------


    When |mode= is not set or when its value is invalid, use config.CitationClass and parameter values to establish
    When |mode= is not set or when its value is invalid, use config.CitationClass and
    rendered style.
    parameter values to establish rendered style.


    ]]
    ]]
    Line 1,660: Line 1,714:
    --[[--------------------------< S E T _ S T Y L E >------------------------------------------------------------
    --[[--------------------------< 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
    Establish basic style settings to be used when rendering the citation. Uses |mode=
    config.CitationClass from the template's #invoke: to establish style.
    if set and valid or uses config.CitationClass from the template's #invoke: to establish style.


    ]]
    ]]
    Line 1,683: Line 1,737:




    --[=[-------------------------< I S _ P D F >------------------------------------------------------------------
    --[=[-------------------------< 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
    Determines if a URL has the file extension that is one of the PDF file extensions
    applying the PDF icon to external links.
    used by [[MediaWiki:Common.css]] when applying the PDF icon to external links.


    returns true if file extension is one of the recognized extensions, else false
    returns true if file extension is one of the recognized extensions, else false
    Line 1,699: Line 1,753:




    --[[--------------------------< S T Y L E _ F O R M A T >------------------------------------------------------
    --[[--------------------------< S T Y L E _ F O R M A T >-----------------------


    Applies CSS style to |format=, |chapter-format=, etc.  Also emits an error message if the format parameter does
    Applies CSS style to |format=, |chapter-format=, etc.  Also emits an error message
    not have a matching URL parameter.  If the format parameter is not set and the URL contains a file extension that
    if the format parameter does not have a matching URL parameter.  If the format parameter
    is recognized as a PDF document by MediaWiki's commons.css, this code will set the format parameter to (PDF) with
    is not set and the URL contains a file extension that is recognized as a PDF document
    by MediaWiki's commons.css, this code will set the format parameter to (PDF) with
    the appropriate styling.
    the appropriate styling.


    Line 1,723: Line 1,778:




    --[[--------------------------< G E T _ D I S P L A Y _ N A M E S >--------------------------------------------
    --[[---------------------< G E T _ D I S P L A Y _ N A M E S >------------------


    Returns a number that defines the number of names displayed for author and editor name lists and a Boolean flag
    Returns a number that defines the number of names displayed for author and editor
    to indicate when et al. should be appended to the name list.
    name lists and a Boolean flag to indicate when et al. should be appended to the name list.


    When the value assigned to |display-xxxxors= is a number greater than or equal to zero, return the number and
    When the value assigned to |display-xxxxors= is a number greater than or equal to zero,
    the previous state of the 'etal' flag (false by default but may have been set to true if the name list contains
    return the number and the previous state of the 'etal' flag (false by default
    some variant of the text 'et al.').
    but may have been set to true if the name list contains some variant of the text 'et al.').


    When the value assigned to |display-xxxxors= is the keyword 'etal', return a number that is one greater than the
    When the value assigned to |display-xxxxors= is the keyword 'etal', return a number
    number of authors in the list and set the 'etal' flag true. This will cause the list_people() to display all of
    that is one greater than the number of authors in the list and set the 'etal' flag true.
    the names in the name list followed by 'et al.'
    This will cause the list_people() to display all of the names in the name list followed by 'et al.'


    In all other cases, returns nil and the previous state of the 'etal' flag.
    In all other cases, returns nil and the previous state of the 'etal' flag.
    Line 1,767: Line 1,822:




    --[[--------------------------< E X T R A _ T E X T _ I N _ P A G E _ C H E C K >------------------------------
    --[[----------< 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.  
    Adds error if |page=, |pages=, |quote-page=, |quote-pages= has what appears to be
    abbreviation in the first characters of the parameter content.
    some form of p. or pp. abbreviation in the first characters of the parameter content.


    check Page and Pages for extraneous p, p., pp, and pp. at start of parameter value:
    check page for extraneous p, p., pp, pp., pg, pg. at start of parameter value:
    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
    good pattern: '^P[^%.P%l]' matches when page begins PX or P# but not Px
    bad pattern: '^[Pp][Pp]' matches matches when |page(s)= begins pp or pP or Pp or PP
          where x and X are letters and # is a digit
    bad pattern: '^[Pp][PpGg]' matches when page begins pp, pP, Pp, PP, pg, pG, Pg, PG


    ]]
    ]]


    local function extra_text_in_page_check (page)
    local function extra_text_in_page_check (page)
    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?
    local good_pattern = '^P[^%.PpGg]'; -- OK to begin with uppercase P: P7 (page 7 of section P), but not p123 (page 123)
    local bad_pattern = '^[Pp]?[Pp]%.?[ %d]';
    local bad_pattern = '^[Pp][PpGg]?%.?[ %d]';


    if not page:match (good_pattern) and (page:match (bad_pattern) or page:match ('^[Pp]ages?')) then
    if not page:match (good_pattern) and (page:match (bad_pattern) or page:match ('^[Pp]ages?') or page:match ('^[Pp]gs.?')) then
    utilities.set_message ('maint_extra_text'); -- add maint cat;
    table.insert( z.message_tail, { utilities.set_message ( 'err_extra_text_pages')}); -- add error
    end
    end
    end
    end
    Line 1,800: Line 1,856:
    local function get_v_name_table (vparam, output_table, output_link_table)
    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 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 place holder
    local wl_type, label, link; -- wl_type not used here; just a placeholder
    local i = 1;
    local i = 1;
    Line 1,922: Line 1,978:


    Select one of |authors=, |authorn= / |lastn / firstn=, or |vauthors= as the source of the author name list or
    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.
    select one of |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
    Only one of these appropriate three will be used.  The hierarchy is: |authorn= (and aliases) highest and |authors= lowest;
    similarly, |editorn= (and aliases) highest and |editors= lowest
    |editorn= (and aliases) highest and |veditors= lowest (support for |editors= withdrawn)


    When looking for |authorn= / |editorn= parameters, test |xxxxor1= and |xxxxor2= (and all of their aliases); stops after the second
    When looking for |authorn= / |editorn= parameters, test |xxxxor1= and |xxxxor2= (and all of their aliases); stops after the second
    Line 1,933: Line 1,989:
    Emits an error message when more than one xxxxor name source is provided.
    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.
    In this function, vxxxxors = vauthors or veditors; xxxxors = authors as appropriate.


    ]]
    ]]
    Line 2,022: Line 2,078:
    if 'magazine' == cite_class or (utilities.in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then
    if 'magazine' == cite_class or (utilities.in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then
    if utilities.is_set (volume) and utilities.is_set (issue) then
    if utilities.is_set (volume) and utilities.is_set (issue) then
    return wrap_msg ('vol-no', {sepc, volume, issue}, lower);
    return wrap_msg ('vol-no', {sepc, hyphen_to_dash (volume), issue}, lower);
    elseif utilities.is_set (volume) then
    elseif utilities.is_set (volume) then
    return wrap_msg ('vol', {sepc, volume}, lower);
    return wrap_msg ('vol', {sepc, hyphen_to_dash (volume)}, lower);
    else
    else
    return wrap_msg ('issue', {sepc, issue}, lower);
    return wrap_msg ('issue', {sepc, issue}, lower);
    Line 2,038: Line 2,094:
    if utilities.is_set (volume) then
    if utilities.is_set (volume) then
    if volume:match ('^[MDCLXVI]+$') or volume:match ('^%d+$') then -- volume value is all digits or all uppercase Roman numerals
    if volume:match ('^[MDCLXVI]+$') or volume:match ('^%d+$') then -- volume value is all digits or all uppercase Roman numerals
    vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, hyphen_to_dash(volume)}); -- render in bold face
    vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, volume}); -- render in bold face
    elseif (4 < mw.ustring.len(volume)) then -- not all digits or Roman numerals and longer than 4 characters
    elseif (4 < mw.ustring.len(volume)) then -- not all digits or Roman numerals and longer than 4 characters
    vol = utilities.substitute (cfg.messages['j-vol'], {sepc, volume}); -- not bold
    vol = utilities.substitute (cfg.messages['j-vol'], {sepc, hyphen_to_dash (volume)}); -- not bold
    utilities.add_prop_cat ('long_vol');
    utilities.add_prop_cat ('long_vol');
    else -- four or less characters
    else -- four or less characters
    vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, hyphen_to_dash(volume)}); -- bold
    vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, hyphen_to_dash (volume)}); -- bold
    end
    end
    end
    end
    Line 2,111: Line 2,167:


    If any of these are interwiki links to Wikisource, returns the label portion of the interwiki-link as plain text
    If any of these are interwiki links to Wikisource, returns the label portion of the interwiki-link as plain text
    for use in COinS.  This COinS thing is done because here we convert an interwiki-link to and external link and
    for use in COinS.  This COinS thing is done because here we convert an interwiki-link to an external link and
    add an icon span around that; get_coins_pages() doesn't know about the span.  TODO: should it?   
    add an icon span around that; get_coins_pages() doesn't know about the span.  TODO: should it?   


    Line 2,129: Line 2,185:
    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. or pp.
    extra_text_in_page_check (page); -- add this page to maint cat if |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,141: Line 2,197:
    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. or pp.
    extra_text_in_page_check (pages); -- add this page to maint cat if |pages= 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,272: Line 2,328:
    local function is_generic_title (title)
    local function is_generic_title (title)
    title = mw.ustring.lower(title); -- switch title to lower case
    title = mw.ustring.lower(title); -- switch title to lower case
    for _, generic_title in ipairs (cfg.special_case_translation['generic_titles']) do --spin through the list of known generic title fragments
    for _, generic_title in ipairs (cfg.special_case_translation['generic_titles']) do -- spin through the list of known generic title fragments
    if title:find (generic_title['en'][1], 1, generic_title['en'][2]) then
    if title:find (generic_title['en'][1], 1, generic_title['en'][2]) then
    return true; -- found English generic title so done
    return true; -- found English generic title so done
    Line 2,286: Line 2,342:
    --[[--------------------------< I S _ A R C H I V E D _ C O P Y >----------------------------------------------
    --[[--------------------------< I S _ A R C H I V E D _ C O P Y >----------------------------------------------


    compares |title= to 'Archived copy' (place holder added by bots that can't find proper title); if matches, return true; nil else
    compares |title= to 'Archived copy' (placeholder added by bots that can't find proper title); if matches, return true; nil else


    ]]
    ]]
    Line 2,353: Line 2,409:


    do -- to limit scope of selected
    do -- to limit scope of selected
    local selected = select_author_editor_source (A['Veditors'], A['Editors'], args, 'EditorList');
    local selected = select_author_editor_source (A['Veditors'], nil, args, 'EditorList'); -- support for |editors= withdrawn
    if 1 == selected then
    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=
    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
    elseif 2 == selected then
    NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
    NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
    e, editor_etal = parse_vauthors_veditors (args, args.veditors, 'EditorList'); -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn=
    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
    end
    end
    Line 2,544: Line 2,598:
    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,601: Line 2,657:
    local ID = A['ID'];
    local ID = A['ID'];
    local ASINTLD = A['ASINTLD'];
    local ASINTLD = A['ASINTLD'];
    local IgnoreISBN = is_valid_parameter_value (A['IgnoreISBN'], A:ORIGIN('IgnoreISBN'), cfg.keywords_lists['yes_true_y'], nil);
    local Embargo = A['Embargo'];
    local Embargo = A['Embargo'];
    local Class = A['Class']; -- arxiv class identifier
    local Class = A['Class']; -- arxiv class identifier


    local Quote = A['Quote'];
    local Quote = A['Quote'];
    local QuotePage = A['QuotePage'];
    local QuotePages = A['QuotePages'];
    local ScriptQuote = A['ScriptQuote'];
    local ScriptQuote = A['ScriptQuote'];
    local TransQuote = A['TransQuote'];
    local TransQuote = A['TransQuote'];
    local LayFormat = A['LayFormat'];
    local LayFormat = A['LayFormat'];
    local LayURL = A['LayURL'];
    local LayURL = A['LayURL'];
    Line 2,619: Line 2,671:
    local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
    local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL


    local LastAuthorAmp = is_valid_parameter_value (A['LastAuthorAmp'], A:ORIGIN('LastAuthorAmp'), 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 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
    if 'nocat' == A:ORIGIN('NoTracking') then
    utilities.set_message ('maint_nocat'); -- this one so that we get the message; see main categorization at end of citation0()
    end
    --local variables that are not cs1 parameters
    local use_lowercase; -- controls capitalization of certain static text
    local use_lowercase; -- controls capitalization of certain static text
    local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
    local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
    Line 2,971: Line 3,018:
    end -- end of do
    end -- end of do


    local ID_list_coins = identifiers.extract_ids (args); -- gets identifiers and their values from args; this list used for COinS and source for build_id_list()
    local ID_list = {}; -- sequence table of rendered identifiers
    if utilities.is_set (DoiBroken) and not ID_list_coins['DOI'] then
    local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
    table.insert (z.message_tail, {utilities.set_message ('err_doibroken_missing_doi', A:ORIGIN('DoiBroken'))});
    ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, Embargo = Embargo, Class = Class});
    end
     
    local ID_access_levels = identifiers.extract_id_access_levels (args, ID_list_coins);
    if utilities.is_set (DoiBroken) and not ID_list_coins['DOI'] then
    local ID_list = identifiers.build_id_list (ID_list_coins, {IdAccessLevels = ID_access_levels, DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN, Embargo = Embargo, Class = Class}); -- render identifiers
    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.
    if utilities.in_array (config.CitationClass, whitelist.preprint_template_list) then
    if utilities.in_array (config.CitationClass, whitelist.preprint_template_list) then
    Line 3,024: Line 3,072:


    check_for_url ({ -- add error message when any of these parameters hold a URL
    check_for_url ({ -- add error message when any of these parameters hold a URL
    ['title']=Title,
    ['title'] = Title,
    [A:ORIGIN('Chapter')]=Chapter,
    [A:ORIGIN('Chapter')] = Chapter,
    [Periodical_origin] = Periodical,
    [Periodical_origin] = Periodical,
    [PublisherName_origin] = PublisherName
    [PublisherName_origin] = PublisherName
    Line 3,063: Line 3,111:
    ['Volume'] = Volume,
    ['Volume'] = Volume,
    ['Issue'] = Issue,
    ['Issue'] = Issue,
    ['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At}, 5)), -- pages stripped of external links
    ['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At, QuotePage, QuotePages}, 7)), -- pages stripped of external links
    ['Edition'] = Edition,
    ['Edition'] = Edition,
    ['PublisherName'] = PublisherName or Newsgroup, -- any apostrophe markup already removed from PublisherName
    ['PublisherName'] = PublisherName or Newsgroup, -- any apostrophe markup already removed from PublisherName
    Line 3,089: Line 3,137:
    local last_first_list;
    local last_first_list;
    local control = {  
    local control = {  
    format = NameListStyle, -- empty string or 'vanc'
    format = NameListStyle, -- empty string or 'vanc'
    maximum = nil, -- as if display-authors or display-editors not set
    maximum = nil, -- as if display-authors or display-editors not set
    lastauthoramp = LastAuthorAmp,
    mode = Mode
    mode = Mode
    };
    };
    Line 3,097: Line 3,144:
    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);
    last_first_list, EditorCount = list_people(control, e, editor_etal);
    Editors, EditorCount = list_people (control, e, editor_etal);
     
    if utilities.is_set (Editors) then
    Editors, editor_etal = name_has_etal (Editors, editor_etal, false, 'editors'); -- find and remove variations on et al.
    if editor_etal then
    Editors = Editors .. ' ' .. cfg.messages['et al']; -- add et al. to editors parameter beause |display-editors=etal
    end
    EditorCount = 2; -- we don't know but assume |editors= is multiple names; spoof to display (eds.) annotation
    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  
    Line 3,305: Line 3,342:
    TitleLink = ''; -- unset
    TitleLink = ''; -- unset
    end
    end
    if not utilities.is_set (TitleLink) and utilities.is_set (URL) then
    if not utilities.is_set (TitleLink) and utilities.is_set (URL) then
    Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. TransError .. Format;
    Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. TransError .. Format;
    Line 3,322: Line 3,359:
    else
    else
    local ws_url, ws_label, L; -- Title has italic or quote markup by the time we get here which causes is_wikilink() to return 0 (not a wikilink)
    local ws_url, ws_label, L; -- Title has italic or quote markup by the time we get here which causes is_wikilink() to return 0 (not a wikilink)
    ws_url, ws_label, L = wikisource_url_make (Title:gsub('[\'"](.-)[\'"]', '%1')); -- make ws URL from |title= interwiki link (strip italic or quote markup); link portion L becomes tooltip label
    ws_url, ws_label, L = wikisource_url_make (Title:gsub('^[\'"]*(.-)[\'"]*$', '%1')); -- make ws URL from |title= interwiki link (strip italic or quote markup); link portion L becomes tooltip label
    if ws_url then
    if ws_url then
    Title = Title:gsub ('%b[]', ws_label); -- replace interwiki link with ws_label to retain markup
    Title = Title:gsub ('%b[]', ws_label); -- replace interwiki link with ws_label to retain markup
    Line 3,415: Line 3,452:
    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
    if Edition:match ('%f[%a][Ee]d%.?$') or Edition:match ('%f[%a][Ee]dition$') then
    if Edition:match ('%f[%a][Ee]d%n?%.?$') or Edition:match ('%f[%a][Ee]dition$') then -- Ed, ed, Ed., ed., Edn, edn, Edn., edn.
    utilities.set_message ('maint_extra_text', 'edition'); -- add maint cat
    table.insert( z.message_tail, { utilities.set_message ( 'err_extra_text_edition')}); -- add error
    end
    end
    Edition = " " .. wrap_msg ('edition', Edition);
    Edition = " " .. wrap_msg ('edition', Edition);
    Line 3,474: Line 3,511:
    end
    end


    if utilities.is_set (QuotePage) or utilities.is_set (QuotePages) then -- add page prefix
    -- 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
    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.
    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,483: Line 3,522:
    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.
    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,726: Line 3,766:
    if utilities.is_set (PostScript) and PostScript ~= sepc then
    if utilities.is_set (PostScript) and PostScript ~= sepc then
    text = safe_join( {text, sepc}, sepc ); --Deals with italics, spaces, etc.
    text = safe_join( {text, sepc}, sepc ); -- Deals with italics, spaces, etc.
    text = text:sub(1, -sepc:len() - 1);
    text = text:sub(1, -sepc:len() - 1);
    end
    end
    Line 3,798: Line 3,838:
    table.insert (maint, v); -- maint msg is the category name
    table.insert (maint, v); -- maint msg is the category name
    table.insert (maint, ' ('); -- open the link text
    table.insert (maint, ' ('); -- open the link text
    table.insert (maint, utilities.make_wikilink (':Category:' .. v, 'link')); -- add the link
    table.insert (maint, utilities.substitute (cfg.messages[':cat wikilink'], {v})); -- add the link
    table.insert (maint, ')'); -- and close it
    table.insert (maint, ')'); -- and close it
    table.insert (maint_msgs, table.concat (maint)); -- assemble new maint message and add it to the maint_msgs table
    table.insert (maint_msgs, table.concat (maint)); -- assemble new maint message and add it to the maint_msgs table
    Line 3,806: Line 3,846:
    if not no_tracking_cats then
    if not no_tracking_cats then
    for _, v in ipairs( z.error_categories ) do
    for _, v in ipairs( z.error_categories ) do -- append error categories
    table.insert (render, utilities.make_wikilink ('Category:' .. v));
    table.insert (render, utilities.substitute (cfg.messages['cat wikilink'], {v}));
    end
    end
    for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
    for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
    table.insert (render, utilities.make_wikilink ('Category:' .. v));
    table.insert (render, utilities.substitute (cfg.messages['cat wikilink'], {v}));
    end
    end
    for _, v in ipairs( z.properties_cats ) do -- append properties categories
    for _, v in ipairs( z.properties_cats ) do -- append properties categories
    table.insert (render, utilities.make_wikilink ('Category:' .. v));
    table.insert (render, utilities.substitute (cfg.messages['cat wikilink'], {v}));
    end
    end
    elseif 'nocat' == A:ORIGIN('NoTracking') then -- peculiar special case; can't track nocat without tracking nocat
    table.insert (render, utilities.make_wikilink ('Category:CS1 maint: nocat')); -- hand-set this category because it is supposed to be temporary
    end
    end


    Line 4,038: Line 4,076:
    end
    end
    end
    end
    if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an expicit 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 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 );
    Line 4,081: Line 4,119:
    end
    end


    return table.concat ({citation0( config, args), frame:extensionTag ('templatestyles', '', {src=styles})});
    return table.concat ({
    frame:extensionTag ('templatestyles', '', {src=styles}),
    citation0( config, args)
    });
    end
    end