Module:Harvc

require('strict')

local code_open_tag = ''; -- cs1-code class defined in Module:Citation/CS1/styles.css

local lock_icons = { --icon classes are defined in Module:Citation/CS1/styles.css

['registration'] = {'id-lock-registration', 'Free registration required'},

['limited'] = {'id-lock-limited', 'Free access subject to limited trial, subscription normally required'},

['subscription'] = {'id-lock-subscription', 'Paid subscription required'},

}

--[[--------------------------< T A R G E T _ C H E C K >------------------------------------------------------

check to see if target of harvc exists in the page somewhere. Uses the target check mechanism from Module:Footnotes

setting |ignore-errors=yes in {{harvc}} will override this check

]]

local function target_check (anchor_id, ignore)

local whitelist_check = require('Module:Footnotes').target_check

local args = {ignore=ignore, template='Harvc', show=true}

return whitelist_check(anchor_id, args)

end

--[[--------------------------< I S _ S E T >------------------------------------------------------------------

Whether variable is set or not. A variable is set when it is not nil and not empty.

]]

local function is_set( var )

return not (var == nil or var == '');

end

--[[--------------------------< C H E C K _ Y E A R S >--------------------------------------------------------

evaluates params to see if they are one of these forms with or without lowercase letter disambiguator (same as in

Module:Footnotes):

YYYY

n.d.

nd

c. YYYY

YYYY–YYYY (separator is endash)

YYYY–YY (separator is endash)

when anchor_year present, year portion must be same as year param and must have disambiguator

returns empty string when params have correct form; error message else

]]

local function check_years (year, anchor_year)

local y, ay;

if not is_set (year) then -- year is required so return error message when not set

return ' missing ' .. code_open_tag .. '|year=.';

end

local patterns = { -- allowed year patterns from Module:Footnotes (captures added here)

'^(%d%d%d%d?)%l?$', -- YYY or YYYY

'^(n%.d%.)%l?$', -- n.d.

'^(nd)%l?$', -- nd

'^(c%. %d%d%d%d?)%l?$', -- c. YYY or c. YYYY

'^(%d%d%d%d–%d%d%d%d)%l?$', -- YYYY–YYYY

'^(%d%d%d%d–%d%d)%l?$' -- YYYY–YY

}

for _, pattern in ipairs (patterns) do -- spin through the patterns

y = year:match (pattern); -- y is the year portion

if y then

break; -- when y is set, we found a match so done

end

end

if not y then

return ' invalid ' .. code_open_tag .. '|year=.'; -- y not set, so year is malformed

end

if is_set (anchor_year) then -- anchor_year is optional

for _, pattern in ipairs (patterns) do -- spin through the patterns

ay = anchor_year:match (pattern); -- ay is the year portion

if ay then

break; -- when ay is set, we found a match so done

end

end

if not ay then

return ' invalid ' .. code_open_tag .. '|anchor-year.'; -- ay not set, so anchor_year is malformed

end

-- if not anchor_year:match ('%l$') then

-- return ' ' .. code_open_tag .. '|anchor-year= missing dab.'; -- anchor_year must end with a disambiguator letter

-- end

if y ~= ay then

return ' ' .. code_open_tag .. '|year= / ' .. code_open_tag .. '|anchor-year= mismatch.'; -- 'year' portions of year and anchor_year must be the same

end

end

return ''; -- both years are good; empty string for concatenation

end

--[[--------------------------< M A K E _ N A M E >------------------------------------------------------------

Assembles last, first, link, or mask into a displayable contributor name.

]]

local function make_name (last, first, link, mask)

local name = last;

if is_set (first) then

name = name .. ', ' .. first; -- concatenate first onto last

end

if is_set (link) then

name = '' .. name .. ''; -- form a wikilink around the name

end

if is_set (mask) then -- mask this author

if tonumber(mask) then

name = string.rep ('—', mask) -- make a string that number length of mdashes

else

name = mask; -- mask is not a number so use the mask text

end

end

return name;

end

