Jump to content

Module:Citation/CS1

Mai Wikipedia

Documentation for this module may be created at Module:Citation/CS1/doc

localz={error_categories={};error_ids={};message_tail={};}-- Include translation message hooks, ID and error handling configuration settings.localcfg=mw.loadData('Module:Citation/CS1/Configuration');-- Contains a list of all recognized parameterslocalwhitelist=mw.loadData('Module:Citation/CS1/Whitelist');-- Whether variable is set or notfunctionis_set(var)returnnot(var==nilorvar=='');end-- First set variable or nil if nonefunctionfirst_set(...)locallist={...};for_,varinpairs(list)doifis_set(var)thenreturnvar;endendend-- Whether needle is in haystackfunctioninArray(needle,haystack)ifneedle==nilthenreturnfalse;endforn,vinipairs(haystack)doifv==needlethenreturnn;endendreturnfalse;end--[[Categorize and emit an error message when the citation contains one or more deprecated parameters. Because deprecated parameters (currently |day=, |month=,|coauthor=, and |coauthors=) aren't related to each other and because these parameters may be concatenated into the variables used by |date= and |author#= (and aliases)details of which parameter caused the error message are not provided. Only one error message is emitted regarless of the number of deprecated parameters in the citation.]]functiondeprecated_parameter()iftrue~=Page_in_deprecated_catthen-- if we haven't been here before then set a Page_in_deprecated_cat=true;-- sticky flag so that if there are more than one deprecated parameter the category is added only oncetable.insert(z.message_tail,{seterror('deprecated_params',{error_message},true)});-- add error messageendend-- Populates numbered arguments in a message string using an argument table.functionsubstitute(msg,args)-- return args and tostring( mw.message.newRawMessage( msg, args ) ) or msg;returnargsandmw.message.newRawMessage(msg,args):plain()ormsg;end--[[Apply kerning to open the space between the quote mark provided by the Module and a leading or trailing quote mark contained in a |title= or |chapter= parameter's value.This function will positive kern either single or double quotes: "'Unkerned title with leading and trailing single quote marks'" " 'Kerned title with leading and trailing single quote marks' " (in real life the kerning isn't as wide as this example)]]functionkern_quotes(str)localleft='<span style="padding-left:0.2em;">%1</span>';-- spacing to use when title contains leading single or double quote marklocalright='<span style="padding-right:0.2em;">%1</span>';-- spacing to use when title contains trailing single or double quote markifstr:match("^[\"\'][^\']")thenstr=string.gsub(str,"^[\"\']",left,1);-- replace (captured) leading single or double quote with left-side <span>endifstr:match("[^\'][\"\']$")thenstr=string.gsub(str,"[\"\']$",right,1);-- replace (captured) trailing single or double quote with right-side <span>endreturnstr;end-- Wraps a string using a message_list configuration taking one argumentfunctionwrap(key,str,lower)ifnotis_set(str)thenreturn"";elseifinArray(key,{'italic-title','trans-italic-title'})thenstr=safeforitalics(str);endiflower==truethenreturnsubstitute(cfg.messages[key]:lower(),{str});elsereturnsubstitute(cfg.messages[key],{str});endend--[[Argument wrapper. This function provides support for argument mapping defined in the configuration file so that multiple namescan be transparently aliased to single internal variable.]]functionargument_wrapper(args)localorigin={};returnsetmetatable({ORIGIN=function(self,k)localdummy=self[k];--force the variable to be loaded.returnorigin[k];end},{__index=function(tbl,k)iforigin[k]~=nilthenreturnnil;endlocalargs,list,v=args,cfg.aliases[k];iftype(list)=='table'thenv,origin[k]=selectone(args,list,'redundant_parameters');iforigin[k]==nilthenorigin[k]='';-- Empty string, not nilendelseiflist~=nilthenv,origin[k]=args[list],list;else-- maybe let through instead of raising an error?-- v, origin[k] = args[k], k;error(cfg.messages['unknown_argument_map']);end-- Empty strings, not nil;ifv==nilthenv=cfg.defaults[k]or'';origin[k]='';endtbl=rawset(tbl,k,v);returnv;end,});end--[[Looks for a parameter's name in the whitelist.Parameters in the whitelist can have three values: true - active, supported parameters false - deprecated, supported parameters nil - unsupported parameters]]functionvalidate(name)localname=tostring(name);localstate=whitelist.basic_arguments[name];-- Normal argumentsiftrue==statethenreturntrue;end-- valid actively supported parameteriffalse==statethendeprecated_parameter();-- parameter is deprecated but still supportedreturntrue;end-- Arguments with numbers in themname=name:gsub("%d+","#");-- replace digit(s) with # (last25 becomes last#state=whitelist.numbered_arguments[name];iftrue==statethenreturntrue;end-- valid actively supported parameteriffalse==statethendeprecated_parameter();-- parameter is deprecated but still supportedreturntrue;endreturnfalse;-- Not supported because not found or name is set to nilend-- Formats a comment for error trappingfunctionerrorcomment(content,hidden)returnwrap(hiddenand'hidden-error'or'visible-error',content);end--[[Sets an error condition and returns the appropriate error message. The actual placementof the error message in the output is the responsibility of the calling function.]]functionseterror(error_id,arguments,raw,prefix,suffix)localerror_state=cfg.error_conditions[error_id];prefix=prefixor"";suffix=suffixor"";iferror_state==nilthenerror(cfg.messages['undefined_error']);elseifis_set(error_state.category)thentable.insert(z.error_categories,error_state.category);endlocalmessage=substitute(error_state.message,arguments);message=message.." ([["..cfg.messages['help page link'].."#"..error_state.anchor.."|"..cfg.messages['help page label'].."]])";z.error_ids[error_id]=true;ifinArray(error_id,{'bare_url_missing_title','trans_missing_title'})andz.error_ids['citation_missing_title']thenreturn'',false;endmessage=table.concat({prefix,message,suffix});ifraw==truethenreturnmessage,error_state.hidden;endreturnerrorcomment(message,error_state.hidden);end-- Formats a wiki style external linkfunctionexternallinkid(options)localurl_string=options.id;ifoptions.encode==trueoroptions.encode==nilthenurl_string=mw.uri.encode(url_string);endreturnmw.ustring.format('[[%s|%s]]%s[%s%s%s %s]',options.link,options.label,options.separatoror"&nbsp;",options.prefix,url_string,options.suffixor"",mw.text.nowiki(options.id));end-- Formats a wiki style internal linkfunctioninternallinkid(options)returnmw.ustring.format('[[%s|%s]]%s[[%s%s%s|%s]]',options.link,options.label,options.separatoror"&nbsp;",options.prefix,options.id,options.suffixor"",mw.text.nowiki(options.id));end-- Format an external link with error checkingfunctionexternallink(URL,label,source)localerror_str="";ifnotis_set(label)thenlabel=URL;ifis_set(source)thenerror_str=seterror('bare_url_missing_title',{wrap('parameter',source)},false," ");elseerror(cfg.messages["bare_url_no_origin"]);endendifnotcheckurl(URL)thenerror_str=seterror('bad_url',{},false," ")..error_str;endreturntable.concat({"[",URL," ",safeforurl(label),"]",error_str});end-- Formats a link to Amazonfunctionamazon(id,domain)ifnotis_set(domain)thendomain="com"elseif("jp"==domainor"uk"==domain)thendomain="co."..domainendlocalhandler=cfg.id_handlers['ASIN'];returnexternallinkid({link=handler.link,label=handler.label,prefix="//www.amazon."..domain.."/dp/",id=id,encode=handler.encode,separator=handler.separator})end--[[Format PMID and do simple error checking. PMIDs are sequential numbers beginning at 1 and counting up. This code checks the PMID to see that itcontains only digits and is less than test_limit; the value in local variable test_limit will need to be updated periodically as more PMIDs are issued.]]functionpmid(id)localtest_limit=30000000;-- update this value as PMIDs approachlocalhandler=cfg.id_handlers['PMID'];localerr_cat='';-- presume that PMID is validifid:match("[^%d]")then-- if PMID has anything but digitserr_cat=' '..seterror('bad_pmid');-- set an error messageelse-- PMID is only digitslocalid_num=tonumber(id);-- convert id to a number for range testingif1>id_numortest_limit<id_numthen-- if PMID is outside test limit boundarieserr_cat=' '..seterror('bad_pmid');-- set an error messageendendreturnexternallinkid({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})..err_cat;end--[[Determines if a PMC identifier's online version is embargoed. Compares the date in |embargo= against today's date. If embargo date isin the future, returns true; otherwse, returns false because the embargo has expired or |embargo= not set in this cite.]]functionis_embargoed(embargo)ifis_set(embargo)thenlocallang=mw.getContentLanguage();localgood1,embargo_date,good2,todays_date;good1,embargo_date=pcall(lang.formatDate,lang,'U',embargo);good2,todays_date=pcall(lang.formatDate,lang,'U');ifgood1andgood2andtonumber(embargo_date)>=tonumber(todays_date)then--is embargo date is in the future?returntrue;-- still embargoedendendreturnfalse;-- embargo expired or |embargo= not setend--[[Formats a PMC and checks for embargoed articles. The embargo parameter takes a date for a value. If the embargo date is in the futurethe PMC identifier will not be linked to the article. If the embargo specifies a date in the past, or if it is empty or omitted, thenthe PMC identifier is linked to the article through the link at cfg.id_handlers['PMC'].prefix.]]functionpmc(id,embargo)localhandler=cfg.id_handlers['PMC'];localtext;ifis_embargoed(embargo)thentext="[["..handler.link.."|"..handler.label.."]]:"..handler.separator..id;--still embargoed so no external linkelsetext=externallinkid({link=handler.link,label=handler.label,--no embargo date, ok to link to articleprefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})endreturntext;end-- Formats a DOI and checks for DOI errors.functiondoi(id,inactive)localcat=""localhandler=cfg.id_handlers['DOI'];localtext;ifis_set(inactive)thenlocalinactive_year=inactive:match("%d%d%d%d")or'';-- try to get the year portion from the inactive datetext="[["..handler.link.."|"..handler.label.."]]:"..id;ifis_set(inactive_year)thentable.insert(z.error_categories,"Pages with DOIs inactive since "..inactive_year);elsetable.insert(z.error_categories,"Pages with inactive DOIs");-- when inactive doesn't contain a recognizable yearendinactive=" ("..cfg.messages['inactive'].." "..inactive..")"elsetext=externallinkid({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})inactive=""endifnil==id:match("^10%.[^%s–]-[^%.,]$")then-- doi must begin with '10.', must not contain spaces or endashes, and must not end with period or commacat=' '..seterror('bad_doi');endreturntext..inactive..catend-- Formats an OpenLibrary link, and checks for associated errors.functionopenlibrary(id)localcode=id:sub(-1,-1)localhandler=cfg.id_handlers['OL'];if(code=="A")thenreturnexternallinkid({link=handler.link,label=handler.label,prefix="http://openlibrary.org/authors/OL",id=id,separator=handler.separator,encode=handler.encode})elseif(code=="M")thenreturnexternallinkid({link=handler.link,label=handler.label,prefix="http://openlibrary.org/books/OL",id=id,separator=handler.separator,encode=handler.encode})elseif(code=="W")thenreturnexternallinkid({link=handler.link,label=handler.label,prefix="http://openlibrary.org/works/OL",id=id,separator=handler.separator,encode=handler.encode})elsereturnexternallinkid({link=handler.link,label=handler.label,prefix="http://openlibrary.org/OL",id=id,separator=handler.separator,encode=handler.encode})..' '..seterror('bad_ol');endend--[[Validate and format an issn. This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of fourdigits with a space. When that condition occurred, the resulting link looked like this: |issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327] -- can't have spaces in an external linkThis code now prevents that by inserting a hyphen at the issn midpoint. It also validates the issn for length and makes sure that the checkdigit agreeswith the calculated value. Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issnerror message. The issn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits.]]functionissn(id)localissn_copy=id;-- save a copy of unadulterated issn; use this version for display if issn does not validatelocalhandler=cfg.id_handlers['ISSN'];localtext;localvalid_issn=true;id=id:gsub("[%s-–]","");-- strip spaces, hyphens, and ndashes from the issnif8~=id:len()ornil==id:match("^%d*X?$")then-- validate the issn: 8 didgits long, containing only 0-9 or X in the last positionvalid_issn=false;-- wrong length or improper characterelsevalid_issn=is_valid_isxn(id,8);-- validate issnendiftrue==valid_issnthenid=string.sub(id,1,4).."-"..string.sub(id,5);-- if valid, display correctly formatted versionelseid=issn_copy;-- if not valid, use the show the invalid issn with error messageendtext=externallinkid({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})iffalse==valid_issnthentext=text..' '..seterror('bad_issn')-- add an error message if the issn is invalidendreturntextend--[[This function sets default title types (equivalent to the citation including |type=<default value>) for those citations that have defaults.Also handles the special case where it is desireable to omit the title type from the rendered citation (|type=none).]]functionset_titletype(cite_class,title_type)ifis_set(title_type)thenif"none"==title_typethentitle_type="";-- if |type=none then type parameter not displayedendreturntitle_type;-- if |type= has been set to any other value use that valueendif"podcast"==cite_classthen-- if this citation is cite podcastreturn"Podcast";-- display podcast annotationelseif"pressrelease"==cite_classthen-- if this citation is cite press releasereturn"Press release";-- display press release annotationelseif"techreport"==cite_classthen-- if this citation is cite techreportreturn"Technical report";-- display techreport annotationelseif"thesis"==cite_classthen-- if this citation is cite thesis (degree option handled after this function returns)return"Thesis";-- display simple thesis annotation (without |degree= modification)endend-- returns a number according to the month in a date: 1 for January, etc. Capitalization and spelling must be correct. If not a valid month, returns 0functionget_month_number(month)locallong_months={['January']=1,['February']=2,['March']=3,['April']=4,['May']=5,['June']=6,['July']=7,['August']=8,['September']=9,['October']=10,['November']=11,['December']=12};localshort_months={['Jan']=1,['Feb']=2,['Mar']=3,['Apr']=4,['May']=5,['Jun']=6,['Jul']=7,['Aug']=8,['Sep']=9,['Oct']=10,['Nov']=11,['Dec']=12};localtemp;temp=long_months[month];iftempthenreturntemp;end-- if month is the long-form nametemp=short_months[month];iftempthenreturntemp;end-- if month is the short-form namereturn0;-- misspelled, improper case, or not a month nameend-- returns a number according to the sequence of seasons in a year: 1 for Winter, etc. Capitalization and spelling must be correct. If not a valid season, returns 0functionget_season_number(season)localseason_list={['Winter']=1,['Spring']=2,['Summer']=3,['Fall']=4,['Autumn']=4}localtemp;temp=season_list[season];iftempthenreturntemp;end-- if season is a valid name return its numberreturn0;-- misspelled, improper case, or not a season nameend--[[Returns true if day is less than or equal to the number of days in month; else returns false.Assumes Julian calendar prior to year 1582 and Gregorian calendar thereafter. Accounts for Julian calendar leap years before 1582 and Gregorian leap years after 1582.Where the two calendars overlap (1582 to approximately 1923) dates are assumed to be Gregorian.]]functionis_valid_date(year,month,day)localdays_in_month={31,28,31,30,31,30,31,31,30,31,30,31};localmonth_length;if(2==month)then-- if Februarymonth_length=28;-- then 28 days unlessif1582>tonumber(year)then-- Julian calendarif0==(year%4)thenmonth_length=29;endelse-- Gregorian calendarif(0==(year%4)and(0~=(year%100)or0==(year%400)))then-- is a leap year?month_length=29;-- if leap year then 29 days in Februaryendendelsemonth_length=days_in_month[month];endiftonumber(day)>month_lengththenreturnfalse;endreturntrue;end--[[Check a pair of months or seasons to see if both are valid members of a month or season pair.Month pairs are expected to be left to right, earliest to latest in time. Similarly, seasons are also left to right, earliest to latest in time. There isan oddity with seasons. Winter is assigned a value of 1, spring 2, ..., fall and autumn 4. Because winter can follow fall/autumn at the end of a calender year, a special testis made to see if |date=Fall-Winter yyyy (4-1) is the date.]]functionis_valid_month_season_range(range_start,range_end)localrange_start_number=get_month_number(range_start);if0==range_start_numberthen-- is this a month range?localrange_start_number=get_season_number(range_start);-- not a month; is it a season? get start season numberlocalrange_end_number=get_season_number(range_end);-- get end season numberif0~=range_start_numberthen-- is start of range a season?ifrange_start_number<range_end_numberthen-- range_start is a seasonreturntrue;-- return true when range_end is also a season and follows start season; else falseendif4==range_start_numberand1==range_end_numberthen-- special case when range is Fall-Winter or Autumn-Winterreturntrue;endendreturnfalse;-- range_start is not a month or a season; or range_start is a season and range_end is not; or improper season sequenceendlocalrange_end_number=get_month_number(range_end);-- get end month numberifrange_start_number<range_end_numberthen-- range_start is a month; does range_start precede range_end?returntrue;-- if yes, return trueendreturnfalse;-- range_start month number is greater than or equal to range end number; or range end isn't a monthend--[[Check date format to see that it is one of the formats approved by MOS:DATE: MMMM D, YYYY; D MMMM YYYY; MMMM YYYY; YYYY-MM-DD; YYYY.Additionally, check the date to see that it is a real date: no 31 in 30-day months; no 29 February when not a leap year. Months, both long-form and threecharacter abbreviations, and seasons must be spelled correctly.If the date fails the fomat tests, this function returns false but does not return values for anchor_year and COinS_date. When this happens, the date parameter isused in the COinS metadata and the CITEREF identifier gets its year from the year parameter if present.Inputs: date_string - date string from date-holding parameters (date, year, accessdate, embargo, archivedate, etc)Returns: false if date string is not a real date; else true, anchor_year, COinS_date anchor_year can be used in CITEREF anchors COinS_date is date_string without anchor_year disambiguator if any]]functioncheck_date(date_string)localyear;localmonth=0;-- assume that month and day are not used; if either is zero then final year/month/day validation is not necessarylocalday=0;localday2=0;-- second day in a day range localanchor_year;localcoins_date;ifdate_string:match("^%d%d%d%d%-%d%d%-%d%d$")then-- Year-initial numerical year month day formatyear,month,day=string.match(date_string,"(%d%d%d%d)%-(%d%d)%-(%d%d)");month=tonumber(month);if12<monthor1>monthor1583>tonumber(year)thenreturnfalse;end-- month number not valid or not Gregorian calendaranchor_year=year;elseifdate_string:match("^%a+ +[1-9]%d?, +[1-9]%d%d%d%a?$")then-- month-initial: month day, yearmonth,day,anchor_year,year=string.match(date_string,"(%a+)%s*(%d%d?),%s*((%d%d%d%d)%a?)");month=get_month_number(month);if0==monththenreturnfalse;end-- return false if month text isn't one of the twelve monthselseifdate_string:match("^%a+ +[1-9]%d?–[1-9]%d?, +[1-9]%d%d%d%a?$")then-- month-initial day range: month day–day, year; days are separated by endashmonth,day,day2,anchor_year,year=string.match(date_string,"(%a+) +(%d%d?)–(%d%d?), +((%d%d%d%d)%a?)");iftonumber(day)>=tonumber(day2)thenreturnfalse;end-- date range order is left to right: earlier to later; dates may not be the same;month=get_month_number(month);if0==monththenreturnfalse;end-- return false if month text isn't one of the twelve monthselseifdate_string:match("^[1-9]%d? +%a+ +[1-9]%d%d%d%a?$")then-- day-initial: day month yearday,month,anchor_year,year=string.match(date_string,"(%d%d*)%s*(%a+)%s*((%d%d%d%d)%a?)");month=get_month_number(month);if0==monththenreturnfalse;end-- return false if month text isn't one of the twelve monthselseifdate_string:match("^[1-9]%d?–[1-9]%d? +%a+ +[1-9]%d%d%d%a?$")then-- day-range-initial: day–day month year; days are separated by endashday,day2,month,anchor_year,year=string.match(date_string,"(%d%d?)–(%d%d?) +(%a+) +((%d%d%d%d)%a?)");iftonumber(day)>=tonumber(day2)thenreturnfalse;end-- date range order is left to right: earlier to later; dates may not be the same;month=get_month_number(month);if0==monththenreturnfalse;end-- return false if month text isn't one of the twelve monthselseifmw.ustring.match(date_string,"^%a+–%a+ +[1-9]%d%d%d%a?$")then-- month/season range year; months separated by endash localmonth2month,month2,anchor_year,year=mw.ustring.match(date_string,"(%a+)[%-/–](%a+)%s*((%d%d%d%d)%a?)");iffalse==is_valid_month_season_range(month,month2)thenreturnfalse;endelseifdate_string:match("^%a+ +%d%d%d%d%a?$")then-- month/season yearmonth,anchor_year,year=string.match(date_string,"(%a+)%s*((%d%d%d%d)%a?)");if0==get_month_number(month)then-- if month text isn't one of the twelve months, might be a seasonif0==get_season_number(month)then-- not a month, is it a season?returnfalse;-- return false not a month or one of the five seasonsendendelseifdate_string:match("^[1-9]%d%d%d?%a?$")then-- year; here accept either YYY or YYYYanchor_year,year=string.match(date_string,"((%d%d%d%d?)%a?)");elsereturnfalse;-- date format not one of the MOS:DATE approved formatsendif0~=monthand0~=daythen-- check year month day dates for validityif0~=day2then-- If there is a second day (d–d Mmm YYYY or Mmm d–d, YYYY) test the second dateiffalse==is_valid_date(year,month,day2)thenreturnfalse;-- second date in date range string is not a real date return false; unset anchor_year and coins_dateend-- if second date range string is valid, fall through to test the first date rangeendiffalse==is_valid_date(year,month,day)thenreturnfalse;-- date string is not a real date return false; unset anchor_year and coins_dateendendcoins_date=mw.ustring.gsub(date_string,"–","-");-- if here, then date_string is valid; set coins_date and replace any ndash with a hyphenreturntrue,anchor_year,coins_date;-- format is good and date string represents a real dateend--[[Cycle the date-holding parameters in passed table date_parameters_list through check_date() to check compliance with MOS:DATE. For all valid dates, check_date() returnstrue and values for anchor_year (used in CITEREF identifiers) and COinS_date (used in the COinS metadata). The |date= parameter test is unique. This function only accepts anchor_year and COinS_date results from the |date= parameter test and |date= is the only date-holding parameter that is allowed to contain the no-date keywords"n.d." or "nd" (without quotes).Unlike most error messages created in this module, only one error message is created by this function. Because all of the date holding parameters are processed serially,a single error message is created as the dates are tested.]]functiondates(date_parameters_list)localanchor_year;-- will return as nil if the date being tested is not |date=localCOinS_date;-- will return as nil if the date being tested is not |date=localerror_message="";localgood_date=false;fork,vinpairs(date_parameters_list)do-- for each date-holding parameter in the listifis_set(v)then-- if the parameter has a valueifv:match("^c%. [1-9]%d%d%d?%a?$")then-- special case for c. year or with or without CITEREF disambiguator - only |date= and |year=if'date'==kthengood_date,anchor_year,COinS_date=true,v:match("((c%. [1-9]d%d%d?)%a?)");-- anchor year and COinS_date only from |date= parameterelseif'year'==kthengood_date=true;endelseif'year'==kthen-- if the parameter is |year= (but not c. year)ifv:match("^[1-9]%d%d%d?%a?$")then-- year with or without CITEREF disambiguatorgood_date=true;endelseif'date'==kthen-- if the parameter is |date=ifv:match("n%.d%.%a?")then-- if |date=n.d. with or without a CITEREF disambiguatorgood_date,anchor_year,COinS_date=true,v:match("((n%.d%.)%a?)");--"n.d."; no error when date parameter is set to no dateelseifv:match("nd%a?$")then-- if |date=nd with or without a CITEREF disambiguatorgood_date,anchor_year,COinS_date=true,v:match("((nd)%a?)");--"nd"; no error when date parameter is set to no dateelsegood_date,anchor_year,COinS_date=check_date(v);-- go test the dateendelse-- any other date-holding parametergood_date=check_date(v);-- go test the dateendiffalse==good_datethen-- assemble one error message so we don't add the tracking category multiple timesifis_set(error_message)then-- once we've added the first portion of the error message ...error_message=error_message..", ";-- ... add a comma space separatorenderror_message=error_message.."&#124;"..k.."=";-- add the failed parameterendendendifis_set(error_message)thentable.insert(z.message_tail,{seterror('bad_date',{error_message},true)});-- add this error messageendreturnanchor_year,COinS_date;-- and doneend--[[Determines whether a URL string is validAt present the only check is whether the string appears to be prefixed with a URI scheme. It is not determined whether the URI scheme is valid or whether the URL is otherwise well formed.]]functioncheckurl(url_str)-- Protocol-relative or URL schemereturnurl_str:sub(1,2)=="//"orurl_str:match("^[^/]*:")~=nil;end-- Removes irrelevant text and dashes from ISBN number-- Similar to that used for Special:BookSourcesfunctioncleanisbn(isbn_str)returnisbn_str:gsub("[^-0-9X]","");end--[[ISBN-10 and ISSN validator code calculates checksum across all isbn/issn digits including the check digit. ISBN-13 is checked in checkisbn().If the number is valid the result will be 0. Before calling this function, issbn/issn must be checked for length and stripped of dashes,spaces and other non-isxn characters.]]functionis_valid_isxn(isxn_str,len)localtemp=0;isxn_str={isxn_str:byte(1,len)};-- make a table of byteslen=len+1;-- adjust to be a loop counterfori,vinipairs(isxn_str)do-- loop through all of the bytes and calculate the checksumifv==string.byte("X")then-- if checkdigit is Xtemp=temp+10*(len-i);-- it represents 10 decimalelsetemp=temp+tonumber(string.char(v))*(len-i);endendreturntemp%11==0;-- returns true if calculation result is zeroend-- Determines whether an ISBN string is validfunctioncheckisbn(isbn_str)ifnil~=isbn_str:match("[^%s-0-9X]")thenreturnfalse;end-- fail if isbn_str contains anything but digits, hyphens, or the uppercase Xisbn_str=isbn_str:gsub("-",""):gsub(" ","");-- remove hyphens and spaceslocallen=isbn_str:len();iflen~=10andlen~=13thenreturnfalse;endiflen==10thenifisbn_str:match("^%d*X?$")==nilthenreturnfalse;endreturnis_valid_isxn(isbn_str,10);elselocaltemp=0;ifisbn_str:match("^97[89]%d*$")==nilthenreturnfalse;end-- isbn13 begins with 978 or 979isbn_str={isbn_str:byte(1,len)};fori,vinipairs(isbn_str)dotemp=temp+(3-2*(i%2))*tonumber(string.char(v));endreturntemp%10==0;endend-- Gets the display text for a wikilink like [[A|B]] or [[B]] gives Bfunctionremovewikilink(str)return(str:gsub("%[%[([^%[%]]*)%]%]",function(l)returnl:gsub("^[^|]*|(.*)$","%1"):gsub("^%s*(.-)%s*$","%1");end));end-- Escape sequences for content that will be used for URL descriptionsfunctionsafeforurl(str)ifstr:match("%[%[.-%]%]")~=nilthentable.insert(z.message_tail,{seterror('wikilink_in_url',{},true)});endreturnstr:gsub('[%[%]\n]',{['[']='&#91;',[']']='&#93;',['\n']=' '});end-- Converts a hyphen to a dashfunctionhyphentodash(str)ifnotis_set(str)orstr:match("[%[%]{}<>]")~=nilthenreturnstr;endreturnstr:gsub('-','–');end-- Protects a string that will be wrapped in wiki italic markup '' ... ''functionsafeforitalics(str)--[[ Note: We can not use <i> for italics, as the expected behavior for italics specified by ''...'' in the title is that they will be inverted (i.e. unitalicized) in the resulting references. In addition, <i> and '' tend to interact poorly under Mediawiki's HTML tidy. ]]ifnotis_set(str)thenreturnstr;elseifstr:sub(1,1)=="'"thenstr="<span />"..str;endifstr:sub(-1,-1)=="'"thenstr=str.."<span />";end-- Remove newlines as they break italics.returnstr:gsub('\n',' ');endend--[[Joins a sequence of strings together while checking for duplicate separationcharacters.]]functionsafejoin(tbl,duplicate_char)--[[ Note: we use string functions here, rather than ustring functions. This has considerably faster performance and should work correctly as  long as the duplicate_char is strict ASCII. The strings in tbl may be ASCII or UTF8. ]]localstr='';localcomp='';localend_chr='';localtrim;for_,valueinipairs(tbl)doifvalue==nilthenvalue='';endifstr==''thenstr=value;elseifvalue~=''thenifvalue:sub(1,1)=='<'then-- Special case of values enclosed in spans and other markup.comp=value:gsub("%b<>","");elsecomp=value;endifcomp:sub(1,1)==duplicate_charthentrim=false;end_chr=str:sub(-1,-1);-- str = str .. "<HERE(enchr=" .. end_chr.. ")"ifend_chr==duplicate_charthenstr=str:sub(1,-2);elseifend_chr=="'"thenifstr:sub(-3,-1)==duplicate_char.."''"thenstr=str:sub(1,-4).."''";elseifstr:sub(-5,-1)==duplicate_char.."]]''"thentrim=true;elseifstr:sub(-4,-1)==duplicate_char.."]''"thentrim=true;endelseifend_chr=="]"thenifstr:sub(-3,-1)==duplicate_char.."]]"thentrim=true;elseifstr:sub(-2,-1)==duplicate_char.."]"thentrim=true;endelseifend_chr==" "thenifstr:sub(-2,-1)==duplicate_char.." "thenstr=str:sub(1,-3);endendiftrimthenifvalue~=compthenlocaldup2=duplicate_char;ifdup2:match("%A")thendup2="%"..dup2;endvalue=value:gsub("(%b<>)"..dup2,"%1",1)elsevalue=value:sub(2,-1);endendendstr=str..value;endendreturnstr;end-- Attempts to convert names to initials.functionreducetoinitials(first)localinitials={}forwordinstring.gmatch(first,"%S+")dotable.insert(initials,string.sub(word,1,1))-- Vancouver format does not include full stops.endreturntable.concat(initials)-- Vancouver format does not include spaces.end-- Formats a list of people (e.g. authors / editors) functionlistpeople(control,people)localsep=control.sep;localnamesep=control.nameseplocalformat=control.formatlocalmaximum=control.maximumlocallastauthoramp=control.lastauthoramp;localtext={}localetal=false;ifsep:sub(-1,-1)~=" "thensep=sep.." "endifmaximum~=nilandmaximum<1thenreturn"",0;endfori,personinipairs(people)doifis_set(person.last)thenlocalmask=person.masklocalonelocalsep_one=sep;ifmaximum~=nilandi>maximumthenetal=true;break;elseif(mask~=nil)thenlocaln=tonumber(mask)if(n~=nil)thenone=string.rep("&mdash;",n)elseone=mask;sep_one=" ";endelseone=person.lastlocalfirst=person.firstifis_set(first)thenif("vanc"==format)thenfirst=reducetoinitials(first)endone=one..namesep..firstendifis_set(person.link)thenone="[["..person.link.."|"..one.."]]"endendtable.insert(text,one)table.insert(text,sep_one)endendlocalcount=#text/2;ifcount>0thenifcount>1andis_set(lastauthoramp)andnotetalthentext[#text-2]=" & ";endtext[#text]=nil;endlocalresult=table.concat(text)-- construct listifetalthenlocaletal_text=cfg.messages['et al'];result=result.." "..etal_text;end-- if necessary wrap result in <span> tag to format in Small Capsif("scap"==format)thenresult='<span class="smallcaps" style="font-variant:small-caps">'..result..'</span>';endreturnresult,countend-- Generates a CITEREF anchor ID.functionanchorid(options)return"CITEREF"..table.concat(options);end-- Gets name list from the input argumentsfunctionextractnames(args,list_name)localnames={};locali=1;locallast;whiletruedolast=selectone(args,cfg.aliases[list_name..'-Last'],'redundant_parameters',i);ifnotis_set(last)then-- just in case someone passed in an empty parameterbreak;endnames[i]={last=last,first=selectone(args,cfg.aliases[list_name..'-First'],'redundant_parameters',i),link=selectone(args,cfg.aliases[list_name..'-Link'],'redundant_parameters',i),mask=selectone(args,cfg.aliases[list_name..'-Mask'],'redundant_parameters',i)};i=i+1;endreturnnames;end-- Populates ID table from arguments using configuration settingsfunctionextractids(args)localid_list={};fork,vinpairs(cfg.id_handlers)dov=selectone(args,v.parameters,'redundant_parameters');ifis_set(v)thenid_list[k]=v;endendreturnid_list;end-- Takes a table of IDs and turns it into a table of formatted ID outputs.functionbuildidlist(id_list,options)localnew_list,handler={};functionfallback(k)return{__index=function(t,i)returncfg.id_handlers[k][i]end}end;fork,vinpairs(id_list)do-- fallback to read-only cfghandler=setmetatable({['id']=v},fallback(k));ifhandler.mode=='external'thentable.insert(new_list,{handler.label,externallinkid(handler)});elseifhandler.mode=='internal'thentable.insert(new_list,{handler.label,internallinkid(handler)});elseifhandler.mode~='manual'thenerror(cfg.messages['unknown_ID_mode']);elseifk=='DOI'thentable.insert(new_list,{handler.label,doi(v,options.DoiBroken)});elseifk=='ASIN'thentable.insert(new_list,{handler.label,amazon(v,options.ASINTLD)});elseifk=='OL'thentable.insert(new_list,{handler.label,openlibrary(v)});elseifk=='PMC'thentable.insert(new_list,{handler.label,pmc(v,options.Embargo)});elseifk=='PMID'thentable.insert(new_list,{handler.label,pmid(v)});elseifk=='ISSN'thentable.insert(new_list,{handler.label,issn(v)});elseifk=='ISBN'thenlocalISBN=internallinkid(handler);ifnotcheckisbn(v)andnotis_set(options.IgnoreISBN)thenISBN=ISBN..seterror('bad_isbn',{},false," ","");endtable.insert(new_list,{handler.label,ISBN});elseerror(cfg.messages['unknown_manual_ID']);endendfunctioncomp(a,b)-- used in following table.sort()returna[1]<b[1];endtable.sort(new_list,comp);fork,vinipairs(new_list)donew_list[k]=v[2];endreturnnew_list;end-- Chooses one matching parameter from a list of parameters to consider-- Generates an error if more than one match is present.functionselectone(args,possible,error_condition,index)localvalue=nil;localselected='';localerror_list={};ifindex~=nilthenindex=tostring(index);end-- Handle special case of "#" replaced by empty stringifindex=='1'thenfor_,vinipairs(possible)dov=v:gsub("#","");ifis_set(args[v])thenifvalue~=nilandselected~=vthentable.insert(error_list,v);elsevalue=args[v];selected=v;endendendendfor_,vinipairs(possible)doifindex~=nilthenv=v:gsub("#",index);endifis_set(args[v])thenifvalue~=nilandselected~=vthentable.insert(error_list,v);elsevalue=args[v];selected=v;endendendif#error_list>0thenlocalerror_str="";for_,kinipairs(error_list)doiferror_str~=""thenerror_str=error_str..cfg.messages['parameter-separator']enderror_str=error_str..wrap('parameter',k);endif#error_list>1thenerror_str=error_str..cfg.messages['parameter-final-separator'];elseerror_str=error_str..cfg.messages['parameter-pair-separator'];enderror_str=error_str..wrap('parameter',selected);table.insert(z.message_tail,{seterror(error_condition,{error_str},true)});endreturnvalue,selected;end-- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse-- the citation information.functionCOinS(data)if'table'~=type(data)ornil==next(data)thenreturn'';endlocalctx_ver="Z39.88-2004";-- treat table strictly as an array with only set values.localOCinSoutput=setmetatable({},{__newindex=function(self,key,value)ifis_set(value)thenrawset(self,#self+1,table.concat{key,'=',mw.uri.encode(removewikilink(value))});endend});ifis_set(data.Chapter)thenOCinSoutput.rft_val_fmt="info:ofi/fmt:kev:mtx:book";OCinSoutput["rft.genre"]="bookitem";OCinSoutput["rft.btitle"]=data.Chapter;OCinSoutput["rft.atitle"]=data.Title;elseifis_set(data.Periodical)thenOCinSoutput.rft_val_fmt="info:ofi/fmt:kev:mtx:journal";OCinSoutput["rft.genre"]="article";OCinSoutput["rft.jtitle"]=data.Periodical;OCinSoutput["rft.atitle"]=data.Title;elseOCinSoutput.rft_val_fmt="info:ofi/fmt:kev:mtx:book";OCinSoutput["rft.genre"]="book"OCinSoutput["rft.btitle"]=data.Title;endOCinSoutput["rft.place"]=data.PublicationPlace;OCinSoutput["rft.date"]=data.Date;OCinSoutput["rft.series"]=data.Series;OCinSoutput["rft.volume"]=data.Volume;OCinSoutput["rft.issue"]=data.Issue;OCinSoutput["rft.pages"]=data.Pages;OCinSoutput["rft.edition"]=data.Edition;OCinSoutput["rft.pub"]=data.PublisherName;fork,vinpairs(data.ID_list)dolocalid,value=cfg.id_handlers[k].COinS;ifk=='ISBN'thenvalue=cleanisbn(v);elsevalue=v;endifstring.sub(idor"",1,4)=='info'thenOCinSoutput["rft_id"]=table.concat{id,"/",v};elseOCinSoutput[id]=value;endendlocallast,first;fork,vinipairs(data.Authors)dolast,first=v.last,v.first;ifk==1thenifis_set(last)thenOCinSoutput["rft.aulast"]=last;endifis_set(first)thenOCinSoutput["rft.aufirst"]=first;endendifis_set(last)andis_set(first)thenOCinSoutput["rft.au"]=table.concat{last,", ",first};elseifis_set(last)thenOCinSoutput["rft.au"]=last;endendOCinSoutput.rft_id=data.URL;OCinSoutput.rfr_id=table.concat{"info:sid/",mw.site.server:match("[^/]*$"),":",data.RawPage};OCinSoutput=setmetatable(OCinSoutput,nil);-- sort with version string always first, and combine.table.sort(OCinSoutput);table.insert(OCinSoutput,1,"ctx_ver="..ctx_ver);-- such as "Z39.88-2004"returntable.concat(OCinSoutput,"&");end--[[This is the main function doing the majority of the citationformatting.]]functioncitation0(config,args)--[[  Load Input Parameters The argment_wrapper facillitates the mapping of multiple aliases to single internal variable. ]]localA=argument_wrapper(args);localilocalPPrefix=A['PPrefix']localPPPrefix=A['PPPrefix']ifis_set(A['NoPP'])thenPPPrefix=""PPrefix=""end-- Pick out the relevant fields from the arguments. Different citation templates-- define different field names for the same underlying things. localAuthors=A['Authors'];locala=extractnames(args,'AuthorList');localCoauthors=A['Coauthors'];localOthers=A['Others'];localEditors=A['Editors'];locale=extractnames(args,'EditorList');localYear=A['Year'];localPublicationDate=A['PublicationDate'];localOrigYear=A['OrigYear'];localDate=A['Date'];localLayDate=A['LayDate'];------------------------------------------------- Get title datalocalTitle=A['Title'];localBookTitle=A['BookTitle'];localConference=A['Conference'];localTransTitle=A['TransTitle'];localTitleNote=A['TitleNote'];localTitleLink=A['TitleLink'];localChapter=A['Chapter'];localChapterLink=A['ChapterLink'];localTransChapter=A['TransChapter'];localTitleType=A['TitleType'];localDegree=A['Degree'];localDocket=A['Docket'];localArchiveURL=A['ArchiveURL'];localURL=A['URL']localURLorigin=A:ORIGIN('URL');localChapterURL=A['ChapterURL'];localChapterURLorigin=A:ORIGIN('ChapterURL');localConferenceURL=A['ConferenceURL'];localConferenceURLorigin=A:ORIGIN('ConferenceURL');localPeriodical=A['Periodical'];--[[Parameter remapping for cite encyclopedia:When the citation has these parameters: |encyclopedia and |title then map |title to |article and |encyclopedia to |title |encyclopedia and |article then map |encyclopedia to |title |encyclopedia then map |encyclopedia to |title |trans_title maps to |trans_chapter when |title is re-mappedAll other combinations of |encyclopedia, |title, and |article are not modified]]if(config.CitationClass=="encyclopaedia")thenifis_set(Periodical)then-- Periodical is set when |encyclopedia is setifis_set(Title)thenifnotis_set(Chapter)thenChapter=Title;-- |encyclopedia and |title are set so map |title to |article and |encyclopedia to |titleTransChapter=TransTitle;Title=Periodical;Periodical='';-- redundant so unsetTransTitle='';-- redundant so unsetendelse-- |title not setTitle=Periodical;-- |encyclopedia set and |article set or not set so map |encyclopedia to |titlePeriodical='';-- redundant so unsetendendendlocalSeries=A['Series'];localVolume=A['Volume'];localIssue=A['Issue'];localPosition='';localPage,Pages,At,page_type;Page=A['Page'];Pages=hyphentodash(A['Pages']);At=A['At'];ifis_set(Page)thenifis_set(Pages)oris_set(At)thenPage=Page.." "..seterror('extra_pages');Pages='';At='';endelseifis_set(Pages)thenifis_set(At)thenPages=Pages.." "..seterror('extra_pages');At='';endendlocalEdition=A['Edition'];localPublicationPlace=A['PublicationPlace']localPlace=A['Place'];ifnotis_set(PublicationPlace)andis_set(Place)thenPublicationPlace=Place;endifPublicationPlace==PlacethenPlace='';endlocalPublisherName=A['PublisherName'];localRegistrationRequired=A['RegistrationRequired'];localSubscriptionRequired=A['SubscriptionRequired'];localVia=A['Via'];localAccessDate=A['AccessDate'];localArchiveDate=A['ArchiveDate'];localAgency=A['Agency'];localDeadURL=A['DeadURL']localLanguage=A['Language'];localFormat=A['Format'];localRef=A['Ref'];localDoiBroken=A['DoiBroken'];-- Special case for cite techreport.localID=A['ID'];if(config.CitationClass=="techreport")then-- special case for cite techreportifis_set(Issue)then-- cite techreport uses 'number', which other citations aliase to 'issue'ifnotis_set(ID)then-- can we use ID for the "number"?ID=Issue;-- yes, use itIssue="";-- unset Issue so that "number" isn't duplicated in the rendered citation or COinS metadataelse-- can't use ID so emit error messageID=ID.." "..seterror('redundant_parameters','<code>&#124;id=</code> and <code>&#124;number=</code>');endendendlocalASINTLD=A['ASINTLD'];localIgnoreISBN=A['IgnoreISBN'];localEmbargo=A['Embargo'];localID_list=extractids(args);localQuote=A['Quote'];localPostScript=A['PostScript'];localLayURL=A['LayURL'];localLaySource=A['LaySource'];localTranscript=A['Transcript'];localTranscriptURL=A['TranscriptURL']localTranscriptURLorigin=A:ORIGIN('TranscriptURL');localsepc=A['Separator'];localLastAuthorAmp=A['LastAuthorAmp'];localno_tracking_cats=A['NoTracking'];localuse_lowercase=(sepc~='.');localthis_page=mw.title.getCurrentTitle();--Also used for COinS and for languageifnotis_set(no_tracking_cats)thenfork,vinpairs(cfg.uncategorized_namespaces)doifthis_page.nsText==vthenno_tracking_cats="true";break;endendendlocalanchor_year;-- used in the CITEREF identifierlocalCOinS_date;-- used in the COinS metadata-- legacy: promote concatenation of |day=, |month=, and |year= to Date if Date not set; or, promote PublicationDate to Date if neither Date nor Year are set.ifnotis_set(Date)thenDate=Year;-- promote Year to DateYear=nil;-- make nil so Year as empty string isn't used for CITEREFifis_set(Date)thenlocalMonth=A['Month'];ifis_set(Month)thenDate=Month.." "..Date;localDay=A['Day']ifis_set(Day)thenDate=Day.." "..Dateendendelseifis_set(PublicationDate)then-- use PublicationDate when |date= and |year= are not setDate=PublicationDate;-- promonte PublicationDate to DatePublicationDate='';-- unset, no longer neededendendifPublicationDate==DatethenPublicationDate='';end-- if PublicationDate is same as Date, don't display in rendered citation-- Go test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates.-- TODO: 2013-10-27: AirDate is nil when dates() is called because it hasn't been set yet. Move the call to dates() or set AirDate earlier.anchor_year,COinS_date=dates({['accessdate']=AccessDate,['airdate']=AirDate,['archivedate']=ArchiveDate,['date']=Date,['doi_brokendate']=DoiBroken,['embargo']=Embargo,['laydate']=LayDate,['publicationdate']=PublicationDate,['year']=Year});-- At this point fields may be nil if they weren't specified in the template use. We can use that fact.--Account for the oddity that is {{cite journal}} with |pmc= set and |url= not setifconfig.CitationClass=="journal"andnotis_set(URL)andis_set(ID_list['PMC'])thenifnotis_embargoed(Embargo)thenURL=cfg.id_handlers['PMC'].prefix..ID_list['PMC'];-- set url to be the same as the PMC external link if not embargoedURLorigin=cfg.id_handlers['PMC'].parameters[1];-- set URLorigin to parameter name for use in error message if citation is missing a |title=endend-- Account for the oddity that is {{cite conference}}, before generation of COinS data.ifis_set(BookTitle)thenChapter=Title;ChapterLink=TitleLink;TransChapter=TransTitle;Title=BookTitle;TitleLink='';TransTitle='';end-- Account for the oddity that is {{cite episode}}, before generation of COinS data.ifconfig.CitationClass=="episode"thenlocalAirDate=A['AirDate'];localSeriesLink=A['SeriesLink'];localSeason=A['Season'];localSeriesNumber=A['SeriesNumber'];localNetwork=A['Network'];localStation=A['Station'];locals,n={},{};localSep=(first_set(A["SeriesSeparator"],A["Separator"])or"").." ";ifis_set(Issue)thentable.insert(s,cfg.messages["episode"].." "..Issue);Issue='';endifis_set(Season)thentable.insert(s,cfg.messages["season"].." "..Season);endifis_set(SeriesNumber)thentable.insert(s,cfg.messages["series"].." "..SeriesNumber);endifis_set(Network)thentable.insert(n,Network);endifis_set(Station)thentable.insert(n,Station);endDate=DateorAirDate;Chapter=Title;ChapterLink=TitleLink;TransChapter=TransTitle;Title=Series;TitleLink=SeriesLink;TransTitle='';Series=table.concat(s,Sep);ID=table.concat(n,Sep);end-- COinS metadata (see <http://ocoins.info/>) for-- automated parsing of citation information.localOCinSoutput=COinS{['Periodical']=Periodical,['Chapter']=Chapter,['Title']=Title,['PublicationPlace']=PublicationPlace,['Date']=first_set(COinS_date,Date),-- COinS_date has correctly formatted date if Date is valid; any reason to keep Date here? Should we be including invalid dates in metadata?['Series']=Series,['Volume']=Volume,['Issue']=Issue,['Pages']=first_set(Page,Pages,At),['Edition']=Edition,['PublisherName']=PublisherName,['URL']=first_set(URL,ChapterURL),['Authors']=a,['ID_list']=ID_list,['RawPage']=this_page.prefixedText,};ifis_set(Periodical)andnotis_set(Chapter)andis_set(Title)thenChapter=Title;ChapterLink=TitleLink;TransChapter=TransTitle;Title='';TitleLink='';TransTitle='';end-- Now perform various field substitutions.-- We also add leading spaces and surrounding markup and punctuation to the-- various parts of the citation, but only when they are non-nil.ifnotis_set(Authors)thenlocalMaximum=tonumber(A['DisplayAuthors']);-- Preserve old-style implicit et al.ifnotis_set(Maximum)and#a==9thenMaximum=8;table.insert(z.message_tail,{seterror('implict_etal_author',{},true)});elseifnotis_set(Maximum)thenMaximum=#a+1;endlocalcontrol={sep=A["AuthorSeparator"].." ",namesep=(first_set(A["AuthorNameSeparator"],A["NameSeparator"])or"").." ",format=A["AuthorFormat"],maximum=Maximum,lastauthoramp=LastAuthorAmp};-- If the coauthor field is also used, prevent ampersand and et al. formatting.ifis_set(Coauthors)thencontrol.lastauthoramp=nil;control.maximum=#a+1;endAuthors=listpeople(control,a)endifnotis_set(Authors)andis_set(Coauthors)then-- coauthors aren't displayed if one of authors=, authorn=, or lastn= isn't specifiedtable.insert(z.message_tail,{seterror('coauthors_missing_author',{},true)});-- emit error messageendlocalEditorCountifnotis_set(Editors)thenlocalMaximum=tonumber(A['DisplayEditors']);-- Preserve old-style implicit et al.ifnotis_set(Maximum)and#e==4thenMaximum=3;table.insert(z.message_tail,{seterror('implict_etal_editor',{},true)});elseifnotis_set(Maximum)thenMaximum=#e+1;endlocalcontrol={sep=A["EditorSeparator"].." ",namesep=(first_set(A["EditorNameSeparator"],A["NameSeparator"])or"").." ",format=A['EditorFormat'],maximum=Maximum,lastauthoramp=LastAuthorAmp};Editors,EditorCount=listpeople(control,e);elseEditorCount=1;endlocalCartography="";localScale="";ifconfig.CitationClass=="map"thenifnotis_set(Authors)andis_set(PublisherName)thenAuthors=PublisherName;PublisherName="";endCartography=A['Cartography'];ifis_set(Cartography)thenCartography=sepc.." "..wrap('cartography',Cartography,use_lowercase);endScale=A['Scale'];ifis_set(Scale)thenScale=sepc.." "..Scale;endendifnotis_set(URL)andnotis_set(ChapterURL)andnotis_set(ArchiveURL)andnotis_set(ConferenceURL)andnotis_set(TranscriptURL)then-- Test if cite web or cite podcast |url= is missing or empty ifinArray(config.CitationClass,{"web","podcast"})thentable.insert(z.message_tail,{seterror('cite_web_url',{},true)});end-- Test if accessdate is given without giving a URLifis_set(AccessDate)thentable.insert(z.message_tail,{seterror('accessdate_missing_url',{},true)});AccessDate='';end-- Test if format is given without giving a URLifis_set(Format)thenFormat=Format..seterror('format_missing_url');endend-- Test if citation has no titleifnotis_set(Chapter)andnotis_set(Title)andnotis_set(Periodical)andnotis_set(Conference)andnotis_set(TransTitle)andnotis_set(TransChapter)thentable.insert(z.message_tail,{seterror('citation_missing_title',{},true)});endFormat=is_set(Format)and" ("..Format..")"or"";localOriginalURL=URLDeadURL=DeadURL:lower();ifis_set(ArchiveURL)thenif(DeadURL~="no")thenURL=ArchiveURLURLorigin=A:ORIGIN('ArchiveURL')endend-- Format chapter / article titleifis_set(Chapter)andis_set(ChapterLink)thenChapter="[["..ChapterLink.."|"..Chapter.."]]";endifis_set(Periodical)andis_set(Title)thenChapter=wrap('italic-title',Chapter);TransChapter=wrap('trans-italic-title',TransChapter);elseChapter=kern_quotes(Chapter);-- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marksChapter=wrap('quoted-title',Chapter);TransChapter=wrap('trans-quoted-title',TransChapter);endlocalTransError=""ifis_set(TransChapter)thenifnotis_set(Chapter)thenTransError=" "..seterror('trans_missing_chapter');elseTransChapter=" "..TransChapter;endendChapter=Chapter..TransChapter;ifis_set(Chapter)thenifnotis_set(ChapterLink)thenifis_set(ChapterURL)thenChapter=externallink(ChapterURL,Chapter)..TransError;ifnotis_set(URL)thenChapter=Chapter..Format;Format="";endelseifis_set(URL)thenChapter=externallink(URL,Chapter)..TransError..Format;URL="";Format="";elseChapter=Chapter..TransError;endelseifis_set(ChapterURL)thenChapter=Chapter.." "..externallink(ChapterURL,nil,ChapterURLorigin)..TransError;elseChapter=Chapter..TransError;endChapter=Chapter..sepc.." "-- with end-spaceelseifis_set(ChapterURL)thenChapter=" "..externallink(ChapterURL,nil,ChapterURLorigin)..sepc.." ";end-- Format main title.ifis_set(TitleLink)andis_set(Title)thenTitle="[["..TitleLink.."|"..Title.."]]"endifis_set(Periodical)thenTitle=kern_quotes(Title);-- if necessary, separate title's leading and trailing quote marks from Module provided quote marksTitle=wrap('quoted-title',Title);TransTitle=wrap('trans-quoted-title',TransTitle);elseifinArray(config.CitationClass,{"web","news","pressrelease","conference","podcast"})andnotis_set(Chapter)thenTitle=kern_quotes(Title);-- if necessary, separate title's leading and trailing quote marks from Module provided quote marksTitle=wrap('quoted-title',Title);TransTitle=wrap('trans-quoted-title',TransTitle);elseTitle=wrap('italic-title',Title);TransTitle=wrap('trans-italic-title',TransTitle);endTransError="";ifis_set(TransTitle)thenifnotis_set(Title)thenTransError=" "..seterror('trans_missing_title');elseTransTitle=" "..TransTitle;endendTitle=Title..TransTitle;ifis_set(Title)thenifnotis_set(TitleLink)andis_set(URL)thenTitle=externallink(URL,Title)..TransError..FormatURL="";Format="";elseTitle=Title..TransError;endendifis_set(Place)thenPlace=" "..wrap('written',Place,use_lowercase)..sepc.." ";endifis_set(Conference)thenifis_set(ConferenceURL)thenConference=externallink(ConferenceURL,Conference);endConference=sepc.." "..Conferenceelseifis_set(ConferenceURL)thenConference=sepc.." "..externallink(ConferenceURL,nil,ConferenceURLorigin);endifnotis_set(Position)thenlocalMinutes=A['Minutes'];ifis_set(Minutes)thenPosition=" "..Minutes.." "..cfg.messages['minutes'];elselocalTime=A['Time'];ifis_set(Time)thenlocalTimeCaption=A['TimeCaption']ifnotis_set(TimeCaption)thenTimeCaption=cfg.messages['event'];ifsepc~='.'thenTimeCaption=TimeCaption:lower();endendPosition=" "..TimeCaption.." "..Time;endendelsePosition=" "..Position;At='';endifnotis_set(Page)thenifis_set(Pages)thenifis_set(Periodical)andnotinArray(config.CitationClass,{"encyclopaedia","web","book","news","podcast"})thenPages=": "..Pages;elseiftonumber(Pages)~=nilthenPages=sepc.." "..PPrefix..Pages;elsePages=sepc.." "..PPPrefix..Pages;endendelseifis_set(Periodical)andnotinArray(config.CitationClass,{"encyclopaedia","web","book","news","podcast"})thenPage=": "..Page;elsePage=sepc.." "..PPrefix..Page;endendAt=is_set(At)and(sepc.." "..At)or"";Position=is_set(Position)and(sepc.." "..Position)or"";ifconfig.CitationClass=='map'thenlocalSection=A['Section'];localInset=A['Inset'];iffirst_set(Pages,Page,At)~=nilorsepc~='.'thenifis_set(Section)thenSection=", "..wrap('section',Section,true);endifis_set(Inset)thenInset=", "..wrap('inset',Inset,true);endelseifis_set(Section)thenSection=sepc.." "..wrap('section',Section,use_lowercase);ifis_set(Inset)thenInset=", "..wrap('inset',Inset,true);endelseifis_set(Inset)thenInset=sepc.." "..wrap('inset',Inset,use_lowercase);endendAt=At..Section..Inset;end--[[Look in the list of iso639-1 language codes to see if the value provided in the language parameter matches one of them. If a match is found,  use that value; if not, then use the value that was provided with the language parameter. Categories are assigned in a manner similar to the {{xx icon}} templates - categorizes only mainspace citations and only when the language code is not 'en' (English). ]]ifis_set(Language)then-- local name = mw.language.fetchLanguageName( Language:lower(), "en" ); -- experiment: this seems to return correct ISO 639-1 language nameslocalname=cfg.iso639_1[Language:lower()];-- get the language name if Language parameter has a valid iso 639-1 codeifnil==namethenLanguage=" "..wrap('language',Language);-- no match, use parameter's valueelseif0==this_page.namespaceand'en'~=Language:lower()then--found a match; is this page main / article space and English not the language?Language=" "..wrap('language',name..'[[Category:Articles with '..name..'-language external links]]');-- in main space and not English: categorizeelseLanguage=" "..wrap('language',name);--not in mainspace or language is English so don't categorizeendendelseLanguage="";-- language not specified so make sure this is an empty string;endOthers=is_set(Others)and(sepc.." "..Others)or"";-- handle type parameter for those CS1 citations that have default valuesifinArray(config.CitationClass,{"podcast","pressrelease","techreport","thesis"})thenTitleType=set_titletype(config.CitationClass,TitleType);ifis_set(Degree)and"Thesis"==TitleTypethen-- special case for cite thesisTitleType=Degree.." thesis";endendifis_set(TitleType)then-- if type parameter is specifiedTitleType=" ("..TitleType..")";-- display it in parenthesesendTitleNote=is_set(TitleNote)and(sepc.." "..TitleNote)or"";Edition=is_set(Edition)and(" "..wrap('edition',Edition))or"";Issue=is_set(Issue)and(" ("..Issue..")")or"";Series=is_set(Series)and(sepc.." "..Series)or"";OrigYear=is_set(OrigYear)and(" ["..OrigYear.."]")or"";Agency=is_set(Agency)and(sepc.." "..Agency)or"";ifis_set(Volume)thenif(mw.ustring.len(Volume)>4)thenVolume=sepc.." "..Volume;elseVolume=" <b>"..hyphentodash(Volume).."</b>";endend------------------------------------ totally unrelated data--[[ Loosely mimic {{subscription required}} template; Via parameter identifies a delivery source that is not the publisher; these sources often, but not always, exist behind a registration or paywall. So here, we've chosen to decouple via from subscription (via has never been part of the registration required template). Subscription implies paywall; Registration does not. If both are used in a citation, the subscription required link note is displayed. There are no error messages for this condition. ]]ifis_set(Via)thenVia=" "..wrap('via',Via);endifis_set(SubscriptionRequired)thenSubscriptionRequired=sepc.." "..cfg.messages['subscription'];--here when 'via' parameter not used but 'subscription' iselseifis_set(RegistrationRequired)thenSubscriptionRequired=sepc.." "..cfg.messages['registration'];--here when 'via' and 'subscription' parameters not used but 'registration' isendifis_set(AccessDate)thenlocalretrv_text=" "..cfg.messages['retrieved']if(sepc~=".")thenretrv_text=retrv_text:lower()endAccessDate='<span class="reference-accessdate">'..sepc..substitute(retrv_text,{AccessDate})..'</span>'endifis_set(ID)thenID=sepc.." "..ID;endif"thesis"==config.CitationClassandis_set(Docket)thenID=sepc.." Docket "..Docket..ID;endID_list=buildidlist(ID_list,{DoiBroken=DoiBroken,ASINTLD=ASINTLD,IgnoreISBN=IgnoreISBN,Embargo=Embargo});ifis_set(URL)thenURL=" "..externallink(URL,nil,URLorigin);endifis_set(Quote)thenifQuote:sub(1,1)=='"'andQuote:sub(-1,-1)=='"'thenQuote=Quote:sub(2,-2);endQuote=sepc.." "..wrap('quoted-text',Quote);PostScript="";elseifPostScript:lower()=="none"thenPostScript="";endlocalArchivedifis_set(ArchiveURL)thenifnotis_set(ArchiveDate)thenArchiveDate=seterror('archive_missing_date');endif"no"==DeadURLthenlocalarch_text=cfg.messages['archived'];ifsepc~="."thenarch_text=arch_text:lower()endArchived=sepc.." "..substitute(cfg.messages['archived-not-dead'],{externallink(ArchiveURL,arch_text),ArchiveDate});ifnotis_set(OriginalURL)thenArchived=Archived.." "..seterror('archive_missing_url');endelseifis_set(OriginalURL)thenlocalarch_text=cfg.messages['archived-dead'];ifsepc~="."thenarch_text=arch_text:lower()endArchived=sepc.." "..substitute(arch_text,{externallink(OriginalURL,cfg.messages['original']),ArchiveDate});elselocalarch_text=cfg.messages['archived-missing'];ifsepc~="."thenarch_text=arch_text:lower()endArchived=sepc.." "..substitute(arch_text,{seterror('archive_missing_url'),ArchiveDate});endelseArchived=""endlocalLayifis_set(LayURL)thenifis_set(LayDate)thenLayDate=" ("..LayDate..")"endifis_set(LaySource)thenLaySource=" &ndash; ''"..safeforitalics(LaySource).."''";elseLaySource="";endifsepc=='.'thenLay=sepc.." "..externallink(LayURL,cfg.messages['lay summary'])..LaySource..LayDateelseLay=sepc.." "..externallink(LayURL,cfg.messages['lay summary']:lower())..LaySource..LayDateendelseLay="";endifis_set(Transcript)thenifis_set(TranscriptURL)thenTranscript=externallink(TranscriptURL,Transcript);endelseifis_set(TranscriptURL)thenTranscript=externallink(TranscriptURL,nil,TranscriptURLorigin);endlocalPublisher;ifis_set(Periodical)andnotinArray(config.CitationClass,{"encyclopaedia","web","pressrelease","podcast"})thenifis_set(PublisherName)thenifis_set(PublicationPlace)thenPublisher=PublicationPlace..": "..PublisherName;elsePublisher=PublisherName;endelseifis_set(PublicationPlace)thenPublisher=PublicationPlace;elsePublisher="";endifis_set(PublicationDate)thenifis_set(Publisher)thenPublisher=Publisher..", "..wrap('published',PublicationDate);elsePublisher=PublicationDate;endendifis_set(Publisher)thenPublisher=" ("..Publisher..")";endelseifis_set(PublicationDate)thenPublicationDate=" ("..wrap('published',PublicationDate)..")";endifis_set(PublisherName)thenifis_set(PublicationPlace)thenPublisher=sepc.." "..PublicationPlace..": "..PublisherName..PublicationDate;elsePublisher=sepc.." "..PublisherName..PublicationDate;endelseifis_set(PublicationPlace)thenPublisher=sepc.." "..PublicationPlace..PublicationDate;elsePublisher=PublicationDate;endend-- Several of the above rely upon detecting this as nil, so do it last.ifis_set(Periodical)thenifis_set(Title)oris_set(TitleNote)thenPeriodical=sepc.." "..wrap('italic-title',Periodical)elsePeriodical=wrap('italic-title',Periodical)endend--[[Handle the oddity that is cite speech. This code overrides whatever may be the value assigned to TitleNote (through |department=) and forces it to be " (Speech)" so thatthe annotation directly follows the |title= parameter value in the citation rather than the |event= parameter value (if provided).]]if"speech"==config.CitationClassthen-- cite speech onlyTitleNote=" (Speech)";-- annotate the citationifis_set(Periodical)then-- if Periodical, perhaps because of an included |website= or |journal= parameter ifis_set(Conference)then-- and if |event= is setConference=Conference..sepc.." ";-- then add appropriate punctuation to the end of the Conference variable before renderingendendend-- Piece all bits together at last. Here, all should be non-nil.-- We build things this way because it is more efficient in LUA-- not to keep reassigning to the same string variable over and over.localtcommonifinArray(config.CitationClass,{"journal","citation"})andis_set(Periodical)thenifis_set(Others)thenOthers=Others..sepc.." "endtcommon=safejoin({Others,Title,TitleNote,Conference,Periodical,Format,TitleType,Scale,Series,Language,Cartography,Edition,Publisher,Agency,Volume,Issue},sepc);elsetcommon=safejoin({Title,TitleNote,Conference,Periodical,Format,TitleType,Scale,Series,Language,Volume,Issue,Others,Cartography,Edition,Publisher,Agency},sepc);endif#ID_list>0thenID_list=safejoin({sepc.." ",table.concat(ID_list,sepc.." "),ID},sepc);elseID_list=ID;endlocalidcommon=safejoin({ID_list,URL,Archived,AccessDate,Via,SubscriptionRequired,Lay,Quote},sepc);localtext;localpgtext=Position..Page..Pages..At;ifis_set(Authors)thenifis_set(Coauthors)thenAuthors=Authors..A['AuthorSeparator'].." "..Coauthorsendifis_set(Date)thenDate=" ("..Date..")"..OrigYear..sepc.." "elseifstring.sub(Authors,-1,-1)==sepcthenAuthors=Authors.." "elseAuthors=Authors..sepc.." "endifis_set(Editors)thenlocalin_text=" ";localpost_text="";ifis_set(Chapter)thenin_text=in_text..cfg.messages['in'].." "elseifEditorCount<=1thenpost_text=", "..cfg.messages['editor'];elsepost_text=", "..cfg.messages['editors'];endendif(sepc~='.')thenin_text=in_text:lower()endEditors=in_text..Editors..post_text;if(string.sub(Editors,-1,-1)==sepc)thenEditors=Editors.." "elseEditors=Editors..sepc.." "endendtext=safejoin({Authors,Date,Chapter,Place,Editors,tcommon},sepc);text=safejoin({text,pgtext,idcommon},sepc);elseifis_set(Editors)thenifis_set(Date)thenifEditorCount<=1thenEditors=Editors..", "..cfg.messages['editor'];elseEditors=Editors..", "..cfg.messages['editors'];endDate=" ("..Date..")"..OrigYear..sepc.." "elseifEditorCount<=1thenEditors=Editors.." ("..cfg.messages['editor']..")"..sepc.." "elseEditors=Editors.." ("..cfg.messages['editors']..")"..sepc.." "endendtext=safejoin({Editors,Date,Chapter,Place,tcommon},sepc);text=safejoin({text,pgtext,idcommon},sepc);elseifis_set(Date)thenif(string.sub(tcommon,-1,-1)~=sepc)thenDate=sepc.." "..Date..OrigYearelseDate=" "..Date..OrigYearendendifconfig.CitationClass=="journal"andis_set(Periodical)thentext=safejoin({Chapter,Place,tcommon},sepc);text=safejoin({text,pgtext,Date,idcommon},sepc);elsetext=safejoin({Chapter,Place,tcommon,Date},sepc);text=safejoin({text,pgtext,idcommon},sepc);endendifis_set(PostScript)andPostScript~=sepcthentext=safejoin({text,sepc},sepc);--Deals with italics, spaces, etc.text=text:sub(1,-2);--Remove final seperator endtext=safejoin({text,PostScript},sepc);-- Now enclose the whole thing in a <span/> elementlocaloptions={};ifis_set(config.CitationClass)andconfig.CitationClass~="citation"thenoptions.class="citation "..config.CitationClass;elseoptions.class="citation";endifis_set(Ref)andRef:lower()~="none"thenlocalid=Refif("harv"==Ref)thenlocalnames={}--table of last names & yearif#a>0thenfori,vinipairs(a)donames[i]=v.lastifi==4thenbreakendendelseif#e>0thenfori,vinipairs(e)donames[i]=v.lastifi==4thenbreakendendendnames[#names+1]=first_set(Year,anchor_year);-- Year first for legacy citationsid=anchorid(names)endoptions.id=id;endifstring.len(text:gsub("<span[^>/]*>.-</span>",""):gsub("%b<>",""))<=2thenz.error_categories={};text=seterror('empty_citation');z.message_tail={};endifis_set(options.id)thentext='<span id="'..mw.uri.anchorEncode(options.id)..'" class="'..mw.text.nowiki(options.class)..'">'..text.."</span>";elsetext='<span class="'..mw.text.nowiki(options.class)..'">'..text.."</span>";endlocalempty_span='<span style="display:none;">&nbsp;</span>';-- Note: Using display: none on then COinS span breaks some clients.localOCinS='<span title="'..OCinSoutput..'" class="Z3988">'..empty_span..'</span>';text=text..OCinS;if#z.message_tail~=0thentext=text.." ";fori,vinipairs(z.message_tail)doifis_set(v[1])thenifi==#z.message_tailthentext=text..errorcomment(v[1],v[2]);elsetext=text..errorcomment(v[1].."; ",v[2]);endendendendno_tracking_cats=no_tracking_cats:lower();ifinArray(no_tracking_cats,{"","no","false","n"})thenfor_,vinipairs(z.error_categories)dotext=text..'[[Category:'..v..']]';endendreturntextend-- This is used by templates such as {{cite book}} to create the actual citation text.functionz.citation(frame)localpframe=frame:getParent()localargs={};localsuggestions={};localerror_text,error_state;localconfig={};fork,vinpairs(frame.args)doconfig[k]=v;args[k]=v;endfork,vinpairs(pframe.args)doifv~=''thenifnotvalidate(k)thenerror_text="";iftype(k)~='string'then-- Exclude empty numbered parametersifv:match("%S+")~=nilthenerror_text,error_state=seterror('text_ignored',{v},true);endelseifvalidate(k:lower())thenerror_text,error_state=seterror('parameter_ignored_suggest',{k,k:lower()},true);elseif#suggestions==0thensuggestions=mw.loadData('Module:Citation/CS1/Suggestions');endifsuggestions[k:lower()]~=nilthenerror_text,error_state=seterror('parameter_ignored_suggest',{k,suggestions[k:lower()]},true);elseerror_text,error_state=seterror('parameter_ignored',{k},true);endendiferror_text~=''thentable.insert(z.message_tail,{error_text,error_state});endendargs[k]=v;elseifargs[k]~=nilor(k=='postscript')thenargs[k]=v;endendreturncitation0(config,args)endreturnz
close