Module:Footnotes/sandbox
Appearance
From Wikipedia, the free encyclopedia
This is the module sandbox page for Module:Footnotes (diff).
See also the companion subpage for test cases (run).
See also the companion subpage for test cases (run).
This module is rated as ready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned on help pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed through sandbox testing rather than repeated trial-and-error editing.
Warning This Lua module is used on approximately 346,000 pages .
To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them.
To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them.
This module depends on the following other modules:
Implements {{sfn }}, {{harvard citation }}, and variants of those templates.
For an explanation of generated error messages and how to alter their appearance, see here.
For end-to-end tests of the sandbox version of this module, see Template:Sfnp/test 1, Template:Sfnp/test 2 and Template:Sfnp/test 3.
Tracking categories
[edit ]The above documentation is transcluded from Module:Footnotes/doc. (edit | history)
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Add categories to the /doc subpage. Subpages of this module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Add categories to the /doc subpage. Subpages of this module.
require('strict'); localgetArgs=require('Module:Arguments').getArgs; --[[--------------------------< A R G S _ D E F A U L T >------------------------------------------------------ a table to specify initial values. ]] localargs_default={ group='', bracket_left='', bracket_right='', bracket_year_left='', bracket_year_right='', postscript='', page='', pages='', location='', page_sep=", p. ", pages_sep=", pp. ", ref='', template='harv',-- if template name not provided in {{#invoke:}} use this }; --[[--------------------------< 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_list the 'no target' error may be suppressed with |ignore-err=yes when target cannot be found because target is inside a template that wraps another template; 'multiple targets' error may not be suppressed ]] localfunctiontarget_check(anchor_id,args) localnamespace=mw.title.getCurrentTitle().namespace; localanchor_id_list_module=mw.loadData('Module:Footnotes/anchor_id_list/sandbox'); localanchor_id_list=anchor_id_list_module.anchor_id_list; localarticle_whitelist=anchor_id_list_module.article_whitelist; localtemplate_list=anchor_id_list_module.template_list; localciteref_patterns=anchor_id_list_module.citeref_patterns localwhitelist_module=mw.loadData('Module:Footnotes/whitelist'); localwhitelist=whitelist_module.whitelist; localtally=anchor_id_list[anchor_id];-- nil when anchor_id not in list; else a tally localmsg; localcategory; ifnottallythen ifargs.ignorethen return'';-- if ignore is true then no message, no category end ifarticle_whitelistandarticle_whitelist[anchor_id]then-- if an article-local whitelist and anchor ID is in it return'';-- done end localwl_anchor_id=anchor_id;-- copy to be modified to index into the whitelist ifargs.yearthen-- for anchor IDs created by this template (not in |ref=) that have a date ifargs.year:match('%d%l$')or-- use the date value to determine if we should remove the disambiguator args.year:match('n%.d%.%l$')or args.year:match('nd%l$')then wl_anchor_id=wl_anchor_id:gsub('%l$','');-- remove the disambiguator end end localt_tbl=whitelist[wl_anchor_id];-- get list of templates associated with this anchor ID ift_tblthen-- when anchor ID not whitelisted t_tbl is nil for_,tinipairs(t_tbl)do-- spin through the list of templates associated with this anchor ID iftemplate_list[t]then-- if associated template is found in the list of templates in the article return'';-- anchor ID is whitlisted and article has matching template so no error end end end for_,patterninipairs(citeref_patterns)do-- load patterns for wrapper templates on this page ifanchor_id:match(pattern)then-- spin through the special patterns and try to match return'' end end msg='no target: '..anchor_id;-- anchor_id not found mw.log(msg) ifnamespace==10andnotargs.showthen-- do not generate error message in template namespace return'' end category='[[Category:Harv and Sfn no-target errors]]'; elseif1<tallythen msg='multiple targets ('..tally..×ばつ): '..anchor_id;-- more than one anchor_id in this article mw.log(msg) ifnamespace==10andnotargs.showthen-- do not generate error message in template namespace return'' end category=0==namespaceand'[[Category:Harv and Sfn multiple-target errors]]'or'';-- only categorize in article space return'<span class="error harv-error" style="display: inline; font-size:100%"> '..args.template..' error: '..msg..' ([[:Category:Harv and Sfn template errors|help]])</span>'..category; end -- category = 0 == namespace and '[[Category:Harv and Sfn template errors]]' or ''; -- only categorize in article space category=0==namespaceandcategoryor'';-- only categorize in article space -- display based on args.show (no display by default) localdisplay=args.showand'inline'or'none' returnmsgand'<span class="error harv-error" style="display: '..display..'; font-size:100%"> '..args.template..' error: '..msg..' ([[:Category:Harv and Sfn template errors|help]])</span>'..categoryor''; end --[[--------------------------< I S _ Y E A R >---------------------------------------------------------------- evaluates param to see if it is one of these forms with or without lowercase letter disambiguator: YYYY n.d. n.d.- (This will be suffixed by a letter when using author-date citations for the same author.) nd c. YYYY YYYY–YYYY (separator is endash) YYYY–YY (separator is endash) return true when param has a recognized form; false else ]] localpatterns_date={ '^%d%d%d%d?%l?$', '^n%.d%.%l?$', '^n%.d%.%-%l$', '^nd%l?$', '^c%. %d%d%d%d?%l?$', '^%d%d%d%d–%d%d%d%d%l?$', '^%d%d%d%d–%d%d%l?$', } localfunctionis_year(param,args) args.year='';-- used for harv error; for_,patterninipairs(patterns_date)do ifmw.ustring.match(param,pattern)then args.year=param;-- used for harv error; returntrue; end end end --[[--------------------------< C O R E >---------------------------------------------------------------------- returns an anchor link (CITEREF) formed from one to four author names, year, and insource location (|p=, |pp=, loc=) ]] localfunctioncore(args) localresult; localerr_msg='' ifargs.P5~=''then ifis_year(args.P5,args)then result=table.concat({args.P1,' et al. ',args.bracket_year_left,args.P5,args.bracket_year_right}); else args.P5='';-- when P5 not a year don't include in anchor result=table.concat({args.P1,' et al.'});-- and don't render it end elseifargs.P4~=''then ifis_year(args.P4,args)then result=table.concat({args.P1,', ',args.P2,' & ',args.P3,' ',args.bracket_year_left,args.P4,args.bracket_year_right});-- three names and a year else result=table.concat({args.P1,' et al.'});-- four names end elseifargs.P3~=''then ifis_year(args.P3,args)then result=table.concat({args.P1,' & ',args.P2,' ',args.bracket_year_left,args.P3,args.bracket_year_right});-- two names and a year else result=table.concat({args.P1,', ',args.P2,' ',' & ',args.P3});-- three names end elseifargs.P2~=''then ifis_year(args.P2,args)then result=table.concat({args.P1,' ',args.bracket_year_left,args.P2,args.bracket_year_right});-- one name and year else result=table.concat({args.P1,' & ',args.P2});-- two names end else result=args.P1;-- one name end -- when author-date result ends with a dot (typically when the last positional parameter holds 'n.d.') -- and when no in-source location (no |p=, |pp=, or |loc=) -- and when the first or only character in args.postscript is a dot -- remove the author-date result trailing dot -- the author-date result trailing dot will be replaced later with the content of args.postscript (usually a dot) if('.'==result:sub(-1))and('.'==args.postscript:sub(1))and(''==args.page)and(''==args.pages)and(''==args.location)then result=result:gsub('%.$',''); end ifargs.ref~='none'then localanchor_id; ifargs.ref~=''then anchor_id=mw.uri.anchorEncode(args.ref); err_msg=target_check(anchor_id,args); result=table.concat({'[[#',anchor_id,'|',result,']]'}); else anchor_id=mw.uri.anchorEncode(table.concat({'CITEREF',args.P1,args.P2,args.P3,args.P4,args.P5})); err_msg=target_check(anchor_id,args); result=table.concat({'[[#',anchor_id,'|',result,']]'}); end end ifargs.page~=''then result=table.concat({result,args.page_sep,args.page}); elseifargs.pages~=''then result=table.concat({result,args.pages_sep,args.pages}); end ifargs.location~=''then result=table.concat({result,', ',args.location}); end result=table.concat({args.bracket_left,result,args.bracket_right,args.postscript}):gsub('%s+',' ');-- strip redundant spaces returnresult..err_msg; end --[[--------------------------< H Y P H E N _ T O _ D A S H >-------------------------------------------------- Converts a hyphen to a dash under certain conditions. The hyphen must separate like items; unlike items are returned unmodified. These forms are modified: letter - letter (A - B) digit - digit (4-5) digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5) letterdigit - letterdigit (A1-A5) (an optional separator between letter and digit is supported – a.1-a.5 or a-1-a-5) digitletter - digitletter (5a - 5d) (an optional separator between letter and digit is supported – 5.a-5.d or 5-a-5-d) any other forms are returned unmodified. str may be a comma- or semicolon-separated list This code copied from Module:Citation/CS1. The only modification is to require Module:Citation/CS1/Utilities so that it has access to the functions is_set() and has_accept_as_written() ]] localfunctionhyphen_to_dash(str) localutilities=require('Module:Citation/CS1/Utilities');-- only modification so that this function has access to is_set() and has_accept_as_written() ifnotutilities.is_set(str)then returnstr; end localaccept;-- Boolean str=str:gsub('&[nm]dash;',{['–']='–',['—']='—'});-- replace — and – entities with their characters; semicolon mucks up the text.split str=str:gsub('-','-');-- replace HTML numeric entity with hyphen character str=str:gsub(' ',' ');-- replace entity with generic keyboard space character localout={}; locallist=mw.text.split(str,'%s*[,;]%s*');-- split str at comma or semicolon separators if there are any for_,iteminipairs(list)do-- for each item in the list item,accept=utilities.has_accept_as_written(item);-- remove accept-this-as-written markup when it wraps all of item ifnotacceptandmw.ustring.match(item,'^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$')then-- if a hyphenated range or has endash or emdash separators ifitem:match('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$')or-- letterdigit hyphen letterdigit (optional separator between letter and digit) item:match('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$')or-- digitletter hyphen digitletter (optional separator between digit and letter) item:match('^%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+$')or-- digit separator digit hyphen digit separator digit item:match('^%d+%s*%-%s*%d+$')or-- digit hyphen digit item:match('^%a+%s*%-%s*%a+$')then-- letter hyphen letter item=item:gsub('(%w*[%.%-]?%w+)%s*%-%s*(%w*[%.%-]?%w+)','%1–%2');-- replace hyphen, remove extraneous space characters else item=mw.ustring.gsub(item,'%s*[–—]%s*','–');-- for endash or emdash separated ranges, replace em with en, remove extraneous whitespace end end table.insert(out,item);-- add the (possibly modified) item to the output table end localtemp_str='';-- concatenate the output table into a comma separated string temp_str,accept=utilities.has_accept_as_written(table.concat(out,', '));-- remove accept-this-as-written markup when it wraps all of concatenated out ifacceptthen temp_str=utilities.has_accept_as_written(str);-- when global markup removed, return original str; do it this way to suppress boolean second return value returntemp_str; else returntemp_str;-- else, return assembled temp_str end end --[[--------------------------< A R G S _ F E T C H >--------------------------------------------------------- Because all of the templates share a common set of parameters, a single common function to fetch those parameters from frame and parent frame. ]] localfunctionargs_fetch(frame,ps) localargs=args_default;-- create a copy of the default table localpframe=frame:getParent();-- point to the template's parameter table fork,vinpairs(frame.args)do-- override defaults with values provided in the #invoke: if any args[k]=v; end args.postscript=pframe.args.postscriptorpframe.args.psorps; if'none'==args.postscriptthen args.postscript=''; end args.group=pframe.args.groupor''; args.page=pframe.args.porpframe.args.pageor''; args.pages=pframe.args.pporpframe.args.pagesor''; args.pages=(''~=args.pages)andhyphen_to_dash(args.pages)or''; args.location=pframe.args.atorpframe.args.locor''; args.ref=pframe.args.reforpframe.args.Refor''; args.ignore=('yes'==pframe.args['ignore-false-positive'])or('yes'==pframe.args['ignore-err']); fori,vinipairs({'P1','P2','P3','P4','P5'})do-- loop through the five positional parameters and trim if set else empty string args[v]=(pframe.args[i]andmw.text.trim(pframe.args[i]))or''; end ifargs.P5andnotis_year(args.P5,args)then locali=6;-- initialize the indexer to the sixth positional parameter whilepframe.args[i]do-- in case there are too many authors loop through the authors looking for a year localv=mw.text.trim(pframe.args[i]);-- trim ifis_year(v,args)then-- if a year args.P5=v;-- overwrite whatever was in args.P5 with year break;-- and abandon the search end i=i+1;-- bump the indexer end end returnargs; end --[[--------------------------< H A R V A R D _ C I T A T I O N >---------------------------------------------- common entry point for: {{harvard citation}} aka {{harv}} {{Harvard citation no brackets}} aka {{harvnb}} {{harvcol}} {{harvcolnb}} {{harvcoltxt}} {{Harvard citation text}} aka {{harvtxt}} {{Harvp}} Distinguishing features (brackets and page separators) are specified in this module's {{#invoke}} in the respective templates. ]] localfunctionharvard_citation(frame) localargs=args_fetch(frame,'');-- get the template and invoke parameters; default postscript is empty string returncore(args); end --[[--------------------------< S T R I P _ U R L >------------------------------------------------------------ used by sfn() and sfnm(). This function fixes an issue with reference tooltip gadget where the tooltip is not displayed when an insource locator (|p=, |pp=, |loc=) has an external wikilink that contains a # character strip uri-reserved characters from urls in |p=, |pp-, and |loc= parameters The researved characters are: !#$&'()*+,/:;=?@[] ]] localfunctionstrip_url(pages) localescaped_uri; ifnotpagesor(''==pages)then returnpages; end foruriinpages:gmatch('%[(%a[%w%+%.%-]*://%S+)')do-- for each external link get the uri escaped_uri=uri:gsub("([%(%)%.%%%+%-%*%?%[%^%$%]])","%%%1");-- save a copy with lua pattern characters escaped uri=uri:gsub("[!#%$&'%(%)%*%+,/:;=%?@%[%]%.%%]",'');-- remove reserved characters and '%' because '%20' (space character) is a lua 'invalid capture index' pages=pages:gsub(escaped_uri,uri,1);-- replace original uri with the stripped version end returnpages; end --[[--------------------------< S F N >------------------------------------------------------------------------ entry point for {{sfn}} and {{sfnp}} ]] localfunctionsfn(frame) localargs=args_fetch(frame,'.');-- get the template and invoke parameters; default postscript is a dot localresult=core(args);-- go make a CITEREF anchor -- put it all together and then strip redundant spaces localname=table.concat({'FOOTNOTE',args.P1,args.P2,args.P3,args.P4,args.P5,strip_url(args.page),strip_url(args.pages),strip_url(args.location)}):gsub('%s+',' '); returnframe:extensionTag({name='ref',args={group=args.group,name=name},content=result}); end --[[--------------------------< S F N M >---------------------------------------------------------------------- common entry point for {{sfnm}} and {{sfnmp}} Distinguishing features (brackets) are specified in this module's {{#invoke}} in the respective templates. ]] localfunctionsfnm(frame) localargs=args_default;-- create a copy of the default table localpframe=frame:getParent();-- point to the template's parameter table localn=1;-- index of source; this is the 'n' in na1, ny, etc localfirst_pnum=1;-- first of a pair of positional parameters localsecond_pnum=2;-- second of a pair of positional parameters locallast_ps=0;-- index of the last source with |nps= set locallast_index=0;-- index of the last source; these used to determine which of |ps= or |nps= will terminate the whole rendering localout={};-- table to hold rendered sources localfootnote={'FOOTNOTE'};-- all author, date, insource location stuff becomes part of the reference's footnote id; added as we go fork,vinpairs(frame.args)do-- override defaults with values provided in the #invoke: if any args[k]=v; end whiletruedo ifnotpframe.args[table.concat({n,'a1'})]andnotpframe.args[first_pnum]then break;-- no na1 or matching positional parameter so done end ifpframe.args[table.concat({n,'a1'})]then-- does this source use named parameters? for_,vinipairs({'P1','P2','P3','P4','P5'})do-- initialize for this source args[v]=''; end fori,vinipairs({'P1','P2','P3','P4','P5'})do-- extract author and year parameters for this source args[v]=pframe.args[table.concat({n,'a',i})]or'';-- attempt to assign author name if''==args[v]then-- when there wasn't an author name args[v]=pframe.args[table.concat({n,'y'})]or'';-- attempt to assign year break;-- done with author/date for this source end end else-- this source uses positional parameters args.P1=mw.text.trim(pframe.args[first_pnum]);-- yes, only one author supported args.P2=(pframe.args[second_pnum]andmw.text.trim(pframe.args[second_pnum]))or'';-- when positional author, year must also be positional for_,vinipairs({'P3','P4','P5'})do-- blank the rest of these for this source args[v]=''; end first_pnum=first_pnum+2;-- source must use positional author and positional year second_pnum=first_pnum+1;-- bump these for possible next positional source end args.postscript=pframe.args[table.concat({n,'ps'})]or''; if'none'==args.postscriptthen-- this for compatibility with other footnote templates; does nothing args.postscript=''; end args.group=pframe.args.groupor'';-- reference group args.ref=pframe.args[table.concat({n,'ref'})]or'';-- alternate reference for this source args.page=pframe.args[table.concat({n,'p'})]or'';-- insource locations for this source args.pages=pframe.args[table.concat({n,'pp'})]or''; args.pages=(''~=args.pages)andhyphen_to_dash(args.pages)or''; args.location=pframe.args[table.concat({n,'loc'})]orpframe.args[table.concat({n,'at'})]or''; args.ignore=('yes'==pframe.args[table.concat({n,'ignore-false-positive'})])or('yes'==pframe.args[table.concat({n,'ignore-err'})]); table.insert(out,core(args));-- save the rendering of this source fork,vinipairs({'P1','P2','P3','P4','P5'})do-- create the FOOTNOTE id if''~=args[v]then table.insert(footnote,args[v]); end end fork,vinipairs({'page','pages','location'})do-- these done separately so that we can strip uri-reserved characters from extlinked page numbers if''~=args[v]then table.insert(footnote,strip_url(args[v])) end end last_index=n;-- flags used to select terminal postscript from nps or from end_ps if''~=args.postscriptthen last_ps=n; end n=n+1;-- bump for the next one end localname=table.concat(footnote):gsub('%s+',' ');-- put the footnote together and strip redundant space args.end_ps=pframe.args.postscriptorpframe.args.psor'.';-- this is the postscript for the whole not for the individual sources if'none'==args.end_psthen-- not an original sfnm parameter value; added for compatibility with other footnote templates args.end_ps=''; end localresult=table.concat({table.concat(out,'; '),(last_index==last_ps)and''orargs.end_ps}); returnframe:extensionTag({name='ref',args={group=args.group,name=name},content=result}); end --[[--------------------------< S F N R E F >------------------------------------------------------------------ implements {{sfnref}} ]] localfunctionsfnref(frame) localargs=getArgs(frame); localout={}; fori=1,5do-- get the first five args if there are five args ifargs[i]then out[i]=args[i]; else break;-- less than 5 args break out end end if5==#outthen-- when we have seen five args there may bemore locali=6;-- initialize the indexer to the sixth positional parameter whileargs[i]do-- in case there are too many authors loop through the authors looking for a year ifis_year(args[i],args)then-- if a year out[5]=args[i];-- overwrite whatever was in args[5] with year break;-- and abandon the search end i=i+1;-- bump the indexer end end returnmw.uri.anchorEncode('CITEREF'..table.concat(out)); end --[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------ ]] return{ harvard_citation=harvard_citation, sfn=sfn, sfnm=sfnm, sfnref=sfnref, target_check=target_check, };