--[[--------------------------< C O R E >----------------------------------------------------------------------

Assembles the various parts provided by the template into a properly formatted bridging citation. Adds punctuation

and text; encloses the whole within a span with id and class attributes.

This creates a CITEREF anchor from |last1= through |last4= and |year=. It also creates a CITEREF link from |in1= through

|in4= and |year=. It is presumed that the dates of contributions are the same as the date of the enclosing work.

Even though not displayed, a year parameter is still required for the CITEREF anchor

]]

local function core( args )

local span_open_tag; -- holds CITEREF and css

local contributors = ''; -- chapter or contribution authors

local source = ''; -- editor/author date list that forms a CITEREF link to a full citation

local in_text = ' In ';

-- form the CITEREF anchor

if is_set (args.id) then

args.id = mw.uri.anchorEncode (args.id)

span_open_tag = ''; -- for use when contributor name is same as source name

else

local citeref = 'CITEREF' .. table.concat (args.citeref) .. (is_set (args['anchor-year']) and args['anchor-year'] or args.year);

citeref = mw.uri.anchorEncode (citeref);

span_open_tag = '';

end

--[[

form the contributors display list:

if |name-list-style=harv, display is similar to {{sfn}} and {{harv}}, 1 to 4 last names;

if |display-authors= is empty or omitted, display is similar to cs1|2: display all names in last, first order

if |display-authors=etal then displays all author names in last, first order and append et al.

if value assigned to |display-authors= is less than the number of author last names, displays the specified number of author names in last, first order followed by et al.

]]

if 'harv' ~= args.name_list_style then -- default cs1|2 style contributor list

local i = 1;

local count;

local etal = false; -- when |display-authors= is same as number of authors in contributor list

if is_set (args.display_authors) then

if 'etal' == args.display_authors:lower():gsub("[ '%.]", '') then -- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings

count = #args.last; -- display all authors and ...

etal = true; -- ... append 'et al.'

else

count = tonumber (args.display_authors) or 0; -- 0 if can't be converted to a number

if 0 >= count then

args.err_msg = args.err_msg .. ' invalid ' .. code_open_tag .. '|display-authors='; -- if zero, then emit error message

end

end

if count > #args.last then

count = #args.last; -- when |display-authors= is more than the number of authors, use the number of authors

end

if count < #args.last then -- when |display-authors= is less than the number of authors

etal = true; -- append 'et al.'

end

else

count = #args.last; -- set count to display all of the authors

end

while i <= count do

if is_set (contributors) then

contributors = contributors .. '; ' .. make_name (args.last[i], args.first[i], args.link[i], args.mask[i]); -- the rest of the contributors

else

contributors = make_name (args.last[i], args.first[i], args.link[i], args.mask[i]); -- first contributor's name

end

i = i+1; -- bump the index

end

if true == etal then

contributors = contributors .. ' et al.'; -- append et al.

elseif 'amp' == args.name_list_style then

contributors = contributors:gsub('; ([^;]+)$', ' & %1') -- replace last separator with ' & '

end

else -- do default harv- or sfn-style contributor display

if 4 <= #args.last then -- four or more contributors (first followed by et al.)

contributors = args.last[1] .. ' et al.';

elseif 3 == #args.last then -- three (display them all)

contributors = args.last[1] .. ', ' .. args.last[2] .. ' & ' .. args.last[3];

elseif 2 == #args.last then -- two (first & second)

contributors = args.last[1] .. ' & ' .. args.last[2];

elseif 1 == #args.last then -- just one (first)

contributors = args.last[1];

else

args.err_msg = args.err_msg .. ' no authors in contributor list.'; -- this code used to find holes in the list; no more

end

end

--form the source author-date list

if is_set (args.in4) and is_set (args.in3) and is_set (args.in2) and is_set (args.in1) then

source = args.in1 .. ' et al.';

elseif not is_set (args.in4) and is_set (args.in3) and is_set (args.in2) and is_set (args.in1) then

source = args.in1 .. ', ' .. args.in2 .. ' & ' .. args.in3;

