en:Module:Wd 2017年7月18日 (火) 06:28(UTC)より
template>K-iczn (en:Module:Wd 2017年5月28日 (日) 18:42(UTC)より) |
template>K-iczn (en:Module:Wd 2017年7月18日 (火) 06:28(UTC)より) |
||
25行目: | 25行目: | ||
importedFrom = "P143", | importedFrom = "P143", | ||
statedIn = "P248", | statedIn = "P248", | ||
pages = "P304", | |||
publicationDate = "P577", | publicationDate = "P577", | ||
startTime = "P580", | startTime = "P580", | ||
endTime = "P582", | endTime = "P582", | ||
chapter = "P792", | |||
retrieved = "P813", | retrieved = "P813", | ||
referenceURL = "P854", | referenceURL = "P854", | ||
96行目: | 98行目: | ||
cfg.propertyValue = nil | cfg.propertyValue = nil | ||
cfg.qualifierIDs = {} | cfg.qualifierIDs = {} | ||
cfg.qualifierIDsAndValues = {} | |||
cfg.bestRank = true | cfg.bestRank = true | ||
107行目: | 110行目: | ||
cfg.mdyDate = false | cfg.mdyDate = false | ||
cfg.singleClaim = false | |||
cfg.sourcedOnly = false | |||
cfg.pageTitle = false | cfg.pageTitle = false | ||
117行目: | 122行目: | ||
cfg.states.qualifiersCount = 0 | cfg.states.qualifiersCount = 0 | ||
cfg.curState = nil | cfg.curState = nil | ||
cfg.prefetchedRefs = nil | |||
return cfg | return cfg | ||
143行目: | 150行目: | ||
return stt | return stt | ||
end | |||
function replaceAlias(ID) | |||
if aliasesP[ID] then | |||
ID = aliasesP[ID] | |||
end | |||
return ID | |||
end | end | ||
177行目: | 192行目: | ||
end | end | ||
-- | function replaceSpecialChar(chr) | ||
function | if chr == '_' then | ||
-- replace underscores with spaces | |||
return ' ' | |||
else | |||
return chr | |||
end | |||
end | |||
function replaceSpecialChars(str) | |||
local chr | |||
local esc = false | |||
local strOut = "" | |||
for i = 1, #str do | |||
chr = str:sub(i,i) | |||
if not esc then | |||
if chr == '\\' then | |||
esc = true | |||
else | |||
strOut = strOut .. replaceSpecialChar(chr) | |||
end | |||
else | |||
strOut = strOut .. chr | |||
esc = false | |||
end | |||
end | |||
return strOut | |||
end | end | ||
245行目: | 287行目: | ||
end | end | ||
end | end | ||
return outString | return outString | ||
end | end | ||
366行目: | 408行目: | ||
if hookNames[param] then | if hookNames[param] then | ||
return hookNames[param][index] | return hookNames[param][index] | ||
elseif | elseif param:len() > 2 then | ||
return hookNames[ | return hookNames[param:sub(1, 2).."\\d"][index] | ||
else | else | ||
return nil | return nil | ||
494行目: | 536行目: | ||
param = param - 1 | param = param - 1 | ||
elseif param == 1 then | elseif param == 1 then | ||
if not | if not chr:match('%d') then | ||
endParam() | endParam() | ||
end | end | ||
end | end | ||
cur.str = cur.str .. chr | cur.str = cur.str .. replaceSpecialChar(chr) | ||
end | end | ||
else | else | ||
603行目: | 645行目: | ||
end | end | ||
function Config:getValue(snak, raw, link, short, anyLang) | function Config:getValue(snak, raw, link, short, anyLang, noSpecial) | ||
raw = raw or false | raw = raw or false | ||
link = link or false | link = link or false | ||
short = short or false | short = short or false | ||
anyLang = anyLang or false | anyLang = anyLang or false | ||
noSpecial = noSpecial or false | |||
if snak.snaktype == 'value' then | if snak.snaktype == 'value' then | ||
950行目: | 993行目: | ||
-- use string.format() to strip decimal point followed by a zero (.0) for whole numbers | -- use string.format() to strip decimal point followed by a zero (.0) for whole numbers | ||
latSeconds = tonumber( | latSeconds = tonumber(strFormat:format(math.floor(latitude * 3600 * 10^numDigits + 0.5) / 10^numDigits)) | ||
lonSeconds = tonumber( | lonSeconds = tonumber(strFormat:format(math.floor(longitude * 3600 * 10^numDigits + 0.5) / 10^numDigits)) | ||
latMinutes = math.floor(latSeconds / 60) | latMinutes = math.floor(latSeconds / 60) | ||
974行目: | 1,017行目: | ||
if precision < (1 / 60) then | if precision < (1 / 60) then | ||
latSeconds = | latSeconds = strFormat:format(latSeconds) | ||
lonSeconds = | lonSeconds = strFormat:format(lonSeconds) | ||
if not raw then | if not raw then | ||
1,050行目: | 1,093行目: | ||
return '<strong class="error">' .. unknownDataTypeError(snak.datavalue.type) .. '</strong>' | return '<strong class="error">' .. unknownDataTypeError(snak.datavalue.type) .. '</strong>' | ||
end | end | ||
elseif snak.snaktype == 'somevalue' then | elseif snak.snaktype == 'somevalue' and not noSpecial then | ||
if raw then | if raw then | ||
return " " -- single space represents 'somevalue' | return " " -- single space represents 'somevalue' | ||
1,056行目: | 1,099行目: | ||
return i18n['values']['unknown'] | return i18n['values']['unknown'] | ||
end | end | ||
elseif snak.snaktype == 'novalue' then | elseif snak.snaktype == 'novalue' and not noSpecial then | ||
if raw then | if raw then | ||
return "" -- empty string represents 'novalue' | return "" -- empty string represents 'novalue' | ||
1,172行目: | 1,215行目: | ||
elseif flag == "mdy" then | elseif flag == "mdy" then | ||
self.mdyDate = true | self.mdyDate = true | ||
return true | |||
elseif flag == "single" then | |||
self.singleClaim = true | |||
return true | |||
elseif flag == "sourced" then | |||
self.sourcedOnly = true | |||
return true | return true | ||
elseif flag == "best" or flag:match('^preferred[+-]?$') or flag:match('^normal[+-]?$') or flag:match('^deprecated[+-]?$') then | elseif flag == "best" or flag:match('^preferred[+-]?$') or flag:match('^normal[+-]?$') or flag:match('^deprecated[+-]?$') then | ||
1,212行目: | 1,261行目: | ||
end | end | ||
-- create a new | -- create a new state for each command | ||
self.states[param] = State.new(self) | self.states[param] = State.new(self) | ||
1,221行目: | 1,270行目: | ||
self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately | self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately | ||
if | if flag:sub(-1) ~= 's' then | ||
self.states[param].singleValue = true | self.states[param].singleValue = true | ||
end | end | ||
1,230行目: | 1,279行目: | ||
end | end | ||
function Config:rankMatches(rankPos) | function Config:qualifierMatches(claim, ID, value) | ||
local qualifiers | |||
if claim.qualifiers then qualifiers = claim.qualifiers[ID] end | |||
if qualifiers then | |||
for i, v in pairs(qualifiers) do | |||
if self:snakEqualsValue(v, value) then | |||
return true | |||
end | |||
end | |||
elseif value == "" then | |||
-- if the qualifier is not present then treat it the same as the special value 'novalue' | |||
return true | |||
end | |||
return false | |||
end | |||
function Config:rankMatches(rankPos) | |||
if self.bestRank then | if self.bestRank then | ||
return (self.ranks[rankPos] and self.foundRank >= rankPos) | return (self.ranks[rankPos] and self.foundRank >= rankPos) | ||
1,301行目: | 1,368行目: | ||
return false | return false | ||
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 | |||
function State:isSourced(claim) | |||
self.conf.prefetchedRefs = self:getReferences(claim) | |||
return (#self.conf.prefetchedRefs > 0) | |||
end | |||
function State:resetCaches() | |||
-- any prefetched references of the previous claim must not be used | |||
self.conf.prefetchedRefs = nil | |||
end | end | ||
function State:claimMatches(claim) | function State:claimMatches(claim) | ||
local matches, rankPos | local matches, 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 | -- if a property value was given, check if it matches the claim's property value | ||
1,311行目: | 1,394行目: | ||
else | else | ||
matches = true | matches = true | ||
end | |||
-- if any qualifier values were given, check if each matches one of the claim's qualifier values | |||
for i, v in pairs(self.conf.qualifierIDsAndValues) do | |||
matches = (matches and self.conf:qualifierMatches(claim, i, v)) | |||
end | end | ||
1,316行目: | 1,404行目: | ||
rankPos = convertRank(claim.rank) | rankPos = convertRank(claim.rank) | ||
matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) | matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) | ||
-- if only claims with references must be returned, check if this one has any | |||
if self.conf.sourcedOnly then | |||
matches = (matches and self:isSourced(claim)) -- prefetches and caches references | |||
end | |||
return matches, rankPos | return matches, rankPos | ||
1,401行目: | 1,494行目: | ||
-- iterate through claim's qualifier statements to collect their values; | -- iterate through claim's qualifier statements to collect their values; | ||
-- return array with multiple value objects | -- return array with multiple value objects | ||
return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier | return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook | ||
else | else | ||
return {} -- return empty array | return {} -- return empty array | ||
1,448行目: | 1,541行目: | ||
-- level 1 hook | -- level 1 hook | ||
function State:getReferences(claim) | function State:getReferences(claim) | ||
if self.conf.prefetchedRefs then | |||
-- return references that have been prefetched by isSourced | |||
return self.conf.prefetchedRefs | |||
end | |||
if claim.references then | if claim.references then | ||
-- iterate through claim's reference statements to collect their values; | -- iterate through claim's reference statements to collect their values; | ||
-- return array with multiple value objects | -- return array with multiple value objects | ||
return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference | return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook | ||
else | else | ||
return {} -- return empty array | return {} -- return empty array | ||
1,460行目: | 1,558行目: | ||
-- logic determined based on https://www.wikidata.org/wiki/Help:Sources | -- logic determined based on https://www.wikidata.org/wiki/Help:Sources | ||
function State:getReference(statement) | function State:getReference(statement) | ||
local | local language, referenceURL, title, statedIn, statedInRaw | ||
local authors = {} | |||
local params = {} | |||
local citeParams = {} | |||
local value = "" | local value = "" | ||
local ref = {} | local ref = {} | ||
-- number of parameters that do not go along with "stated in"-sources other than web pages as per https://www.wikidata.org/wiki/Help:Sources; | |||
local | -- these are parameters of properties other than "pages(s)" and "chapter" (for books) and "title" and "publication date" (for databases) and also "stated in" | ||
local hasExtraParams = false | |||
if statement.snaks then | if statement.snaks then | ||
-- don't include "imported from", which has been added by a bot | |||
if statement.snaks[aliasesP.importedFrom] then | |||
statement.snaks[aliasesP.importedFrom] = nil | |||
end | end | ||
-- | -- not linked yet because we need the plain value for comparison first | ||
language = self:getReferenceDetail(statement.snaks, aliasesP.language, false, false, false, false, true) -- (noUnset = true) | |||
if language then | |||
-- not part of a "stated in"-source | |||
hasExtraParams = true | |||
if | -- only add language to the reference if it differs from the local one | ||
if | if self.conf.langName ~= language then | ||
if self.linked then | |||
-- retrieve language again, but this time with link | |||
params[aliasesP.language] = self:getReferenceDetail(statement.snaks, aliasesP.language, false, true) -- link = true | |||
else | |||
params[aliasesP.language] = language | |||
params[ | |||
end | end | ||
end | |||
-- we have to manually unset, since the first call to getReferenceDetail was with noUnset and the second call might not have happened | |||
statement.snaks[aliasesP.language] = nil | |||
end | |||
authors = self:getReferenceDetails(statement.snaks, aliasesP.author, false, self.linked) -- link = true/false | |||
if #authors > 0 then | |||
-- not part of a "stated in"-source | |||
hasExtraParams = true | |||
end | |||
referenceURL = self:getReferenceDetail(statement.snaks, aliasesP.referenceURL) | |||
if referenceURL then | |||
-- not part of a "stated in"-source | |||
hasExtraParams = true | |||
end | |||
-- the next two may be part of a "stated in"-source, so retrieve them already so that they won't count as hasExtraParams | |||
title = self:getReferenceDetail(statement.snaks, aliasesP.title, false, false, false, true) -- anyLang = true | |||
statedIn = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, true, false, false, true) -- link = true, (noUnset = true) | |||
-- (1) if both "reference URL" and "title" are present, then use the general template for citing web references | |||
if referenceURL and title and i18n['cite']['cite-web'] and i18n['cite']['cite-web'] ~= "" then | |||
citeParams[i18n['cite']['url']] = referenceURL | |||
citeParams[i18n['cite']['title']] = title | |||
citeParams[i18n['cite']['website']] = statedIn | |||
citeParams[i18n['cite']['language']] = params[aliasesP.language] | |||
citeParams[i18n['cite']['date']] = self:getReferenceDetail(statement.snaks, aliasesP.publicationDate) | |||
citeParams[i18n['cite']['access-date']] = self:getReferenceDetail(statement.snaks, aliasesP.retrieved) | |||
citeParams[i18n['cite']['archive-url']] = self:getReferenceDetail(statement.snaks, aliasesP.archiveURL) | |||
citeParams[i18n['cite']['archive-date']] = self:getReferenceDetail(statement.snaks, aliasesP.archiveDate) | |||
citeParams[i18n['cite']['publisher']] = self:getReferenceDetail(statement.snaks, aliasesP.publisher, false, self.linked) -- link = true/false | |||
citeParams[i18n['cite']['quote']] = self:getReferenceDetail(statement.snaks, aliasesP.quote, false, false, false, true) -- anyLang = true | |||
for i, v in ipairs(authors) do | |||
citeParams[i18n['cite']['author']..i] = v | |||
end | end | ||
-- if this module is being substituted then build a regular template call, otherwise expand the template | -- if this module is being substituted then build a regular template call, otherwise expand the template | ||
if mw.isSubsting() then | if mw.isSubsting() then | ||
for i, v in pairs( | for i, v in pairs(citeParams) do | ||
value = value .. "|" .. i .. "=" .. v | value = value .. "|" .. i .. "=" .. v | ||
end | end | ||
1,508行目: | 1,638行目: | ||
value = "{{" .. i18n['cite']['cite-web'] .. value .. "}}" | value = "{{" .. i18n['cite']['cite-web'] .. value .. "}}" | ||
else | else | ||
value = mw.getCurrentFrame():expandTemplate{title=i18n['cite']['cite-web'], args= | value = mw.getCurrentFrame():expandTemplate{title=i18n['cite']['cite-web'], args=citeParams} | ||
end | end | ||
else | else | ||
-- | -- we need the raw Q-identifier for the next template | ||
statedInRaw = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true | |||
-- the next three may be part of a "stated in"-source, so retrieve them already so that they won't count as hasExtraParams | |||
params[aliasesP.pages] = self:getReferenceDetail(statement.snaks, aliasesP.pages) | |||
params[aliasesP.chapter] = self:getReferenceDetail(statement.snaks, aliasesP.chapter) | |||
params[aliasesP.publicationDate] = self:getReferenceDetail(statement.snaks, aliasesP.publicationDate) | |||
-- retrieve the rest of the parameters and make them count as hasExtraParams | |||
for i in pairs(statement.snaks) do | |||
params[i] = self:getReferenceDetail(statement.snaks, i, false, self.linked, false, true) -- link = true/false, anyLang = true | |||
hasExtraParams = true | |||
end | end | ||
-- (2) if "stated in" is present without any parameters not belonging to a "stated in"-source, then use this template which expands the stated-in item | |||
if statedInRaw and not hasExtraParams and i18n['cite']['cite-q'] and i18n['cite']['cite-q'] ~= "" then | |||
citeParams[i18n['cite']['pages']] = params[aliasesP.pages] | |||
citeParams[i18n['cite']['chapter']] = params[aliasesP.chapter] | |||
citeParams[i18n['cite']['date']] = params[aliasesP.publicationDate] | |||
if | if mw.isSubsting() then | ||
for i, v in pairs(citeParams) do | |||
value = value .. "|" .. i .. "=" .. v | |||
end | end | ||
value = "{{" .. i18n['cite']['cite-q'] .. "|" .. statedInRaw .. value .. "}}" | |||
else | |||
citeParams[1] = statedInRaw | |||
value = mw.getCurrentFrame():expandTemplate{title=i18n['cite']['cite-q'], args=citeParams} | |||
end | |||
-- (3) else, do some default rendering of name-value pairs, but only if at least "stated in" or "reference URL" is present | |||
elseif statedIn or referenceURL then | |||
-- authors were already retrieved; start by adding them up front | |||
if #authors > 0 then | |||
citeParams[#citeParams + 1] = table.concat(authors, " & ") | |||
end | |||
-- combine "reference URL" and "title" into one link if both are present | |||
if referenceURL and title then | |||
citeParams[#citeParams + 1] = "[" .. referenceURL .. " " .. title .. "]" | |||
elseif referenceURL then | |||
citeParams[#citeParams + 1] = referenceURL | |||
elseif title then | |||
citeParams[#citeParams + 1] = title | |||
end | |||
if statedIn then | |||
citeParams[#citeParams + 1] = statedIn | |||
end | |||
for i, v in pairs(params) do | |||
i = getLabel(i) | |||
if i = | if i ~= "" then | ||
citeParams[#citeParams + 1] = i .. ": " .. v | |||
end | end | ||
end | end | ||
value = table.concat(citeParams, "; ") | |||
if value ~= "" then | if value ~= "" then | ||
value = value .. " | value = value .. "." | ||
end | end | ||
end | end | ||
end | end | ||
if value ~= "" then | if value ~= "" then | ||
value = {value} -- create one value object | |||
if not self.rawValue then | if not self.rawValue then | ||
-- this should become a <ref> tag, so safe the reference's hash for later | -- this should become a <ref> tag, so safe the reference's hash for later | ||
value.refHash = statement.hash | |||
end | end | ||
ref = { | ref = {value} -- wrap the value object in an array | ||
end | end | ||
end | end | ||
1,571行目: | 1,722行目: | ||
end | end | ||
function State: | -- gets a detail of one particular type for a reference | ||
local | function State:getReferenceDetail(snaks, dType, raw, link, short, anyLang, noUnset) | ||
raw = raw or false | |||
link = link or false | |||
short = short or false | |||
anyLang = anyLang or false | |||
noUnset = noUnset or false | |||
local switchLang = anyLang | |||
local value = nil | |||
if not snaks[dType] then | |||
if not | return nil | ||
end | end | ||
return | -- if anyLang, first try the local language and otherwise any language | ||
repeat | |||
for i, v in ipairs(snaks[dType]) do | |||
value = self.conf:getValue(v, raw, link, short, anyLang and not switchLang, true) -- noSpecial = true | |||
if value then | |||
break | |||
end | |||
end | |||
if value or not anyLang then | |||
break | |||
end | |||
switchLang = not switchLang | |||
until anyLang and switchLang | |||
if not noUnset then | |||
-- remove detail(s) to make it possible to get the rest of the details in one loop | |||
snaks[dType] = nil | |||
end | |||
return value | |||
end | end | ||
-- | -- gets the details of one particular type for a reference | ||
function State: | function State:getReferenceDetails(snaks, dType, raw, link, short, anyLang, noUnset) | ||
raw = raw or false | |||
link = link or false | |||
short = short or false | |||
anyLang = anyLang or false | |||
noUnset = noUnset or false | |||
local values = {} | |||
if not snaks[dType] then | |||
return {} | |||
end | |||
for i, v in ipairs(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, short, anyLang, true) -- noSpecial = true | |||
end | |||
if not noUnset then | |||
-- remove detail(s) to make it possible to get the rest of the details in one loop | |||
snaks[dType] = nil | |||
end | |||
for i, v in ipairs(statements) do | return values | ||
-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) | end | ||
matches, rankPos = matchHook(self, v) | |||
function State:callHook(param, hooks, statement, result) | |||
if matches then | local valuesArray, refHash | ||
result = {count = 0} -- collection of arrays with value objects | |||
-- call a parameter's hook if it has been defined and if it has not been called before | |||
local function walk(formatTable) | if not result[param] and hooks[param] then | ||
local miss | valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects | ||
-- add to the result | |||
if #valuesArray > 0 then | |||
result[param] = valuesArray | |||
result.count = result.count + 1 | |||
else | |||
result[param] = {} -- an empty array to indicate that we've tried this hook already | |||
return true -- miss == true | |||
end | |||
end | |||
return false | |||
end | |||
-- iterate through claims, claim's qualifiers or claim's references to collect values | |||
function State:iterate(statements, hooks, matchHook) | |||
matchHook = matchHook or alwaysTrue | |||
local matches = false | |||
local rankPos = nil | |||
local result, gotRequired | |||
for i, v in ipairs(statements) do | |||
-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) | |||
matches, rankPos = matchHook(self, v) | |||
if matches then | |||
result = {count = 0} -- collection of arrays with value objects | |||
local function walk(formatTable) | |||
local miss | |||
for i2, v2 in ipairs(formatTable) do | for i2, v2 in pairs(formatTable.req) do | ||
if result.count == hooks.count then | -- call a hook, adding its return value to the result | ||
miss = self:callHook(i2, hooks, v, result) | |||
if miss then | |||
-- we miss a required value for this level, so return false | |||
return false | |||
end | |||
if result.count == hooks.count then | |||
-- we're done if all hooks have been called; | |||
-- returning at this point breaks the loop | |||
return true | |||
end | |||
end | |||
for i2, v2 in ipairs(formatTable) do | |||
if result.count == hooks.count then | |||
-- we're done if all hooks have been called; | -- we're done if all hooks have been called; | ||
-- returning at this point prevents further childs from being processed | -- returning at this point prevents further childs from being processed | ||
1,750行目: | 1,968行目: | ||
nextIndex = nextIndex + 1 | nextIndex = nextIndex + 1 | ||
elseif nextArg:sub(1,9):lower() == "property:" then | elseif nextArg:sub(1,9):lower() == "property:" then | ||
nextArg = mw.text.trim(nextArg:sub(10)) | nextArg = replaceAlias(mw.text.trim(nextArg:sub(10))) | ||
_.entity = mw.wikibase.getEntity(nextArg) -- entity ID of a property given | _.entity = mw.wikibase.getEntity(nextArg) -- entity ID of a property given | ||
_.propertyID = mw.text.trim(args[nextIndex] or "") -- property ID | _.propertyID = mw.text.trim(args[nextIndex] or "") -- property ID | ||
1,765行目: | 1,978行目: | ||
-- check if given property ID is an alias | -- check if given property ID is an alias | ||
_.propertyID = replaceAlias(_.propertyID):upper() | |||
_.propertyID = _.propertyID:upper() | |||
if _.states.qualifiersCount > 0 then | if _.states.qualifiersCount > 0 then | ||
1,777行目: | 1,986行目: | ||
-- claim ID or literal value has been given | -- claim ID or literal value has been given | ||
_.propertyValue = mw.text.trim(args[nextIndex]) | |||
nextIndex = nextIndex + 1 | nextIndex = nextIndex + 1 | ||
end | end | ||
1,787行目: | 1,994行目: | ||
nextIndex = nextIndex + 1 | nextIndex = nextIndex + 1 | ||
-- check if given qualifier ID is an alias | -- check if given qualifier ID is an alias and add it | ||
_.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg):upper() | |||
_.qualifierIDs[parameters.qualifier..i] = nextArg:upper() | |||
end | end | ||
elseif _.states[parameters.reference] then | elseif _.states[parameters.reference] then | ||
-- do further processing if "reference(s)" command was given | -- do further processing if "reference(s)" command was given | ||
nextArg = args[nextIndex] | nextArg = args[nextIndex] -- claim ID or literal value (possibly nil) | ||
nextIndex = nextIndex + 1 | nextIndex = nextIndex + 1 | ||
_.propertyValue = nextArg | if nextArg then | ||
_.propertyValue = mw.text.trim(nextArg) | |||
end | |||
end | end | ||
-- check for special property value 'somevalue' or 'novalue' | -- check for special property value 'somevalue' or 'novalue' | ||
if _.propertyValue then | if _.propertyValue then | ||
_.propertyValue = replaceSpecialChars(_.propertyValue) | |||
if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then | if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then | ||
_.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' | _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' | ||
1,814行目: | 2,021行目: | ||
-- parse the desired format, or choose an appropriate format | -- parse the desired format, or choose an appropriate format | ||
if args["format"] then | if args["format"] then | ||
parsedFormat, formatParams = parseFormat | parsedFormat, formatParams = parseFormat(args["format"]) | ||
elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given | elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given | ||
if _.states[parameters.property] then -- "propert(y|ies)" command given | if _.states[parameters.property] then -- "propert(y|ies)" command given | ||
1,833行目: | 2,040行目: | ||
-- if only "reference(s)" has been given, set the default separator to none (except when raw) | -- if only "reference(s)" has been given, set the default separator to none (except when raw) | ||
if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 | if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 | ||
and not _.states[parameters.reference].rawValue then | and not _.states[parameters.reference].rawValue then | ||
_.separators["sep"][1] = nil | _.separators["sep"][1] = nil | ||
end | end | ||
-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent; | -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent; | ||
-- must come BEFORE overriding the separator values | -- must come BEFORE overriding the separator values | ||
if _.states.qualifiersCount == 1 then | if _.states.qualifiersCount == 1 then | ||
_.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] | _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] | ||
end | end | ||
-- process overridden separator values; | -- process overridden separator values; | ||
-- must come AFTER parsing the formats | -- must come AFTER parsing the formats | ||
for i, v in pairs(_.separators) do | for i, v in pairs(_.separators) do | ||
if args[i] then | if args[i] then | ||
sep = | sep = replaceSpecialChars(args[i]) | ||
if sep ~= "" then | |||
_.separators[i][1] = {sep} | |||
else | |||
_.separators[i][1] = nil | |||
end | |||
end | |||
end | |||
-- make sure that at least one required parameter has been defined | |||
if not next(parsedFormat.req) then | |||
error(missingRequiredParameterError()) | |||
end | |||
-- make sure that the separator parameter "%s" is not amongst the required parameters | |||
if parsedFormat.req[parameters.separator] then | |||
error(extraRequiredParameterError(parameters.separator)) | |||
end | |||
-- 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 | |||
for i, v in pairs(_.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" | |||
if formatParams[i] or formatParams[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 | |||
if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then | |||
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 | |||
if not _.states[parameters.property] then | |||
_.states[parameters.property] = State.new(_) | |||
-- if the "single" flag has been given then this state should be equivalent to "property" (singular) | |||
if _.singleClaim then | |||
_.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 | |||
if _.sourcedOnly and not _.states[parameters.reference] then | |||
_:processFlagOrCommand("reference") -- use singular "reference" to minimize overhead | |||
end | |||
-- set the parsed format and the separators (and optional punctuation mark) | |||
_.states[parameters.property].parsedFormat = parsedFormat | |||
_.states[parameters.property].separator = _.separators["sep"] | |||
_.states[parameters.property].movSeparator = _.separators["sep"..parameters.separator] | |||
_.states[parameters.property].puncMark = _.separators["punc"] | |||
-- process qualifier matching values, analogous to _.propertyValue | |||
for i, v in pairs(args) do | |||
i = tostring(i) | |||
if i:match('^[Pp]%d+$') or aliasesP[i] then | |||
v = replaceSpecialChars(v) | |||
-- check for special qualifier value 'somevalue' | |||
if v ~= "" and mw.text.trim(v) == "" then | |||
v = " " -- single space represents 'somevalue' | |||
end | |||
_.qualifierIDsAndValues[replaceAlias(i):upper()] = v | |||
end | end | ||
end | end | ||
if _.entity and _.entity.claims then claims = _.entity.claims[_.propertyID] end | if _.entity and _.entity.claims then claims = _.entity.claims[_.propertyID] end | ||
1,903行目: | 2,138行目: | ||
-- then iterate through the claims to collect values | -- then iterate through the claims to collect values | ||
return concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property | return concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook | ||
else | else | ||
return "" | return "" | ||
1,945行目: | 2,180行目: | ||
if ID then | if ID then | ||
ID = replaceAlias(ID):upper() | |||
ID = ID:upper() | |||
-- check if this is a valid ID, and if the number is not larger than max int (to prevent error) | -- check if this is a valid ID, and if the number is not larger than max int (to prevent error) | ||
if not | if not ID:match('^[QP]%d+$') or tonumber(ID:match('%d+')) > 2147483647 then | ||
return "" | return "" | ||
end | end | ||
2,005行目: | 2,236行目: | ||
if not ID then | if not ID then | ||
label = mw.wikibase.getEntityIdForCurrentPage() | label = mw.wikibase.getEntityIdForCurrentPage() | ||
elseif mw.wikibase.getEntity | elseif mw.wikibase.getEntity(ID) then | ||
label = ID | label = ID | ||