Jump to content
Wikipedia The Free Encyclopedia

Module:Footnotes/sandbox

From Wikipedia, the free encyclopedia
This is the module sandbox page for Module:Footnotes (diff).
See also the companion subpage for test cases (run).
Module documentation[view] [edit] [history] [purge]
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.

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.
 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.&nbsp;",
 pages_sep=", pp.&nbsp;",
 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,' &amp; ',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,' &amp; ',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,' ',' &amp; ',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,' &amp; ',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;',{['&ndash;']='–',['&mdash;']='—'});-- replace &mdash; and &ndash; entities with their characters; semicolon mucks up the text.split
 str=str:gsub('&#45;','-');-- replace HTML numeric entity with hyphen character

 str=str:gsub('&nbsp;',' ');-- replace &nbsp; 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,
 };

AltStyle によって変換されたページ (->オリジナル) /