Module:Harvc: Difference between revisions

Content deleted Content added
No edit summary
replace target_check() with call to target_check() in Module:Footnotes, which has full wrapper/whitelist functionality
 
(33 intermediate revisions by 6 users not shown)
Line 1:
require('strict')
f = {
 
args_default = {
local code_open_tag = '<code class="cs1-code">'; -- cs1-code class defined in Module:Citation/CS1/styles.css
err_msg = '',
local lock_icons = { --icon classes are defined in Module:Citation/CS1/styles.css
page_sep = ", p.&nbsp;",
['registration'] = {'id-lock-registration', 'Free registration required'},
pages_sep = ", pp.&nbsp;",
['limited'] = {'id-lock-limited', 'Free access subject to limited trial, subscription normally required'},
['subscription'] = {'id-lock-subscription', 'Paid subscription required'},
}
};
 
--[[--------------------------< T R I M >----------------------------------------------------------------------
 
--[[--------------------------< T A R G E T _ C H E C K >------------------------------------------------------
strip leading and trailing white space from positional parameters.
 
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 trim(target_check str(anchor_id, ignore)
local whitelist_check = require('Module:Footnotes').target_check
if str == nil then
local args = {ignore=ignore, template='Harvc', show=true}
return '';
return whitelist_check(anchor_id, args)
end
end
return str:match( "^%s*(.-)%s*$");
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=</code>.';
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=</code>.'; -- 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</code>.'; -- ay not set, so anchor_year is malformed
end
-- if not anchor_year:match ('%l$') then
-- return ' ' .. code_open_tag .. '|anchor-year=</code> missing dab.'; -- anchor_year must end with a disambiguator letter
-- end
if y ~= ay then
return ' ' .. code_open_tag .. '|year=</code> / ' .. code_open_tag .. '|anchor-year=</code> 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 = '[[' .. link .. '|' .. 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
 
Line 37 ⟶ 138:
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
This function mimics the {{sfn}} and {{harv}} templates, but it does not display an author-date list, just an
author|in4= listand |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
Line 44 ⟶ 145:
]]
 
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 mimicsin_text harvnb= output' exceptIn year in parentheses';
local in_text = ' In '; --
local result; -- the assemby of the above output
 
-- form the CITEREF anchor
if is_set (args.id) then
args.id = mw.uri.anchorEncode (args.id)
span_open_tag = '<span id="' .. args.id .. '" class="citation">'; -- for use when contributor name is same as source name
else
span_open_taglocal citeref = '<span id="CITEREF' .. table.concat ({args.last1,citeref) args.last2, args.last3, args.last4,(is_set (args.['anchor-year}']) ..and args['" class="citation">anchor-year'] or args.year);
citeref = mw.uri.anchorEncode (citeref);
span_open_tag = '<span id="' .. citeref .. '" class="citation">';
end
--[[
-- form the contributors list; similar to {{sfn}} and {{harv}}, 1 to 4 names but no date and also allows first contributor given name
form the contributors display list:
if args.first ~= '' then
if |name-list-style=harv, display is similar to {{sfn}} and {{harv}}, 1 to 4 last names;
args.first = ', ' .. args.first;
if |display-authors= is empty or omitted, display is similar to cs1|2: display all names in last, first order
end
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=</code>'; -- 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 (args.last4) and is_set (args.last3) and is_set (args.last2) and is_set (args.last1) then
if is_set (contributors) then
contributors = args.last1 .. args.first .. ' et al.';
contributors = contributors .. '; ' .. make_name (args.last[i], args.first[i], args.link[i], args.mask[i]); -- the rest of the contributors
elseif is_set (args.last3) and is_set (args.last2) and is_set (args.last1) then
else
contributors = args.last1 .. args.first .. ', ' .. args.last2 .. ' &amp; ' .. args.last3;
contributors = make_name (args.last[i], args.first[i], args.link[i], args.mask[i]); -- first contributor's name
elseif is_set (args.last2) and is_set (args.last1) then
end
contributors = args.last1 .. args.first .. ' &amp; ' .. args.last2;
i = i+1; -- bump the index
elseif is_set (args.last1) then
end
contributors = args.last1 .. args.first;
if true == etal then
else
contributors = contributors .. ' et al.'; -- append et al.
args.err_msg = args.err_msg .. ' author missing from contributor list.';
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] .. ' &amp; ' .. args.last[3];
elseif 2 == #args.last then -- two (first & second)
contributors = args.last[1] .. ' &amp; ' .. 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
 
Line 78 ⟶ 223:
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 .. ' &amp; ' .. 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 .. ' &amp; ' .. 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
Line 88 ⟶ 233:
end
 
source = source .. ' ' .. args.open .. args.year .. args.close; -- add the year with or without brackets
if args.year:match('^[1-9]%d%d%d?%l?$') or args.year:match('^n%.d%.%l?$') or args.year:match('^nd%l?$') then -- 3 or 4 digits, n.d., or nd and optional disambiguator
source = source .. ' (' .. args.year .. ')';
else
args.err_msg = args.err_msg .. ' invalid or missing year.'; -- error message if year not provided or malformed
end
 
--assemble CITEREF wikilink
local anchor_id;
source = "[[#CITEREF" .. mw.uri.anchorEncode(table.concat ({args.in1, args.in2, args.in3, args.in4, args.year})) .. "|" .. source .. "]]";
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 = '[[#' .. anchor_id .. "|" .. 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
'<span class="', -- open the opening span tag; icon classes are defined in Module:Citation/CS1/styles.css
lock_icons[args['url-access']][1], -- add the appropriate lock icon class
'" title="', -- and the title attribute
lock_icons[args['url-access']][2], -- for an appropriate tool tip
'">', -- close the opening span tag
args.contribution,
'</span>', -- and close the span
});
end
end
end
 
if args.separatoris_set ~= contributors:sub(args['anchor-1year']) then
contributors = contributors .. args.separator; --' add(' separator.. ifargs['anchor-year'] not.. same')' as last character in name list (|first=John S.. or et alargs.)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
 
Line 117 ⟶ 287:
end
 
--wrap error messages in span and add help link
if is_set (args.err_msg) then
args.err_msg = '<span style="font-size:100%" class="error">' .. args.err_msg .. '</span>';
args.err_msg = '<span style="font-size:100%" class="error"> harvc:' .. args.err_msg .. ' ([[Template:Harvc|help]])</span>';
end
 
if ',' == args.separatorsepc 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
result = span_open_tag .. contributors .. ' "' .. args.contribution .. '"' .. args.separator .. in_text .. source .. args.p .. args.postscript .. '</span>' .. args.err_msg;
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, '</span>');
 
return table.concat (result); -- make a string and done
end
 
 
--[[--------------------------< F . H A R V C >--------------------------------------------------------------------
 
Entry point from {{harvc}} template. Fetches parent frame parameters, does a bit of simple error checking
 
]]
 
function f.harvc (frame)
local argsfunction =harvc f.args_default;(frame)
local args = {
pframe = frame:getParent();
err_msg = '',
page_sep = ", p.&nbsp;",
pages_sep = ", pp.&nbsp;",
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.first = pframe.args.first or pframe.args.first1 or ''; -- (optional) first author's given name; given names for other authors not supported
 
args.id = pframe.args.id or '';
Line 151 ⟶ 352:
args.in3 = pframe.args.in3 or '';
args.in4 = pframe.args.in4 or '';
args.last1 = pframe.args.last or pframe.args.last1 or ''; -- contribution author(s)
args.last2 = pframe.args.last2 or '';
args.last3 = pframe.args.last3 or '';
args.last4 = pframe.args.last4 or '';
 
args.pdisplay_authors = pframe.args.p or ['display-authors']; -- source pagethe number(s) orof contributor names to display; cs1|2 format includes first ___locationnames
args.ppname_list_style = pframe.args.pp['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
args.loc = pframe.args.loc or '';
 
if is_set (pframe.args.last) or is_set (pframe.args.last1) or
args.separator = pframe.args.separator; -- !! before postscript!! element separator defaults to full stop
args.postscript = is_set (pframe.args.psauthor) or is_set (pframe.args.postscript;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
if not is_set (args.postscript) then
args.citeref[1] = args.last[1]; -- add it to the citeref
if not is_set (args.separator) then
args.first[1] = pframe.args.first or pframe.args.first1; -- get first contributor's first name
args.postscript = '.'; -- neither set so set to defaults
args.link[1] = pframe.args['author-link'] or pframe.args['author-link1']; -- get first contributor's article link
args.separator = '.';
args.mask[1] = pframe.args['author-mask'] or pframe.args['author-mask1']; -- get first contributor's article link
elseif ',' == args.separator then
args.postscript = ''; -- CS2 and postscript not set; no terminal punctuation
local i = 2; -- index for the rest of the names
else
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.postscript = '.'; -- separator not CS2 and postscript not set; use default terminal punctuation
args.last[i] = pframe.args['last'..i] or pframe.args['author'..i]; -- last names
end
args.first[i] = pframe.args['first'..i]; -- first names
else -- if here, postscript set
args.link[i] = pframe.args['author-link'..i]; -- links
if 'none' == args.postscript:lower() then -- if |ps=none or |postscript=none then
args.postscriptmask[i] = pframe.args['author-mask'..i]; -- no postscriptmasks
if 5 > i then
end
args.citeref[i] = args.last[i]; -- collect first four last names for CITEREF anchor
end
i = i + 1 -- bump the index
end
end
 
if not is_set (args.separator) then
if 0 == #args.last then -- |last= is required
args.separator = '.'; -- not set in template and we didn't set it here so set to default
args.err_msg = args.err_msg .. ' no authors in contributor list.';
end
 
args.urlp = pframe.args.urlp or pframe.args.page or ''; -- urlsource forpage chapternumber(s) or contribution___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=<text> 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
Line 190 ⟶ 424:
end
if args.last1last[1] == args.in1 and
args.last2last[2] == args.in2 and
args.last3last[3] == args.in3 and
args.last4last[4] == args.in4 and
not is_set (args.id) then
args.err_msg = args.err_msg .. ' required &#124id' .. code_open_tag .. '|id=</code> 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)});
return core (args);
end
 
 
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
--[[
function f.sfn( frame )
local args = f.args_default;
pframe = frame:getParent();
args.postscript = pframe.args.postscript or pframe.args.ps or ".";
args.page = pframe.args.p or pframe.args.page or "";
args.pages = pframe.args.pp or pframe.args.pages or "";
args.___location = pframe.args.loc or "";
args.ref = pframe.args.ref or pframe.args.Ref or "";
args.P1 = trim( pframe.args[1] ) or "";
args.P2 = trim( pframe.args[2] ) or "";
args.P3 = trim( pframe.args[3] ) or "";
args.P4 = trim( pframe.args[4] ) or "";
args.P5 = trim( pframe.args[5] ) or "";
local result = core( args );
local name = "FOOTNOTE" .. args.P1 .. args.P2 ..
args.P3 .. args.P4 .. args.P5 .. args.page .. args.pages .. args.___location;
result = frame:extensionTag{ name = "ref", args = {name=name}, content=result };
return result;
end
]]
 
return f;{
harvc = harvc
};