elseif not is_set (args.in4) and not is_set (args.in3) and is_set (args.in2) and is_set (args.in1) then

source = args.in1 .. ' & ' .. args.in2;

elseif not is_set (args.in4) and not is_set (args.in3) and not is_set (args.in2) and is_set (args.in1) then

source = args.in1;

else

args.err_msg = args.err_msg .. ' author missing from source list.'

end

source = source .. ' ' .. args.open .. args.year .. args.close; -- add the year with or without brackets

--assemble CITEREF wikilink

local anchor_id;

local target_err_msg;

if '' ~= args.ref then

anchor_id = mw.uri.anchorEncode (args.ref)

else

anchor_id = mw.uri.anchorEncode(table.concat ({'CITEREF', args.in1, args.in2, args.in3, args.in4, args.year}));

end

target_err_msg = target_check (anchor_id, args.ignore); -- see if there is a target for this anchor_id

source = '" .. source .. "";

-- special case for afterword, foreword, introduction, preface

local no_quotes = ({['afterword']=true, ['foreword']=true, ['introduction']=true, ['preface']=true})[args.contribution:lower()];

--combine contribution with url to make external link

if args.url ~= '' then

args.contribution = '[' .. args.url .. ' ' .. args.contribution .. ']'; -- format external link

if args['url-access'] then

if lock_icons[args['url-access']] then

args.contribution = table.concat ({ -- add access icon markup to this item

'', -- close the opening span tag

args.contribution,

'', -- and close the span

});

end

end

end

if is_set (args['anchor-year']) then

contributors = contributors .. ' (' .. args['anchor-year'] .. ')' .. args.sepc;

elseif args.sepc ~= contributors:sub(-1) and args.sepc .. ']]' ~= contributors:sub(-3) then

contributors = contributors .. args.sepc; -- add separator if not same as last character in name list (|first=John S. or et al.)

end

-- pages and other insource location

if args.p ~= '' then

args.p = args.page_sep .. args.p;

elseif args.pp ~= '' then

args.p = args.pages_sep .. args.pp; -- args.p not set so use it to hold common insource location info

end

if args.loc ~= '' then

args.p = args.p .. ', ' .. args.loc; -- add arg.loc to args.p

end

--wrap error messages in span and add help link

if is_set (args.err_msg) then

args.err_msg = ' harvc:' .. args.err_msg .. ' (help)';

end

if ',' == args.sepc then

in_text = in_text:lower(); -- CS2 style use lower case

end

-- and put it all together

local result = {}; -- the assemby of the above output

table.insert (result, span_open_tag);

table.insert (result, contributors);

table.insert (result, no_quotes and ' ' or ' "'); -- foreword, afterword, introduction, preface contributions are not quoted; all other contributions are

table.insert (result, args.contribution);

table.insert (result, no_quotes and '' or '"'); -- foreword, afterword, introduction, preface contributions are not quoted; all other contributions are

table.insert (result, args.sepc);

table.insert (result, in_text);

table.insert (result, source);

table.insert (result, args.p);

table.insert (result, args.ps);

table.insert (result, args.err_msg);

if not is_set(args.err_msg) then

table.insert (result, target_err_msg)

end

table.insert (result, '');

return table.concat (result); -- make a string and done

end

--[[--------------------------< H A R V C >--------------------------------------------------------------------

Entry point from {{harvc}} template. Fetches parent frame parameters, does a bit of simple error checking

]]

local function harvc (frame)

local args = {

err_msg = '',

page_sep = ", p. ",

pages_sep = ", pp. ",

sepc = '.',

ps = '.',

open = '(', -- year brackets for source year

close = ')',

last = {},

first = {},

link = {},

mask = {},

citeref = {}

}

local pframe = frame:getParent();

args.contribution = pframe.args.c or -- chapter or contribution

pframe.args.chapter or

pframe.args.contribution or '';

args.id = pframe.args.id or '';

args.in1 = pframe.args['in'] or pframe.args.in1 or ''; -- source editor surnames; 'in' is a Lua reserved keyword

