Module:Citation/CS1: Difference between revisions

    (sync from sandbox;)
    (sync from sandbox;)
    Line 22: Line 22:
    --[[--------------------------< P A G E  S C O P E  V A R I A B L E S >--------------------------------------
    --[[--------------------------< P A G E  S C O P E  V A R I A B L E S >--------------------------------------


    delare variables here that have page-wide scope that are not brought in from other modules; thatare created here
    declare variables here that have page-wide scope that are not brought in from other modules; that are created here and used here
    and used here


    ]]
    ]]
    Line 695: Line 694:
    ]]
    ]]


    local function format_chapter_title (scriptchapter, script_chapter_source, chapter, transchapter, chapterurl, chapter_url_source, no_quotes, access)
    local function format_chapter_title (script_chapter, script_chapter_source, chapter, chapter_source, trans_chapter, trans_chapter_source, chapter_url, chapter_url_source, no_quotes, access)
    local chapter_error = '';
    local chapter_error = '';


    Line 713: Line 712:
    end
    end


    chapter = script_concatenate (chapter, scriptchapter, script_chapter_source); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
    chapter = script_concatenate (chapter, script_chapter, script_chapter_source); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped


    if is_set (chapterurl) then
    if is_set (chapter_url) then
    chapter = external_link (chapterurl, chapter, chapter_url_source, access); -- adds bare_url_missing_title error if appropriate
    chapter = external_link (chapter_url, chapter, chapter_url_source, access); -- adds bare_url_missing_title error if appropriate
    elseif ws_url then
    elseif ws_url then
    chapter = external_link (ws_url, chapter .. '&nbsp;', 'ws link in chapter'); -- adds bare_url_missing_title error if appropriate; space char to move icon away from chap text; TODO: better way to do this?
    chapter = external_link (ws_url, chapter .. '&nbsp;', 'ws link in chapter'); -- adds bare_url_missing_title error if appropriate; space char to move icon away from chap text; TODO: better way to do this?
    Line 722: Line 721:
    end
    end


    if is_set (transchapter) then
    if is_set (trans_chapter) then
    transchapter = wrap_style ('trans-quoted-title', transchapter);
    trans_chapter = wrap_style ('trans-quoted-title', trans_chapter);
    if is_set (chapter) then
    if is_set (chapter) then
    chapter = chapter ..  ' ' .. transchapter;
    chapter = chapter ..  ' ' .. trans_chapter;
    else -- here when transchapter without chapter or script-chapter
    else -- here when trans_chapter without chapter or script-chapter
    chapter = transchapter;
    chapter = trans_chapter;
    chapter_error = ' ' .. set_error ('trans_missing_title', {'chapter'});
    chapter_source = trans_chapter_source:match ('trans%-?(.+)'); -- when no chapter, get matching name from trans-<param>
    chapter_error = ' ' .. set_error ('trans_missing_title', {chapter_source});
    end
    end
    end
    end
    Line 882: Line 882:


    local function set_titletype (cite_class, title_type)
    local function set_titletype (cite_class, title_type)
    if is_set(title_type) then
    if is_set (title_type) then
     
    if 'none' == cfg.keywords_xlate[title_type] then
    if cfg.keywords_xlate[title_type] == 'none' then
    title_type = ''; -- if |type=none then type parameter not displayed
    title_type = ""; -- if |type=none then type parameter not displayed
    end
    end
    return title_type; -- if |type= has been set to any other value use that value
    return title_type; -- if |type= has been set to any other value use that value
    Line 1,074: Line 1,073:
    ]]
    ]]


    local function is_good_vanc_name (last, first)
    local function is_good_vanc_name (last, first, suffix)
    local first, suffix = first:match ('(.-),?%s*([%dJS][%drndth]+)%.?$') or first; -- if first has something that looks like a generational suffix, get it
    if not suffix then
     
    if first:find ('[,%s]') then -- when there is a space or comma, might be first name/initials + generational suffix
    first = first:match ('(.-)[,%s]+'); -- get name/initials
    suffix = first:match ('[,%s]+(.+)$'); -- get generational suffix
    end
    end
    if is_set (suffix) then
    if is_set (suffix) then
    if not is_suffix (suffix) then
    if not is_suffix (suffix) then
    Line 1,085: Line 1,088:
    if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
    if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
    nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
    nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
    add_vanc_error (cfg.err_msg_supl['non-Latin character']);
    add_vanc_error (cfg.err_msg_supl['non-Latin char']);
    return false; -- not a string of latin characters; Vancouver requires Romanization
    return false; -- not a string of latin characters; Vancouver requires Romanization
    end;
    end;
    Line 1,118: Line 1,121:
    return first; -- one or two initials and a valid suffix so nothing to do
    return first; -- one or two initials and a valid suffix so nothing to do
    else
    else
    add_vanc_error (cfg.err_msg_supl.suffix); -- one or two initials with invalid suffix so error message
    add_vanc_error (cfg.err_msg_supl.suffix); -- one or two initials with invalid suffix so error message
    return first; -- and return first unmolested
    return first; -- and return first unmolested
    end
    end
    Line 1,549: Line 1,552:
    add_prop_cat ('foreign_lang_source' .. code, {name, code}); -- categorize it; code appended to allow for multiple language categorization
    add_prop_cat ('foreign_lang_source' .. code, {name, code}); -- categorize it; code appended to allow for multiple language categorization
    else -- or is a recognized language (but has a three-character code)
    else -- or is a recognized language (but has a three-character code)
    add_prop_cat ('foreign_lang_source_2' .. code, {code}); -- categorize it differently TODO: support mutliple three-character code categories per cs1|2 template
    add_prop_cat ('foreign_lang_source_2' .. code, {code}); -- categorize it differently TODO: support multiple three-character code categories per cs1|2 template
    end
    end
    elseif cfg.local_lang_cat_enable then -- when the language and this wiki's language are the same and categorization is enabled
    elseif cfg.local_lang_cat_enable then -- when the language and this wiki's language are the same and categorization is enabled
    Line 1,835: Line 1,838:


    for i, v_name in ipairs(v_name_table) do
    for i, v_name in ipairs(v_name_table) do
    first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
    if v_name:match ('^%(%(.+%)%)$') then -- corporate authors are wrapped in doubled parentheses to supress vanc formatting and error detection
    if v_name:match ('^%(%(.+%)%)$') then -- corporate authors are wrapped in doubled parentheses to supress vanc formatting and error detection
    first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
    last = v_name:match ('^%(%((.+)%)%)$') -- remove doubled parntheses
    last = v_name:match ('^%(%((.+)%)%)$') -- remove doubled parntheses
    corporate = true; -- flag used in list_people()
    corporate = true; -- flag used in list_people()
    Line 1,844: Line 1,847:
    end
    end
    local lastfirstTable = {}
    local lastfirstTable = {}
    lastfirstTable = mw.text.split(v_name, "%s")
    lastfirstTable = mw.text.split(v_name, "%s+")
    first = table.remove(lastfirstTable); -- removes and returns value of last element in table which should be author intials
    first = table.remove(lastfirstTable); -- removes and returns value of last element in table which should be intials or generational suffix
    if is_suffix (first) then -- if a valid suffix
     
    suffix = first -- save it as a suffix and
    if not mw.ustring.match (first, '^%u+$') then -- mw.ustring here so that later we will catch non-latin characters
    suffix = first; -- not initials so assume that whatever we got is a generational suffix
    first = table.remove(lastfirstTable); -- get what should be the initials from the table
    first = table.remove(lastfirstTable); -- get what should be the initials from the table
    end -- no suffix error message here because letter combination may be result of Romanization; check for digits?
    end
    last = table.concat(lastfirstTable, " ") -- returns a string that is the concatenation of all other names that are not initials
    last = table.concat(lastfirstTable, ' ') -- returns a string that is the concatenation of all other names that are not initials and generational suffix
    if not is_set (last) then
    first = ''; -- unset
    last = v_name; -- last empty because something wrong with first
    add_vanc_error (cfg.err_msg_supl.name);
    end
    if mw.ustring.match (last, '%a+%s+%u+%s+%a+') then
    if mw.ustring.match (last, '%a+%s+%u+%s+%a+') then
    add_vanc_error (cfg.err_msg_supl['missing comma']); -- matches last II last; the case when a comma is missing
    add_vanc_error (cfg.err_msg_supl['missing comma']); -- matches last II last; the case when a comma is missing
    end
    end
    if mw.ustring.match (v_name, ' %u %u$') then -- this test is in the wrong place TODO: move or replace with a more appropriate test
    if mw.ustring.match (v_name, ' %u %u$') then -- this test is in the wrong place TODO: move or replace with a more appropriate test
    add_vanc_error (cfg.err_msg_supl.name); -- matches a space between two intiials
    add_vanc_error (cfg.err_msg_supl.name); -- matches a space between two intiials
    end
    end
    else
    else
    first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
    last = v_name; -- last name or single corporate name?  Doesn't support multiword corporate names? do we need this?
    last = v_name; -- last name or single corporate name?  Doesn't support multiword corporate names? do we need this?
    end
    end
    Line 1,864: Line 1,872:
    if is_set (first) then
    if is_set (first) then
    if not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
    if not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
    add_vanc_error (cfg.err_msg_supl.initials); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials
    add_vanc_error (cfg.err_msg_supl.initials); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials
    end
    end
    is_good_vanc_name (last, first); -- check first and last before restoring the suffix which may have a non-Latin digit
    is_good_vanc_name (last, first, suffix); -- check first and last before restoring the suffix which may have a non-Latin digit
    if is_set (suffix) then
    if is_set (suffix) then
    first = first .. ' ' .. suffix; -- if there was a suffix concatenate with the initials
    first = first .. ' ' .. suffix; -- if there was a suffix concatenate with the initials
    Line 2,300: Line 2,308:
    local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
    local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
    local Contributors; -- assembled contributors name list
    local Contributors; -- assembled contributors name list
    local Contribution = A['Contribution']; -- TODO: move to after chapter use if A:ORIGIN ('Chapter') ... to set Contribution; this to remove duplicate in aliases list
     
    local Chapter = A['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases
    local Chapter_origin = A:ORIGIN ('Chapter');
    local Contribution; -- because contribution is required for contributor(s)
    if 'contribution' == A:ORIGIN ('Chapter') then
    Contribution = A['Chapter']; -- get the name of the contribution
    end
     
    if in_array(config.CitationClass, {"book","citation"}) and not is_set(A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
    if in_array(config.CitationClass, {"book","citation"}) and not is_set(A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
    c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
    c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
    Line 2,338: Line 2,353:
    local Conference = A['Conference'];
    local Conference = A['Conference'];
    local TransTitle = A['TransTitle'];
    local TransTitle = A['TransTitle'];
    local TransTitle_origin = A:ORIGIN ('TransTitle');
    local TitleNote = A['TitleNote'];
    local TitleNote = A['TitleNote'];
    local TitleLink = A['TitleLink'];
    local TitleLink = A['TitleLink'];
    link_title_ok (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wikimarkup in |title-link= or wikimarkup in |title= when |title-link= is set
    link_title_ok (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wikimarkup in |title-link= or wikimarkup in |title= when |title-link= is set


    local Chapter = A['Chapter']; -- TODO: insert test, assignment, and then unset Chapter when |section= in cite map; test is at c. line 3220
    local Section = ''; -- {{cite map}} only; preset to empty string for concatnation if not used
    if 'map' == config.CitationClass and 'section' == A:ORIGIN ('Chapter') then
    Section = A['Chapter']; -- get |section= from |chapter= alias list; |chapter= and the other aliases not supported in {{cite map}}
    Chapter = ''; -- unset for now; will be reset later from |map= if present
    end
     
    local ScriptChapter = A['ScriptChapter'];
    local ScriptChapter = A['ScriptChapter'];
    local ScriptChapterOrigin = A:ORIGIN ('ScriptChapter');
    local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
    local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
    local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
    local TransChapter = A['TransChapter'];
    local TransChapter = A['TransChapter'];
    local TransChapter_origin = A:ORIGIN ('TransChapter');
    local TitleType = A['TitleType'];
    local TitleType = A['TitleType'];
    local Degree = A['Degree'];
    local Degree = A['Degree'];
    Line 2,361: Line 2,383:


    local URL = A['URL']
    local URL = A['URL']
    local URLorigin = A:ORIGIN('URL'); -- get name of parameter that holds URL
    local URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
    local ChapterURL = A['ChapterURL'];
    local ChapterURL = A['ChapterURL'];
    local ChapterURLorigin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
    local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
    local ConferenceFormat = A['ConferenceFormat'];
    local ConferenceFormat = A['ConferenceFormat'];
    local ConferenceURL = A['ConferenceURL'];
    local ConferenceURL = A['ConferenceURL'];
    local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
    local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL


    -- TODO: mailinglist should be removed from Periodical alias list; Periodical should be assigned value from |mailinglist= here wh
    local Periodical = A['Periodical'];
    local Periodical = A['Periodical']; -- cite mailinglist or citation and |mailinglist=; error check for duplicate work params (Periodical already set)
    local Periodical_origin = '';
    local Periodical_origin = '';
    if is_set (Periodical) then
    if is_set (Periodical) then
    Line 2,379: Line 2,400:
    end
    end
    end
    end
    -- TODO: mailinglist assignment here? iff Periodical not set; err msg else; see TODO c. line 2607
     
    if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
    if is_set (Periodical) and is_set (A ['MailingList']) then -- both set emit an error
    table.insert( z.message_tail, { set_error('redundant_parameters', {wrap_style ('parameter', Periodical_origin) .. ' and ' .. wrap_style ('parameter', 'mailinglist')}, true )});
    end
     
    Periodical = A ['MailingList']; -- error or no, set Periodical to |mailinglist= value because this template is {{cite mailing list}}
    Periodical_origin = A:ORIGIN('MailingList');
    end
     
     
     
    local ScriptPeriodical = A['ScriptPeriodical'];
    local ScriptPeriodical = A['ScriptPeriodical'];
    local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
    local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
    Line 2,393: Line 2,426:


    local TransPeriodical =  A['TransPeriodical'];
    local TransPeriodical =  A['TransPeriodical'];
    local TransPeriodical_origin =  A:ORIGIN ('TransPeriodical');


    local Series = A['Series'];
    local Series = A['Series'];
    Line 2,511: Line 2,545:
    local TranscriptFormat = A['TranscriptFormat'];
    local TranscriptFormat = A['TranscriptFormat'];
    local TranscriptURL = A['TranscriptURL']  
    local TranscriptURL = A['TranscriptURL']  
    local TranscriptURLorigin = 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 LastAuthorAmp = is_valid_parameter_value (A['LastAuthorAmp'], A:ORIGIN('LastAuthorAmp'), cfg.keywords_lists['yes_true_y'], nil);
    Line 2,585: Line 2,619:
    ]]
    ]]


    local Encyclopedia = A['Encyclopedia'];
    local Encyclopedia = A['Encyclopedia']; -- used as a flag by this module and by ~/COinS
     
    if is_set (Encyclopedia) then -- emit error message when Encyclopedia set but template is other than {{cite encyclopedia}} or {{citation}}
    if 'encyclopaedia' ~= config.CitationClass and 'citation' ~= config.CitationClass then
    table.insert (z.message_tail, {set_error ('parameter_ignored', {A:ORIGIN ('Encyclopedia')}, true)});
    Encyclopedia = nil; -- unset because not supported by this template
    end
    end
     
    if ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and is_set (Encyclopedia)) then
    if is_set (Periodical) and is_set (Encyclopedia) then -- when both set emit an error
    table.insert (z.message_tail, {set_error('redundant_parameters', {wrap_style ('parameter', A:ORIGIN ('Encyclopedia')) .. ' and ' .. wrap_style ('parameter', Periodical_origin)}, true )});
    end
     
    if is_set (Encyclopedia) then
    Periodical = Encyclopedia; -- error or no, set Periodical to Encyclopedia; allow periodical without encyclopedia
    Periodical_origin = A:ORIGIN ('Encyclopedia');
    end


    if ( config.CitationClass == "encyclopaedia" ) or ( config.CitationClass == "citation" and is_set (Encyclopedia)) then -- test code for citation
    if is_set (Periodical) then -- Periodical is set when |encyclopedia is set
    if is_set (Periodical) then -- Periodical is set when |encyclopedia is set
    if is_set(Title) or is_set (ScriptTitle) then
    if is_set(Title) or is_set (ScriptTitle) then
    Line 2,593: Line 2,643:
    Chapter = Title; -- |encyclopedia and |title are set so map |title to |article and |encyclopedia to |title
    Chapter = Title; -- |encyclopedia and |title are set so map |title to |article and |encyclopedia to |title
    ScriptChapter = ScriptTitle;
    ScriptChapter = ScriptTitle;
    ScriptChapterOrigin = A:ORIGIN('ScriptTitle')
    ScriptChapter_origin = A:ORIGIN('ScriptTitle')
    TransChapter = TransTitle;
    TransChapter = TransTitle;
    ChapterURL = URL;
    ChapterURL = URL;
    ChapterURLorigin = A:ORIGIN('URL')
    ChapterURL_origin = A:ORIGIN('URL')


    ChapterUrlAccess = UrlAccess;
    ChapterUrlAccess = UrlAccess;
    Line 2,628: Line 2,678:
    end
    end
    end
    end
    end
    -- special case for cite mailing list
    if (config.CitationClass == "mailinglist") then -- TODO: move this to Periodical assignment; see TODOs at c. line 2360
    Periodical = A ['MailingList'];
    elseif 'mailinglist' == Periodical_origin then
    Periodical = ''; -- unset because mailing list is only used for cite mailing list
    end
    end


    Line 2,641: Line 2,684:
    if is_set(BookTitle) then
    if is_set(BookTitle) then
    Chapter = Title;
    Chapter = Title;
    Chapter_origin = 'title';
    -- ChapterLink = TitleLink; -- |chapterlink= is deprecated
    -- ChapterLink = TitleLink; -- |chapterlink= is deprecated
    ChapterURL = URL;
    ChapterURL = URL;
    ChapterUrlAccess = UrlAccess;
    ChapterUrlAccess = UrlAccess;
    ChapterURLorigin = URLorigin;
    ChapterURL_origin = URL_origin;
    URLorigin = '';
    URL_origin = '';
    ChapterFormat = Format;
    ChapterFormat = Format;
    TransChapter = TransTitle;
    TransChapter = TransTitle;
    TransChapter_origin = TransTitle_origin;
    Title = BookTitle;
    Title = BookTitle;
    Format = '';
    Format = '';
    Line 2,664: Line 2,709:
    local Sheets = A['Sheets'] or '';
    local Sheets = A['Sheets'] or '';
    if config.CitationClass == "map" then
    if config.CitationClass == "map" then
    if is_set (Chapter) then
    table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'map') .. ' and ' .. wrap_style ('parameter', Chapter_origin)}, true ) } ); -- add error message
    end
    Chapter = A['Map'];
    Chapter = A['Map'];
    Chapter_origin = A:ORIGIN('Map');
    ChapterURL = A['MapURL'];
    ChapterURL = A['MapURL'];
    ChapterURLorigin = A:ORIGIN('MapURL');
    ChapterURL_origin = A:ORIGIN('MapURL');
    TransChapter = A['TransMap'];
    TransChapter = A['TransMap'];
    ScriptChapter = A['ScriptMap']
    ScriptChapter = A['ScriptMap']
    ScriptChapterOrigin = A:ORIGIN('ScriptMap')
    ScriptChapter_origin = A:ORIGIN('ScriptMap')


    ChapterUrlAccess = MapUrlAccess;
    ChapterUrlAccess = MapUrlAccess;
    Line 2,714: Line 2,763:
    Chapter = Title; -- promote title parameters to chapter
    Chapter = Title; -- promote title parameters to chapter
    ScriptChapter = ScriptTitle;
    ScriptChapter = ScriptTitle;
    ScriptChapterOrigin = A:ORIGIN('ScriptTitle');
    ScriptChapter_origin = A:ORIGIN('ScriptTitle');
    ChapterLink = TitleLink; -- alias episodelink
    ChapterLink = TitleLink; -- alias episodelink
    TransChapter = TransTitle;
    TransChapter = TransTitle;
    ChapterURL = URL;
    ChapterURL = URL;
    ChapterUrlAccess = UrlAccess;
    ChapterUrlAccess = UrlAccess;
    ChapterURLorigin = A:ORIGIN('URL');
    ChapterURL_origin = A:ORIGIN('URL');
    Title = Series; -- promote series to title
    Title = Series; -- promote series to title
    Line 2,859: Line 2,908:
    end -- end of do
    end -- end of do


    -- Account for the oddity that is {{cite journal}} with |pmc= set and |url= not set.  Do this after date check but before COInS.
    -- Link the title of the work if no |url= was provided, but we have a |pmc= or a |doi= with |doi-access=free
    -- Here we unset Embargo if PMC not embargoed (|embargo= not set in the citation) or if the embargo time has expired. Otherwise, holds embargo date
    -- Here we unset Embargo if PMC not embargoed (|embargo= not set in the citation) or if the embargo time has expired. Otherwise, holds embargo date
    Embargo = is_embargoed (Embargo);
    Embargo = is_embargoed (Embargo);
     
    if config.CitationClass == "journal" and not is_set(URL) and is_set(ID_list['PMC']) then
    if config.CitationClass == "journal" and not is_set(URL) and not is_set(TitleLink) then
    if not is_set (Embargo) then -- if not embargoed or embargo has expired
    if is_set(ID_list['PMC']) and not is_set (Embargo) then -- if not embargoed or embargo has expired
    URL=cfg.id_handlers['PMC'].prefix .. ID_list['PMC']; -- set url to be the same as the PMC external link if not embargoed
    URL=cfg.id_handlers['PMC'].prefix .. ID_list['PMC']; -- set url to be the same as the PMC external link if not embargoed
    URLorigin = cfg.id_handlers['PMC'].parameters[1]; -- set URLorigin to parameter name for use in error message if citation is missing a |title=
    URL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
    if is_set(AccessDate) then -- access date requires |url=; pmc created url is not |url=
    elseif is_set(ID_list['DOI']) and ID_access_levels['DOI'] == "free" then
    table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } );
    URL=cfg.id_handlers['DOI'].prefix .. ID_list['DOI'];
    AccessDate = ''; -- unset
    URL_origin = cfg.id_handlers['DOI'].parameters[1];
    end
    end
     
    if is_set(URL) and is_set(AccessDate) then -- access date requires |url=; pmc or doi created url is not |url=
    table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } );
    AccessDate = ''; -- unset
    end
    end
    end
    end
    Line 2,877: Line 2,928:
    -- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
    -- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
    -- Test if citation has no title
    -- Test if citation has no title
    if not is_set(Title) and
    if not is_set(Title) and not is_set(TransTitle) and not is_set(ScriptTitle) then -- has special case for cite episode
    not is_set(TransTitle) and
    table.insert( z.message_tail, { set_error( 'citation_missing_title', {'episode' == config.CitationClass and 'series' or 'title'}, true ) } );
    not is_set(ScriptTitle) then
    if 'episode' == config.CitationClass then -- special case for cite episode; TODO: is there a better way to do this?
    table.insert( z.message_tail, { set_error( 'citation_missing_title', {'series'}, true ) } );
    else
    table.insert( z.message_tail, { set_error( 'citation_missing_title', {'title'}, true ) } );
    end
    end
    end


    Line 2,923: Line 2,968:
    local OCinSoutput = COinS({
    local OCinSoutput = COinS({
    ['Periodical'] = strip_apostrophe_markup (Periodical), -- no markup in the metadata
    ['Periodical'] = strip_apostrophe_markup (Periodical), -- no markup in the metadata
    ['Encyclopedia'] = strip_apostrophe_markup (Encyclopedia),
    ['Encyclopedia'] = Encyclopedia, -- just a flag; content ignored by ~/COinS
    ['Chapter'] = make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic wikimarkup
    ['Chapter'] = make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic wikimarkup
    ['Degree'] = Degree; -- cite thesis only
    ['Degree'] = Degree; -- cite thesis only
    Line 2,930: Line 2,975:
    ['Date'] = COinS_date.rftdate, -- COinS_date has correctly formatted date if Date is valid;
    ['Date'] = COinS_date.rftdate, -- COinS_date has correctly formatted date if Date is valid;
    ['Season'] = COinS_date.rftssn,
    ['Season'] = COinS_date.rftssn,
    ['Quarter'] = COinS_date.rftquarter,
    ['Chron'] =  COinS_date.rftchron or (not COinS_date.rftdate and Date) or '', -- chron but if not set and invalid date format use Date; keep this last bit?
    ['Chron'] =  COinS_date.rftchron or (not COinS_date.rftdate and Date) or '', -- chron but if not set and invalid date format use Date; keep this last bit?
    ['Series'] = Series,
    ['Series'] = Series,
    Line 3,044: Line 3,090:
    end
    end


    local OriginalURL, OriginalURLorigin, OriginalFormat, OriginalAccess;
    local OriginalURL, OriginalURL_origin, OriginalFormat, OriginalAccess;
    UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
    UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
    if is_set( ArchiveURL ) then
    if is_set( ArchiveURL ) then
    if is_set (ChapterURL) then -- if chapter-url is set apply archive url to it
    if is_set (ChapterURL) then -- if chapter-url is set apply archive url to it
    OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text
    OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text
    OriginalURLorigin = ChapterURLorigin; -- name of chapter-url parameter for error messages
    OriginalURL_origin = ChapterURL_origin; -- name of chapter-url parameter for error messages
    OriginalFormat = ChapterFormat; -- and original |chapter-format=
    OriginalFormat = ChapterFormat; -- and original |chapter-format=


    if 'live' ~= UrlStatus then
    if 'live' ~= UrlStatus then
    ChapterURL = ArchiveURL -- swap-in the archive's url
    ChapterURL = ArchiveURL -- swap-in the archive's url
    ChapterURLorigin = A:ORIGIN('ArchiveURL') -- name of archive-url parameter for error messages
    ChapterURL_origin = A:ORIGIN('ArchiveURL') -- name of archive-url parameter for error messages
    ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
    ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
    ChapterUrlAccess = nil; -- restricted access levels do not make sense for archived urls
    ChapterUrlAccess = nil; -- restricted access levels do not make sense for archived urls
    Line 3,060: Line 3,106:
    elseif is_set (URL) then
    elseif is_set (URL) then
    OriginalURL = URL; -- save copy of original source URL
    OriginalURL = URL; -- save copy of original source URL
    OriginalURLorigin = URLorigin; -- name of url parameter for error messages
    OriginalURL_origin = URL_origin; -- name of url parameter for error messages
    OriginalFormat = Format; -- and original |format=
    OriginalFormat = Format; -- and original |format=
    OriginalAccess = UrlAccess;
    OriginalAccess = UrlAccess;
    Line 3,066: Line 3,112:
    if 'live' ~= UrlStatus then -- if URL set then archive-url applies to it
    if 'live' ~= UrlStatus then -- if URL set then archive-url applies to it
    URL = ArchiveURL -- swap-in the archive's url
    URL = ArchiveURL -- swap-in the archive's url
    URLorigin = A:ORIGIN('ArchiveURL') -- name of archive url parameter for error messages
    URL_origin = A:ORIGIN('ArchiveURL') -- name of archive url parameter for error messages
    Format = ArchiveFormat or ''; -- swap in archive's format
    Format = ArchiveFormat or ''; -- swap in archive's format
    UrlAccess = nil; -- restricted access levels do not make sense for archived urls
    UrlAccess = nil; -- restricted access levels do not make sense for archived urls
    Line 3,083: Line 3,129:
    chap_param = A:ORIGIN ('ChapterURL')
    chap_param = A:ORIGIN ('ChapterURL')
    elseif is_set (ScriptChapter) then
    elseif is_set (ScriptChapter) then
    chap_param = ScriptChapterOrigin;
    chap_param = ScriptChapter_origin;
    else is_set (ChapterFormat)
    else is_set (ChapterFormat)
    chap_param = A:ORIGIN ('ChapterFormat')
    chap_param = A:ORIGIN ('ChapterFormat')
    Line 3,104: Line 3,150:
    end
    end


    Chapter = format_chapter_title (ScriptChapter, ScriptChapterOrigin, Chapter, TransChapter, ChapterURL, ChapterURLorigin, no_quotes, ChapterUrlAccess); -- Contribution is also in Chapter
    Chapter = format_chapter_title (ScriptChapter, ScriptChapter_origin, Chapter, Chapter_origin, TransChapter, TransChapter_origin, ChapterURL, ChapterURL_origin, no_quotes, ChapterUrlAccess); -- Contribution is also in Chapter
    if is_set (Chapter) then
    if is_set (Chapter) then
    Chapter = Chapter .. ChapterFormat ;
    Chapter = Chapter .. ChapterFormat ;
    Line 3,116: Line 3,162:
    end
    end


    -- Format main title. TODO: add support for non-English versions of 'Archived copy' when used on other-language wikis
    -- Format main title
    if is_set (ArchiveURL) and  
    if is_set (ArchiveURL) and  
    (mw.ustring.match (mw.ustring.lower(Title), cfg.special_case_translation.archived_copy.en) or -- if title is 'Archived copy' (place holder added by bots that can't find proper title)
    (mw.ustring.match (mw.ustring.lower(Title), cfg.special_case_translation.archived_copy.en) or -- if title is 'Archived copy' (place holder added by bots that can't find proper title)
    Line 3,160: Line 3,206:


    if is_set (Title) then -- TODO: is this the right place to be making wikisource urls?
    if is_set (Title) then -- TODO: is this the right place to be making wikisource urls?
    if is_set (TitleLink) and is_set (URL) then
    table.insert( z.message_tail, { set_error( 'wikilink_in_url', {}, true ) } ); -- set an error message because we can't have both
    TitleLink = ''; -- unset
    end
    if not is_set (TitleLink) and is_set (URL) then
    if not is_set (TitleLink) and is_set (URL) then
    Title = external_link (URL, Title, URLorigin, UrlAccess) .. TransTitle .. TransError .. Format;
    Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. TransError .. Format;
    URL = ''; -- unset these because no longer needed
    URL = ''; -- unset these because no longer needed
    Format = "";
    Format = "";
    Line 3,196: Line 3,247:
    if is_set (Conference) then
    if is_set (Conference) then
    if is_set (ConferenceURL) then
    if is_set (ConferenceURL) then
    Conference = external_link( ConferenceURL, Conference, ConferenceURLorigin, nil );
    Conference = external_link( ConferenceURL, Conference, ConferenceURL_origin, nil );
    end
    end
    Conference = sepc .. " " .. Conference .. ConferenceFormat;
    Conference = sepc .. " " .. Conference .. ConferenceFormat;
    elseif is_set(ConferenceURL) then
    elseif is_set(ConferenceURL) then
    Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURLorigin, nil );
    Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURL_origin, nil );
    end
    end


    Line 3,233: Line 3,284:
    At = is_set(At) and (sepc .. " " .. At) or "";
    At = is_set(At) and (sepc .. " " .. At) or "";
    Position = is_set(Position) and (sepc .. " " .. Position) or "";
    Position = is_set(Position) and (sepc .. " " .. Position) or "";
    if config.CitationClass == 'map' then -- TODO copy this line and
    if config.CitationClass == 'map' then
    local Section = A['Section']; -- TODO move this line to c. line 2337 so that separate Sections in aliases{} not required? then unset Chapter?
    local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
    local Sections = A['Sections'];
    local Inset = A['Inset'];
    local Inset = A['Inset'];
    Line 3,307: Line 3,357:


    if is_set(URL) then
    if is_set(URL) then
    URL = " " .. external_link( URL, nil, URLorigin, UrlAccess );
    URL = " " .. external_link( URL, nil, URL_origin, UrlAccess );
    end
    end


    Line 3,346: Line 3,396:
    if sepc ~= "." then arch_text = arch_text:lower() end
    if sepc ~= "." then arch_text = arch_text:lower() end
    Archived = sepc .. " " .. substitute( arch_text,
    Archived = sepc .. " " .. substitute( arch_text,
    { external_link( OriginalURL, cfg.messages['original'], OriginalURLorigin, OriginalAccess ) .. OriginalFormat, ArchiveDate } ); -- format already styled
    { external_link( OriginalURL, cfg.messages['original'], OriginalURL_origin, OriginalAccess ) .. OriginalFormat, ArchiveDate } ); -- format already styled
    end
    end
    else -- OriginalUrl not set
    else -- OriginalUrl not set
    Line 3,379: Line 3,429:
    if is_set(Transcript) then
    if is_set(Transcript) then
    if is_set(TranscriptURL) then
    if is_set(TranscriptURL) then
    Transcript = external_link( TranscriptURL, Transcript, TranscriptURLorigin, nil );
    Transcript = external_link( TranscriptURL, Transcript, TranscriptURL_origin, nil );
    end
    end
    Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
    Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
    elseif is_set(TranscriptURL) then
    elseif is_set(TranscriptURL) then
    Transcript = external_link( TranscriptURL, nil, TranscriptURLorigin, nil );
    Transcript = external_link( TranscriptURL, nil, TranscriptURL_origin, nil );
    end
    end


    Line 3,405: Line 3,455:
    if (is_set (Periodical) or is_set (ScriptPeriodical) or is_set (TransPeriodical)) then
    if (is_set (Periodical) or is_set (ScriptPeriodical) or is_set (TransPeriodical)) then
    if is_set(Title) or is_set(TitleNote) then  
    if is_set(Title) or is_set(TitleNote) then  
    Periodical = sepc .. " " .. format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical);
    Periodical = sepc .. " " .. format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin);
    else  
    else  
    Periodical = format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical);
    Periodical = format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin);
    end
    end
    end
    end
    Line 3,554: Line 3,604:
    if is_set(config.CitationClass) and config.CitationClass ~= "citation" then
    if is_set(config.CitationClass) and config.CitationClass ~= "citation" then
    options.class = config.CitationClass;
    options.class = string.format ('%s %s %s', 'citation', config.CitationClass, is_set (Mode) and Mode or 'cs1'); -- class=citation required for blue highlight when used with |ref=
    options.class = "citation " .. config.CitationClass; -- class=citation required for blue highlight when used with |ref=
    else
    else
    options.class = "citation";
    options.class = string.format ('%s %s', 'citation', is_set (Mode) and Mode or 'cs2');
    end
    end
    Line 3,703: Line 3,752:
    Look at the contents of a parameter. If the content has a string of characters and digits followed by an equal
    Look at the contents of a parameter. If the content has a string of characters and digits followed by an equal
    sign, compare the alphanumeric string to the list of cs1|2 parameters.  If found, then the string is possibly a
    sign, compare the alphanumeric string to the list of cs1|2 parameters.  If found, then the string is possibly a
    parameter that is missing its pipe:
    parameter that is missing its pipe.  There are two tests made:
    {{cite ... |title=Title access-date=2016-03-17}}
    {{cite ... |title=Title access-date=2016-03-17}} -- the first parameter has a value and whitespace separates that value from the missing pipe parameter name
     
    {{cite ... |title=access-date=2016-03-17}} -- the first parameter has no value (whitespace after the first = is trimmed by mediawiki)
    cs1|2 shares some parameter names with xml/html atributes: class=, title=, etc.  To prevent false positives xml/html
    cs1|2 shares some parameter names with xml/html atributes: class=, title=, etc.  To prevent false positives xml/html
    tags are removed before the search.
    tags are removed before the search.
    Line 3,718: Line 3,767:


    capture = value:match ('%s+(%a[%w%-]+)%s*=') or value:match ('^(%a[%w%-]+)%s*='); -- find and categorize parameters with possible missing pipes
    capture = value:match ('%s+(%a[%w%-]+)%s*=') or value:match ('^(%a[%w%-]+)%s*='); -- find and categorize parameters with possible missing pipes
    if capture and validate (capture) then -- if the capture is a valid parameter name
    if capture and validate (capture) then -- if the capture is a valid parameter name
    table.insert( z.message_tail, {set_error ('missing_pipe',parameter)});
    table.insert( z.message_tail, {set_error ('missing_pipe', parameter)});
    end
    end
    end
    end
    Line 3,731: Line 3,780:


    local function has_extraneous_punc (param, value)
    local function has_extraneous_punc (param, value)
    if 'number' == type (param) then
    return;
    end
    param = param:gsub ('%d+', '#'); -- enumerated name-list mask params allow terminal punct; normalize
    if cfg.punct_skip[param] then
    if cfg.punct_skip[param] then
    return; -- parameter name found in the skip table so done
    return; -- parameter name found in the skip table so done
    Line 3,812: Line 3,866:


    local config = {}; -- table to store parameters from the module {{#invoke:}}
    local config = {}; -- table to store parameters from the module {{#invoke:}}
    for k, v in pairs( frame.args ) do
    for k, v in pairs( frame.args ) do -- get parameters from the {{#invoke}} frame
    config[k] = v;
    config[k] = v;
    -- args[k] = v; -- debug tool that allows us to render a citation from module {{#invoke:}}
    -- args[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
    end
    end


    local capture; -- the single supported capture when matching unknown parameters using patterns
    local capture; -- the single supported capture when matching unknown parameters using patterns
    for k, v in pairs( pframe.args ) do
    for k, v in pairs( pframe.args ) do -- get parameters from the parent (template) frame
    if v ~= '' then
    if v ~= '' then
    if ('string' == type (k)) then
    if ('string' == type (k)) then
    Line 3,865: Line 3,919:
    end
    end
    end
    end
    missing_pipe_check (k, v); -- do we think that there is a parameter that is missing a pipe?
     
    -- TODO: is this the best place for this translation?
    args[k] = v; -- save this parameter and its value
    args[k] = v;
     
    elseif args[k] ~= nil or (k == 'postscript') then -- here when v is empty string
    -- crude debug support that allows us to render a citation from module {{#invoke:}} TODO: keep?
    args[k] = v; -- why do we do this?  we don't support 'empty' parameters
    -- elseif args[k] ~= nil or (k == 'postscript') then -- when args[k] has a value from {{#invoke}} frame (we don't normally do that)
    end
    -- args[k] = v; -- overwrite args[k] with empty string from pframe.args[k] (template frame); v is empty string here
    end -- not sure about the postscript bit; that gets handled in parameter validation; historical artifact?
    end
    end


    for k, v in pairs( args ) do
    for k, v in pairs( args ) do
    if 'string' == type (k) then -- don't evaluate positional parameters
    has_invisible_chars (k, v); -- look for invisible characters
    has_invisible_chars (k, v);
    has_extraneous_punc (k, v); -- look for extraneous terminal punctuation in parameter values
    has_extraneous_punc (k, v); -- look for extraneous terminal punctuation in parameter values
    missing_pipe_check (k, v); -- do we think that there is a parameter that is missing a pipe?
    end
    end
    end