Module:Harvc

require('strict')local anchor_id_list = mw.loadData ('Module:Footnotes/anchor_id_list').anchor_id_list;local code_open_tag = '<code class="cs1-code">';-- cs1-code class defined in Module:Citation/CS1/styles.csslocal 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 >------------------------------------------------------look for anchor_id (CITEREF name-list and year or text from |ref=) in anchor_id_listthe 'no target' error may be suppressed with |ignore-err=yes when target cannot be found because target is insidea template that wraps another template; 'multiple targets' error may not be suppressed]]local function target_check (anchor_id, ignore)local number = anchor_id_list[anchor_id];-- nil when anchor_id not in list; else a numberlocal msg;local category;if not number thenif ignore thenreturn '';-- if ignore is true then no message, no categoryendmsg = 'no target: ' .. anchor_id;-- anchor_id not found in this articlecategory = '[[Category:Harv and Sfn no-target errors]]';elseif 1 < number thenmsg = 'multiple targets (' .. number .. '×): ' .. anchor_id;-- more than one anchor_id in this articlecategory = '[[Category:Harv and Sfn multiple-target errors]]';endcategory = 0 == mw.title.getCurrentTitle().namespace and category or '';-- only categorize in article space--use this version to show error messagesreturn msg and ' <span class="error harv-error" style="display: inline; font-size:100%">Harvc error: ' .. msg .. ' ([[:Category:Harv and Sfn template errors|help]])</span>' .. category or '';--use this version to hide error messages--return msg and ' <span class="error harv-error" style="display: none; font-size:100%">Harvc error: ' .. msg .. ' ([[:Category:Harv and Sfn template errors|help]])</span>' .. category or '';end--[[--------------------------< I S _ S E T >------------------------------------------------------------------Whether variable is set or not.  A varable 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 inModule:Footnotes):YYYYn.d.ndc. YYYYYYYY–YYYY(separator is endash)YYYY–YY(separator is endash)when anchor_year present, year portion must be same as year param and must have disambiguatorreturns 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 setreturn ' missing ' .. code_open_tag .. '|year=</code>.';endlocal 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 patternsy = year:match (pattern);-- y is the year portionif y thenbreak;-- when y is set, we found a match so doneendendif not y thenreturn ' invalid ' .. code_open_tag .. '|year=</code>.';-- y not set, so year is malformedendif is_set (anchor_year) then-- anchor_year is optionalfor _, pattern in ipairs (patterns) do-- spin through the patternsay = anchor_year:match (pattern);-- ay is the year portionif ay thenbreak;-- when ay is set, we found a match so doneendendif not ay thenreturn ' invalid ' .. code_open_tag .. '|anchor-year</code>.';-- ay not set, so anchor_year is malformedend--if not anchor_year:match ('%l$') then--return ' ' .. code_open_tag .. '|anchor-year=</code> missing dab.';-- anchor_year must end with a disambiguator letter--endif y ~= ay thenreturn ' ' .. code_open_tag .. '|year=</code> / ' .. code_open_tag .. '|anchor-year=</code> mismatch.';-- 'year' portions of year and anchor_year must be the sameendendreturn '';-- both years are good; empty string for concatenationend--[[--------------------------< 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) thenname = name .. ', ' .. first;-- concatenate first onto lastendif is_set (link) thenname = '[[' .. link .. '|' .. name .. ']]';-- form a wikilink around the nameendif is_set (mask) then-- mask this authorif tonumber(mask) thenname = string.rep ('—', mask)-- make a string that number length of mdasheselsename = mask;-- mask is not a number so use the mask textendendreturn name;end--[[--------------------------< C O R E >----------------------------------------------------------------------Assembles the various parts provided by the template into a properly formatted bridging citation.  Adds punctuationand 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 csslocal contributors = '';-- chapter or contribution authorslocal source = '';-- editor/author date list that forms a CITEREF link to a full citationlocal in_text = ' In ';-- form the CITEREF anchorif is_set (args.id) thenargs.id = mw.uri.anchorEncode (args.id)span_open_tag = '<span id="' .. args.id .. '" class="citation">';-- for use when contributor name is same as source nameelselocal 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 = '<span id="' .. citeref .. '" class="citation">';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 listlocal i = 1;local count;local etal = false;-- when |display-authors= is same as number of authors in contributor listif is_set (args.display_authors) thenif 'etal' == args.display_authors:lower():gsub("[ '%.]", '') then-- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylingscount = #args.last;-- display all authors and ...etal = true;-- ... append 'et al.'elsecount = tonumber (args.display_authors) or 0;-- 0 if can't be converted to a numberif 0 >= count thenargs.err_msg = args.err_msg .. ' invalid ' .. code_open_tag .. '|display-authors=</code>';-- if zero, then emit error messageendendif count > #args.last thencount = #args.last;-- when |display-authors= is more than the number of authors, use the number of authorsendif count < #args.last then-- when |display-authors= is less than the number of authorsetal = true;-- append 'et al.'endelsecount = #args.last;-- set count to display all of the authorsendwhile i <= count doif is_set (contributors) thencontributors = contributors .. '; ' .. make_name (args.last[i], args.first[i], args.link[i], args.mask[i]);-- the rest of the contributorselsecontributors = make_name (args.last[i], args.first[i], args.link[i], args.mask[i]);-- first contributor's nameendi = i+1;-- bump the indexendif true == etal thencontributors = contributors .. ' et al.';-- append et al.elseif 'amp' == args.name_list_style thencontributors = contributors:gsub('; ([^;]+)$', ' & %1')-- replace last separator with ' & 'endelse-- do default harv- or sfn-style contributor displayif 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];elseargs.err_msg = args.err_msg .. ' no authors in contributor list.';-- this code used to find holes in the list; no moreendend--form the source author-date listif is_set (args.in4) and is_set (args.in3) and is_set (args.in2) and is_set (args.in1) thensource = 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) thensource = 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) thensource = 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) thensource = args.in1;elseargs.err_msg = args.err_msg .. ' author missing from source list.'endsource = source .. ' ' .. args.open .. args.year .. args.close;-- add the year with or without brackets--assemble CITEREF wikilinklocal anchor_id;local target_err_msg;if '' ~= args.ref thenanchor_id = mw.uri.anchorEncode (args.ref)elseanchor_id = mw.uri.anchorEncode(table.concat ({'CITEREF', args.in1, args.in2, args.in3, args.in4, args.year}));endtarget_err_msg = target_check (anchor_id, args.ignore);-- see if there is a target for this anchor_idsource = '[[#' .. anchor_id .. "|" .. source .. "]]";-- special case for afterword, foreword, introduction, prefacelocal no_quotes = ({['afterword']=true, ['foreword']=true, ['introduction']=true, ['preface']=true})[args.contribution:lower()];--combine contribution with url to make external linkif args.url ~= '' thenargs.contribution = '[' .. args.url .. ' ' .. args.contribution .. ']';-- format external linkif args['url-access'] thenif lock_icons[args['url-access']] thenargs.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.csslock_icons[args['url-access']][1],-- add the appropriate lock icon class'" title="',-- and the title attributelock_icons[args['url-access']][2],-- for an appropriate tool tip'">',-- close the opening span tagargs.contribution,'</span>',-- and close the span});endendendif is_set (args['anchor-year']) thencontributors = contributors .. ' (' .. args['anchor-year'] .. ')' .. args.sepc;elseif args.sepc ~= contributors:sub(-1) and args.sepc .. ']]' ~= contributors:sub(-3) thencontributors = 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 locationif args.p ~= '' thenargs.p = args.page_sep .. args.p;elseif args.pp ~= '' thenargs.p = args.pages_sep .. args.pp;-- args.p not set so use it to hold common insource location infoend       if args.loc ~= '' thenargs.p = args.p .. ', ' .. args.loc;-- add arg.loc to args.pend--wrap error messages in span and add help linkif is_set (args.err_msg) thenargs.err_msg = '<span style="font-size:100%" class="error"> harvc:' .. args.err_msg .. ' ([[Template:Harvc|help]])</span>';endif ',' == args.sepc thenin_text = in_text:lower();-- CS2 style use lower caseend-- and put it all togetherlocal result = {};-- the assemby of the above outputtable.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 aretable.insert (result, args.contribution);table.insert (result, no_quotes and '' or '"');-- foreword, afterword, introduction, preface contributions are not quoted; all other contributions aretable.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);table.insert (result, target_err_msg);table.insert (result, '</span>');return table.concat (result);-- make a string and doneend--[[--------------------------< 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.&nbsp;",pages_sep = ", pp.&nbsp;",sepc = '.',ps = '.',open = '(',-- year brackets for source yearclose = ')',last = {},first = {},link = {},mask = {},citeref = {}}local pframe = frame:getParent(); args.contribution =  pframe.args.c or-- chapter or contributionpframe.args.chapter orpframe.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 keywordargs.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 namesargs.name_list_style = pframe.args['name-list-style'] or '';-- when set to 'harv' display contributor list in sfn or harv styleargs.name_list_style = args.name_list_style:lower();-- make it case agnosticif is_set (pframe.args.last) or is_set (pframe.args.last1) oris_set (pframe.args.author) or is_set (pframe.args.author1) then-- must have at least this to continueargs.last[1] = pframe.args.last or pframe.args.last1 or pframe.args.author or pframe.args.author1;-- get first contributor's last nameargs.citeref[1] = args.last[1];-- add it to the citerefargs.first[1] = pframe.args.first or pframe.args.first1;-- get first contributor's first nameargs.link[1] = pframe.args['author-link'] or pframe.args['author-link1'];-- get first contributor's article linkargs.mask[1] = pframe.args['author-mask'] or pframe.args['author-mask1'];-- get first contributor's article linklocal i = 2;-- index for the rest of the nameswhile is_set (pframe.args['last'..i]) or is_set (pframe.args['author'..i]) do-- loop through pframe.args and get the rest of the namesargs.last[i] = pframe.args['last'..i] or pframe.args['author'..i];-- last namesargs.first[i] = pframe.args['first'..i];-- first namesargs.link[i] = pframe.args['author-link'..i];-- linksargs.mask[i] = pframe.args['author-mask'..i];-- masksif 5 > i thenargs.citeref[i] = args.last[i];-- collect first four last names for CITEREF anchorendi = i + 1-- bump the indexendendif 0 == #args.last then-- |last= is requiredargs.err_msg = args.err_msg .. ' no authors in contributor list.';endargs.p = pframe.args.p or pframe.args.page or '';-- source page number(s) or locationargs.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 templateargs.ignore = 'yes' == pframe.args['ignore-err'];-- suppress false-positive 'no target' errorsif 'cs2' == pframe.args.mode thenargs.ps = '';-- set postscript character to empty string, cs2 modeargs.sepc = ',';-- set seperator character to comma, cs2 modeenddo-- to limit scope of local templocal temp = pframe.args.ps or pframe.args.postscript;if is_set (temp) thenif 'none' == temp:lower() then-- if |ps=none or |postscript=none thenargs.ps = '';-- no postscriptelseargs.ps = temp;-- override default postscriptendendend-- end of scope limitif 'yes' == pframe.args.nb then-- if no brackets around year in link to cs1|2 templateargs.open = '';-- unset theseargs.close = '';endargs.url = pframe.args.url or-- url for chapter or contributionpframe.args['chapter-url'] orpframe.args['contribution-url'] or '';args['url-access'] = pframe.args['url-access'];args.year = pframe.args.year or '';-- requiredargs['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) thenargs.err_msg = args.err_msg .. ' required contribution is missing.';-- error message if source not providedargs.contribution = args.url;-- if set it will give us linkable textendif args.last[1] == args.in1 andargs.last[2] == args.in2 andargs.last[3] == args.in3 andargs.last[4] == args.in4 andnot is_set (args.id) thenargs.err_msg = args.err_msg .. ' required ' .. code_open_tag .. '|id=</code> parameter missing.';-- error message if contributor and source are the sameendreturn 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};