-- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].
require("strict")
localp={}
localmodule_arg=...
locali18n
locali18nPath
localfunctionloadI18n(aliasesP,frame)
localtitle
ifframethen
-- current module invoked by page/template, get its title from frame
title=frame:getTitle()
else
-- current module included by other module, get its title from ...
title=module_arg
end
ifnoti18nthen
i18nPath=title.."/i18n"
i18n=require(i18nPath).init(aliasesP)
end
end
p.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",
lastUpdate="P5017"
}
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 copied
localdefaultSeparators={
["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]then
id=aliasesP[id]
end
returnid
end
localfunctionerrorText(code,...)
localtext=i18n["errors"][code]
ifargthentext=mw.ustring.format(text,unpack(arg))end
returntext
end
localfunctionthrowError(errorMessage,...)
error(errorText(errorMessage,unpack(arg)))
end
localfunctionreplaceDecimalMark(num)
returnmw.ustring.gsub(num,"[.]",i18n['numeric']['decimal-mark'],1)
end
localfunctionpadZeros(num,numDigits)
localnumZeros
localnegative=false
ifnum<0then
negative=true
num=num*-1
end
num=tostring(num)
numZeros=numDigits-num:len()
for_=1,numZerosdo
num="0"..num
end
ifnegativethen
num="-"..num
end
returnnum
end
localfunctionreplaceSpecialChar(chr)
ifchr=='_'then
-- replace underscores with spaces
return' '
else
returnchr
end
end
localfunctionreplaceSpecialChars(str)
localchr
localesc=false
localstrOut=""
fori=1,#strdo
chr=str:sub(i,i)
ifnotescthen
ifchr=='\\'then
esc=true
else
strOut=strOut..replaceSpecialChar(chr)
end
else
strOut=strOut..chr
esc=false
end
end
returnstrOut
end
localfunctionbuildWikilink(target,label)
ifnotlabelortarget==labelthen
return"[["..target.."]]"
else
return"[["..target.."|"..label.."]]"
end
end
-- used to make frame.args mutable, to replace #frame.args (which is always 0)
-- with the actual amount and to simply copy tables
localfunctioncopyTable(tIn)
ifnottInthen
returnnil
end
localtOut={}
fori,vinpairs(tIn)do
tOut[i]=v
end
returntOut
end
-- used to merge output arrays together;
-- note that it currently mutates the first input array
localfunctionmergeArrays(a1,a2)
fori=1,#a2do
a1[#a1+1]=a2[i]
end
returna1
end
localfunctionsplit(str,del)
localout={}
locali,j=str:find(del)
ifiandjthen
out[1]=str:sub(1,i-1)
out[2]=str:sub(j+1)
else
out[1]=str
end
returnout
end
localfunctionparseWikidataURL(url)
localid
ifurl:match('^http[s]?://')then
id=split(url,"Q")
ifid[2]then
return"Q"..id[2]
end
end
returnnil
end
localfunctionparseDate(dateStr,precision)
precision=precisionor"d"
locali,j,index,ptr
localparts={nil,nil,nil}
ifdateStr==nilthen
returnparts[1],parts[2],parts[3]-- year, month, day
end
-- 'T' for snak values, '/' for outputs with '/Julian' attached
i,j=dateStr:find("[T/]")
ifithen
dateStr=dateStr:sub(1,i-1)
end
localfrom=1
ifdateStr:sub(1,1)=="-"then
-- this is a negative number, look further ahead
from=2
end
index=1
ptr=1
i,j=dateStr:find("-",from)
ifithen
-- year
parts[index]=tonumber(dateStr:sub(ptr,i-1),10)-- explicitly give base 10 to prevent error
ifparts[index]==-0then
parts[index]=tonumber("0")-- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead
end
ifprecision=="y"then
-- we're done
returnparts[1],parts[2],parts[3]-- year, month, day
end
index=index+1
ptr=i+1
i,j=dateStr:find("-",ptr)
ifithen
-- month
parts[index]=tonumber(dateStr:sub(ptr,i-1),10)
ifprecision=="m"then
-- we're done
returnparts[1],parts[2],parts[3]-- year, month, day
end
index=index+1
ptr=i+1
end
end
ifdateStr:sub(ptr)~=""then
-- day if we have month, month if we have year, or year
parts[index]=tonumber(dateStr:sub(ptr),10)
end
returnparts[1],parts[2],parts[3]-- year, month, day
end
localfunctiondatePrecedesDate(aY,aM,aD,bY,bM,bD)
ifaY==nilorbY==nilthen
returnnil
end
aM=aMor1
aD=aDor1
bM=bMor1
bD=bDor1
ifaY<bYthen
returntrue
end
ifaY>bYthen
returnfalse
end
ifaM<bMthen
returntrue
end
ifaM>bMthen
returnfalse
end
ifaD<bDthen
returntrue
end
returnfalse
end
localfunctiongetHookName(param,index)
ifhookNames[param]then
returnhookNames[param][index]
elseifparam:len()>2then
returnhookNames[param:sub(1,2).."\\d"][index]
else
returnnil
end
end
localfunctionalwaysTrue()
returntrue
end
-- 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,new
localparams={}
localfunctionnewObject(array)
localobj={}-- new object
obj.str=""
array[#array+1]=obj-- array{object}
obj.parent=array
returnobj
end
localfunctionendParam()
ifparam>0then
ifcur.str~=""then
cur.str="%"..cur.str
cur.param=true
params[cur.str]=true
cur.parent.req[cur.str]=true
prev=cur
cur=newObject(cur.parent)
end
param=0
end
end
root={}-- array
root.req={}
cur=newObject(root)
prev=nil
esc=false
param=0
fori=1,#strdo
chr=str:sub(i,i)
ifnotescthen
ifchr=='\\'then
endParam()
esc=true
elseifchr=='%'then
endParam()
ifcur.str~=""then
cur=newObject(cur.parent)
end
param=2
elseifchr=='['then
endParam()
ifprevandcur.str==""then
table.remove(cur.parent)
cur=prev
end
cur.child={}-- new array
cur.child.req={}
cur.child.parent=cur
cur=newObject(cur.child)
elseifchr==']'then
endParam()
ifcur.parent.parentthen
new=newObject(cur.parent.parent.parent)
ifcur.str==""then
table.remove(cur.parent)
end
cur=new
end
else
ifparam>1then
param=param-1
elseifparam==1then
ifnotchr:match('%d')then
endParam()
end
end
cur.str=cur.str..replaceSpecialChar(chr)
end
else
cur.str=cur.str..chr
esc=false
end
prev=nil
end
endParam()
-- make sure that at least one required parameter has been defined
ifnotnext(root.req)then
throwError("missing-required-parameter")
end
-- make sure that the separator parameter "%s" is not amongst the required parameters
ifroot.req[parameters.separator]then
throwError("extra-required-parameter",parameters.separator)
end
returnroot,params
end
localfunctionsortOnRank(claims)
localrankPos
localranks={{},{},{},{}}-- preferred, normal, deprecated, (default)
localsorted={}
for_,vinipairs(claims)do
rankPos=rankTable[v.rank]or4
ranks[rankPos][#ranks[rankPos]+1]=v
end
sorted=ranks[1]
sorted=mergeArrays(sorted,ranks[2])
sorted=mergeArrays(sorted,ranks[3])
returnsorted
end
localfunctionisValueInTable(searchedItem,inputTable)
for_,iteminpairs(inputTable)do
ifitem==searchedItemthen
returntrue
end
end
returnfalse
end
localConfig={}
-- allows for recursive calls
functionConfig:new()
localcfg={}
setmetatable(cfg,self)
self.__index=self
cfg.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=nil
cfg.entityID=nil
cfg.propertyID=nil
cfg.propertyValue=nil
cfg.qualifierIDs={}
cfg.qualifierIDsAndValues={}
cfg.bestRank=true
cfg.ranks={true,true,false}-- preferred = true, normal = true, deprecated = false
cfg.foundRank=#cfg.ranks
cfg.flagBest=false
cfg.flagRank=false
cfg.periods={true,true,true}-- future = true, current = true, former = true
cfg.flagPeriod=false
cfg.atDate={parseDate(os.date('!%Y-%m-%d'))}-- today as {year, month, day}
cfg.mdyDate=false
cfg.singleClaim=false
cfg.sourcedOnly=false
cfg.editable=false
cfg.editAtEnd=false
cfg.inSitelinks=false
cfg.langCode=mw.language.getContentLanguage().code
cfg.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=0
cfg.curState=nil
cfg.prefetchedRefs=nil
returncfg
end
localState={}
functionState:new(cfg,type)
localstt={}
setmetatable(stt,self)
self.__index=self
stt.conf=cfg
stt.type=type
stt.results={}
stt.parsedFormat={}
stt.separator={}
stt.movSeparator={}
stt.puncMark={}
stt.linked=false
stt.rawValue=false
stt.shortName=false
stt.anyLanguage=false
stt.unitOnly=false
stt.singleValue=false
returnstt
end
-- if id == nil then item connected to current page is used
functionConfig:getLabel(id,raw,link,short)
locallabel=nil
localprefix,title="",nil
ifnotidthen
id=mw.wikibase.getEntityIdForCurrentPage()
ifnotidthen
return""
end
end
id=id:upper()-- just to be sure
ifrawthen
-- check if given id actually exists
ifmw.wikibase.isValidEntityId(id)andmw.wikibase.entityExists(id)then
label=id
end
prefix,title="d:Special:EntityPage/",label-- may be nil
else
-- try short name first if requested
ifshortthen
label=p._property{aliasesP.shortName,[p.args.eid]=id}-- get short name
iflabel==""then
label=nil
end
end
-- get label
ifnotlabelthen
label=mw.wikibase.getLabel(id)
end
end
ifnotlabelthen
label=""
elseiflinkthen
-- build a link if requested
ifnottitlethen
ifid:sub(1,1)=="Q"then
title=mw.wikibase.getSitelink(id)
elseifid:sub(1,1)=="P"then
-- properties have no sitelink, link to Wikidata instead
prefix,title="d:Special:EntityPage/",id
end
end
label=mw.text.nowiki(label)-- escape raw label text so it cannot be wikitext markup
iftitlethen
label=buildWikilink(prefix..title,label)
end
end
returnlabel
end
functionConfig:getEditIcon()
localvalue=""
localprefix=""
localfront=" "
localback=""
ifself.entityID:sub(1,1)=="P"then
prefix="Property:"
end
ifself.editAtEndthen
front='<span style="float:'
ifself.langObj:isRTL()then
front=front..'left'
else
front=front..'right'
end
front=front..'">'
back='</span>'
end
value="[[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.langCode
ifself.propertyIDthen
value=value.."#"..self.propertyID
elseifself.inSitelinksthen
value=value.."#sitelinks-wikipedia"
end
value=value.."|"..i18n['info']['edit-on-wikidata'].."]]"
returnfront..value..back
end
-- 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 output
functionConfig:concatValues(valuesArray)
localoutString=""
localj,skip
fori=1,#valuesArraydo
-- check if this is a reference
ifvaluesArray[i].refHashthen
j=i-1
skip=false
-- skip this reference if it is part of a continuous row of references that already contains the exact same reference
whilevaluesArray[j]andvaluesArray[j].refHashdo
ifvaluesArray[i].refHash==valuesArray[j].refHashthen
skip=true
break
end
j=j-1
end
ifnotskipthen
-- 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})
end
else
outString=outString..valuesArray[i][1]
end
end
returnoutString
end
functionConfig:convertUnit(unit,raw,link,short,unitOnly)
localspace=" "
locallabel=""
localitemID
ifunit==""orunit=="1"then
returnnil
end
ifunitOnlythen
space=""
end
itemID=parseWikidataURL(unit)
ifitemIDthen
ifitemID==aliasesQ.percentagethen
return"%"
else
label=self:getLabel(itemID,raw,link,short)
iflabel~=""then
returnspace..label
end
end
end
return""
end
functionState:getValue(snak)
returnself.conf:getValue(snak,self.rawValue,self.linked,self.shortName,self.anyLanguage,self.unitOnly,false,self.type:sub(1,2))
end
functionConfig:getValue(snak,raw,link,short,anyLang,unitOnly,noSpecial,type)
ifsnak.snaktype=='value'then
localdatatype=snak.datavalue.type
localsubtype=snak.datatype
localdatavalue=snak.datavalue.value
ifdatatype=='string'then
ifsubtype=='url'andlinkthen
-- create link explicitly
ifrawthen
-- will render as a linked number like [1]
return"["..datavalue.."]"
else
return"["..datavalue.." "..datavalue.."]"
end
elseifsubtype=='commonsMedia'then
iflinkthen
returnbuildWikilink("c:File:"..datavalue,datavalue)
elseifnotrawthen
return"[[File:"..datavalue.."]]"
else
returndatavalue
end
elseifsubtype=='geo-shape'andlinkthen
returnbuildWikilink("c:"..datavalue,datavalue)
elseifsubtype=='math'andnotrawthen
localattribute=nil
if(type==parameters.propertyor(type==parameters.qualifierandself.propertyID==aliasesP.hasPart))andsnak.property==aliasesP.definingFormulathen
attribute={qid=self.entityID}
end
returnmw.getCurrentFrame():extensionTag("math",datavalue,attribute)
elseifsubtype=='external-id'andlinkthen
localurl=p._property{aliasesP.formatterURL,[p.args.eid]=snak.property}-- get formatter URL
ifurl~=""then
url=mw.ustring.gsub(url,"$1",datavalue)
return"["..url.." "..datavalue.."]"
else
returndatavalue
end
else
returndatavalue
end
elseifdatatype=='monolingualtext'then
ifanyLangordatavalue['language']==self.langCodethen
returndatavalue['text']
else
returnnil
end
elseifdatatype=='quantity'then
localvalue=""
localunit
ifnotunitOnlythen
-- get value and strip + signs from front
value=mw.ustring.gsub(datavalue['amount'],"^%+(.+)$","%1")
ifrawthen
returnvalue
end
-- replace decimal mark based on locale
value=replaceDecimalMark(value)
-- add delimiters for readability
value=i18n.addDelimiters(value)
end
unit=self:convertUnit(datavalue['unit'],raw,link,short,unitOnly)
ifunitthen
value=value..unit
end
returnvalue
elseifdatatype=='time'then
localy,m,d,p,yDiv,yRound,yFull,value,calendarID,dateStr
localyFactor=1
localsign=1
localprefix=""
localsuffix=""
localmayAddCalendar=false
localcalendar=""
localprecision=datavalue['precision']
ifprecision==11then
p="d"
elseifprecision==10then
p="m"
else
p="y"
yFactor=10^(9-precision)
end
y,m,d=parseDate(datavalue['time'],p)
ify<0then
sign=-1
y=y*sign
end
-- if precision is tens/hundreds/thousands/millions/billions of years
ifprecision<=8then
yDiv=y/yFactor
-- if precision is tens/hundreds/thousands of years
ifprecision>=6then
mayAddCalendar=true
ifprecision<=7then
-- round centuries/millenniums up (e.g. 20th century or 3rd millennium)
yRound=math.ceil(yDiv)
ifnotrawthen
ifprecision==6then
suffix=i18n['datetime']['suffixes']['millennium']
else
suffix=i18n['datetime']['suffixes']['century']
end
suffix=i18n.getOrdinalSuffix(yRound)..suffix
else
-- 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+1
end
else
-- precision == 8
-- round decades down (e.g. 2010s)
yRound=math.floor(yDiv)*yFactor
ifnotrawthen
prefix=i18n['datetime']['prefixes']['decade-period']
suffix=i18n['datetime']['suffixes']['decade-period']
end
end
ifrawandsign<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-1
end
else
localyReFactor,yReDiv,yReRound
-- round to nearest for tens of thousands of years or more
yRound=math.floor(yDiv+0.5)
ifyRound==0then
ifprecision<=2andy~=0then
yReFactor=1e6
yReDiv=y/yReFactor
yReRound=math.floor(yReDiv+0.5)
ifyReDiv==yReRoundthen
-- change precision to millions of years only if we have a whole number of them
precision=3
yFactor=yReFactor
yRound=yReRound
end
end
ifyRound==0then
-- otherwise, take the unrounded (original) number of years
precision=5
yFactor=1
yRound=y
mayAddCalendar=true
end
end
ifprecision>=1andy~=0then
yFull=yRound*yFactor
yReFactor=1e9
yReDiv=yFull/yReFactor
yReRound=math.floor(yReDiv+0.5)
ifyReDiv==yReRoundthen
-- change precision to billions of years if we're in that range
precision=0
yFactor=yReFactor
yRound=yReRound
else
yReFactor=1e6
yReDiv=yFull/yReFactor
yReRound=math.floor(yReDiv+0.5)
ifyReDiv==yReRoundthen
-- change precision to millions of years if we're in that range
precision=3
yFactor=yReFactor
yRound=yReRound
end
end
end
ifnotrawthen
ifprecision==3then
suffix=i18n['datetime']['suffixes']['million-years']
elseifprecision==0then
suffix=i18n['datetime']['suffixes']['billion-years']
else
yRound=yRound*yFactor
ifyRound==1then
suffix=i18n['datetime']['suffixes']['year']
else
suffix=i18n['datetime']['suffixes']['years']
end
end
else
yRound=yRound*yFactor
end
end
else
yRound=y
mayAddCalendar=true
end
ifmayAddCalendarthen
calendarID=parseWikidataURL(datavalue['calendarmodel'])
ifcalendarIDandcalendarID==aliasesQ.prolepticJulianCalendarthen
ifnotrawthen
iflinkthen
calendar=" ("..buildWikilink(i18n['datetime']['julian-calendar'],i18n['datetime']['julian'])..")"
else
calendar=" ("..i18n['datetime']['julian']..")"
end
else
calendar="/"..i18n['datetime']['julian']
end
end
end
ifnotrawthen
localce=nil
ifsign<0then
ce=i18n['datetime']['BCE']
elseifprecision<=5then
ce=i18n['datetime']['CE']
end
ifcethen
iflinkthen
ce=buildWikilink(i18n['datetime']['common-era'],ce)
end
suffix=suffix.." "..ce
end
value=tostring(yRound)
ifmthen
dateStr=self.langObj:formatDate("F","1-"..m.."-1")
ifdthen
ifself.mdyDatethen
dateStr=dateStr.." "..d..","
else
dateStr=d.." "..dateStr
end
end
value=dateStr.." "..value
end
value=prefix..value..suffix..calendar
else
value=padZeros(yRound*sign,4)
ifmthen
value=value.."-"..padZeros(m,2)
ifdthen
value=value.."-"..padZeros(d,2)
end
end
value=value..calendar
end
returnvalue
elseifdatatype=='globecoordinate'then
-- logic from https://github.com/DataValues/Geo (v4.0.1)
localprecision,unitsPerDegree,numDigits,strFormat,value,globe
locallatitude,latConv,latValue,latLink
locallongitude,lonConv,lonValue,lonLink
locallatDirection,latDirectionN,latDirectionS,latDirectionEN
locallonDirection,lonDirectionE,lonDirectionW,lonDirectionEN
localdegSymbol,minSymbol,secSymbol,separator
locallatDegrees=nil
locallatMinutes=nil
locallatSeconds=nil
locallonDegrees=nil
locallonMinutes=nil
locallonSeconds=nil
locallatDegSym=""
locallatMinSym=""
locallatSecSym=""
locallonDegSym=""
locallonMinSym=""
locallonSecSym=""
locallatDirectionEN_N="N"
locallatDirectionEN_S="S"
locallonDirectionEN_E="E"
locallonDirectionEN_W="W"
ifnotrawthen
latDirectionN=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']
else
latDirectionN=latDirectionEN_N
latDirectionS=latDirectionEN_S
lonDirectionE=lonDirectionEN_E
lonDirectionW=lonDirectionEN_W
degSymbol="/"
minSymbol="/"
secSymbol="/"
separator="/"
end
latitude=datavalue['latitude']
longitude=datavalue['longitude']
iflatitude<0then
latDirection=latDirectionS
latDirectionEN=latDirectionEN_S
latitude=math.abs(latitude)
else
latDirection=latDirectionN
latDirectionEN=latDirectionEN_N
end
iflongitude<0then
lonDirection=lonDirectionW
lonDirectionEN=lonDirectionEN_W
longitude=math.abs(longitude)
else
lonDirection=lonDirectionE
lonDirectionEN=lonDirectionEN_E
end
precision=datavalue['precision']
ifnotprecisionorprecision<=0then
precision=1/3600-- precision not set (correctly), set to arcsecond
end
-- remove insignificant detail
latitude=math.floor(latitude/precision+0.5)*precision
longitude=math.floor(longitude/precision+0.5)*precision
ifprecision>=1-(1/60)andprecision<1then
precision=1
elseifprecision>=(1/60)-(1/3600)andprecision<(1/60)then
precision=1/60
end
ifprecision>=1then
unitsPerDegree=1
elseifprecision>=(1/60)then
unitsPerDegree=60
else
unitsPerDegree=3600
end
numDigits=math.ceil(-math.log10(unitsPerDegree*precision))
ifnumDigits<=0then
numDigits=tonumber("0")-- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead
end
strFormat="%."..numDigits.."f"
ifprecision>=1then
latDegrees=strFormat:format(latitude)
lonDegrees=strFormat:format(longitude)
ifnotrawthen
latDegSym=replaceDecimalMark(latDegrees)..degSymbol
lonDegSym=replaceDecimalMark(lonDegrees)..degSymbol
else
latDegSym=latDegrees..degSymbol
lonDegSym=lonDegrees..degSymbol
end
else
latConv=math.floor(latitude*unitsPerDegree*10^numDigits+0.5)/10^numDigits
lonConv=math.floor(longitude*unitsPerDegree*10^numDigits+0.5)/10^numDigits
ifprecision>=(1/60)then
latMinutes=latConv
lonMinutes=lonConv
else
latSeconds=latConv
lonSeconds=lonConv
latMinutes=math.floor(latSeconds/60)
lonMinutes=math.floor(lonSeconds/60)
latSeconds=strFormat:format(latSeconds-(latMinutes*60))
lonSeconds=strFormat:format(lonSeconds-(lonMinutes*60))
ifnotrawthen
latSecSym=replaceDecimalMark(latSeconds)..secSymbol
lonSecSym=replaceDecimalMark(lonSeconds)..secSymbol
else
latSecSym=latSeconds..secSymbol
lonSecSym=lonSeconds..secSymbol
end
end
latDegrees=math.floor(latMinutes/60)
lonDegrees=math.floor(lonMinutes/60)
latDegSym=latDegrees..degSymbol
lonDegSym=lonDegrees..degSymbol
latMinutes=latMinutes-(latDegrees*60)
lonMinutes=lonMinutes-(lonDegrees*60)
ifprecision>=(1/60)then
latMinutes=strFormat:format(latMinutes)
lonMinutes=strFormat:format(lonMinutes)
ifnotrawthen
latMinSym=replaceDecimalMark(latMinutes)..minSymbol
lonMinSym=replaceDecimalMark(lonMinutes)..minSymbol
else
latMinSym=latMinutes..minSymbol
lonMinSym=lonMinutes..minSymbol
end
else
latMinSym=latMinutes..minSymbol
lonMinSym=lonMinutes..minSymbol
end
end
latValue=latDegSym..latMinSym..latSecSym..latDirection
lonValue=lonDegSym..lonMinSym..lonSecSym..lonDirection
value=latValue..separator..lonValue
iflinkthen
globe=parseWikidataURL(datavalue['globe'])
ifglobethen
globe=mw.wikibase.getLabelByLang(globe,"en"):lower()
else
globe="earth"
end
latLink=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.."]"
end
returnvalue
elseifdatatype=='wikibase-entityid'then
locallabel
localitemID=datavalue['numeric-id']
ifsubtype=='wikibase-item'then
itemID="Q"..itemID
elseifsubtype=='wikibase-property'then
itemID="P"..itemID
else
return'<strong class="error">'..errorText('unknown-data-type',subtype)..'</strong>'
end
label=self:getLabel(itemID,raw,link,short)
iflabel==""then
label=nil
end
returnlabel
else
return'<strong class="error">'..errorText('unknown-data-type',datatype)..'</strong>'
end
elseifsnak.snaktype=='somevalue'andnotnoSpecialthen
ifrawthen
return" "-- single space represents 'somevalue'
else
returni18n['values']['unknown']
end
elseifsnak.snaktype=='novalue'andnotnoSpecialthen
ifrawthen
return""-- empty string represents 'novalue'
else
returni18n['values']['none']
end
else
returnnil
end
end
functionConfig:getSingleRawQualifier(claim,qualifierID)
localqualifiers
ifclaim.qualifiersthenqualifiers=claim.qualifiers[qualifierID]end
ifqualifiersandqualifiers[1]then
returnself:getValue(qualifiers[1],true)-- raw = true
else
returnnil
end
end
functionConfig:snakEqualsValue(snak,value)
localsnakValue=self:getValue(snak,true)-- raw = true
ifsnakValueandsnak.snaktype=='value'andsnak.datavalue.type=='wikibase-entityid'thenvalue=value:upper()end
returnsnakValue==value
end
functionConfig:setRank(rank)
localrankPos
ifrank==p.flags.bestthen
self.bestRank=true
self.flagBest=true-- mark that 'best' flag was given
return
end
ifrank:sub(1,9)==p.flags.preferredthen
rankPos=1
elseifrank:sub(1,6)==p.flags.normalthen
rankPos=2
elseifrank:sub(1,10)==p.flags.deprecatedthen
rankPos=3
else
return
end
-- one of the rank flags was given, check if another one was given before
ifnotself.flagRankthen
self.ranks={false,false,false}-- no other rank flag given before, so unset ranks
self.bestRank=self.flagBest-- unsets bestRank only if 'best' flag was not given before
self.flagRank=true-- mark that a rank flag was given
end
ifrank:sub(-1)=="+"then
fori=rankPos,1,-1do
self.ranks[i]=true
end
elseifrank:sub(-1)=="-"then
fori=rankPos,#self.ranksdo
self.ranks[i]=true
end
else
self.ranks[rankPos]=true
end
end
functionConfig:setPeriod(period)
localperiodPos
ifperiod==p.flags.futurethen
periodPos=1
elseifperiod==p.flags.currentthen
periodPos=2
elseifperiod==p.flags.formerthen
periodPos=3
else
return
end
-- one of the period flags was given, check if another one was given before
ifnotself.flagPeriodthen
self.periods={false,false,false}-- no other period flag given before, so unset periods
self.flagPeriod=true-- mark that a period flag was given
end
self.periods[periodPos]=true
end
functionConfig:qualifierMatches(claim,id,value)
localqualifiers
ifclaim.qualifiersthenqualifiers=claim.qualifiers[id]end
ifqualifiersthen
for_,vinpairs(qualifiers)do
ifself:snakEqualsValue(v,value)then
returntrue
end
end
elseifvalue==""then
-- if the qualifier is not present then treat it the same as the special value 'novalue'
returntrue
end
returnfalse
end
functionConfig:rankMatches(rankPos)
ifself.bestRankthen
return(self.ranks[rankPos]andself.foundRank>=rankPos)
else
returnself.ranks[rankPos]
end
end
functionConfig:timeMatches(claim)
localstartTime=nil
localstartTimeY=nil
localstartTimeM=nil
localstartTimeD=nil
localendTime=nil
localendTimeY=nil
localendTimeM=nil
localendTimeD=nil
ifself.periods[1]andself.periods[2]andself.periods[3]then
-- any time
returntrue
end
startTime=self:getSingleRawQualifier(claim,aliasesP.startTime)
ifstartTimeandstartTime~=""andstartTime~=" "then
startTimeY,startTimeM,startTimeD=parseDate(startTime)
end
endTime=self:getSingleRawQualifier(claim,aliasesP.endTime)
ifendTimeandendTime~=""andendTime~=" "then
endTimeY,endTimeM,endTimeD=parseDate(endTime)
end
ifstartTimeY~=nilandendTimeY~=nilanddatePrecedesDate(endTimeY,endTimeM,endTimeD,startTimeY,startTimeM,startTimeD)then
-- invalidate end time if it precedes start time
endTimeY=nil
endTimeM=nil
endTimeD=nil
end
ifself.periods[1]then
-- future
ifstartTimeYanddatePrecedesDate(self.atDate[1],self.atDate[2],self.atDate[3],startTimeY,startTimeM,startTimeD)then
returntrue
end
end
ifself.periods[2]then
-- current
if(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))then
returntrue
end
end
ifself.periods[3]then
-- former
ifendTimeYandnotdatePrecedesDate(self.atDate[1],self.atDate[2],self.atDate[3],endTimeY,endTimeM,endTimeD)then
returntrue
end
end
returnfalse
end
functionConfig:processFlag(flag)
ifnotflagthen
returnfalse
end
ifflag==p.flags.linkedthen
self.curState.linked=true
returntrue
elseifflag==p.flags.rawthen
self.curState.rawValue=true
ifself.curState==self.states[parameters.reference]then
-- raw reference values end with periods and require a separator (other than none)
self.separators["sep%r"][1]={" "}
end
returntrue
elseifflag==p.flags.shortthen
self.curState.shortName=true
returntrue
elseifflag==p.flags.multilanguagethen
self.curState.anyLanguage=true
returntrue
elseifflag==p.flags.unitthen
self.curState.unitOnly=true
returntrue
elseifflag==p.flags.mdythen
self.mdyDate=true
returntrue
elseifflag==p.flags.singlethen
self.singleClaim=true
returntrue
elseifflag==p.flags.sourcedthen
self.sourcedOnly=true
returntrue
elseifflag==p.flags.editthen
self.editable=true
returntrue
elseifflag==p.flags.editAtEndthen
self.editable=true
self.editAtEnd=true
returntrue
elseifflag==p.flags.bestorflag:match('^'..p.flags.preferred..'[+-]?$')orflag:match('^'..p.flags.normal..'[+-]?$')orflag:match('^'..p.flags.deprecated..'[+-]?$')then
self:setRank(flag)
returntrue
elseifflag==p.flags.futureorflag==p.flags.currentorflag==p.flags.formerthen
self:setPeriod(flag)
returntrue
elseifflag==""then
-- ignore empty flags and carry on
returntrue
else
returnfalse
end
end
functionConfig:processFlagOrCommand(flag)
localparam=""
ifnotflagthen
returnfalse
end
ifflag==p.claimCommands.propertyorflag==p.claimCommands.propertiesthen
param=parameters.property
elseifflag==p.claimCommands.qualifierorflag==p.claimCommands.qualifiersthen
self.states.qualifiersCount=self.states.qualifiersCount+1
param=parameters.qualifier..self.states.qualifiersCount
self.separators["sep"..param]={copyTable(defaultSeparators["sep%q\\d"])}
elseifflag==p.claimCommands.referenceorflag==p.claimCommands.referencesthen
param=parameters.reference
else
returnself:processFlag(flag)
end
ifself.states[param]then
returnfalse
end
-- create a new state for each command
self.states[param]=State:new(self,param)
-- use "%x" as the general parameter name
self.states[param].parsedFormat=parseFormat(parameters.general)-- will be overwritten for param=="%p"
-- set the separator
self.states[param].separator=self.separators["sep"..param]-- will be nil for param=="%p", which will be set separately
ifflag==p.claimCommands.propertyorflag==p.claimCommands.qualifierorflag==p.claimCommands.referencethen
self.states[param].singleValue=true
end
self.curState=self.states[param]
returntrue
end
functionConfig:processSeparators(args)
localsep
fori,vinpairs(self.separators)do
ifargs[i]then
sep=replaceSpecialChars(args[i])
ifsep~=""then
self.separators[i][1]={sep}
else
self.separators[i][1]=nil
end
end
end
end
functionConfig:setFormatAndSeparators(state,parsedFormat)
state.parsedFormat=parsedFormat
state.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 use
functionState:isSourced(claim)
self.conf.prefetchedRefs=self:getReferences(claim)
return(#self.conf.prefetchedRefs>0)
end
functionState:resetCaches()
-- any prefetched references of the previous claim must not be used
self.conf.prefetchedRefs=nil
end
functionState:claimMatches(claim)
localmatches,rankPos
-- first of all, reset any cached values used for the previous claim
self:resetCaches()
-- if a property value was given, check if it matches the claim's property value
ifself.conf.propertyValuethen
matches=self.conf:snakEqualsValue(claim.mainsnak,self.conf.propertyValue)
else
matches=true
end
-- if any qualifier values were given, check if each matches one of the claim's qualifier values
fori,vinpairs(self.conf.qualifierIDsAndValues)do
matches=(matchesandself.conf:qualifierMatches(claim,i,v))
end
-- check if the claim's rank and time period match
rankPos=rankTable[claim.rank]or4
matches=(matchesandself.conf:rankMatches(rankPos)andself.conf:timeMatches(claim))
-- if only claims with references must be returned, check if this one has any
ifself.conf.sourcedOnlythen
matches=(matchesandself:isSourced(claim))-- prefetches and caches references
end
returnmatches,rankPos
end
functionState:out()
localresult-- collection of arrays with value objects
localvaluesArray-- array with value objects
localsep=nil-- value object
localout={}-- array with value objects
localfunctionwalk(formatTable,result)
localvaluesArray={}-- array with value objects
fori,vinpairs(formatTable.req)do
ifnotresult[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 result
return{}
end
end
for_,vinipairs(formatTable)do
ifv.paramthen
valuesArray=mergeArrays(valuesArray,result[v.str])
elseifv.str~=""then
valuesArray[#valuesArray+1]={v.str}
end
ifv.childthen
valuesArray=mergeArrays(valuesArray,walk(v.child,result))
end
end
returnvaluesArray
end
-- iterate through the results from back to front, so that we know when to add separators
fori=#self.results,1,-1do
result=self.results[i]
-- if there is already some output, then add the separators
if#out>0then
sep=self.separator[1]-- fixed separator
result[parameters.separator]={self.movSeparator[1]}-- movable separator
else
sep=nil
result[parameters.separator]={self.puncMark[1]}-- optional punctuation mark
end
valuesArray=walk(self.parsedFormat,result)
if#valuesArray>0then
ifsepthen
valuesArray[#valuesArray+1]=sep
end
out=mergeArrays(valuesArray,out)
end
end
-- reset state before next iteration
self.results={}
returnout
end
-- level 1 hook
functionState:getProperty(claim)
localvalue={self:getValue(claim.mainsnak)}-- create one value object
if#value>0then
return{value}-- wrap the value object in an array and return it
else
return{}-- return empty array if there was no value
end
end
-- level 1 hook
functionState:getQualifiers(claim,param)
localqualifiers
ifclaim.qualifiersthenqualifiers=claim.qualifiers[self.conf.qualifierIDs[param]]end
ifqualifiersthen
-- iterate through claim's qualifier statements to collect their values;
-- return array with multiple value objects
returnself.conf.states[param]:iterate(qualifiers,{[parameters.general]=hookNames[parameters.qualifier.."\\d"][2],count=1})-- pass qualifier state with level 2 hook
else
return{}-- return empty array
end
end
-- level 2 hook
functionState:getQualifier(snak)
localvalue={self:getValue(snak)}-- create one value object
if#value>0then
return{value}-- wrap the value object in an array and return it
else
return{}-- return empty array if there was no value
end
end
-- level 1 hook
functionState:getAllQualifiers(claim,param,result,hooks)
localout={}-- array with value objects
localsep=self.conf.separators["sep"..parameters.qualifier][1]-- value object
-- iterate through the output of the separate "qualifier(s)" commands
fori=1,self.conf.states.qualifiersCountdo
-- if a hook has not been called yet, call it now
ifnotresult[parameters.qualifier..i]then
self:callHook(parameters.qualifier..i,hooks,claim,result)
end
-- if there is output for this particular "qualifier(s)" command, then add it
ifresult[parameters.qualifier..i]andresult[parameters.qualifier..i][1]then
-- if there is already some output, then add the separator
if#out>0andsepthen
out[#out+1]=sep
end
out=mergeArrays(out,result[parameters.qualifier..i])
end
end
returnout
end
-- level 1 hook
functionState:getReferences(claim)
ifself.conf.prefetchedRefsthen
-- return references that have been prefetched by isSourced
returnself.conf.prefetchedRefs
end
ifclaim.referencesthen
-- iterate through claim's reference statements to collect their values;
-- return array with multiple value objects
returnself.conf.states[parameters.reference]:iterate(claim.references,{[parameters.general]=hookNames[parameters.reference][2],count=1})-- pass reference state with level 2 hook
else
return{}-- return empty array
end
end
-- level 2 hook
functionState: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, is 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 parameters
localcandidateParams={}
-- 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 unremoved
localversion=12-- increment this each time the below logic is changed to avoid conflict errors
ifnotstatement.snaksthen
return{}
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]then
return{}
end
-- don't include "type of reference"
ifstatement.snaks[aliasesP.typeOfReference]then
statement.snaks[aliasesP.typeOfReference]=nil
end
-- don't include "image" to prevent littering
ifstatement.snaks[aliasesP.image]then
statement.snaks[aliasesP.image]=nil
end
-- don't include "language" if it is equal to the local one
ifself:getReferenceDetail(statement.snaks,aliasesP.language)==self.conf.langNamethen
statement.snaks[aliasesP.language]=nil
end
ifstatement.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)do
ifstatement.snaks[wikidataPropertyOfSource]andstatement.snaks[wikidataPropertyOfSource][1].datatype=="external-id"then
localtempLink=self:getReferenceDetail(statement.snaks,wikidataPropertyOfSource,false,true)-- not raw, linked
ifmw.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 first
tempLink=mw.ustring.gsub(tempLink,"^%[(%Z-) %Z+%]$","%1")
additionalProcessedProperties[aliasesP.referenceURL]={tempLink}
statement.snaks[wikidataPropertyOfSource]=nil
break
end
end
end
end
-- initialize candidateParams and citeParams
for_,citeTypeinipairs(citeTypes)do
candidateParams[citeType]={}
citeParams[citeType]={}
end
-- fill candidateParams
for_,citeTypeinipairs(citeTypes)do
-- This will contain value--priority pairs for each param name.
localcandidateValuesAndPriorities={}
-- fill candidateValuesAndPriorities
forrefPropertyinpairs(statement.snaks)do
ifciteErrors[citeType]then
break
end
repeat-- just a simple wrapper to emulate "continue"
-- set mappingKey and prefix
localmappingKey
localprefix=""
ifstatement.snaks[refProperty][1].datatype=='external-id'then
mappingKey="external-id"
prefix=self.conf:getLabel(refProperty)
ifprefix~=""then
prefix=prefix.." "
end
else
mappingKey=refProperty
end
localparamName=citeParamMapping[citeType][mappingKey]
-- skip properties with empty parameter name
ifparamName==""then
break-- skip this property for this value of citeType
end
-- handle unknown properties in the reference
ifnotparamNamethen
referenceEmpty=false
localerror_message=errorText("unknown-property-in-ref",refProperty)
assert(error_message)-- Should not be nil
citeErrors[citeType]=error_message
break
end
-- set processedProperty
localprocessedProperty
localraw=false-- if the value is wanted raw
ifisValueInTable(paramName,citeConfig[citeType]["raw-value-params"]or{})then
raw=true
end
ifisValueInTable(paramName,citeConfig[citeType]["numbered-params"]or{})then
-- Multiple values may be given.
processedProperty=self:getReferenceDetails(statement.snaks,refProperty,raw,self.linked,true)-- anyLang = true
else
-- 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 = true
end
if#processedProperty==0then
break
end
referenceEmpty=false
-- add an empty entry to candidateValuesAndPriorities, if there isn't one already
ifnotcandidateValuesAndPriorities[paramName]then
candidateValuesAndPriorities[paramName]={}
end
-- find the priority of refProperty
localthisPropertyPriority=-1
localthisParamPrioritization=citeConfig[citeType]["prioritization"][paramName]
ifthisParamPrioritizationthen
fori_priority,i_propertyinipairs(thisParamPrioritization)do
ifi_property==refPropertythen
thisPropertyPriority=i_priority
end
end
end
for_,propertyValueinpairs(processedProperty)do
table.insert(
candidateValuesAndPriorities[paramName],
{prefix..propertyValue,thisPropertyPriority}
)
end
untiltrue
end
-- fill candidateParams[citeType]
ifnotciteErrors[citeType]then
localcompareValuePriorities=function(pair1,pair2)
ifpair1[2]==-1andpair2[2]~=-1then
returnfalse
end
ifpair1[2]~=-1andpair2[2]==-1then
returntrue
end
returnpair1[2]<pair2[2]
end
-- fill candidateParams[citeType][paramName] for each used param
forparamName,_inpairs(candidateValuesAndPriorities)do
table.sort(candidateValuesAndPriorities[paramName],compareValuePriorities)
candidateParams[citeType][paramName]={}
for_,valuePriorityPairinipairs(candidateValuesAndPriorities[paramName])do
table.insert(candidateParams[citeType][paramName],valuePriorityPair[1])
end
end
end
end
-- handle additional properties
forrefPropertyinpairs(additionalProcessedProperties)do
for_,citeTypeinipairs(citeTypes)do
repeat
-- skip if there already have been errors
ifciteErrors[citeType]then
break
end
localparamName=citeParamMapping[citeType][refProperty]
-- handle unknown properties in the reference
ifnotparamNamethen
-- Skip this additional property, but do not cause an error.
break
end
ifparamName==""then
break
end
referenceEmpty=false
ifnotcandidateParams[citeType][paramName]then
candidateParams[citeType][paramName]={}
end
for_,propertyValueinpairs(additionalProcessedProperties[refProperty])do
table.insert(candidateParams[citeType][paramName],propertyValue)
end
untiltrue
end
end
-- fill citeParams
for_,citeTypeinipairs(citeTypes)do
forparamName,paramValuesinpairs(candidateParams[citeType])do
if#paramValues==1ornotisValueInTable(paramName,citeConfig[citeType]["numbered-params"]or{})then
citeParams[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)do
citeParams[citeType][paramName..paramNum]=paramValue
end
end
end
end
-- handle missing mandatory parameters for the templates
for_,citeTypeinipairs(citeTypes)do
for_,requiredCiteParaminpairs(citeConfig[citeType]["mandatory-params"]or{})do
ifnotciteParams[citeType][requiredCiteParam]then-- The required param is not present.
ifciteErrors[citeType]then-- Do not override the previous error, if it exists.
break
end
localerror_message=errorText("missing-mandatory-param",requiredCiteParam)
assert(error_message)-- Should not be nil
citeErrors[citeType]=error_message
end
end
end
localciteTypeToUse=nil
-- choose the output template
for_,citeTypeinipairs(citeTypes)do
ifnotciteErrors[citeType]then
citeTypeToUse=citeType
break
end
end
-- set refContent
localrefContent=""
ifciteTypeToUsethen
localtemplateToUse=citeConfig[citeTypeToUse]["template"]
localparamsToUse=citeParams[citeTypeToUse]
ifnottemplateToUseortemplateToUse==""then
throwError("no-such-reference-template",tostring(templateToUse),i18nPath,citeTypeToUse)
end
-- if this module is being substituted then build a regular template call, otherwise expand the template
ifmw.isSubsting()then
fori,vinpairs(paramsToUse)do
refContent=refContent.."|"..i.."="..v
end
refContent="{{"..templateToUse..refContent.."}}"
else
xpcall(
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.
elseifnotreferenceEmptythen
refContent=errorText("malformed-reference-header")
for_,citeTypeinipairs(citeTypes)do
refContent=refContent..errorText("template-failure-reason",citeConfig[citeType]["template"],citeErrors[citeType])
end
refContent=refContent..errorText("malformed-reference-footer")
end
-- wrap refContent
localref={}
ifrefContent~=""then
ref={refContent}
ifnotself.rawValuethen
-- this should become a <ref> tag, so save the reference's hash for later
ref.refHash="wikidata-"..statement.hash.."-v"..(tonumber(i18n['version'])+version)
end
return{ref}
else
return{}
end
end
-- gets a detail of one particular type for a reference
functionState:getReferenceDetail(snaks,dType,raw,link,anyLang)
localswitchLang=anyLang
localvalue=nil
ifnotsnaks[dType]then
returnnil
end
-- if anyLang, first try the local language and otherwise any language
repeat
for_,vinipairs(snaks[dType])do
value=self.conf:getValue(v,raw,link,false,anyLangandnotswitchLang,false,true)-- noSpecial = true
ifvaluethen
break
end
end
ifvalueornotanyLangthen
break
end
switchLang=notswitchLang
untilanyLangandswitchLang
returnvalue
end
-- gets the details of one particular type for a reference
functionState:getReferenceDetails(snaks,dType,raw,link,anyLang)
localvalues={}
ifnotsnaks[dType]then
return{}
end
for_,vinipairs(snaks[dType])do
-- if nil is returned then it will not be added to the table
values[#values+1]=self.conf:getValue(v,raw,link,false,anyLang,false,true)-- noSpecial = true
end
returnvalues
end
-- level 1 hook
functionState:getAlias(object)
localvalue=object.value
localtitle=nil
ifvalueandself.linkedthen
ifself.conf.entityID:sub(1,1)=="Q"then
title=mw.wikibase.getSitelink(self.conf.entityID)
elseifself.conf.entityID:sub(1,1)=="P"then
title="d:Property:"..self.conf.entityID
end
iftitlethen
value=buildWikilink(title,value)
end
end
value={value}-- create one value object
if#value>0then
return{value}-- wrap the value object in an array and return it
else
return{}-- return empty array if there was no value
end
end
-- level 1 hook
functionState:getBadge(value)
value=self.conf:getLabel(value,self.rawValue,self.linked,self.shortName)
ifvalue==""then
value=nil
end
value={value}-- create one value object
if#value>0then
return{value}-- wrap the value object in an array and return it
else
return{}-- return empty array if there was no value
end
end
functionState:callHook(param,hooks,statement,result)
-- call a parameter's hook if it has been defined and if it has not been called before
ifnotresult[param]andhooks[param]then
localvaluesArray=self[hooks[param]](self,statement,param,result,hooks)-- array with value objects
-- add to the result
if#valuesArray>0then
result[param]=valuesArray
result.count=result.count+1
else
result[param]={}-- an empty array to indicate that we've tried this hook already
returntrue-- miss == true
end
end
returnfalse
end
-- iterate through claims, claim's qualifiers or claim's references to collect values
functionState:iterate(statements,hooks,matchHook)
matchHook=matchHookoralwaysTrue
localmatches=false
localrankPos=nil
localresult,gotRequired
for_,vinipairs(statements)do
-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.)
matches,rankPos=matchHook(self,v)
ifmatchesthen
result={count=0}-- collection of arrays with value objects
localfunctionwalk(formatTable)
localmiss
fori2,v2inpairs(formatTable.req)do
-- call a hook, adding its return value to the result
miss=self:callHook(i2,hooks,v,result)
ifmissthen
-- we miss a required value for this level, so return false
returnfalse
end
ifresult.count==hooks.countthen
-- we're done if all hooks have been called;
-- returning at this point breaks the loop
returntrue
end
end
for_,v2inipairs(formatTable)do
ifresult.count==hooks.countthen
-- we're done if all hooks have been called;
-- returning at this point prevents further childs from being processed
returntrue
end
ifv2.childthen
walk(v2.child)
end
end
returntrue
end
gotRequired=walk(self.parsedFormat)
-- only append the result if we got values for all required parameters on the root level
ifgotRequiredthen
-- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank
ifrankPosandself.conf.foundRank>rankPosthen
self.conf.foundRank=rankPos
end
-- append the result
self.results[#self.results+1]=result
-- break if we only need a single value
ifself.singleValuethen
break
end
end
end
end
returnself:out()
end
localfunctiongetEntityId(arg,eid,page,allowOmitPropPrefix,globalSiteId)
localid=nil
localprop=nil
ifargthen
ifarg:sub(1,1)==":"then
page=arg
eid=nil
elseifarg:sub(1,1):upper()=="Q"orarg:sub(1,9):lower()=="property:"orallowOmitPropPrefixthen
eid=arg
page=nil
else
prop=arg
end
end
ifeidthen
ifeid:sub(1,9):lower()=="property:"then
id=replaceAlias(mw.text.trim(eid:sub(10)))
ifid:sub(1,1):upper()~="P"then
id=""
end
else
id=replaceAlias(eid)
end
elseifpagethen
ifpage:sub(1,1)==":"then
page=mw.text.trim(page:sub(2))
end
id=mw.wikibase.getEntityIdForTitle(page,globalSiteId)or""
end
ifnotidthen
id=mw.wikibase.getEntityIdForCurrentPage()or""
end
id=id:upper()
ifnotmw.wikibase.isValidEntityId(id)then
id=""
end
returnid,prop
end
localfunctionnextArg(args)
localarg=args[args.pointer]
ifargthen
args.pointer=args.pointer+1
returnmw.text.trim(arg)
else
returnnil
end
end
localfunctionclaimCommand(args,funcName)
localcfg=Config:new()
cfg:processFlagOrCommand(funcName)-- process first command (== function name)
locallastArg,parsedFormat,formatParams,claims,value
localhooks={count=0}
-- set the date if given;
-- must come BEFORE processing the flags
ifargs[p.args.date]then
cfg.atDate={parseDate(args[p.args.date])}
cfg.periods={false,true,false}-- change default time constraint to 'current'
end
-- process flags and commands
repeat
lastArg=nextArg(args)
untilnotcfg:processFlagOrCommand(lastArg)
-- get the entity ID from either the positional argument, the eid argument or the page argument
cfg.entityID,cfg.propertyID=getEntityId(lastArg,args[p.args.eid],args[p.args.page],false,args[p.args.globalSiteId])
ifcfg.entityID==""then
return""-- we cannot continue without a valid entity ID
end
cfg.entity=mw.wikibase.getEntity(cfg.entityID)
ifnotcfg.propertyIDthen
cfg.propertyID=nextArg(args)
end
cfg.propertyID=replaceAlias(cfg.propertyID)
ifnotcfg.entityornotcfg.propertyIDthen
return""-- we cannot continue without an entity or a property ID
end
cfg.propertyID=cfg.propertyID:upper()
ifnotcfg.entity.claimsornotcfg.entity.claims[cfg.propertyID]then
return""-- there is no use to continue without any claims
end
claims=cfg.entity.claims[cfg.propertyID]
ifcfg.states.qualifiersCount>0then
-- do further processing if "qualifier(s)" command was given
if#args-args.pointer+1>cfg.states.qualifiersCountthen
-- claim ID or literal value has been given
cfg.propertyValue=nextArg(args)
end
fori=1,cfg.states.qualifiersCountdo
-- check if given qualifier ID is an alias and add it
cfg.qualifierIDs[parameters.qualifier..i]=replaceAlias(nextArg(args)or""):upper()
end
elseifcfg.states[parameters.reference]then
-- do further processing if "reference(s)" command was given
cfg.propertyValue=nextArg(args)
end
-- check for special property value 'somevalue' or 'novalue'
ifcfg.propertyValuethen
cfg.propertyValue=replaceSpecialChars(cfg.propertyValue)
ifcfg.propertyValue~=""andmw.text.trim(cfg.propertyValue)==""then
cfg.propertyValue=" "-- single space represents 'somevalue', whereas empty string represents 'novalue'
else
cfg.propertyValue=mw.text.trim(cfg.propertyValue)
end
end
-- parse the desired format, or choose an appropriate format
ifargs["format"]then
parsedFormat,formatParams=parseFormat(args["format"])
elseifcfg.states.qualifiersCount>0then-- "qualifier(s)" command given
ifcfg.states[parameters.property]then-- "propert(y|ies)" command given
parsedFormat,formatParams=parseFormat(formats.propertyWithQualifier)
else
parsedFormat,formatParams=parseFormat(formats.qualifier)
end
elseifcfg.states[parameters.property]then-- "propert(y|ies)" command given
parsedFormat,formatParams=parseFormat(formats.property)
else-- "reference(s)" command given
parsedFormat,formatParams=parseFormat(formats.reference)
end
-- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon
ifcfg.states.qualifiersCount>0andnotcfg.states[parameters.property]then
cfg.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==0
andnotcfg.states[parameters.reference].rawValuethen
cfg.separators["sep"][1]=nil
end
-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent
ifcfg.states.qualifiersCount==1then
cfg.separators["sep"..parameters.qualifier]=cfg.separators["sep"..parameters.qualifier.."1"]
end
-- process overridden separator values;
-- must come AFTER tweaking the default separators
cfg: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 given
fori,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)]then
hooks[i]=getHookName(i,1)
hooks.count=hooks.count+1
end
end
-- 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 given
ifformatParams[parameters.qualifier]andcfg.states.qualifiersCount>0then
hooks[parameters.qualifier]=getHookName(parameters.qualifier,1)
hooks.count=hooks.count+1
end
-- 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 hooks
ifnotcfg.states[parameters.property]then
cfg.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.singleClaimthen
cfg.states[parameters.property].singleValue=true
end
end
-- 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 hooks
ifcfg.sourcedOnlyandnotcfg.states[parameters.reference]then
cfg:processFlagOrCommand(p.claimCommands.reference)-- use singular "reference" to minimize overhead
end
-- set the parsed format and the separators (and optional punctuation mark);
-- must come AFTER creating the additonal states
cfg:setFormatAndSeparators(cfg.states[parameters.property],parsedFormat)
-- process qualifier matching values, analogous to cfg.propertyValue
fori,vinpairs(args)do
i=tostring(i)
ifi:match('^[Pp]%d+$')oraliasesP[i]then
v=replaceSpecialChars(v)
-- check for special qualifier value 'somevalue'
ifv~=""andmw.text.trim(v)==""then
v=" "-- single space represents 'somevalue'
end
cfg.qualifierIDsAndValues[replaceAlias(i):upper()]=v
end
end
-- 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 values
value=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 Wikidata
ifcfg.editableandvalue~=""then
value=value..cfg:getEditIcon()
end
returnvalue
end
localfunctiongeneralCommand(args,funcName)
localcfg=Config:new()
cfg.curState=State:new(cfg)
locallastArg
localvalue=nil
repeat
lastArg=nextArg(args)
untilnotcfg:processFlag(lastArg)
-- get the entity ID from either the positional argument, the eid argument or the page argument
cfg.entityID=getEntityId(lastArg,args[p.args.eid],args[p.args.page],true,args[p.args.globalSiteId])
ifcfg.entityID==""ornotmw.wikibase.entityExists(cfg.entityID)then
return""-- we cannot continue without an entity
end
-- serve according to the given command
iffuncName==p.generalCommands.labelthen
value=cfg:getLabel(cfg.entityID,cfg.curState.rawValue,cfg.curState.linked,cfg.curState.shortName)
elseiffuncName==p.generalCommands.titlethen
cfg.inSitelinks=true
ifcfg.entityID:sub(1,1)=="Q"then
value=mw.wikibase.getSitelink(cfg.entityID)
end
ifcfg.curState.linkedandvaluethen
value=buildWikilink(value)
end
elseiffuncName==p.generalCommands.descriptionthen
value=mw.wikibase.getDescription(cfg.entityID)
else
localparsedFormat,formatParams
localhooks={count=0}
cfg.entity=mw.wikibase.getEntity(cfg.entityID)
iffuncName==p.generalCommands.aliasorfuncName==p.generalCommands.badgethen
cfg.curState.singleValue=true
end
iffuncName==p.generalCommands.aliasorfuncName==p.generalCommands.aliasesthen
ifnotcfg.entity.aliasesornotcfg.entity.aliases[cfg.langCode]then
return""-- there is no use to continue without any aliasses
end
localaliases=cfg.entity.aliases[cfg.langCode]
-- parse the desired format, or parse the default aliases format
ifargs["format"]then
parsedFormat,formatParams=parseFormat(args["format"])
else
parsedFormat,formatParams=parseFormat(formats.alias)
end
-- process overridden separator values;
-- must come AFTER tweaking the default separators
cfg:processSeparators(args)
-- define the hook that should be called (getAlias);
-- only define the hook if the parameter ("%a") has been given
ifformatParams[parameters.alias]then
hooks[parameters.alias]=getHookName(parameters.alias,1)
hooks.count=hooks.count+1
end
-- set the parsed format and the separators (and optional punctuation mark)
cfg:setFormatAndSeparators(cfg.curState,parsedFormat)
-- iterate to collect values
value=cfg:concatValues(cfg.curState:iterate(aliases,hooks))
elseiffuncName==p.generalCommands.badgeorfuncName==p.generalCommands.badgesthen
ifnotcfg.entity.sitelinksornotcfg.entity.sitelinks[cfg.siteID]ornotcfg.entity.sitelinks[cfg.siteID].badgesthen
return""-- there is no use to continue without any badges
end
localbadges=cfg.entity.sitelinks[cfg.siteID].badges
cfg.inSitelinks=true
-- parse the desired format, or parse the default aliases format
ifargs["format"]then
parsedFormat,formatParams=parseFormat(args["format"])
else
parsedFormat,formatParams=parseFormat(formats.badge)
end
-- process overridden separator values;
-- must come AFTER tweaking the default separators
cfg:processSeparators(args)
-- define the hook that should be called (getBadge);
-- only define the hook if the parameter ("%b") has been given
ifformatParams[parameters.badge]then
hooks[parameters.badge]=getHookName(parameters.badge,1)
hooks.count=hooks.count+1
end
-- set the parsed format and the separators (and optional punctuation mark)
cfg:setFormatAndSeparators(cfg.curState,parsedFormat)
-- iterate to collect values
value=cfg:concatValues(cfg.curState:iterate(badges,hooks))
end
end
value=valueor""
ifcfg.editableandvalue~=""then
-- if desired, add a clickable icon that may be used to edit the returned value on Wikidata
value=value..cfg:getEditIcon()
end
returnvalue
end
-- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args)
localfunctionestablishCommands(commandList,commandFunc)
for_,commandNameinpairs(commandList)do
localfunctionwikitextWrapper(frame)
localargs=copyTable(frame.args)
args.pointer=1
loadI18n(aliasesP,frame)
returncommandFunc(args,commandName)
end
p[commandName]=wikitextWrapper
localfunctionluaWrapper(args)
args=copyTable(args)
args.pointer=1
loadI18n(aliasesP)
returncommandFunc(args,commandName)
end
p["_"..commandName]=luaWrapper
end
end
establishCommands(p.claimCommands,claimCommand)
establishCommands(p.generalCommands,generalCommand)
-- main function that is supposed to be used by wrapper templates
functionp.main(frame)
ifnotmw.wikibasethenreturnnilend
localf,args
loadI18n(aliasesP,frame)
-- get the parent frame to take the arguments that were passed to the wrapper template
frame=frame:getParent()orframe
ifnotframe.args[1]then
throwError("no-function-specified")
end
f=mw.text.trim(frame.args[1])
iff=="main"then
throwError("main-called-twice")
end
assert(p["_"..f],errorText('no-such-function',f))
-- copy arguments from immutable to mutable table
args=copyTable(frame.args)
-- remove the function name from the list
table.remove(args,1)
returnp["_"..f](args)
end
returnp