Jump to content

Module:Wd

From Meta, a Wikimedia project coordination wiki
Module documentation


Usage

[edit]

Outputs a link target toward a local-language Wikipedia page from a Wikidata entity. Interface language is used as local language. If the Wikipedia in local language does not have this article, it will try to fallback to English, French then German Wikipedia.

Template parameters[Edit template data]

This template prefers inline formatting of parameters.

ParameterDescriptionTypeStatus
Élément Wikidata1

Identifiant de la forme Qxxx (xxx étant des chiffres) désignant un élément sur Wikidata.

Example
Q42
Stringrequired

Example

[edit]

[[{{wd|Q42}}|Douglas Adams]] will output Douglas Adams. This link should send the user to their own language edition of Wikipedia.

It is often used in combination with <tvar> markup, for example [[<tvar name="1">{{wd|Q42}}</tvar>|Douglas Adams]].

See also

[edit]
  • {{lwp}}: same behavior, but gets a Wikipedia article title as input, instead of Wikidata element Id.
  • {{label}}: gets the localized label of a Wikidata item.
  • {{Wda}}: gets any property and value from Wikidata through complex request.
  • {{wq}}: gets a Wikipedia link from a Wikidata ID.

-- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].require("strict")localp={}localarg=...locali18nlocalfunctionloadI18n(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=argendifnoti18ntheni18n=require(title.."/i18n").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"}localaliasesP={coord="P625",-----------------------image="P18",author="P50",authorNameString="P2093",publisher="P123",importedFrom="P143",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"}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,param)localtext=i18n["errors"][code]ifparamthentext=mw.ustring.gsub(text,"$1",param)endreturntextendlocalfunctionthrowError(errorMessage,param)error(errorText(errorMessage,param))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(mw.ustring.gsub(dateStr:sub(ptr,i-1),"^\+(.+)$","%1"),10)-- remove '+' sign (explicitly give base 10 to prevent error)ifparts[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])returnsortedendlocalConfig={}-- 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.getLabelByLang(id,self.langCode)-- XXX: should use fallback labels?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="&nbsp;"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.."&params="..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)localkey,citeWeb,citeQ,labellocalparams={}localciteParams={['web']={},['q']={}}localciteMismatch={}localuseCite=nillocaluseParams=nillocalvalue=""localref={}localreferenceEmpty=true-- will be set to false if at least one parameter is left unremovedlocalnumAuthorParameters=0localnumAuthorNameStringParameters=0localversion=2-- increment this each time the below logic is changed to avoid conflict errorsifstatement.snaksthen-- don't include "imported from", which is added by a botifstatement.snaks[aliasesP.importedFrom]thenstatement.snaks[aliasesP.importedFrom]=nilend-- don't include "inferred from", which is added by a botifstatement.snaks[aliasesP.inferredFrom]thenstatement.snaks[aliasesP.inferredFrom]=nilend-- 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]=nilend-- retrieve all the parametersforiinpairs(statement.snaks)dolabel=""-- multiple authors may be givenifi==aliasesP.authorori==aliasesP.authorNameStringthenparams[i]=self:getReferenceDetails(statement.snaks,i,false,self.linked,true)-- link = true/false, anyLang = trueelseparams[i]={self:getReferenceDetail(statement.snaks,i,false,(self.linkedor(i==aliasesP.statedIn))and(statement.snaks[i][1].datatype~='url'),true)}-- link = true/false, anyLang = trueendif#params[i]==0thenparams[i]=nilelsereferenceEmpty=falseifstatement.snaks[i][1].datatype=='external-id'thenkey="external-id"label=self.conf:getLabel(i)iflabel~=""thenlabel=label.." "endelsekey=iend-- add the parameter to each matching type of citationforjinpairs(citeParams)do-- do so if there was no mismatch with a previous parameterifnotciteMismatch[j]then-- check if this parameter is not mismatching itselfifi18n['cite'][j][key]then-- continue if an option is available in the corresponding cite templateifi18n['cite'][j][key]~=""then-- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter)if(i~=aliasesP.authorandi~=aliasesP.authorNameString)or(i18n['cite'][j][aliasesP.author]~=i18n['cite'][j][aliasesP.authorNameString])thenciteParams[j][i18n['cite'][j][key]]=label..params[i][1]-- to avoid problems with non-author multiple parameters (if existent), the following old code is retainedfork=2,#params[i]dociteParams[j][i18n['cite'][j][key]..k]=label..params[i][k]end-- handle "author" and "author name string" specially if they use the same template parameterelseifi==aliasesP.authorori==aliasesP.authorNameStringthenifparams[aliasesP.author]~=nilthennumAuthorParameters=#params[aliasesP.author]elsenumAuthorParameters=0endifparams[aliasesP.authorNameString]~=nilthennumAuthorNameStringParameters=#params[aliasesP.authorNameString]elsenumAuthorNameStringParameters=0end-- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks-- reason: parameters are added to params each iteration of the loop, not before the loopif((statement.snaks[aliasesP.author]==nil)==(numAuthorParameters==0))and((statement.snaks[aliasesP.authorNameString]==nil)==(numAuthorNameStringParameters==0))thenfork=1,numAuthorParameters+numAuthorNameStringParametersdoifk<=numAuthorParametersthen-- now handling the authors from the "author" propertyciteParams[j][i18n['cite'][j][aliasesP.author]..k]=label..params[aliasesP.author][k]else-- now handling the authors from "author name string"citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k]=label..params[aliasesP.authorNameString][k-numAuthorParameters]endendendendendelseciteMismatch[j]=trueendendendendend-- get title of general template for citing web referencesciteWeb=split(mw.wikibase.getSitelink(aliasesQ.citeWeb)or"",":")[2]-- split off namespace from front-- get title of template that expands stated-in references into citationsciteQ=split(mw.wikibase.getSitelink(aliasesQ.citeQ)or"",":")[2]-- split off namespace from front-- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are presentifciteWebandnotciteMismatch['web']andciteParams['web'][i18n['cite']['web'][aliasesP.referenceURL]]andciteParams['web'][i18n['cite']['web'][aliasesP.title]]thenuseCite=citeWebuseParams=citeParams['web']-- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is presentelseifciteQandnotciteMismatch['q']andciteParams['q'][i18n['cite']['q'][aliasesP.statedIn]]then-- we need the raw "stated in" Q-identifier for the this templateciteParams['q'][i18n['cite']['q'][aliasesP.statedIn]]=self:getReferenceDetail(statement.snaks,aliasesP.statedIn,true)-- raw = trueuseCite=citeQuseParams=citeParams['q']endifuseCiteanduseParamsthen-- if this module is being substituted then build a regular template call, otherwise expand the templateifmw.isSubsting()thenfori,vinpairs(useParams)dovalue=value.."|"..i.."="..vendvalue="{{"..useCite..value.."}}"elsevalue=mw.getCurrentFrame():expandTemplate{title=useCite,args=useParams}end-- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an errorelseifnotreferenceEmptythenvalue="<span style=\"color: crimson\">"..errorText("malformed-reference").."</span>"endifvalue~=""thenvalue={value}-- create one value objectifnotself.rawValuethen-- this should become a <ref> tag, so save the reference's hash for latervalue.refHash="wikidata-"..statement.hash.."-v"..(tonumber(i18n['cite']['version'])+version)endref={value}-- wrap the value object in an arrayendendreturnrefend-- 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)localvaluesArray,refHash-- call a parameter's hook if it has been defined and if it has not been called beforeifnotresult[param]andhooks[param]thenvaluesArray=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)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)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])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)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
close