-- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].require("strict")localp={}localmodule_arg=...locali18nlocali18nPathlocalfunctionloadI18n(aliasesP,frame)localtitleifframethen-- current module invoked by page/template, get its title from frametitle=frame:getTitle()else-- current module included by other module, get its title from ...title=module_argendifnoti18ntheni18nPath=title.."/i18n"i18n=require(i18nPath).init(aliasesP)endendp.claimCommands={property="property",properties="properties",qualifier="qualifier",qualifiers="qualifiers",reference="reference",references="references"}p.generalCommands={label="label",title="title",description="description",alias="alias",aliases="aliases",badge="badge",badges="badges"}p.flags={linked="linked",short="short",raw="raw",multilanguage="multilanguage",unit="unit",-------------preferred="preferred",normal="normal",deprecated="deprecated",best="best",future="future",current="current",former="former",edit="edit",editAtEnd="edit@end",mdy="mdy",single="single",sourced="sourced"}p.args={eid="eid",page="page",date="date",globalSiteId="globalSiteId"}localaliasesP={coord="P625",-----------------------image="P18",author="P50",authorNameString="P2093",publisher="P123",importedFrom="P143",wikimediaImportURL="P4656",statedIn="P248",pages="P304",language="P407",hasPart="P527",publicationDate="P577",startTime="P580",endTime="P582",chapter="P792",retrieved="P813",referenceURL="P854",sectionVerseOrParagraph="P958",archiveURL="P1065",title="P1476",formatterURL="P1630",quote="P1683",shortName="P1813",definingFormula="P2534",archiveDate="P2960",inferredFrom="P3452",typeOfReference="P3865",column="P3903",subjectNamedAs="P1810",wikidataProperty="P1687",publishedIn="P1433"}localaliasesQ={percentage="Q11229",prolepticJulianCalendar="Q1985786",citeWeb="Q5637226",citeQ="Q22321052"}localparameters={property="%p",qualifier="%q",reference="%r",alias="%a",badge="%b",separator="%s",general="%x"}localformats={property="%p[%s][%r]",qualifier="%q[%s][%r]",reference="%r",propertyWithQualifier="%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]",alias="%a[%s]",badge="%b[%s]"}localhookNames={-- {level_1, level_2}[parameters.property]={"getProperty"},[parameters.reference]={"getReferences","getReference"},[parameters.qualifier]={"getAllQualifiers"},[parameters.qualifier.."\\d"]={"getQualifiers","getQualifier"},[parameters.alias]={"getAlias"},[parameters.badge]={"getBadge"}}-- default value objects, should NOT be mutated but instead copiedlocaldefaultSeparators={["sep"]={" "},["sep%s"]={","},["sep%q"]={"; "},["sep%q\\d"]={", "},["sep%r"]=nil,-- none["punc"]=nil-- none}localrankTable={["preferred"]=1,["normal"]=2,["deprecated"]=3}localfunctionreplaceAlias(id)ifaliasesP[id]thenid=aliasesP[id]endreturnidendlocalfunctionerrorText(code,...)localtext=i18n["errors"][code]ifargthentext=mw.ustring.format(text,unpack(arg))endreturntextendlocalfunctionthrowError(errorMessage,...)error(errorText(errorMessage,unpack(arg)))endlocalfunctionreplaceDecimalMark(num)returnmw.ustring.gsub(num,"[.]",i18n['numeric']['decimal-mark'],1)endlocalfunctionpadZeros(num,numDigits)localnumZeroslocalnegative=falseifnum<0thennegative=truenum=num*-1endnum=tostring(num)numZeros=numDigits-num:len()for_=1,numZerosdonum="0"..numendifnegativethennum="-"..numendreturnnumendlocalfunctionreplaceSpecialChar(chr)ifchr=='_'then-- replace underscores with spacesreturn' 'elsereturnchrendendlocalfunctionreplaceSpecialChars(str)localchrlocalesc=falselocalstrOut=""fori=1,#strdochr=str:sub(i,i)ifnotescthenifchr=='\\'thenesc=trueelsestrOut=strOut..replaceSpecialChar(chr)endelsestrOut=strOut..chresc=falseendendreturnstrOutendlocalfunctionbuildWikilink(target,label)ifnotlabelortarget==labelthenreturn"[["..target.."]]"elsereturn"[["..target.."|"..label.."]]"endend-- used to make frame.args mutable, to replace #frame.args (which is always 0)-- with the actual amount and to simply copy tableslocalfunctioncopyTable(tIn)ifnottInthenreturnnilendlocaltOut={}fori,vinpairs(tIn)dotOut[i]=vendreturntOutend-- used to merge output arrays together;-- note that it currently mutates the first input arraylocalfunctionmergeArrays(a1,a2)fori=1,#a2doa1[#a1+1]=a2[i]endreturna1endlocalfunctionsplit(str,del)localout={}locali,j=str:find(del)ifiandjthenout[1]=str:sub(1,i-1)out[2]=str:sub(j+1)elseout[1]=strendreturnoutendlocalfunctionparseWikidataURL(url)localidifurl:match('^http[s]?://')thenid=split(url,"Q")ifid[2]thenreturn"Q"..id[2]endendreturnnilendlocalfunctionparseDate(dateStr,precision)precision=precisionor"d"locali,j,index,ptrlocalparts={nil,nil,nil}ifdateStr==nilthenreturnparts[1],parts[2],parts[3]-- year, month, dayend-- 'T' for snak values, '/' for outputs with '/Julian' attachedi,j=dateStr:find("[T/]")ifithendateStr=dateStr:sub(1,i-1)endlocalfrom=1ifdateStr:sub(1,1)=="-"then-- this is a negative number, look further aheadfrom=2endindex=1ptr=1i,j=dateStr:find("-",from)ifithen-- yearparts[index]=tonumber(dateStr:sub(ptr,i-1),10)-- explicitly give base 10 to prevent errorifparts[index]==-0thenparts[index]=tonumber("0")-- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string insteadendifprecision=="y"then-- we're donereturnparts[1],parts[2],parts[3]-- year, month, dayendindex=index+1ptr=i+1i,j=dateStr:find("-",ptr)ifithen-- monthparts[index]=tonumber(dateStr:sub(ptr,i-1),10)ifprecision=="m"then-- we're donereturnparts[1],parts[2],parts[3]-- year, month, dayendindex=index+1ptr=i+1endendifdateStr:sub(ptr)~=""then-- day if we have month, month if we have year, or yearparts[index]=tonumber(dateStr:sub(ptr),10)endreturnparts[1],parts[2],parts[3]-- year, month, dayendlocalfunctiondatePrecedesDate(aY,aM,aD,bY,bM,bD)ifaY==nilorbY==nilthenreturnnilendaM=aMor1aD=aDor1bM=bMor1bD=bDor1ifaY<bYthenreturntrueendifaY>bYthenreturnfalseendifaM<bMthenreturntrueendifaM>bMthenreturnfalseendifaD<bDthenreturntrueendreturnfalseendlocalfunctiongetHookName(param,index)ifhookNames[param]thenreturnhookNames[param][index]elseifparam:len()>2thenreturnhookNames[param:sub(1,2).."\\d"][index]elsereturnnilendendlocalfunctionalwaysTrue()returntrueend-- The following function parses a format string.---- The example below shows how a parsed string is structured in memory.-- Variables other than 'str' and 'child' are left out for clarity's sake.---- Example:-- "A %p B [%s[%q1]] C [%r] D"---- Structure:-- [-- {-- str = "A "-- },-- {-- str = "%p"-- },-- {-- str = " B ",-- child =-- [-- {-- str = "%s",-- child =-- [-- {-- str = "%q1"-- }-- ]-- }-- ]-- },-- {-- str = " C ",-- child =-- [-- {-- str = "%r"-- }-- ]-- },-- {-- str = " D"-- }-- ]--localfunctionparseFormat(str)localchr,esc,param,root,cur,prev,newlocalparams={}localfunctionnewObject(array)localobj={}-- new objectobj.str=""array[#array+1]=obj-- array{object}obj.parent=arrayreturnobjendlocalfunctionendParam()ifparam>0thenifcur.str~=""thencur.str="%"..cur.strcur.param=trueparams[cur.str]=truecur.parent.req[cur.str]=trueprev=curcur=newObject(cur.parent)endparam=0endendroot={}-- arrayroot.req={}cur=newObject(root)prev=nilesc=falseparam=0fori=1,#strdochr=str:sub(i,i)ifnotescthenifchr=='\\'thenendParam()esc=trueelseifchr=='%'thenendParam()ifcur.str~=""thencur=newObject(cur.parent)endparam=2elseifchr=='['thenendParam()ifprevandcur.str==""thentable.remove(cur.parent)cur=prevendcur.child={}-- new arraycur.child.req={}cur.child.parent=curcur=newObject(cur.child)elseifchr==']'thenendParam()ifcur.parent.parentthennew=newObject(cur.parent.parent.parent)ifcur.str==""thentable.remove(cur.parent)endcur=newendelseifparam>1thenparam=param-1elseifparam==1thenifnotchr:match('%d')thenendParam()endendcur.str=cur.str..replaceSpecialChar(chr)endelsecur.str=cur.str..chresc=falseendprev=nilendendParam()-- make sure that at least one required parameter has been definedifnotnext(root.req)thenthrowError("missing-required-parameter")end-- make sure that the separator parameter "%s" is not amongst the required parametersifroot.req[parameters.separator]thenthrowError("extra-required-parameter",parameters.separator)endreturnroot,paramsendlocalfunctionsortOnRank(claims)localrankPoslocalranks={{},{},{},{}}-- preferred, normal, deprecated, (default)localsorted={}for_,vinipairs(claims)dorankPos=rankTable[v.rank]or4ranks[rankPos][#ranks[rankPos]+1]=vendsorted=ranks[1]sorted=mergeArrays(sorted,ranks[2])sorted=mergeArrays(sorted,ranks[3])returnsortedendlocalfunctionisValueInTable(searchedItem,inputTable)for_,iteminpairs(inputTable)doifitem==searchedItemthenreturntrueendendreturnfalseendlocalConfig={}-- allows for recursive callsfunctionConfig:new()localcfg={}setmetatable(cfg,self)self.__index=selfcfg.separators={-- single value objects wrapped in arrays so that we can pass by reference["sep"]={copyTable(defaultSeparators["sep"])},["sep%s"]={copyTable(defaultSeparators["sep%s"])},["sep%q"]={copyTable(defaultSeparators["sep%q"])},["sep%r"]={copyTable(defaultSeparators["sep%r"])},["punc"]={copyTable(defaultSeparators["punc"])}}cfg.entity=nilcfg.entityID=nilcfg.propertyID=nilcfg.propertyValue=nilcfg.qualifierIDs={}cfg.qualifierIDsAndValues={}cfg.bestRank=truecfg.ranks={true,true,false}-- preferred = true, normal = true, deprecated = falsecfg.foundRank=#cfg.rankscfg.flagBest=falsecfg.flagRank=falsecfg.periods={true,true,true}-- future = true, current = true, former = truecfg.flagPeriod=falsecfg.atDate={parseDate(os.date('!%Y-%m-%d'))}-- today as {year, month, day}cfg.mdyDate=falsecfg.singleClaim=falsecfg.sourcedOnly=falsecfg.editable=falsecfg.editAtEnd=falsecfg.inSitelinks=falsecfg.langCode=mw.language.getContentLanguage().codecfg.langName=mw.language.fetchLanguageName(cfg.langCode,cfg.langCode)cfg.langObj=mw.language.new(cfg.langCode)cfg.siteID=mw.wikibase.getGlobalSiteId()cfg.states={}cfg.states.qualifiersCount=0cfg.curState=nilcfg.prefetchedRefs=nilreturncfgendlocalState={}functionState:new(cfg,type)localstt={}setmetatable(stt,self)self.__index=selfstt.conf=cfgstt.type=typestt.results={}stt.parsedFormat={}stt.separator={}stt.movSeparator={}stt.puncMark={}stt.linked=falsestt.rawValue=falsestt.shortName=falsestt.anyLanguage=falsestt.unitOnly=falsestt.singleValue=falsereturnsttend-- if id == nil then item connected to current page is usedfunctionConfig:getLabel(id,raw,link,short)locallabel=nillocalprefix,title="",nilifnotidthenid=mw.wikibase.getEntityIdForCurrentPage()ifnotidthenreturn""endendid=id:upper()-- just to be sureifrawthen-- check if given id actually existsifmw.wikibase.isValidEntityId(id)andmw.wikibase.entityExists(id)thenlabel=idendprefix,title="d:Special:EntityPage/",label-- may be nilelse-- try short name first if requestedifshortthenlabel=p._property{aliasesP.shortName,[p.args.eid]=id}-- get short nameiflabel==""thenlabel=nilendend-- get labelifnotlabelthenlabel=mw.wikibase.getLabel(id)endendifnotlabelthenlabel=""elseiflinkthen-- build a link if requestedifnottitlethenifid:sub(1,1)=="Q"thentitle=mw.wikibase.getSitelink(id)elseifid:sub(1,1)=="P"then-- properties have no sitelink, link to Wikidata insteadprefix,title="d:Special:EntityPage/",idendendlabel=mw.text.nowiki(label)-- escape raw label text so it cannot be wikitext markupiftitlethenlabel=buildWikilink(prefix..title,label)endendreturnlabelendfunctionConfig:getEditIcon()localvalue=""localprefix=""localfront=" "localback=""ifself.entityID:sub(1,1)=="P"thenprefix="Property:"endifself.editAtEndthenfront='<span style="float:'ifself.langObj:isRTL()thenfront=front..'left'elsefront=front..'right'endfront=front..'">'back='</span>'endvalue="[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt="..i18n['info']['edit-on-wikidata'].."|link=https://www.wikidata.org/wiki/"..prefix..self.entityID.."?uselang="..self.langCodeifself.propertyIDthenvalue=value.."#"..self.propertyIDelseifself.inSitelinksthenvalue=value.."#sitelinks-wikipedia"endvalue=value.."|"..i18n['info']['edit-on-wikidata'].."]]"returnfront..value..backend-- used to create the final output string when it's all done, so that for references the-- function extensionTag("ref", ...) is only called when they really ended up in the final outputfunctionConfig:concatValues(valuesArray)localoutString=""localj,skipfori=1,#valuesArraydo-- check if this is a referenceifvaluesArray[i].refHashthenj=i-1skip=false-- skip this reference if it is part of a continuous row of references that already contains the exact same referencewhilevaluesArray[j]andvaluesArray[j].refHashdoifvaluesArray[i].refHash==valuesArray[j].refHashthenskip=truebreakendj=j-1endifnotskipthen-- add <ref> tag with the reference's hash as its name (to deduplicate references)outString=outString..mw.getCurrentFrame():extensionTag("ref",valuesArray[i][1],{name=valuesArray[i].refHash})endelseoutString=outString..valuesArray[i][1]endendreturnoutStringendfunctionConfig:convertUnit(unit,raw,link,short,unitOnly)localspace=" "locallabel=""localitemIDifunit==""orunit=="1"thenreturnnilendifunitOnlythenspace=""enditemID=parseWikidataURL(unit)ifitemIDthenifitemID==aliasesQ.percentagethenreturn"%"elselabel=self:getLabel(itemID,raw,link,short)iflabel~=""thenreturnspace..labelendendendreturn""endfunctionState:getValue(snak)returnself.conf:getValue(snak,self.rawValue,self.linked,self.shortName,self.anyLanguage,self.unitOnly,false,self.type:sub(1,2))endfunctionConfig:getValue(snak,raw,link,short,anyLang,unitOnly,noSpecial,type)ifsnak.snaktype=='value'thenlocaldatatype=snak.datavalue.typelocalsubtype=snak.datatypelocaldatavalue=snak.datavalue.valueifdatatype=='string'thenifsubtype=='url'andlinkthen-- create link explicitlyifrawthen-- will render as a linked number like [1]return"["..datavalue.."]"elsereturn"["..datavalue.." "..datavalue.."]"endelseifsubtype=='commonsMedia'theniflinkthenreturnbuildWikilink("c:File:"..datavalue,datavalue)elseifnotrawthenreturn"[[File:"..datavalue.."]]"elsereturndatavalueendelseifsubtype=='geo-shape'andlinkthenreturnbuildWikilink("c:"..datavalue,datavalue)elseifsubtype=='math'andnotrawthenlocalattribute=nilif(type==parameters.propertyor(type==parameters.qualifierandself.propertyID==aliasesP.hasPart))andsnak.property==aliasesP.definingFormulathenattribute={qid=self.entityID}endreturnmw.getCurrentFrame():extensionTag("math",datavalue,attribute)elseifsubtype=='external-id'andlinkthenlocalurl=p._property{aliasesP.formatterURL,[p.args.eid]=snak.property}-- get formatter URLifurl~=""thenurl=mw.ustring.gsub(url,"$1",datavalue)return"["..url.." "..datavalue.."]"elsereturndatavalueendelsereturndatavalueendelseifdatatype=='monolingualtext'thenifanyLangordatavalue['language']==self.langCodethenreturndatavalue['text']elsereturnnilendelseifdatatype=='quantity'thenlocalvalue=""localunitifnotunitOnlythen-- get value and strip + signs from frontvalue=mw.ustring.gsub(datavalue['amount'],"^%+(.+)$","%1")ifrawthenreturnvalueend-- replace decimal mark based on localevalue=replaceDecimalMark(value)-- add delimiters for readabilityvalue=i18n.addDelimiters(value)endunit=self:convertUnit(datavalue['unit'],raw,link,short,unitOnly)ifunitthenvalue=value..unitendreturnvalueelseifdatatype=='time'thenlocaly,m,d,p,yDiv,yRound,yFull,value,calendarID,dateStrlocalyFactor=1localsign=1localprefix=""localsuffix=""localmayAddCalendar=falselocalcalendar=""localprecision=datavalue['precision']ifprecision==11thenp="d"elseifprecision==10thenp="m"elsep="y"yFactor=10^(9-precision)endy,m,d=parseDate(datavalue['time'],p)ify<0thensign=-1y=y*signend-- if precision is tens/hundreds/thousands/millions/billions of yearsifprecision<=8thenyDiv=y/yFactor-- if precision is tens/hundreds/thousands of yearsifprecision>=6thenmayAddCalendar=trueifprecision<=7then-- round centuries/millenniums up (e.g. 20th century or 3rd millennium)yRound=math.ceil(yDiv)ifnotrawthenifprecision==6thensuffix=i18n['datetime']['suffixes']['millennium']elsesuffix=i18n['datetime']['suffixes']['century']endsuffix=i18n.getOrdinalSuffix(yRound)..suffixelse-- if not verbose, take the first year of the century/millennium-- (e.g. 1901 for 20th century or 2001 for 3rd millennium)yRound=(yRound-1)*yFactor+1endelse-- precision == 8-- round decades down (e.g. 2010s)yRound=math.floor(yDiv)*yFactorifnotrawthenprefix=i18n['datetime']['prefixes']['decade-period']suffix=i18n['datetime']['suffixes']['decade-period']endendifrawandsign<0then-- if BCE then compensate for "counting backwards"-- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE)yRound=yRound+yFactor-1endelselocalyReFactor,yReDiv,yReRound-- round to nearest for tens of thousands of years or moreyRound=math.floor(yDiv+0.5)ifyRound==0thenifprecision<=2andy~=0thenyReFactor=1e6yReDiv=y/yReFactoryReRound=math.floor(yReDiv+0.5)ifyReDiv==yReRoundthen-- change precision to millions of years only if we have a whole number of themprecision=3yFactor=yReFactoryRound=yReRoundendendifyRound==0then-- otherwise, take the unrounded (original) number of yearsprecision=5yFactor=1yRound=ymayAddCalendar=trueendendifprecision>=1andy~=0thenyFull=yRound*yFactoryReFactor=1e9yReDiv=yFull/yReFactoryReRound=math.floor(yReDiv+0.5)ifyReDiv==yReRoundthen-- change precision to billions of years if we're in that rangeprecision=0yFactor=yReFactoryRound=yReRoundelseyReFactor=1e6yReDiv=yFull/yReFactoryReRound=math.floor(yReDiv+0.5)ifyReDiv==yReRoundthen-- change precision to millions of years if we're in that rangeprecision=3yFactor=yReFactoryRound=yReRoundendendendifnotrawthenifprecision==3thensuffix=i18n['datetime']['suffixes']['million-years']elseifprecision==0thensuffix=i18n['datetime']['suffixes']['billion-years']elseyRound=yRound*yFactorifyRound==1thensuffix=i18n['datetime']['suffixes']['year']elsesuffix=i18n['datetime']['suffixes']['years']endendelseyRound=yRound*yFactorendendelseyRound=ymayAddCalendar=trueendifmayAddCalendarthencalendarID=parseWikidataURL(datavalue['calendarmodel'])ifcalendarIDandcalendarID==aliasesQ.prolepticJulianCalendarthenifnotrawtheniflinkthencalendar=" ("..buildWikilink(i18n['datetime']['julian-calendar'],i18n['datetime']['julian'])..")"elsecalendar=" ("..i18n['datetime']['julian']..")"endelsecalendar="/"..i18n['datetime']['julian']endendendifnotrawthenlocalce=nilifsign<0thence=i18n['datetime']['BCE']elseifprecision<=5thence=i18n['datetime']['CE']endifcetheniflinkthence=buildWikilink(i18n['datetime']['common-era'],ce)endsuffix=suffix.." "..ceendvalue=tostring(yRound)ifmthendateStr=self.langObj:formatDate("F","1-"..m.."-1")ifdthenifself.mdyDatethendateStr=dateStr.." "..d..","elsedateStr=d.." "..dateStrendendvalue=dateStr.." "..valueendvalue=prefix..value..suffix..calendarelsevalue=padZeros(yRound*sign,4)ifmthenvalue=value.."-"..padZeros(m,2)ifdthenvalue=value.."-"..padZeros(d,2)endendvalue=value..calendarendreturnvalueelseifdatatype=='globecoordinate'then-- logic from https://github.com/DataValues/Geo (v4.0.1)localprecision,unitsPerDegree,numDigits,strFormat,value,globelocallatitude,latConv,latValue,latLinklocallongitude,lonConv,lonValue,lonLinklocallatDirection,latDirectionN,latDirectionS,latDirectionENlocallonDirection,lonDirectionE,lonDirectionW,lonDirectionENlocaldegSymbol,minSymbol,secSymbol,separatorlocallatDegrees=nillocallatMinutes=nillocallatSeconds=nillocallonDegrees=nillocallonMinutes=nillocallonSeconds=nillocallatDegSym=""locallatMinSym=""locallatSecSym=""locallonDegSym=""locallonMinSym=""locallonSecSym=""locallatDirectionEN_N="N"locallatDirectionEN_S="S"locallonDirectionEN_E="E"locallonDirectionEN_W="W"ifnotrawthenlatDirectionN=i18n['coord']['latitude-north']latDirectionS=i18n['coord']['latitude-south']lonDirectionE=i18n['coord']['longitude-east']lonDirectionW=i18n['coord']['longitude-west']degSymbol=i18n['coord']['degrees']minSymbol=i18n['coord']['minutes']secSymbol=i18n['coord']['seconds']separator=i18n['coord']['separator']elselatDirectionN=latDirectionEN_NlatDirectionS=latDirectionEN_SlonDirectionE=lonDirectionEN_ElonDirectionW=lonDirectionEN_WdegSymbol="/"minSymbol="/"secSymbol="/"separator="/"endlatitude=datavalue['latitude']longitude=datavalue['longitude']iflatitude<0thenlatDirection=latDirectionSlatDirectionEN=latDirectionEN_Slatitude=math.abs(latitude)elselatDirection=latDirectionNlatDirectionEN=latDirectionEN_Nendiflongitude<0thenlonDirection=lonDirectionWlonDirectionEN=lonDirectionEN_Wlongitude=math.abs(longitude)elselonDirection=lonDirectionElonDirectionEN=lonDirectionEN_Eendprecision=datavalue['precision']ifnotprecisionorprecision<=0thenprecision=1/3600-- precision not set (correctly), set to arcsecondend-- remove insignificant detaillatitude=math.floor(latitude/precision+0.5)*precisionlongitude=math.floor(longitude/precision+0.5)*precisionifprecision>=1-(1/60)andprecision<1thenprecision=1elseifprecision>=(1/60)-(1/3600)andprecision<(1/60)thenprecision=1/60endifprecision>=1thenunitsPerDegree=1elseifprecision>=(1/60)thenunitsPerDegree=60elseunitsPerDegree=3600endnumDigits=math.ceil(-math.log10(unitsPerDegree*precision))ifnumDigits<=0thennumDigits=tonumber("0")-- for some reason, 'numDigits = 0' may actually store '-0', so parse from string insteadendstrFormat="%."..numDigits.."f"ifprecision>=1thenlatDegrees=strFormat:format(latitude)lonDegrees=strFormat:format(longitude)ifnotrawthenlatDegSym=replaceDecimalMark(latDegrees)..degSymbollonDegSym=replaceDecimalMark(lonDegrees)..degSymbolelselatDegSym=latDegrees..degSymbollonDegSym=lonDegrees..degSymbolendelselatConv=math.floor(latitude*unitsPerDegree*10^numDigits+0.5)/10^numDigitslonConv=math.floor(longitude*unitsPerDegree*10^numDigits+0.5)/10^numDigitsifprecision>=(1/60)thenlatMinutes=latConvlonMinutes=lonConvelselatSeconds=latConvlonSeconds=lonConvlatMinutes=math.floor(latSeconds/60)lonMinutes=math.floor(lonSeconds/60)latSeconds=strFormat:format(latSeconds-(latMinutes*60))lonSeconds=strFormat:format(lonSeconds-(lonMinutes*60))ifnotrawthenlatSecSym=replaceDecimalMark(latSeconds)..secSymbollonSecSym=replaceDecimalMark(lonSeconds)..secSymbolelselatSecSym=latSeconds..secSymbollonSecSym=lonSeconds..secSymbolendendlatDegrees=math.floor(latMinutes/60)lonDegrees=math.floor(lonMinutes/60)latDegSym=latDegrees..degSymbollonDegSym=lonDegrees..degSymbollatMinutes=latMinutes-(latDegrees*60)lonMinutes=lonMinutes-(lonDegrees*60)ifprecision>=(1/60)thenlatMinutes=strFormat:format(latMinutes)lonMinutes=strFormat:format(lonMinutes)ifnotrawthenlatMinSym=replaceDecimalMark(latMinutes)..minSymbollonMinSym=replaceDecimalMark(lonMinutes)..minSymbolelselatMinSym=latMinutes..minSymbollonMinSym=lonMinutes..minSymbolendelselatMinSym=latMinutes..minSymbollonMinSym=lonMinutes..minSymbolendendlatValue=latDegSym..latMinSym..latSecSym..latDirectionlonValue=lonDegSym..lonMinSym..lonSecSym..lonDirectionvalue=latValue..separator..lonValueiflinkthenglobe=parseWikidataURL(datavalue['globe'])ifglobethenglobe=mw.wikibase.getLabelByLang(globe,"en"):lower()elseglobe="earth"endlatLink=table.concat({latDegrees,latMinutes,latSeconds},"_")lonLink=table.concat({lonDegrees,lonMinutes,lonSeconds},"_")value="[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."¶ms="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]"endreturnvalueelseifdatatype=='wikibase-entityid'thenlocallabellocalitemID=datavalue['numeric-id']ifsubtype=='wikibase-item'thenitemID="Q"..itemIDelseifsubtype=='wikibase-property'thenitemID="P"..itemIDelsereturn'<strong class="error">'..errorText('unknown-data-type',subtype)..'</strong>'endlabel=self:getLabel(itemID,raw,link,short)iflabel==""thenlabel=nilendreturnlabelelsereturn'<strong class="error">'..errorText('unknown-data-type',datatype)..'</strong>'endelseifsnak.snaktype=='somevalue'andnotnoSpecialthenifrawthenreturn" "-- single space represents 'somevalue'elsereturni18n['values']['unknown']endelseifsnak.snaktype=='novalue'andnotnoSpecialthenifrawthenreturn""-- empty string represents 'novalue'elsereturni18n['values']['none']endelsereturnnilendendfunctionConfig:getSingleRawQualifier(claim,qualifierID)localqualifiersifclaim.qualifiersthenqualifiers=claim.qualifiers[qualifierID]endifqualifiersandqualifiers[1]thenreturnself:getValue(qualifiers[1],true)-- raw = trueelsereturnnilendendfunctionConfig:snakEqualsValue(snak,value)localsnakValue=self:getValue(snak,true)-- raw = trueifsnakValueandsnak.snaktype=='value'andsnak.datavalue.type=='wikibase-entityid'thenvalue=value:upper()endreturnsnakValue==valueendfunctionConfig:setRank(rank)localrankPosifrank==p.flags.bestthenself.bestRank=trueself.flagBest=true-- mark that 'best' flag was givenreturnendifrank:sub(1,9)==p.flags.preferredthenrankPos=1elseifrank:sub(1,6)==p.flags.normalthenrankPos=2elseifrank:sub(1,10)==p.flags.deprecatedthenrankPos=3elsereturnend-- one of the rank flags was given, check if another one was given beforeifnotself.flagRankthenself.ranks={false,false,false}-- no other rank flag given before, so unset ranksself.bestRank=self.flagBest-- unsets bestRank only if 'best' flag was not given beforeself.flagRank=true-- mark that a rank flag was givenendifrank:sub(-1)=="+"thenfori=rankPos,1,-1doself.ranks[i]=trueendelseifrank:sub(-1)=="-"thenfori=rankPos,#self.ranksdoself.ranks[i]=trueendelseself.ranks[rankPos]=trueendendfunctionConfig:setPeriod(period)localperiodPosifperiod==p.flags.futurethenperiodPos=1elseifperiod==p.flags.currentthenperiodPos=2elseifperiod==p.flags.formerthenperiodPos=3elsereturnend-- one of the period flags was given, check if another one was given beforeifnotself.flagPeriodthenself.periods={false,false,false}-- no other period flag given before, so unset periodsself.flagPeriod=true-- mark that a period flag was givenendself.periods[periodPos]=trueendfunctionConfig:qualifierMatches(claim,id,value)localqualifiersifclaim.qualifiersthenqualifiers=claim.qualifiers[id]endifqualifiersthenfor_,vinpairs(qualifiers)doifself:snakEqualsValue(v,value)thenreturntrueendendelseifvalue==""then-- if the qualifier is not present then treat it the same as the special value 'novalue'returntrueendreturnfalseendfunctionConfig:rankMatches(rankPos)ifself.bestRankthenreturn(self.ranks[rankPos]andself.foundRank>=rankPos)elsereturnself.ranks[rankPos]endendfunctionConfig:timeMatches(claim)localstartTime=nillocalstartTimeY=nillocalstartTimeM=nillocalstartTimeD=nillocalendTime=nillocalendTimeY=nillocalendTimeM=nillocalendTimeD=nilifself.periods[1]andself.periods[2]andself.periods[3]then-- any timereturntrueendstartTime=self:getSingleRawQualifier(claim,aliasesP.startTime)ifstartTimeandstartTime~=""andstartTime~=" "thenstartTimeY,startTimeM,startTimeD=parseDate(startTime)endendTime=self:getSingleRawQualifier(claim,aliasesP.endTime)ifendTimeandendTime~=""andendTime~=" "thenendTimeY,endTimeM,endTimeD=parseDate(endTime)endifstartTimeY~=nilandendTimeY~=nilanddatePrecedesDate(endTimeY,endTimeM,endTimeD,startTimeY,startTimeM,startTimeD)then-- invalidate end time if it precedes start timeendTimeY=nilendTimeM=nilendTimeD=nilendifself.periods[1]then-- futureifstartTimeYanddatePrecedesDate(self.atDate[1],self.atDate[2],self.atDate[3],startTimeY,startTimeM,startTimeD)thenreturntrueendendifself.periods[2]then-- currentif(startTimeY==nilornotdatePrecedesDate(self.atDate[1],self.atDate[2],self.atDate[3],startTimeY,startTimeM,startTimeD))and(endTimeY==nilordatePrecedesDate(self.atDate[1],self.atDate[2],self.atDate[3],endTimeY,endTimeM,endTimeD))thenreturntrueendendifself.periods[3]then-- formerifendTimeYandnotdatePrecedesDate(self.atDate[1],self.atDate[2],self.atDate[3],endTimeY,endTimeM,endTimeD)thenreturntrueendendreturnfalseendfunctionConfig:processFlag(flag)ifnotflagthenreturnfalseendifflag==p.flags.linkedthenself.curState.linked=truereturntrueelseifflag==p.flags.rawthenself.curState.rawValue=trueifself.curState==self.states[parameters.reference]then-- raw reference values end with periods and require a separator (other than none)self.separators["sep%r"][1]={" "}endreturntrueelseifflag==p.flags.shortthenself.curState.shortName=truereturntrueelseifflag==p.flags.multilanguagethenself.curState.anyLanguage=truereturntrueelseifflag==p.flags.unitthenself.curState.unitOnly=truereturntrueelseifflag==p.flags.mdythenself.mdyDate=truereturntrueelseifflag==p.flags.singlethenself.singleClaim=truereturntrueelseifflag==p.flags.sourcedthenself.sourcedOnly=truereturntrueelseifflag==p.flags.editthenself.editable=truereturntrueelseifflag==p.flags.editAtEndthenself.editable=trueself.editAtEnd=truereturntrueelseifflag==p.flags.bestorflag:match('^'..p.flags.preferred..'[+-]?$')orflag:match('^'..p.flags.normal..'[+-]?$')orflag:match('^'..p.flags.deprecated..'[+-]?$')thenself:setRank(flag)returntrueelseifflag==p.flags.futureorflag==p.flags.currentorflag==p.flags.formerthenself:setPeriod(flag)returntrueelseifflag==""then-- ignore empty flags and carry onreturntrueelsereturnfalseendendfunctionConfig:processFlagOrCommand(flag)localparam=""ifnotflagthenreturnfalseendifflag==p.claimCommands.propertyorflag==p.claimCommands.propertiesthenparam=parameters.propertyelseifflag==p.claimCommands.qualifierorflag==p.claimCommands.qualifiersthenself.states.qualifiersCount=self.states.qualifiersCount+1param=parameters.qualifier..self.states.qualifiersCountself.separators["sep"..param]={copyTable(defaultSeparators["sep%q\\d"])}elseifflag==p.claimCommands.referenceorflag==p.claimCommands.referencesthenparam=parameters.referenceelsereturnself:processFlag(flag)endifself.states[param]thenreturnfalseend-- create a new state for each commandself.states[param]=State:new(self,param)-- use "%x" as the general parameter nameself.states[param].parsedFormat=parseFormat(parameters.general)-- will be overwritten for param=="%p"-- set the separatorself.states[param].separator=self.separators["sep"..param]-- will be nil for param=="%p", which will be set separatelyifflag==p.claimCommands.propertyorflag==p.claimCommands.qualifierorflag==p.claimCommands.referencethenself.states[param].singleValue=trueendself.curState=self.states[param]returntrueendfunctionConfig:processSeparators(args)localsepfori,vinpairs(self.separators)doifargs[i]thensep=replaceSpecialChars(args[i])ifsep~=""thenself.separators[i][1]={sep}elseself.separators[i][1]=nilendendendendfunctionConfig:setFormatAndSeparators(state,parsedFormat)state.parsedFormat=parsedFormatstate.separator=self.separators["sep"]state.movSeparator=self.separators["sep"..parameters.separator]state.puncMark=self.separators["punc"]end-- determines if a claim has references by prefetching them from the claim using getReferences,-- which applies some filtering that determines if a reference is actually returned,-- and caches the references for later usefunctionState:isSourced(claim)self.conf.prefetchedRefs=self:getReferences(claim)return(#self.conf.prefetchedRefs>0)endfunctionState:resetCaches()-- any prefetched references of the previous claim must not be usedself.conf.prefetchedRefs=nilendfunctionState:claimMatches(claim)localmatches,rankPos-- first of all, reset any cached values used for the previous claimself:resetCaches()-- if a property value was given, check if it matches the claim's property valueifself.conf.propertyValuethenmatches=self.conf:snakEqualsValue(claim.mainsnak,self.conf.propertyValue)elsematches=trueend-- if any qualifier values were given, check if each matches one of the claim's qualifier valuesfori,vinpairs(self.conf.qualifierIDsAndValues)domatches=(matchesandself.conf:qualifierMatches(claim,i,v))end-- check if the claim's rank and time period matchrankPos=rankTable[claim.rank]or4matches=(matchesandself.conf:rankMatches(rankPos)andself.conf:timeMatches(claim))-- if only claims with references must be returned, check if this one has anyifself.conf.sourcedOnlythenmatches=(matchesandself:isSourced(claim))-- prefetches and caches referencesendreturnmatches,rankPosendfunctionState:out()localresult-- collection of arrays with value objectslocalvaluesArray-- array with value objectslocalsep=nil-- value objectlocalout={}-- array with value objectslocalfunctionwalk(formatTable,result)localvaluesArray={}-- array with value objectsfori,vinpairs(formatTable.req)doifnotresult[i]ornotresult[i][1]then-- we've got no result for a parameter that is required on this level,-- so skip this level (and its children) by returning an empty resultreturn{}endendfor_,vinipairs(formatTable)doifv.paramthenvaluesArray=mergeArrays(valuesArray,result[v.str])elseifv.str~=""thenvaluesArray[#valuesArray+1]={v.str}endifv.childthenvaluesArray=mergeArrays(valuesArray,walk(v.child,result))endendreturnvaluesArrayend-- iterate through the results from back to front, so that we know when to add separatorsfori=#self.results,1,-1doresult=self.results[i]-- if there is already some output, then add the separatorsif#out>0thensep=self.separator[1]-- fixed separatorresult[parameters.separator]={self.movSeparator[1]}-- movable separatorelsesep=nilresult[parameters.separator]={self.puncMark[1]}-- optional punctuation markendvaluesArray=walk(self.parsedFormat,result)if#valuesArray>0thenifsepthenvaluesArray[#valuesArray+1]=sependout=mergeArrays(valuesArray,out)endend-- reset state before next iterationself.results={}returnoutend-- level 1 hookfunctionState:getProperty(claim)localvalue={self:getValue(claim.mainsnak)}-- create one value objectif#value>0thenreturn{value}-- wrap the value object in an array and return itelsereturn{}-- return empty array if there was no valueendend-- level 1 hookfunctionState:getQualifiers(claim,param)localqualifiersifclaim.qualifiersthenqualifiers=claim.qualifiers[self.conf.qualifierIDs[param]]endifqualifiersthen-- iterate through claim's qualifier statements to collect their values;-- return array with multiple value objectsreturnself.conf.states[param]:iterate(qualifiers,{[parameters.general]=hookNames[parameters.qualifier.."\\d"][2],count=1})-- pass qualifier state with level 2 hookelsereturn{}-- return empty arrayendend-- level 2 hookfunctionState:getQualifier(snak)localvalue={self:getValue(snak)}-- create one value objectif#value>0thenreturn{value}-- wrap the value object in an array and return itelsereturn{}-- return empty array if there was no valueendend-- level 1 hookfunctionState:getAllQualifiers(claim,param,result,hooks)localout={}-- array with value objectslocalsep=self.conf.separators["sep"..parameters.qualifier][1]-- value object-- iterate through the output of the separate "qualifier(s)" commandsfori=1,self.conf.states.qualifiersCountdo-- if a hook has not been called yet, call it nowifnotresult[parameters.qualifier..i]thenself:callHook(parameters.qualifier..i,hooks,claim,result)end-- if there is output for this particular "qualifier(s)" command, then add itifresult[parameters.qualifier..i]andresult[parameters.qualifier..i][1]then-- if there is already some output, then add the separatorif#out>0andsepthenout[#out+1]=sependout=mergeArrays(out,result[parameters.qualifier..i])endendreturnoutend-- level 1 hookfunctionState:getReferences(claim)ifself.conf.prefetchedRefsthen-- return references that have been prefetched by isSourcedreturnself.conf.prefetchedRefsendifclaim.referencesthen-- iterate through claim's reference statements to collect their values;-- return array with multiple value objectsreturnself.conf.states[parameters.reference]:iterate(claim.references,{[parameters.general]=hookNames[parameters.reference][2],count=1})-- pass reference state with level 2 hookelsereturn{}-- return empty arrayendend-- level 2 hookfunctionState:getReference(statement)localciteParamMapping=i18n['cite']['param-mapping']localciteConfig=i18n['cite']['config']localciteTypes=i18n['cite']['output-types']-- will hold rendered properties of the reference which are not directly from statement.snaks,-- Namely, these are a backup title from "subject named as" and a URL generated from an external ID.localadditionalProcessedProperties={}-- for each citation type, there will be an associative array that associates lists of rendered properties-- to citation-template parameterslocalgroupedProcessedProperties={}-- like above, but only associates one rendered property to each parameter; if the above variable-- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1")localciteParams={}localciteErrors={}localreferenceEmpty=true-- will be set to false if at least one parameter is left unremovedlocalversion=11-- increment this each time the below logic is changed to avoid conflict errorsifnotstatement.snaksthenreturn{}end-- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia)ifstatement.snaks[aliasesP.importedFrom]orstatement.snaks[aliasesP.wikimediaImportURL]orstatement.snaks[aliasesP.inferredFrom]thenreturn{}end-- don't include "type of reference"ifstatement.snaks[aliasesP.typeOfReference]thenstatement.snaks[aliasesP.typeOfReference]=nilend-- don't include "image" to prevent litteringifstatement.snaks[aliasesP.image]thenstatement.snaks[aliasesP.image]=nilend-- don't include "language" if it is equal to the local oneifself:getReferenceDetail(statement.snaks,aliasesP.language)==self.conf.langNamethenstatement.snaks[aliasesP.language]=nilendifstatement.snaks[aliasesP.statedIn]andnotstatement.snaks[aliasesP.referenceURL]then-- "stated in" was given but "reference URL" was not.-- get "Wikidata property" properties from the item in "stated in"-- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference-- find the "Wikidata property" properties in the item from "stated in"localwikidataPropertiesOfSource=mw.text.split(p._properties{p.flags.raw,aliasesP.wikidataProperty,[p.args.eid]=self.conf:getValue(statement.snaks[aliasesP.statedIn][1],true,false)},", ",true)fori,wikidataPropertyOfSourceinpairs(wikidataPropertiesOfSource)doifstatement.snaks[wikidataPropertyOfSource]andstatement.snaks[wikidataPropertyOfSource][1].datatype=="external-id"thenlocaltempLink=self:getReferenceDetail(statement.snaks,wikidataPropertyOfSource,false,true)-- not raw, linkedifmw.ustring.match(tempLink,"^%[%Z- %Z+%]$")then-- getValue returned a URL in square brackets.-- the link is in wiki markup, so strip the square brackets and the display text-- gsub also returns another, discarted value, therefore the result is assigned to tempLink firsttempLink=mw.ustring.gsub(tempLink,"^%[(%Z-) %Z+%]$","%1")additionalProcessedProperties[aliasesP.referenceURL]={tempLink}statement.snaks[wikidataPropertyOfSource]=nilbreakendendendend-- don't include "subject named as", but use it as the title when "title" is not present but a URL isifstatement.snaks[aliasesP.subjectNamedAs]thenifnotstatement.snaks[aliasesP.title]and(statement.snaks[aliasesP.referenceURL]oradditionalProcessedProperties[aliasesP.referenceURL])thenadditionalProcessedProperties[aliasesP.title]={self:getReferenceDetail(statement.snaks,aliasesP.subjectNamedAs,false,false,true)}-- not raw, not linked, anyLangendstatement.snaks[aliasesP.subjectNamedAs]=nilend-- initialize groupedProcessedProperties and citeParamsfor_,citeTypeinipairs(citeTypes)dogroupedProcessedProperties[citeType]={}citeParams[citeType]={}end-- fill groupedProcessedPropertiesforrefPropertyinpairs(statement.snaks)do-- add the parameter to each matching type of citationfor_,citeTypeinipairs(citeTypes)dorepeat-- just a simple wrapper to emulate "continue"-- skip if there already have been errorsifciteErrors[citeType]thenbreakend-- set mappingKey and prefixlocalmappingKeylocalprefix=""ifstatement.snaks[refProperty][1].datatype=='external-id'thenmappingKey="external-id"prefix=self.conf:getLabel(refProperty)ifprefix~=""thenprefix=prefix.." "endelsemappingKey=refPropertyendlocalparamName=citeParamMapping[citeType][mappingKey]-- skip properties with empty parameter nameifparamName==""thenbreakendreferenceEmpty=false-- handle unknown properties in the referenceifnotparamNamethenlocalerror_message=errorText("unknown-property-in-ref",refProperty)assert(error_message)-- Should not be nilciteErrors[citeType]=error_messagebreakend-- set processedPropertylocalprocessedPropertylocalraw=false-- if the value is wanted rawifisValueInTable(paramName,citeConfig[citeType]["raw-value-params"]or{})thenraw=trueendifisValueInTable(paramName,citeConfig[citeType]["numbered-params"]or{})then-- Multiple values may be given.processedProperty=self:getReferenceDetails(statement.snaks,refProperty,raw,self.linked,true)-- anyLang = trueelse-- If multiple values are given, all but the first suitable one are discarted.processedProperty={self:getReferenceDetail(statement.snaks,refProperty,raw,self.linkedand(statement.snaks[refProperty][1].datatype~='url'),true)}-- link = true/false, anyLang = trueendif#processedProperty==0thenbreakend-- add an entry to groupedProcessedPropertiesifnotgroupedProcessedProperties[citeType][paramName]thengroupedProcessedProperties[citeType][paramName]={}endfor_,propertyValueinpairs(processedProperty)dotable.insert(groupedProcessedProperties[citeType][paramName],prefix..propertyValue)enduntiltrueendend-- handle additional propertiesforrefPropertyinpairs(additionalProcessedProperties)dofor_,citeTypeinipairs(citeTypes)dorepeat-- skip if there already have been errorsifciteErrors[citeType]thenbreakendlocalparamName=citeParamMapping[citeType][refProperty]-- handle unknown properties in the referenceifnotparamNamethen-- Skip this additional property, but do not cause an error.breakendifparamName==""thenbreakendreferenceEmpty=falseifnotgroupedProcessedProperties[citeType][paramName]thengroupedProcessedProperties[citeType][paramName]={}endfor_,propertyValueinpairs(additionalProcessedProperties[refProperty])dotable.insert(groupedProcessedProperties[citeType][paramName],propertyValue)enduntiltrueendend-- fill citeParamsfor_,citeTypeinipairs(citeTypes)doforparamName,paramValuesinpairs(groupedProcessedProperties[citeType])doif#paramValues==1ornotisValueInTable(paramName,citeConfig[citeType]["numbered-params"]or{})thenciteParams[citeType][paramName]=paramValues[1]else-- There is more than one value for this parameter - the values will-- go into separate numbered parameters (e.g. "author1", "author2")forparamNum,paramValueinpairs(paramValues)dociteParams[citeType][paramName..paramNum]=paramValueendendendend-- handle missing mandatory parameters for the templatesfor_,citeTypeinipairs(citeTypes)dofor_,requiredCiteParaminpairs(citeConfig[citeType]["mandatory-params"]or{})doifnotciteParams[citeType][requiredCiteParam]then-- The required param is not present.ifciteErrors[citeType]then-- Do not override the previous error, if it exists.breakendlocalerror_message=errorText("missing-mandatory-param",requiredCiteParam)assert(error_message)-- Should not be nilciteErrors[citeType]=error_messageendendendlocalciteTypeToUse=nil-- choose the output templatefor_,citeTypeinipairs(citeTypes)doifnotciteErrors[citeType]thenciteTypeToUse=citeTypebreakendend-- set refContentlocalrefContent=""ifciteTypeToUsethenlocaltemplateToUse=citeConfig[citeTypeToUse]["template"]localparamsToUse=citeParams[citeTypeToUse]ifnottemplateToUseortemplateToUse==""thenthrowError("no-such-reference-template",tostring(templateToUse),i18nPath,citeTypeToUse)end-- if this module is being substituted then build a regular template call, otherwise expand the templateifmw.isSubsting()thenfori,vinpairs(paramsToUse)dorefContent=refContent.."|"..i.."="..vendrefContent="{{"..templateToUse..refContent.."}}"elsexpcall(function()refContent=mw.getCurrentFrame():expandTemplate{title=templateToUse,args=paramsToUse}end,function()throwError("no-such-reference-template",templateToUse,i18nPath,citeTypeToUse)end)end-- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error.elseifnotreferenceEmptythenrefContent=errorText("malformed-reference-header")for_,citeTypeinipairs(citeTypes)dorefContent=refContent..errorText("template-failure-reason",citeConfig[citeType]["template"],citeErrors[citeType])endrefContent=refContent..errorText("malformed-reference-footer")end-- wrap refContentlocalref={}ifrefContent~=""thenref={refContent}ifnotself.rawValuethen-- this should become a <ref> tag, so save the reference's hash for laterref.refHash="wikidata-"..statement.hash.."-v"..(tonumber(i18n['version'])+version)endreturn{ref}elsereturn{}endend-- gets a detail of one particular type for a referencefunctionState:getReferenceDetail(snaks,dType,raw,link,anyLang)localswitchLang=anyLanglocalvalue=nilifnotsnaks[dType]thenreturnnilend-- if anyLang, first try the local language and otherwise any languagerepeatfor_,vinipairs(snaks[dType])dovalue=self.conf:getValue(v,raw,link,false,anyLangandnotswitchLang,false,true)-- noSpecial = trueifvaluethenbreakendendifvalueornotanyLangthenbreakendswitchLang=notswitchLanguntilanyLangandswitchLangreturnvalueend-- gets the details of one particular type for a referencefunctionState:getReferenceDetails(snaks,dType,raw,link,anyLang)localvalues={}ifnotsnaks[dType]thenreturn{}endfor_,vinipairs(snaks[dType])do-- if nil is returned then it will not be added to the tablevalues[#values+1]=self.conf:getValue(v,raw,link,false,anyLang,false,true)-- noSpecial = trueendreturnvaluesend-- level 1 hookfunctionState:getAlias(object)localvalue=object.valuelocaltitle=nilifvalueandself.linkedthenifself.conf.entityID:sub(1,1)=="Q"thentitle=mw.wikibase.getSitelink(self.conf.entityID)elseifself.conf.entityID:sub(1,1)=="P"thentitle="d:Property:"..self.conf.entityIDendiftitlethenvalue=buildWikilink(title,value)endendvalue={value}-- create one value objectif#value>0thenreturn{value}-- wrap the value object in an array and return itelsereturn{}-- return empty array if there was no valueendend-- level 1 hookfunctionState:getBadge(value)value=self.conf:getLabel(value,self.rawValue,self.linked,self.shortName)ifvalue==""thenvalue=nilendvalue={value}-- create one value objectif#value>0thenreturn{value}-- wrap the value object in an array and return itelsereturn{}-- return empty array if there was no valueendendfunctionState:callHook(param,hooks,statement,result)-- call a parameter's hook if it has been defined and if it has not been called beforeifnotresult[param]andhooks[param]thenlocalvaluesArray=self[hooks[param]](self,statement,param,result,hooks)-- array with value objects-- add to the resultif#valuesArray>0thenresult[param]=valuesArrayresult.count=result.count+1elseresult[param]={}-- an empty array to indicate that we've tried this hook alreadyreturntrue-- miss == trueendendreturnfalseend-- iterate through claims, claim's qualifiers or claim's references to collect valuesfunctionState:iterate(statements,hooks,matchHook)matchHook=matchHookoralwaysTruelocalmatches=falselocalrankPos=nillocalresult,gotRequiredfor_,vinipairs(statements)do-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.)matches,rankPos=matchHook(self,v)ifmatchesthenresult={count=0}-- collection of arrays with value objectslocalfunctionwalk(formatTable)localmissfori2,v2inpairs(formatTable.req)do-- call a hook, adding its return value to the resultmiss=self:callHook(i2,hooks,v,result)ifmissthen-- we miss a required value for this level, so return falsereturnfalseendifresult.count==hooks.countthen-- we're done if all hooks have been called;-- returning at this point breaks the loopreturntrueendendfor_,v2inipairs(formatTable)doifresult.count==hooks.countthen-- we're done if all hooks have been called;-- returning at this point prevents further childs from being processedreturntrueendifv2.childthenwalk(v2.child)endendreturntrueendgotRequired=walk(self.parsedFormat)-- only append the result if we got values for all required parameters on the root levelifgotRequiredthen-- if we have a rankPos (only with matchHook() for complete claims), then update the foundRankifrankPosandself.conf.foundRank>rankPosthenself.conf.foundRank=rankPosend-- append the resultself.results[#self.results+1]=result-- break if we only need a single valueifself.singleValuethenbreakendendendendreturnself:out()endlocalfunctiongetEntityId(arg,eid,page,allowOmitPropPrefix,globalSiteId)localid=nillocalprop=nilifargthenifarg:sub(1,1)==":"thenpage=argeid=nilelseifarg:sub(1,1):upper()=="Q"orarg:sub(1,9):lower()=="property:"orallowOmitPropPrefixtheneid=argpage=nilelseprop=argendendifeidthenifeid:sub(1,9):lower()=="property:"thenid=replaceAlias(mw.text.trim(eid:sub(10)))ifid:sub(1,1):upper()~="P"thenid=""endelseid=replaceAlias(eid)endelseifpagethenifpage:sub(1,1)==":"thenpage=mw.text.trim(page:sub(2))endid=mw.wikibase.getEntityIdForTitle(page,globalSiteId)or""endifnotidthenid=mw.wikibase.getEntityIdForCurrentPage()or""endid=id:upper()ifnotmw.wikibase.isValidEntityId(id)thenid=""endreturnid,propendlocalfunctionnextArg(args)localarg=args[args.pointer]ifargthenargs.pointer=args.pointer+1returnmw.text.trim(arg)elsereturnnilendendlocalfunctionclaimCommand(args,funcName)localcfg=Config:new()cfg:processFlagOrCommand(funcName)-- process first command (== function name)locallastArg,parsedFormat,formatParams,claims,valuelocalhooks={count=0}-- set the date if given;-- must come BEFORE processing the flagsifargs[p.args.date]thencfg.atDate={parseDate(args[p.args.date])}cfg.periods={false,true,false}-- change default time constraint to 'current'end-- process flags and commandsrepeatlastArg=nextArg(args)untilnotcfg:processFlagOrCommand(lastArg)-- get the entity ID from either the positional argument, the eid argument or the page argumentcfg.entityID,cfg.propertyID=getEntityId(lastArg,args[p.args.eid],args[p.args.page],false,args[p.args.globalSiteId])ifcfg.entityID==""thenreturn""-- we cannot continue without a valid entity IDendcfg.entity=mw.wikibase.getEntity(cfg.entityID)ifnotcfg.propertyIDthencfg.propertyID=nextArg(args)endcfg.propertyID=replaceAlias(cfg.propertyID)ifnotcfg.entityornotcfg.propertyIDthenreturn""-- we cannot continue without an entity or a property IDendcfg.propertyID=cfg.propertyID:upper()ifnotcfg.entity.claimsornotcfg.entity.claims[cfg.propertyID]thenreturn""-- there is no use to continue without any claimsendclaims=cfg.entity.claims[cfg.propertyID]ifcfg.states.qualifiersCount>0then-- do further processing if "qualifier(s)" command was givenif#args-args.pointer+1>cfg.states.qualifiersCountthen-- claim ID or literal value has been givencfg.propertyValue=nextArg(args)endfori=1,cfg.states.qualifiersCountdo-- check if given qualifier ID is an alias and add itcfg.qualifierIDs[parameters.qualifier..i]=replaceAlias(nextArg(args)or""):upper()endelseifcfg.states[parameters.reference]then-- do further processing if "reference(s)" command was givencfg.propertyValue=nextArg(args)end-- check for special property value 'somevalue' or 'novalue'ifcfg.propertyValuethencfg.propertyValue=replaceSpecialChars(cfg.propertyValue)ifcfg.propertyValue~=""andmw.text.trim(cfg.propertyValue)==""thencfg.propertyValue=" "-- single space represents 'somevalue', whereas empty string represents 'novalue'elsecfg.propertyValue=mw.text.trim(cfg.propertyValue)endend-- parse the desired format, or choose an appropriate formatifargs["format"]thenparsedFormat,formatParams=parseFormat(args["format"])elseifcfg.states.qualifiersCount>0then-- "qualifier(s)" command givenifcfg.states[parameters.property]then-- "propert(y|ies)" command givenparsedFormat,formatParams=parseFormat(formats.propertyWithQualifier)elseparsedFormat,formatParams=parseFormat(formats.qualifier)endelseifcfg.states[parameters.property]then-- "propert(y|ies)" command givenparsedFormat,formatParams=parseFormat(formats.property)else-- "reference(s)" command givenparsedFormat,formatParams=parseFormat(formats.reference)end-- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolonifcfg.states.qualifiersCount>0andnotcfg.states[parameters.property]thencfg.separators["sep"..parameters.separator][1]={";"}end-- if only "reference(s)" has been given, set the default separator to none (except when raw)ifcfg.states[parameters.reference]andnotcfg.states[parameters.property]andcfg.states.qualifiersCount==0andnotcfg.states[parameters.reference].rawValuethencfg.separators["sep"][1]=nilend-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalentifcfg.states.qualifiersCount==1thencfg.separators["sep"..parameters.qualifier]=cfg.separators["sep"..parameters.qualifier.."1"]end-- process overridden separator values;-- must come AFTER tweaking the default separatorscfg:processSeparators(args)-- define the hooks that should be called (getProperty, getQualifiers, getReferences);-- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been givenfori,vinpairs(cfg.states)do-- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q"ifformatParams[i]orformatParams[i:sub(1,2)]thenhooks[i]=getHookName(i,1)hooks.count=hooks.count+1endend-- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...);-- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been givenifformatParams[parameters.qualifier]andcfg.states.qualifiersCount>0thenhooks[parameters.qualifier]=getHookName(parameters.qualifier,1)hooks.count=hooks.count+1end-- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration;-- must come AFTER defining the hooksifnotcfg.states[parameters.property]thencfg.states[parameters.property]=State:new(cfg,parameters.property)-- if the "single" flag has been given then this state should be equivalent to "property" (singular)ifcfg.singleClaimthencfg.states[parameters.property].singleValue=trueendend-- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values,-- which must exist in order to be able to determine if a claim has any references;-- must come AFTER defining the hooksifcfg.sourcedOnlyandnotcfg.states[parameters.reference]thencfg:processFlagOrCommand(p.claimCommands.reference)-- use singular "reference" to minimize overheadend-- set the parsed format and the separators (and optional punctuation mark);-- must come AFTER creating the additonal statescfg:setFormatAndSeparators(cfg.states[parameters.property],parsedFormat)-- process qualifier matching values, analogous to cfg.propertyValuefori,vinpairs(args)doi=tostring(i)ifi:match('^[Pp]%d+$')oraliasesP[i]thenv=replaceSpecialChars(v)-- check for special qualifier value 'somevalue'ifv~=""andmw.text.trim(v)==""thenv=" "-- single space represents 'somevalue'endcfg.qualifierIDsAndValues[replaceAlias(i):upper()]=vendend-- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated)claims=sortOnRank(claims)-- then iterate through the claims to collect valuesvalue=cfg:concatValues(cfg.states[parameters.property]:iterate(claims,hooks,State.claimMatches))-- pass property state with level 1 hooks and matchHook-- if desired, add a clickable icon that may be used to edit the returned values on Wikidataifcfg.editableandvalue~=""thenvalue=value..cfg:getEditIcon()endreturnvalueendlocalfunctiongeneralCommand(args,funcName)localcfg=Config:new()cfg.curState=State:new(cfg)locallastArglocalvalue=nilrepeatlastArg=nextArg(args)untilnotcfg:processFlag(lastArg)-- get the entity ID from either the positional argument, the eid argument or the page argumentcfg.entityID=getEntityId(lastArg,args[p.args.eid],args[p.args.page],true,args[p.args.globalSiteId])ifcfg.entityID==""ornotmw.wikibase.entityExists(cfg.entityID)thenreturn""-- we cannot continue without an entityend-- serve according to the given commandiffuncName==p.generalCommands.labelthenvalue=cfg:getLabel(cfg.entityID,cfg.curState.rawValue,cfg.curState.linked,cfg.curState.shortName)elseiffuncName==p.generalCommands.titlethencfg.inSitelinks=trueifcfg.entityID:sub(1,1)=="Q"thenvalue=mw.wikibase.getSitelink(cfg.entityID)endifcfg.curState.linkedandvaluethenvalue=buildWikilink(value)endelseiffuncName==p.generalCommands.descriptionthenvalue=mw.wikibase.getDescription(cfg.entityID)elselocalparsedFormat,formatParamslocalhooks={count=0}cfg.entity=mw.wikibase.getEntity(cfg.entityID)iffuncName==p.generalCommands.aliasorfuncName==p.generalCommands.badgethencfg.curState.singleValue=trueendiffuncName==p.generalCommands.aliasorfuncName==p.generalCommands.aliasesthenifnotcfg.entity.aliasesornotcfg.entity.aliases[cfg.langCode]thenreturn""-- there is no use to continue without any aliassesendlocalaliases=cfg.entity.aliases[cfg.langCode]-- parse the desired format, or parse the default aliases formatifargs["format"]thenparsedFormat,formatParams=parseFormat(args["format"])elseparsedFormat,formatParams=parseFormat(formats.alias)end-- process overridden separator values;-- must come AFTER tweaking the default separatorscfg:processSeparators(args)-- define the hook that should be called (getAlias);-- only define the hook if the parameter ("%a") has been givenifformatParams[parameters.alias]thenhooks[parameters.alias]=getHookName(parameters.alias,1)hooks.count=hooks.count+1end-- set the parsed format and the separators (and optional punctuation mark)cfg:setFormatAndSeparators(cfg.curState,parsedFormat)-- iterate to collect valuesvalue=cfg:concatValues(cfg.curState:iterate(aliases,hooks))elseiffuncName==p.generalCommands.badgeorfuncName==p.generalCommands.badgesthenifnotcfg.entity.sitelinksornotcfg.entity.sitelinks[cfg.siteID]ornotcfg.entity.sitelinks[cfg.siteID].badgesthenreturn""-- there is no use to continue without any badgesendlocalbadges=cfg.entity.sitelinks[cfg.siteID].badgescfg.inSitelinks=true-- parse the desired format, or parse the default aliases formatifargs["format"]thenparsedFormat,formatParams=parseFormat(args["format"])elseparsedFormat,formatParams=parseFormat(formats.badge)end-- process overridden separator values;-- must come AFTER tweaking the default separatorscfg:processSeparators(args)-- define the hook that should be called (getBadge);-- only define the hook if the parameter ("%b") has been givenifformatParams[parameters.badge]thenhooks[parameters.badge]=getHookName(parameters.badge,1)hooks.count=hooks.count+1end-- set the parsed format and the separators (and optional punctuation mark)cfg:setFormatAndSeparators(cfg.curState,parsedFormat)-- iterate to collect valuesvalue=cfg:concatValues(cfg.curState:iterate(badges,hooks))endendvalue=valueor""ifcfg.editableandvalue~=""then-- if desired, add a clickable icon that may be used to edit the returned value on Wikidatavalue=value..cfg:getEditIcon()endreturnvalueend-- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args)localfunctionestablishCommands(commandList,commandFunc)for_,commandNameinpairs(commandList)dolocalfunctionwikitextWrapper(frame)localargs=copyTable(frame.args)args.pointer=1loadI18n(aliasesP,frame)returncommandFunc(args,commandName)endp[commandName]=wikitextWrapperlocalfunctionluaWrapper(args)args=copyTable(args)args.pointer=1loadI18n(aliasesP)returncommandFunc(args,commandName)endp["_"..commandName]=luaWrapperendendestablishCommands(p.claimCommands,claimCommand)establishCommands(p.generalCommands,generalCommand)-- main function that is supposed to be used by wrapper templatesfunctionp.main(frame)ifnotmw.wikibasethenreturnnilendlocalf,argsloadI18n(aliasesP,frame)-- get the parent frame to take the arguments that were passed to the wrapper templateframe=frame:getParent()orframeifnotframe.args[1]thenthrowError("no-function-specified")endf=mw.text.trim(frame.args[1])iff=="main"thenthrowError("main-called-twice")endassert(p["_"..f],errorText('no-such-function',f))-- copy arguments from immutable to mutable tableargs=copyTable(frame.args)-- remove the function name from the listtable.remove(args,1)returnp["_"..f](args)endreturnp