args.in2 = pframe.args.in2 or '';

args.in3 = pframe.args.in3 or '';

args.in4 = pframe.args.in4 or '';

args.display_authors = pframe.args['display-authors']; -- the number of contributor names to display; cs1|2 format includes first names

args.name_list_style = pframe.args['name-list-style'] or ''; -- when set to 'harv' display contributor list in sfn or harv style

args.name_list_style = args.name_list_style:lower(); -- make it case agnostic

if is_set (pframe.args.last) or is_set (pframe.args.last1) or

is_set (pframe.args.author) or is_set (pframe.args.author1) then -- must have at least this to continue

args.last[1] = pframe.args.last or pframe.args.last1 or pframe.args.author or pframe.args.author1; -- get first contributor's last name

args.citeref[1] = args.last[1]; -- add it to the citeref

args.first[1] = pframe.args.first or pframe.args.first1; -- get first contributor's first name

args.link[1] = pframe.args['author-link'] or pframe.args['author-link1']; -- get first contributor's article link

args.mask[1] = pframe.args['author-mask'] or pframe.args['author-mask1']; -- get first contributor's article link

local i = 2; -- index for the rest of the names

while is_set (pframe.args['last'..i]) or is_set (pframe.args['author'..i]) do -- loop through pframe.args and get the rest of the names

args.last[i] = pframe.args['last'..i] or pframe.args['author'..i]; -- last names

args.first[i] = pframe.args['first'..i]; -- first names

args.link[i] = pframe.args['author-link'..i]; -- links

args.mask[i] = pframe.args['author-mask'..i]; -- masks

if 5 > i then

args.citeref[i] = args.last[i]; -- collect first four last names for CITEREF anchor

end

i = i + 1 -- bump the index

end

end

if 0 == #args.last then -- |last= is required

args.err_msg = args.err_msg .. ' no authors in contributor list.';

end

args.p = pframe.args.p or pframe.args.page or ''; -- source page number(s) or location

args.pp = pframe.args.pp or pframe.args.pages or '';

args.loc = pframe.args.loc or '';

args.ref = pframe.args.ref or pframe.args.Ref or ''; -- used to match |ref= in cs1|2 source template

args.ignore = 'yes' == pframe.args['ignore-err']; -- suppress false-positive 'no target' errors

if 'cs2' == pframe.args.mode then

args.ps = ''; -- set postscript character to empty string, cs2 mode

args.sepc = ','; -- set seperator character to comma, cs2 mode

end

do -- to limit scope of local temp

local temp = pframe.args.ps or pframe.args.postscript;

if is_set (temp) then

if 'none' == temp:lower() then -- if |ps=none or |postscript=none then

args.ps = ''; -- no postscript

else

args.ps = temp; -- override default postscript

end

end

end -- end of scope limit

if 'yes' == pframe.args.nb then -- if no brackets around year in link to cs1|2 template

args.open = ''; -- unset these

args.close = '';

end

args.url = pframe.args.url or -- url for chapter or contribution

pframe.args['chapter-url'] or

pframe.args['contribution-url'] or '';

args['url-access'] = pframe.args['url-access'];

args.year = pframe.args.year or ''; -- required

args['anchor-year'] = pframe.args['anchor-year'] or '';

args.err_msg = args.err_msg .. check_years (args.year, args['anchor-year']);

if not is_set (args.contribution) then

args.err_msg = args.err_msg .. ' required contribution is missing.'; -- error message if source not provided

args.contribution = args.url; -- if set it will give us linkable text

end

if args.last[1] == args.in1 and

args.last[2] == args.in2 and

args.last[3] == args.in3 and

args.last[4] == args.in4 and

not is_set (args.id) then

args.err_msg = args.err_msg .. ' required ' .. code_open_tag .. '|id= parameter missing.'; -- error message if contributor and source are the same

end

return table.concat ({frame:extensionTag ('templatestyles', '', {src='Module:Citation/CS1/styles.css'}), core (args)});

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------

]]

return {

harvc = harvc

};