Module:PBD: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
PeaceDeadC (talk | contribs) Created page with "-- Original module located at en:Module:PBD, en:Module:PBD/i18n and en:Module:PBD/aliasesP. local p = {} local arg = ... local i18n function loadSubmodules(fr..." |
PeaceDeadC (talk | contribs) No edit summary |
||
| (One intermediate revision by the same user not shown) | |||
| Line 1: | Line 1: | ||
-- Original module located at [[:en:Module:PBD]], [[:en:Module:PBD/i18n]] and [[:en:Module:PBD/aliasesP]]. |
|||
local p = {} |
local p = {} |
||
local arg = ... |
local arg = ... |
||
local i18n |
local i18n |
||
function |
local function loadI18n(aliasesP, frame) |
||
local title |
local title |
||
if frame then |
if frame then |
||
-- current module invoked by page/template, get its title from frame |
|||
title = frame:getTitle() |
title = frame:getTitle() |
||
else |
else |
||
-- current module included by other module, get its title from ... |
|||
title = arg |
title = arg |
||
end |
end |
||
if not i18n then |
|||
i18n = i18n or require(title .. "/i18n") |
|||
i18n = require(title .. "/i18n").init(aliasesP) |
|||
end |
|||
end |
end |
||
| Line 61: | Line 58: | ||
p.args = { |
p.args = { |
||
eid = "eid" |
eid = "eid", |
||
page = "page", |
|||
date = "date" |
|||
} |
|||
local aliasesP = { |
|||
coord = "P48", |
|||
----------------------- |
|||
image = "P470", |
|||
author = "P215", |
|||
publisher = "P218", |
|||
importedFrom = "P36", |
|||
statedIn = "P15", |
|||
pages = "P219", |
|||
language = "P44", |
|||
hasPart = "P173", |
|||
publicationDate = "P19", |
|||
startTime = "P34", |
|||
endTime = "P41", |
|||
chapter = "P221", |
|||
retrieved = "P17", |
|||
referenceURL = "P35", |
|||
sectionVerseOrParagraph = "P223", |
|||
archiveURL = "P208", |
|||
title = "P225", |
|||
formatterURL = "P167", |
|||
quote = "P226", |
|||
shortName = "P203", |
|||
definingFormula = "P232", |
|||
archiveDate = "P227", |
|||
inferredFrom = "P71", |
|||
typeOfReference = "P228", |
|||
column = "P230" |
|||
} |
} |
||
| Line 90: | Line 119: | ||
} |
} |
||
local hookNames = { |
local hookNames = { |
||
[parameters.property] = {"getProperty"}, |
[parameters.property] = {"getProperty"}, |
||
[parameters.reference] = {"getReferences", "getReference"}, |
[parameters.reference] = {"getReferences", "getReference"}, |
||
| Line 99: | Line 128: | ||
} |
} |
||
-- default value objects, should NOT be mutated but instead copied |
|||
local defaultSeparators = { |
local defaultSeparators = { |
||
["sep"] = {" "}, |
["sep"] = {" "}, |
||
| Line 105: | Line 133: | ||
["sep%q"] = {"; "}, |
["sep%q"] = {"; "}, |
||
["sep%q\\d"] = {", "}, |
["sep%q\\d"] = {", "}, |
||
["sep%r"] = nil, |
["sep%r"] = nil, |
||
["punc"] = nil |
["punc"] = nil |
||
} |
} |
||
| Line 116: | Line 144: | ||
local Config = {} |
local Config = {} |
||
Config.__index = Config |
|||
function Config:new() |
|||
-- allows for recursive calls |
|||
function Config.new() |
|||
local cfg = {} |
local cfg = {} |
||
setmetatable(cfg, |
setmetatable(cfg, self) |
||
self.__index = self |
|||
cfg.separators = { |
cfg.separators = { |
||
-- single value objects wrapped in arrays so that we can pass by reference |
|||
["sep"] = {copyTable(defaultSeparators["sep"])}, |
["sep"] = {copyTable(defaultSeparators["sep"])}, |
||
["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, |
["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, |
||
| Line 131: | Line 157: | ||
["punc"] = {copyTable(defaultSeparators["punc"])} |
["punc"] = {copyTable(defaultSeparators["punc"])} |
||
} |
} |
||
cfg.entity = nil |
cfg.entity = nil |
||
cfg.entityID = nil |
cfg.entityID = nil |
||
| Line 138: | Line 164: | ||
cfg.qualifierIDs = {} |
cfg.qualifierIDs = {} |
||
cfg.qualifierIDsAndValues = {} |
cfg.qualifierIDsAndValues = {} |
||
cfg.bestRank = true |
cfg.bestRank = true |
||
cfg.ranks = {true, true, false} |
cfg.ranks = {true, true, false} |
||
cfg.foundRank = #cfg.ranks |
cfg.foundRank = #cfg.ranks |
||
cfg.flagBest = false |
cfg.flagBest = false |
||
cfg.flagRank = false |
cfg.flagRank = false |
||
cfg.periods = {true, true, true} |
cfg.periods = {true, true, true} |
||
cfg.flagPeriod = false |
cfg.flagPeriod = false |
||
cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} |
|||
cfg.mdyDate = false |
cfg.mdyDate = false |
||
cfg.singleClaim = false |
cfg.singleClaim = false |
||
| Line 153: | Line 180: | ||
cfg.editable = false |
cfg.editable = false |
||
cfg.editAtEnd = false |
cfg.editAtEnd = false |
||
cfg.inSitelinks = false |
cfg.inSitelinks = false |
||
cfg.langCode = mw.language.getContentLanguage().code |
cfg.langCode = mw.language.getContentLanguage().code |
||
cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) |
cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) |
||
cfg.langObj = mw.language.new(cfg.langCode) |
cfg.langObj = mw.language.new(cfg.langCode) |
||
cfg.siteID = mw.wikibase.getGlobalSiteId() |
|||
cfg.states = {} |
cfg.states = {} |
||
cfg.states.qualifiersCount = 0 |
cfg.states.qualifiersCount = 0 |
||
cfg.curState = nil |
cfg.curState = nil |
||
cfg.prefetchedRefs = nil |
cfg.prefetchedRefs = nil |
||
return cfg |
return cfg |
||
end |
end |
||
local State = {} |
local State = {} |
||
State.__index = State |
|||
function State |
function State:new(cfg, type) |
||
local stt = {} |
local stt = {} |
||
setmetatable(stt, |
setmetatable(stt, self) |
||
self.__index = self |
|||
stt.conf = cfg |
stt.conf = cfg |
||
stt.type = type |
|||
stt.results = {} |
stt.results = {} |
||
stt.parsedFormat = {} |
stt.parsedFormat = {} |
||
stt.separator = {} |
stt.separator = {} |
||
stt.movSeparator = {} |
stt.movSeparator = {} |
||
stt.puncMark = {} |
stt.puncMark = {} |
||
stt.linked = false |
stt.linked = false |
||
stt.rawValue = false |
stt.rawValue = false |
||
| Line 191: | Line 221: | ||
stt.unitOnly = false |
stt.unitOnly = false |
||
stt.singleValue = false |
stt.singleValue = false |
||
return stt |
return stt |
||
end |
end |
||
function replaceAlias(id) |
local function replaceAlias(id) |
||
if |
if aliasesP[id] then |
||
id = |
id = aliasesP[id] |
||
end |
end |
||
return id |
return id |
||
end |
end |
||
function errorText(code, param) |
local function errorText(code, param) |
||
local text = i18n["errors"][code] |
local text = i18n["errors"][code] |
||
if param then text = mw.ustring.gsub(text, "$1", param) end |
if param then text = mw.ustring.gsub(text, "$1", param) end |
||
| Line 209: | Line 239: | ||
end |
end |
||
function throwError(errorMessage, param) |
local function throwError(errorMessage, param) |
||
error(errorText(errorMessage, param)) |
error(errorText(errorMessage, param)) |
||
end |
end |
||
function replaceDecimalMark(num) |
local function replaceDecimalMark(num) |
||
return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) |
return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) |
||
end |
end |
||
function padZeros(num, numDigits) |
local function padZeros(num, numDigits) |
||
local numZeros |
local numZeros |
||
local negative = false |
local negative = false |
||
if num < 0 then |
if num < 0 then |
||
negative = true |
negative = true |
||
num = num * -1 |
num = num * -1 |
||
end |
end |
||
num = tostring(num) |
num = tostring(num) |
||
numZeros = numDigits - num:len() |
numZeros = numDigits - num:len() |
||
for |
for _ = 1, numZeros do |
||
num = "0"..num |
num = "0"..num |
||
end |
end |
||
if negative then |
if negative then |
||
num = "-"..num |
num = "-"..num |
||
end |
end |
||
return num |
return num |
||
end |
end |
||
function replaceSpecialChar(chr) |
local function replaceSpecialChar(chr) |
||
if chr == '_' then |
if chr == '_' then |
||
-- replace underscores with spaces |
|||
return ' ' |
return ' ' |
||
else |
else |
||
| Line 249: | Line 278: | ||
end |
end |
||
function replaceSpecialChars(str) |
local function replaceSpecialChars(str) |
||
local chr |
local chr |
||
local esc = false |
local esc = false |
||
local strOut = "" |
local strOut = "" |
||
for i = 1, #str do |
for i = 1, #str do |
||
chr = str:sub(i,i) |
chr = str:sub(i,i) |
||
if not esc then |
if not esc then |
||
if chr == '\\' then |
if chr == '\\' then |
||
| Line 268: | Line 297: | ||
end |
end |
||
end |
end |
||
return strOut |
return strOut |
||
end |
end |
||
function buildWikilink(target, label) |
local function buildWikilink(target, label) |
||
if not label or target == label then |
if not label or target == label then |
||
return "[[" .. target .. "]]" |
return "[[" .. target .. "]]" |
||
| Line 280: | Line 309: | ||
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 |
|||
function copyTable(tIn) |
function copyTable(tIn) |
||
if not tIn then |
if not tIn then |
||
return nil |
return nil |
||
end |
end |
||
local tOut = {} |
local tOut = {} |
||
for i, v in pairs(tIn) do |
for i, v in pairs(tIn) do |
||
tOut[i] = v |
tOut[i] = v |
||
end |
end |
||
return tOut |
return tOut |
||
end |
end |
||
local function mergeArrays(a1, a2) |
|||
-- used to merge output arrays together; |
|||
-- note that it currently mutates the first input array |
|||
function mergeArrays(a1, a2) |
|||
for i = 1, #a2 do |
for i = 1, #a2 do |
||
a1[#a1 + 1] = a2[i] |
a1[#a1 + 1] = a2[i] |
||
end |
end |
||
return a1 |
return a1 |
||
end |
end |
||
function split(str, del) |
local function split(str, del) |
||
local out = {} |
local out = {} |
||
local i, j = str:find(del) |
local i, j = str:find(del) |
||
if i and j then |
if i and j then |
||
out[1] = str:sub(1, i - 1) |
out[1] = str:sub(1, i - 1) |
||
| Line 316: | Line 341: | ||
out[1] = str |
out[1] = str |
||
end |
end |
||
return out |
return out |
||
end |
end |
||
function |
local function parsePornbasedataURL(url) |
||
local id |
local id |
||
if url:match('^http[s]?://') then |
if url:match('^http[s]?://') then |
||
id = split(url, "Q") |
id = split(url, "Q") |
||
if id[2] then |
if id[2] then |
||
return "Q" .. id[2] |
return "Q" .. id[2] |
||
end |
end |
||
end |
end |
||
return nil |
return nil |
||
end |
end |
||
| Line 336: | Line 361: | ||
function parseDate(dateStr, precision) |
function parseDate(dateStr, precision) |
||
precision = precision or "d" |
precision = precision or "d" |
||
local i, j, index, ptr |
local i, j, index, ptr |
||
local parts = {nil, nil, nil} |
local parts = {nil, nil, nil} |
||
if dateStr == nil then |
if dateStr == nil then |
||
return parts[1], parts[2], parts[3] |
return parts[1], parts[2], parts[3] |
||
end |
end |
||
-- 'T' for snak values, '/' for outputs with '/Julian' attached |
|||
i, j = dateStr:find("[T/]") |
i, j = dateStr:find("[T/]") |
||
if i then |
if i then |
||
dateStr = dateStr:sub(1, i-1) |
dateStr = dateStr:sub(1, i-1) |
||
end |
end |
||
local from = 1 |
local from = 1 |
||
if dateStr:sub(1,1) == "-" then |
if dateStr:sub(1,1) == "-" then |
||
-- this is a negative number, look further ahead |
|||
from = 2 |
from = 2 |
||
end |
end |
||
index = 1 |
index = 1 |
||
ptr = 1 |
ptr = 1 |
||
i, j = dateStr:find("-", from) |
i, j = dateStr:find("-", from) |
||
if i then |
if i then |
||
parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) |
|||
-- year |
|||
parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) |
|||
if parts[index] == -0 then |
if parts[index] == -0 then |
||
parts[index] = tonumber("0") |
parts[index] = tonumber("0") |
||
end |
end |
||
if precision == "y" then |
if precision == "y" then |
||
return parts[1], parts[2], parts[3] |
|||
-- we're done |
|||
return parts[1], parts[2], parts[3] -- year, month, day |
|||
end |
end |
||
index = index + 1 |
index = index + 1 |
||
ptr = i + 1 |
ptr = i + 1 |
||
i, j = dateStr:find("-", ptr) |
i, j = dateStr:find("-", ptr) |
||
if i then |
if i then |
||
-- month |
|||
parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) |
parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) |
||
if precision == "m" then |
if precision == "m" then |
||
return parts[1], parts[2], parts[3] |
|||
-- we're done |
|||
return parts[1], parts[2], parts[3] -- year, month, day |
|||
end |
end |
||
index = index + 1 |
index = index + 1 |
||
ptr = i + 1 |
ptr = i + 1 |
||
end |
end |
||
end |
end |
||
if dateStr:sub(ptr) ~= "" then |
if dateStr:sub(ptr) ~= "" then |
||
-- day if we have month, month if we have year, or year |
|||
parts[index] = tonumber(dateStr:sub(ptr), 10) |
parts[index] = tonumber(dateStr:sub(ptr), 10) |
||
end |
end |
||
return parts[1], parts[2], parts[3] |
return parts[1], parts[2], parts[3] |
||
end |
end |
||
function datePrecedesDate(aY, aM, aD, bY, bM, bD) |
local function datePrecedesDate(aY, aM, aD, bY, bM, bD) |
||
if aY == nil or bY == nil then |
if aY == nil or bY == nil then |
||
return nil |
return nil |
||
| Line 411: | Line 429: | ||
bM = bM or 1 |
bM = bM or 1 |
||
bD = bD or 1 |
bD = bD or 1 |
||
if aY < bY then |
if aY < bY then |
||
return true |
return true |
||
end |
end |
||
if aY > bY then |
if aY > bY then |
||
return false |
return false |
||
end |
end |
||
if aM < bM then |
if aM < bM then |
||
return true |
return true |
||
end |
end |
||
if aM > bM then |
if aM > bM then |
||
return false |
return false |
||
end |
end |
||
if aD < bD then |
if aD < bD then |
||
return true |
return true |
||
end |
end |
||
return false |
return false |
||
end |
end |
||
function getHookName(param, index) |
local function getHookName(param, index) |
||
if hookNames[param] then |
if hookNames[param] then |
||
return hookNames[param][index] |
return hookNames[param][index] |
||
| Line 445: | Line 463: | ||
end |
end |
||
function alwaysTrue() |
local function alwaysTrue() |
||
return true |
return true |
||
end |
end |
||
local function parseFormat(str) |
|||
-- |
|||
-- 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" |
|||
-- } |
|||
-- ] |
|||
-- |
|||
function parseFormat(str) |
|||
local chr, esc, param, root, cur, prev, new |
local chr, esc, param, root, cur, prev, new |
||
local params = {} |
local params = {} |
||
local function newObject(array) |
local function newObject(array) |
||
local obj = {} |
local obj = {} |
||
obj.str = "" |
obj.str = "" |
||
array[#array + 1] = obj |
array[#array + 1] = obj |
||
obj.parent = array |
obj.parent = array |
||
return obj |
return obj |
||
end |
end |
||
local function endParam() |
local function endParam() |
||
if param > 0 then |
if param > 0 then |
||
| Line 521: | Line 494: | ||
end |
end |
||
end |
end |
||
root = {} -- array |
root = {} -- array |
||
root.req = {} |
root.req = {} |
||
cur = newObject(root) |
cur = newObject(root) |
||
prev = nil |
prev = nil |
||
esc = false |
esc = false |
||
param = 0 |
param = 0 |
||
for i = 1, #str do |
for i = 1, #str do |
||
chr = str:sub(i,i) |
chr = str:sub(i,i) |
||
if not esc then |
if not esc then |
||
if chr == '\\' then |
if chr == '\\' then |
||
| Line 570: | Line 543: | ||
end |
end |
||
end |
end |
||
cur.str = cur.str .. replaceSpecialChar(chr) |
cur.str = cur.str .. replaceSpecialChar(chr) |
||
end |
end |
||
| Line 577: | Line 550: | ||
esc = false |
esc = false |
||
end |
end |
||
prev = nil |
prev = nil |
||
end |
end |
||
endParam() |
endParam() |
||
-- make sure that at least one required parameter has been defined |
|||
if not next(root.req) then |
if not next(root.req) then |
||
throwError("missing-required-parameter") |
throwError("missing-required-parameter") |
||
end |
end |
||
-- make sure that the separator parameter "%s" is not amongst the required parameters |
|||
if root.req[parameters.separator] then |
if root.req[parameters.separator] then |
||
throwError("extra-required-parameter", parameters.separator) |
throwError("extra-required-parameter", parameters.separator) |
||
end |
end |
||
return root, params |
return root, params |
||
end |
end |
||
function sortOnRank(claims) |
local function sortOnRank(claims) |
||
local rankPos |
local rankPos |
||
local ranks = {{}, {}, {}, {}} |
local ranks = {{}, {}, {}, {}} |
||
local sorted = {} |
local sorted = {} |
||
for |
for _, v in ipairs(claims) do |
||
rankPos = rankTable[v.rank] or 4 |
rankPos = rankTable[v.rank] or 4 |
||
ranks[rankPos][#ranks[rankPos] + 1] = v |
ranks[rankPos][#ranks[rankPos] + 1] = v |
||
end |
end |
||
sorted = ranks[1] |
sorted = ranks[1] |
||
sorted = mergeArrays(sorted, ranks[2]) |
sorted = mergeArrays(sorted, ranks[2]) |
||
sorted = mergeArrays(sorted, ranks[3]) |
sorted = mergeArrays(sorted, ranks[3]) |
||
return sorted |
return sorted |
||
end |
end |
||
-- if id == nil then item connected to current page is used |
|||
function Config:getLabel(id, raw, link, short) |
function Config:getLabel(id, raw, link, short) |
||
local label = nil |
local label = nil |
||
local title = nil |
local title = nil |
||
local prefix= "" |
local prefix= "" |
||
local lang |
|||
if not id then |
if not id then |
||
id = mw.wikibase.getEntityIdForCurrentPage() |
id = mw.wikibase.getEntityIdForCurrentPage() |
||
if not id then |
if not id then |
||
return "" |
return "" |
||
end |
end |
||
end |
end |
||
id = id:upper() |
id = id:upper() |
||
if raw then |
if raw then |
||
if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then |
|||
-- check if given id actually exists |
|||
if mw.wikibase.getEntity(id) then |
|||
label = id |
label = id |
||
if id:sub(1,1) == "P" then |
if id:sub(1,1) == "P" then |
||
prefix = "Property:" |
prefix = "Property:" |
||
end |
end |
||
end |
end |
||
prefix = " |
prefix = "pbd:" .. prefix |
||
title = label |
title = label |
||
else |
else |
||
-- try short name first if requested |
|||
if short then |
if short then |
||
label = p._property{ |
label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name |
||
if label == "" then |
if label == "" then |
||
label = nil |
label = nil |
||
end |
end |
||
end |
end |
||
-- get label |
-- get label |
||
if not label then |
if not label then |
||
label |
label = mw.wikibase.getLabelByLang(id, self.langCode) |
||
-- don't allow language fallback |
|||
if lang ~= self.langCode then |
|||
label = nil |
|||
end |
|||
end |
end |
||
end |
end |
||
if not label then |
if not label then |
||
label = "" |
label = "" |
||
elseif link then |
elseif link then |
||
-- build a link if requested |
|||
if not title then |
if not title then |
||
if id:sub(1,1) == "Q" then |
if id:sub(1,1) == "Q" then |
||
title = mw.wikibase. |
title = mw.wikibase.getSitelink(id) |
||
if not title then |
|||
title = id |
|||
prefix = "pbd:Special:EntityPage/" |
|||
end |
|||
elseif id:sub(1,1) == "P" then |
elseif id:sub(1,1) == "P" then |
||
-- properties have no sitelink, link to Wikidata instead |
|||
title = id |
title = id |
||
prefix = " |
prefix = "pbd:Special:EntityPage/" |
||
end |
end |
||
end |
end |
||
label = mw.text.nowiki(label) |
|||
if title then |
if title then |
||
label = buildWikilink(prefix .. title, label) |
label = buildWikilink(prefix .. title, mw.text.nowiki(label)) |
||
end |
end |
||
end |
end |
||
return label |
return label |
||
end |
end |
||
| Line 689: | Line 653: | ||
local value = "" |
local value = "" |
||
local prefix = "" |
local prefix = "" |
||
local front = " |
local front = " " |
||
local back = "" |
local back = "" |
||
if self.entityID:sub(1,1) == "P" then |
if self.entityID:sub(1,1) == "P" then |
||
prefix = "Property:" |
prefix = "Property:" |
||
end |
end |
||
if self.editAtEnd then |
if self.editAtEnd then |
||
front = '<span style="float:' |
front = '<span style="float:' |
||
if self.langObj:isRTL() then |
if self.langObj:isRTL() then |
||
front = front .. 'left' |
front = front .. 'left' |
||
| Line 704: | Line 668: | ||
front = front .. 'right' |
front = front .. 'right' |
||
end |
end |
||
front = front .. '">' |
front = front .. '">' |
||
back = '</span>' |
back = '</span>' |
||
end |
end |
||
value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on- |
value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-pornbasedata'] .. "|link=https://pornbasedata.com/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode |
||
if self.propertyID then |
if self.propertyID then |
||
value = value .. "#" .. self.propertyID |
value = value .. "#" .. self.propertyID |
||
| Line 716: | Line 680: | ||
value = value .. "#sitelinks-pbc" |
value = value .. "#sitelinks-pbc" |
||
end |
end |
||
value = value .. "|" .. i18n['info']['edit-on- |
value = value .. "|" .. i18n['info']['edit-on-pornbasedata'] .. "]]" |
||
return front .. value .. back |
return front .. value .. back |
||
end |
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 |
|||
function Config:concatValues(valuesArray) |
function Config:concatValues(valuesArray) |
||
local outString = "" |
local outString = "" |
||
local j, skip |
local j, skip |
||
for i = 1, #valuesArray do |
for i = 1, #valuesArray do |
||
-- check if this is a reference |
|||
if valuesArray[i].refHash then |
if valuesArray[i].refHash then |
||
j = i - 1 |
j = i - 1 |
||
skip = false |
skip = false |
||
-- skip this reference if it is part of a continuous row of references that already contains the exact same reference |
|||
while valuesArray[j] and valuesArray[j].refHash do |
while valuesArray[j] and valuesArray[j].refHash do |
||
if valuesArray[i].refHash == valuesArray[j].refHash then |
if valuesArray[i].refHash == valuesArray[j].refHash then |
||
| Line 742: | Line 702: | ||
j = j - 1 |
j = j - 1 |
||
end |
end |
||
if not skip then |
if not skip then |
||
-- 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}) |
outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) |
||
end |
end |
||
| Line 751: | Line 710: | ||
end |
end |
||
end |
end |
||
return outString |
return outString |
||
end |
end |
||
| Line 758: | Line 717: | ||
local space = " " |
local space = " " |
||
local label = "" |
local label = "" |
||
local itemID |
|||
if unit == "" or unit == "1" then |
if unit == "" or unit == "1" then |
||
return nil |
return nil |
||
end |
end |
||
if unitOnly then |
if unitOnly then |
||
space = "" |
space = "" |
||
end |
end |
||
itemID = |
itemID = parsePornbasedataURL(unit) |
||
if itemID then |
if itemID then |
||
if itemID == aliasesQ.percentage then |
if itemID == aliasesQ.percentage then |
||
| Line 774: | Line 734: | ||
else |
else |
||
label = self:getLabel(itemID, raw, link, short) |
label = self:getLabel(itemID, raw, link, short) |
||
if label ~= "" then |
if label ~= "" then |
||
return space .. label |
return space .. label |
||
| Line 780: | Line 740: | ||
end |
end |
||
end |
end |
||
return "" |
return "" |
||
end |
end |
||
function |
function State:getValue(snak) |
||
return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, true, self.unitOnly, false, self.type:sub(1,2)) |
|||
end |
|||
function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) |
|||
if snak.snaktype == 'value' then |
if snak.snaktype == 'value' then |
||
local datatype = snak.datavalue.type |
local datatype = snak.datavalue.type |
||
local subtype = snak.datatype |
local subtype = snak.datatype |
||
local datavalue = snak.datavalue.value |
local datavalue = snak.datavalue.value |
||
if datatype == 'string' then |
if datatype == 'string' then |
||
if subtype == 'url' and link then |
if subtype == 'url' and link then |
||
-- create link explicitly |
|||
if raw then |
if raw then |
||
-- will render as a linked number like [1] |
|||
return "[" .. datavalue .. "]" |
return "[" .. datavalue .. "]" |
||
else |
else |
||
| Line 801: | Line 763: | ||
elseif subtype == 'commonsMedia' then |
elseif subtype == 'commonsMedia' then |
||
if link then |
if link then |
||
return buildWikilink(" |
return buildWikilink("pbc:File:" .. datavalue, datavalue) |
||
elseif not raw then |
elseif not raw then |
||
return "[[File:" .. datavalue .. "]]" |
return "[[File:" .. datavalue .. "]]" |
||
| Line 808: | Line 770: | ||
end |
end |
||
elseif subtype == 'geo-shape' and link then |
elseif subtype == 'geo-shape' and link then |
||
return buildWikilink(" |
return buildWikilink("pbc:" .. datavalue, datavalue) |
||
elseif subtype == 'math' and not raw then |
elseif subtype == 'math' and not raw then |
||
local attribute = nil |
|||
return mw.getCurrentFrame():extensionTag("math", datavalue) |
|||
if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then |
|||
attribute = {qid = self.entityID} |
|||
end |
|||
return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) |
|||
elseif subtype == 'external-id' and link then |
elseif subtype == 'external-id' and link then |
||
local url = p._property{ |
local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} |
||
if url ~= "" then |
if url ~= "" then |
||
url = mw.ustring.gsub(url, "$1", datavalue) |
url = mw.ustring.gsub(url, "$1", datavalue) |
||
| Line 824: | Line 792: | ||
end |
end |
||
elseif datatype == 'monolingualtext' then |
elseif datatype == 'monolingualtext' then |
||
if anyLang then |
if anyLang or datavalue['language'] == self.langCode then |
||
return datavalue['text'], datavalue['language'] |
|||
elseif datavalue['language'] == self.langCode then |
|||
return datavalue['text'] |
return datavalue['text'] |
||
else |
else |
||
| Line 834: | Line 800: | ||
local value = "" |
local value = "" |
||
local unit |
local unit |
||
if not unitOnly then |
if not unitOnly then |
||
-- get value and strip + signs from front |
|||
value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") |
value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") |
||
if raw then |
if raw then |
||
return value |
return value |
||
end |
end |
||
-- replace decimal mark based on locale |
|||
value = replaceDecimalMark(value) |
value = replaceDecimalMark(value) |
||
-- add delimiters for readability |
|||
value = i18n.addDelimiters(value) |
value = i18n.addDelimiters(value) |
||
end |
end |
||
unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) |
unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) |
||
if unit then |
if unit then |
||
value = value .. unit |
value = value .. unit |
||
end |
end |
||
return value |
return value |
||
elseif datatype == 'time' then |
elseif datatype == 'time' then |
||
| Line 866: | Line 829: | ||
local calendar = "" |
local calendar = "" |
||
local precision = datavalue['precision'] |
local precision = datavalue['precision'] |
||
if precision == 11 then |
if precision == 11 then |
||
p = "d" |
p = "d" |
||
| Line 875: | Line 838: | ||
yFactor = 10^(9-precision) |
yFactor = 10^(9-precision) |
||
end |
end |
||
y, m, d = parseDate(datavalue['time'], p) |
y, m, d = parseDate(datavalue['time'], p) |
||
if y < 0 then |
if y < 0 then |
||
sign = -1 |
sign = -1 |
||
y = y * sign |
y = y * sign |
||
end |
end |
||
-- if precision is tens/hundreds/thousands/millions/billions of years |
|||
if precision <= 8 then |
if precision <= 8 then |
||
yDiv = y / yFactor |
yDiv = y / yFactor |
||
-- if precision is tens/hundreds/thousands of years |
|||
if precision >= 6 then |
if precision >= 6 then |
||
mayAddCalendar = true |
mayAddCalendar = true |
||
if precision <= 7 then |
if precision <= 7 then |
||
-- round centuries/millenniums up (e.g. 20th century or 3rd millennium) |
|||
yRound = math.ceil(yDiv) |
yRound = math.ceil(yDiv) |
||
if not raw then |
if not raw then |
||
if precision == 6 then |
if precision == 6 then |
||
| Line 901: | Line 861: | ||
suffix = i18n['datetime']['suffixes']['century'] |
suffix = i18n['datetime']['suffixes']['century'] |
||
end |
end |
||
suffix = i18n.getOrdinalSuffix(yRound) .. suffix |
suffix = i18n.getOrdinalSuffix(yRound) .. suffix |
||
else |
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 |
yRound = (yRound - 1) * yFactor + 1 |
||
end |
end |
||
else |
else |
||
-- precision == 8 |
|||
-- round decades down (e.g. 2010s) |
|||
yRound = math.floor(yDiv) * yFactor |
yRound = math.floor(yDiv) * yFactor |
||
if not raw then |
if not raw then |
||
prefix = i18n['datetime']['prefixes']['decade-period'] |
prefix = i18n['datetime']['prefixes']['decade-period'] |
||
| Line 918: | Line 874: | ||
end |
end |
||
end |
end |
||
if raw and sign < 0 then |
if raw and sign < 0 then |
||
-- 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 |
yRound = yRound + yFactor - 1 |
||
end |
end |
||
else |
else |
||
local yReFactor, yReDiv, yReRound |
local yReFactor, yReDiv, yReRound |
||
-- round to nearest for tens of thousands of years or more |
|||
yRound = math.floor(yDiv + 0.5) |
yRound = math.floor(yDiv + 0.5) |
||
if yRound == 0 then |
if yRound == 0 then |
||
if precision <= 2 and y ~= 0 then |
if precision <= 2 and y ~= 0 then |
||
| Line 935: | Line 888: | ||
yReDiv = y / yReFactor |
yReDiv = y / yReFactor |
||
yReRound = math.floor(yReDiv + 0.5) |
yReRound = math.floor(yReDiv + 0.5) |
||
if yReDiv == yReRound then |
if yReDiv == yReRound then |
||
-- change precision to millions of years only if we have a whole number of them |
|||
precision = 3 |
precision = 3 |
||
yFactor = yReFactor |
yFactor = yReFactor |
||
| Line 943: | Line 895: | ||
end |
end |
||
end |
end |
||
if yRound == 0 then |
if yRound == 0 then |
||
-- otherwise, take the unrounded (original) number of years |
|||
precision = 5 |
precision = 5 |
||
yFactor = 1 |
yFactor = 1 |
||
| Line 952: | Line 903: | ||
end |
end |
||
end |
end |
||
if precision >= 1 and y ~= 0 then |
if precision >= 1 and y ~= 0 then |
||
yFull = yRound * yFactor |
yFull = yRound * yFactor |
||
yReFactor = 1e9 |
yReFactor = 1e9 |
||
yReDiv = yFull / yReFactor |
yReDiv = yFull / yReFactor |
||
yReRound = math.floor(yReDiv + 0.5) |
yReRound = math.floor(yReDiv + 0.5) |
||
if yReDiv == yReRound then |
if yReDiv == yReRound then |
||
-- change precision to billions of years if we're in that range |
|||
precision = 0 |
precision = 0 |
||
yFactor = yReFactor |
yFactor = yReFactor |
||
| Line 969: | Line 919: | ||
yReDiv = yFull / yReFactor |
yReDiv = yFull / yReFactor |
||
yReRound = math.floor(yReDiv + 0.5) |
yReRound = math.floor(yReDiv + 0.5) |
||
if yReDiv == yReRound then |
if yReDiv == yReRound then |
||
-- change precision to millions of years if we're in that range |
|||
precision = 3 |
precision = 3 |
||
yFactor = yReFactor |
yFactor = yReFactor |
||
| Line 978: | Line 927: | ||
end |
end |
||
end |
end |
||
if not raw then |
if not raw then |
||
if precision == 3 then |
if precision == 3 then |
||
| Line 1,000: | Line 949: | ||
mayAddCalendar = true |
mayAddCalendar = true |
||
end |
end |
||
if mayAddCalendar then |
if mayAddCalendar then |
||
calendarID = |
calendarID = parsePornbasedataURL(datavalue['calendarmodel']) |
||
if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then |
if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then |
||
if not raw then |
if not raw then |
||
| Line 1,016: | Line 965: | ||
end |
end |
||
end |
end |
||
if not raw then |
if not raw then |
||
local ce = nil |
local ce = nil |
||
if sign < 0 then |
if sign < 0 then |
||
ce = i18n['datetime']['BCE'] |
ce = i18n['datetime']['BCE'] |
||
| Line 1,025: | Line 974: | ||
ce = i18n['datetime']['CE'] |
ce = i18n['datetime']['CE'] |
||
end |
end |
||
if ce then |
if ce then |
||
if link then |
if link then |
||
| Line 1,032: | Line 981: | ||
suffix = suffix .. " " .. ce |
suffix = suffix .. " " .. ce |
||
end |
end |
||
value = tostring(yRound) |
value = tostring(yRound) |
||
if m then |
if m then |
||
dateStr = self.langObj:formatDate("F", "1-"..m.."-1") |
dateStr = self.langObj:formatDate("F", "1-"..m.."-1") |
||
if d then |
if d then |
||
if self.mdyDate then |
if self.mdyDate then |
||
| Line 1,045: | Line 994: | ||
end |
end |
||
end |
end |
||
value = dateStr .. " " .. value |
value = dateStr .. " " .. value |
||
end |
end |
||
value = prefix .. value .. suffix .. calendar |
value = prefix .. value .. suffix .. calendar |
||
else |
else |
||
value = padZeros(yRound * sign, 4) |
value = padZeros(yRound * sign, 4) |
||
if m then |
if m then |
||
value = value .. "-" .. padZeros(m, 2) |
value = value .. "-" .. padZeros(m, 2) |
||
if d then |
if d then |
||
value = value .. "-" .. padZeros(d, 2) |
value = value .. "-" .. padZeros(d, 2) |
||
end |
end |
||
end |
end |
||
value = value .. calendar |
value = value .. calendar |
||
end |
end |
||
return value |
|||
elseif datatype == 'globecoordinate' then |
|||
-- logic from https://github.com/DataValues/Geo (v4.0.1) |
|||
local precision, unitsPerDegree, numDigits, strFormat, value, globe |
|||
local latitude, latConv, latValue, latLink |
|||
local longitude, lonConv, lonValue, lonLink |
|||
local latDirection, latDirectionN, latDirectionS, latDirectionEN |
|||
local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN |
|||
local latDegrees = nil |
|||
local latMinutes = nil |
|||
local latSeconds = nil |
|||
local lonDegrees = nil |
|||
local lonMinutes = nil |
|||
local lonSeconds = nil |
|||
local latDegSym = "" |
|||
local latMinSym = "" |
|||
local latSecSym = "" |
|||
local lonDegSym = "" |
|||
local lonMinSym = "" |
|||
local lonSecSym = "" |
|||
local latDirectionEN_N = "N" |
|||
local latDirectionEN_S = "S" |
|||
local lonDirectionEN_E = "E" |
|||
local lonDirectionEN_W = "W" |
|||
if not raw then |
|||
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'] |
|||
if latitude < 0 then |
|||
latDirection = latDirectionS |
|||
latDirectionEN = latDirectionEN_S |
|||
latitude = math.abs(latitude) |
|||
else |
|||
latDirection = latDirectionN |
|||
latDirectionEN = latDirectionEN_N |
|||
end |
|||
if longitude < 0 then |
|||
lonDirection = lonDirectionW |
|||
lonDirectionEN = lonDirectionEN_W |
|||
longitude = math.abs(longitude) |
|||
else |
|||
lonDirection = lonDirectionE |
|||
lonDirectionEN = lonDirectionEN_E |
|||
end |
|||
precision = datavalue['precision'] |
|||
if not precision or precision <= 0 then |
|||
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 |
|||
if precision >= 1 - (1 / 60) and precision < 1 then |
|||
precision = 1 |
|||
elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then |
|||
precision = 1 / 60 |
|||
end |
|||
if precision >= 1 then |
|||
unitsPerDegree = 1 |
|||
elseif precision >= (1 / 60) then |
|||
unitsPerDegree = 60 |
|||
else |
|||
unitsPerDegree = 3600 |
|||
end |
|||
numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) |
|||
if numDigits <= 0 then |
|||
numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead |
|||
end |
|||
strFormat = "%." .. numDigits .. "f" |
|||
if precision >= 1 then |
|||
latDegrees = strFormat:format(latitude) |
|||
lonDegrees = strFormat:format(longitude) |
|||
if not raw then |
|||
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 |
|||
if precision >= (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)) |
|||
if not raw then |
|||
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) |
|||
if precision >= (1 / 60) then |
|||
latMinutes = strFormat:format(latMinutes) |
|||
lonMinutes = strFormat:format(lonMinutes) |
|||
if not raw then |
|||
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 |
|||
if link then |
|||
globe = parseWikidataURL(datavalue['globe']) |
|||
if globe then |
|||
globe = mw.wikibase.getEntity(globe):getLabel("en"):lower() |
|||
else |
|||
globe = "earth" |
|||
end |
|||
latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") |
|||
lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") |
|||
value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."¶ms="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" |
|||
end |
|||
return value |
return value |
||
elseif datatype == 'wikibase-entityid' then |
elseif datatype == 'wikibase-entityid' then |
||
local label |
local label |
||
local itemID = datavalue['numeric-id'] |
local itemID = datavalue['numeric-id'] |
||
if subtype == 'wikibase-item' then |
if subtype == 'wikibase-item' then |
||
itemID = "Q" .. itemID |
itemID = "Q" .. itemID |
||
| Line 1,263: | Line 1,025: | ||
return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' |
return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' |
||
end |
end |
||
label = self:getLabel(itemID, raw, link, short) |
label = self:getLabel(itemID, raw, link, short) |
||
if label == "" then |
if label == "" then |
||
label = nil |
label = nil |
||
end |
end |
||
return label |
return label |
||
else |
else |
||
| Line 1,276: | Line 1,038: | ||
elseif snak.snaktype == 'somevalue' and not noSpecial then |
elseif snak.snaktype == 'somevalue' and not noSpecial then |
||
if raw then |
if raw then |
||
return " " |
return " " |
||
else |
else |
||
return i18n['values']['unknown'] |
return i18n['values']['unknown'] |
||
| Line 1,282: | Line 1,044: | ||
elseif snak.snaktype == 'novalue' and not noSpecial then |
elseif snak.snaktype == 'novalue' and not noSpecial then |
||
if raw then |
if raw then |
||
return "" |
return "" |
||
else |
else |
||
return i18n['values']['none'] |
return i18n['values']['none'] |
||
| Line 1,293: | Line 1,055: | ||
function Config:getSingleRawQualifier(claim, qualifierID) |
function Config:getSingleRawQualifier(claim, qualifierID) |
||
local qualifiers |
local qualifiers |
||
if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end |
if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end |
||
if qualifiers and qualifiers[1] then |
if qualifiers and qualifiers[1] then |
||
return self:getValue(qualifiers[1], true) |
return self:getValue(qualifiers[1], true) |
||
else |
else |
||
return nil |
return nil |
||
| Line 1,304: | Line 1,066: | ||
function Config:snakEqualsValue(snak, value) |
function Config:snakEqualsValue(snak, value) |
||
local snakValue = self:getValue(snak, true) |
local snakValue = self:getValue(snak, true) |
||
if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end |
if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end |
||
return snakValue == value |
return snakValue == value |
||
end |
end |
||
| Line 1,313: | Line 1,075: | ||
function Config:setRank(rank) |
function Config:setRank(rank) |
||
local rankPos |
local rankPos |
||
if rank == p.flags.best then |
if rank == p.flags.best then |
||
self.bestRank = true |
self.bestRank = true |
||
self.flagBest = true |
self.flagBest = true |
||
return |
return |
||
end |
end |
||
if rank:sub(1,9) == p.flags.preferred then |
if rank:sub(1,9) == p.flags.preferred then |
||
rankPos = 1 |
rankPos = 1 |
||
| Line 1,329: | Line 1,091: | ||
return |
return |
||
end |
end |
||
-- one of the rank flags was given, check if another one was given before |
|||
if not self.flagRank then |
if not self.flagRank then |
||
self.ranks = {false, false, false} |
self.ranks = {false, false, false} |
||
self.bestRank = self.flagBest |
self.bestRank = self.flagBest |
||
self.flagRank = true |
self.flagRank = true |
||
end |
end |
||
if rank:sub(-1) == "+" then |
if rank:sub(-1) == "+" then |
||
for i = rankPos, 1, -1 do |
for i = rankPos, 1, -1 do |
||
| Line 1,352: | Line 1,113: | ||
function Config:setPeriod(period) |
function Config:setPeriod(period) |
||
local periodPos |
local periodPos |
||
if period == p.flags.future then |
if period == p.flags.future then |
||
periodPos = 1 |
periodPos = 1 |
||
| Line 1,362: | Line 1,123: | ||
return |
return |
||
end |
end |
||
-- one of the period flags was given, check if another one was given before |
|||
if not self.flagPeriod then |
if not self.flagPeriod then |
||
self.periods = {false, false, false} |
self.periods = {false, false, false} |
||
self.flagPeriod = true |
self.flagPeriod = true |
||
end |
end |
||
self.periods[periodPos] = true |
self.periods[periodPos] = true |
||
end |
end |
||
| Line 1,374: | Line 1,134: | ||
function Config:qualifierMatches(claim, id, value) |
function Config:qualifierMatches(claim, id, value) |
||
local qualifiers |
local qualifiers |
||
if claim.qualifiers then qualifiers = claim.qualifiers[id] end |
if claim.qualifiers then qualifiers = claim.qualifiers[id] end |
||
if qualifiers then |
if qualifiers then |
||
for |
for _, v in pairs(qualifiers) do |
||
if self:snakEqualsValue(v, value) then |
if self:snakEqualsValue(v, value) then |
||
return true |
return true |
||
| Line 1,383: | Line 1,143: | ||
end |
end |
||
elseif value == "" then |
elseif value == "" then |
||
-- if the qualifier is not present then treat it the same as the special value 'novalue' |
|||
return true |
return true |
||
end |
end |
||
return false |
return false |
||
end |
end |
||
| Line 1,407: | Line 1,166: | ||
local endTimeM = nil |
local endTimeM = nil |
||
local endTimeD = nil |
local endTimeD = nil |
||
if self.periods[1] and self.periods[2] and self.periods[3] then |
if self.periods[1] and self.periods[2] and self.periods[3] then |
||
-- any time |
-- any time |
||
return true |
return true |
||
end |
end |
||
startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) |
|||
local now = os.date('!*t') |
|||
startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) |
|||
if startTime and startTime ~= "" and startTime ~= " " then |
if startTime and startTime ~= "" and startTime ~= " " then |
||
startTimeY, startTimeM, startTimeD = parseDate(startTime) |
startTimeY, startTimeM, startTimeD = parseDate(startTime) |
||
end |
end |
||
endTime = self:getSingleRawQualifier(claim, |
endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) |
||
if endTime and endTime ~= "" and endTime ~= " " then |
if endTime and endTime ~= "" and endTime ~= " " then |
||
endTimeY, endTimeM, endTimeD = parseDate(endTime) |
endTimeY, endTimeM, endTimeD = parseDate(endTime) |
||
elseif endTime == " " then |
|||
-- end time is 'unknown', assume it is somewhere in the past; |
|||
-- we can do this by taking the current date as a placeholder for the end time |
|||
endTimeY = now['year'] |
|||
endTimeM = now['month'] |
|||
endTimeD = now['day'] |
|||
end |
end |
||
if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then |
if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then |
||
-- invalidate end time if it precedes start time |
|||
endTimeY = nil |
endTimeY = nil |
||
endTimeM = nil |
endTimeM = nil |
||
endTimeD = nil |
endTimeD = nil |
||
end |
end |
||
if self.periods[1] then |
if self.periods[1] then |
||
if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then |
|||
-- future |
|||
if startTimeY and datePrecedesDate(now['year'], now['month'], now['day'], startTimeY, startTimeM, startTimeD) then |
|||
return true |
return true |
||
end |
end |
||
end |
end |
||
if self.periods[2] then |
if self.periods[2] then |
||
if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and |
|||
-- current |
|||
(endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then |
|||
(endTimeY == nil or datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD)) then |
|||
return true |
return true |
||
end |
end |
||
end |
end |
||
if self.periods[3] then |
if self.periods[3] then |
||
if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then |
|||
-- former |
|||
if endTimeY and not datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD) then |
|||
return true |
return true |
||
end |
end |
||
end |
end |
||
return false |
return false |
||
end |
end |
||
| Line 1,467: | Line 1,214: | ||
return false |
return false |
||
end |
end |
||
if flag == p.flags.linked then |
if flag == p.flags.linked then |
||
self.curState.linked = true |
self.curState.linked = true |
||
| Line 1,473: | Line 1,220: | ||
elseif flag == p.flags.raw then |
elseif flag == p.flags.raw then |
||
self.curState.rawValue = true |
self.curState.rawValue = true |
||
if self.curState == self.states[parameters.reference] then |
if self.curState == self.states[parameters.reference] then |
||
-- raw reference values end with periods and require a separator (other than none) |
|||
self.separators["sep%r"][1] = {" "} |
self.separators["sep%r"][1] = {" "} |
||
end |
end |
||
return true |
return true |
||
elseif flag == p.flags.short then |
elseif flag == p.flags.short then |
||
| Line 1,512: | Line 1,258: | ||
return true |
return true |
||
elseif flag == "" then |
elseif flag == "" then |
||
-- ignore empty flags and carry on |
|||
return true |
return true |
||
else |
else |
||
| Line 1,521: | Line 1,266: | ||
function Config:processFlagOrCommand(flag) |
function Config:processFlagOrCommand(flag) |
||
local param = "" |
local param = "" |
||
if not flag then |
if not flag then |
||
return false |
return false |
||
end |
end |
||
if flag == p.claimCommands.property or flag == p.claimCommands.properties then |
if flag == p.claimCommands.property or flag == p.claimCommands.properties then |
||
param = parameters.property |
param = parameters.property |
||
| Line 1,537: | Line 1,282: | ||
return self:processFlag(flag) |
return self:processFlag(flag) |
||
end |
end |
||
if self.states[param] then |
if self.states[param] then |
||
return false |
return false |
||
end |
end |
||
self.states[param] = State:new(self, param) |
|||
-- create a new state for each command |
|||
self.states[param] = State.new(self) |
|||
self.states[param].parsedFormat = parseFormat(parameters.general) |
|||
-- use "%x" as the general parameter name |
|||
self.states[param]. |
self.states[param].separator = self.separators["sep"..param] |
||
if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then |
|||
-- set the separator |
|||
self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately |
|||
if flag:sub(-1) ~= 's' then |
|||
self.states[param].singleValue = true |
self.states[param].singleValue = true |
||
end |
end |
||
self.curState = self.states[param] |
self.curState = self.states[param] |
||
return true |
return true |
||
end |
end |
||
| Line 1,562: | Line 1,304: | ||
function Config:processSeparators(args) |
function Config:processSeparators(args) |
||
local sep |
local sep |
||
for i, v in pairs(self.separators) do |
for i, v in pairs(self.separators) do |
||
if args[i] then |
if args[i] then |
||
sep = replaceSpecialChars(args[i]) |
sep = replaceSpecialChars(args[i]) |
||
if sep ~= "" then |
if sep ~= "" then |
||
self.separators[i][1] = {sep} |
self.separators[i][1] = {sep} |
||
| Line 1,583: | Line 1,325: | ||
end |
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) |
function State:isSourced(claim) |
||
self.conf.prefetchedRefs = self:getReferences(claim) |
self.conf.prefetchedRefs = self:getReferences(claim) |
||
| Line 1,592: | Line 1,331: | ||
function State:resetCaches() |
function State:resetCaches() |
||
-- any prefetched references of the previous claim must not be used |
|||
self.conf.prefetchedRefs = nil |
self.conf.prefetchedRefs = nil |
||
end |
end |
||
| Line 1,598: | Line 1,336: | ||
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() |
self:resetCaches() |
||
-- if a property value was given, check if it matches the claim's property value |
|||
if self.conf.propertyValue then |
if self.conf.propertyValue then |
||
matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) |
matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) |
||
| Line 1,608: | Line 1,344: | ||
matches = true |
matches = true |
||
end |
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 |
for i, v in pairs(self.conf.qualifierIDsAndValues) do |
||
matches = (matches and self.conf:qualifierMatches(claim, i, v)) |
matches = (matches and self.conf:qualifierMatches(claim, i, v)) |
||
end |
end |
||
-- check if the claim's rank and time period match |
|||
rankPos = rankTable[claim.rank] or 4 |
rankPos = rankTable[claim.rank] or 4 |
||
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 |
if self.conf.sourcedOnly then |
||
matches = (matches and self:isSourced(claim)) |
matches = (matches and self:isSourced(claim)) |
||
end |
end |
||
return matches, rankPos |
return matches, rankPos |
||
end |
end |
||
function State:out() |
function State:out() |
||
local result |
local result |
||
local valuesArray |
local valuesArray |
||
local sep = nil |
local sep = nil |
||
local out = {} |
local out = {} |
||
local function walk(formatTable, result) |
local function walk(formatTable, result) |
||
local valuesArray = {} |
local valuesArray = {} |
||
for i, v in pairs(formatTable.req) do |
for i, v in pairs(formatTable.req) do |
||
if not result[i] or not result[i][1] then |
if not result[i] or not result[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 {} |
return {} |
||
end |
end |
||
end |
end |
||
for |
for _, v in ipairs(formatTable) do |
||
if v.param then |
if v.param then |
||
valuesArray = mergeArrays(valuesArray, result[v.str]) |
valuesArray = mergeArrays(valuesArray, result[v.str]) |
||
| Line 1,649: | Line 1,380: | ||
valuesArray[#valuesArray + 1] = {v.str} |
valuesArray[#valuesArray + 1] = {v.str} |
||
end |
end |
||
if v.child then |
if v.child then |
||
valuesArray = mergeArrays(valuesArray, walk(v.child, result)) |
valuesArray = mergeArrays(valuesArray, walk(v.child, result)) |
||
end |
end |
||
end |
end |
||
return valuesArray |
return valuesArray |
||
end |
end |
||
-- iterate through the results from back to front, so that we know when to add separators |
|||
for i = #self.results, 1, -1 do |
for i = #self.results, 1, -1 do |
||
result = self.results[i] |
result = self.results[i] |
||
-- if there is already some output, then add the separators |
|||
if #out > 0 then |
if #out > 0 then |
||
sep = self.separator[1] |
sep = self.separator[1] |
||
result[parameters.separator] = {self.movSeparator[1]} |
result[parameters.separator] = {self.movSeparator[1]} |
||
else |
else |
||
sep = nil |
sep = nil |
||
result[parameters.separator] = {self.puncMark[1]} |
result[parameters.separator] = {self.puncMark[1]} |
||
end |
end |
||
valuesArray = walk(self.parsedFormat, result) |
valuesArray = walk(self.parsedFormat, result) |
||
if #valuesArray > 0 then |
if #valuesArray > 0 then |
||
if sep then |
if sep then |
||
valuesArray[#valuesArray + 1] = sep |
valuesArray[#valuesArray + 1] = sep |
||
end |
end |
||
out = mergeArrays(valuesArray, out) |
out = mergeArrays(valuesArray, out) |
||
end |
end |
||
end |
end |
||
-- reset state before next iteration |
|||
self.results = {} |
self.results = {} |
||
return out |
return out |
||
end |
end |
||
-- level 1 hook |
|||
function State:getProperty(claim) |
function State:getProperty(claim) |
||
local value = {self |
local value = {self:getValue(claim.mainsnak)} |
||
if #value > 0 then |
if #value > 0 then |
||
return {value} |
return {value} |
||
else |
else |
||
return {} |
|||
return {} -- return empty array if there was no value |
|||
end |
end |
||
end |
end |
||
-- level 1 hook |
|||
function State:getQualifiers(claim, param) |
function State:getQualifiers(claim, param) |
||
local qualifiers |
local qualifiers |
||
if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end |
if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end |
||
if qualifiers then |
if qualifiers then |
||
return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) |
|||
-- iterate through claim's qualifier statements to collect their values; |
|||
-- return array with multiple value objects |
|||
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 {} |
||
end |
end |
||
end |
end |
||
-- level 2 hook |
|||
function State:getQualifier(snak) |
function State:getQualifier(snak) |
||
local value = {self |
local value = {self:getValue(snak)} |
||
if #value > 0 then |
if #value > 0 then |
||
return {value} |
return {value} |
||
else |
else |
||
return {} |
|||
return {} -- return empty array if there was no value |
|||
end |
end |
||
end |
end |
||
-- level 1 hook |
|||
function State:getAllQualifiers(claim, param, result, hooks) |
function State:getAllQualifiers(claim, param, result, hooks) |
||
local out = {} |
local out = {} |
||
local sep = self.conf.separators["sep"..parameters.qualifier][1] |
local sep = self.conf.separators["sep"..parameters.qualifier][1] |
||
-- iterate through the output of the separate "qualifier(s)" commands |
|||
for i = 1, self.conf.states.qualifiersCount do |
for i = 1, self.conf.states.qualifiersCount do |
||
-- if a hook has not been called yet, call it now |
|||
if not result[parameters.qualifier..i] then |
if not result[parameters.qualifier..i] then |
||
self:callHook(parameters.qualifier..i, hooks, claim, result) |
self:callHook(parameters.qualifier..i, hooks, claim, result) |
||
end |
end |
||
-- if there is output for this particular "qualifier(s)" command, then add it |
|||
if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then |
if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then |
||
-- if there is already some output, then add the separator |
|||
if #out > 0 and sep then |
if #out > 0 and sep then |
||
out[#out + 1] = sep |
out[#out + 1] = sep |
||
end |
end |
||
out = mergeArrays(out, result[parameters.qualifier..i]) |
out = mergeArrays(out, result[parameters.qualifier..i]) |
||
end |
end |
||
end |
end |
||
return out |
return out |
||
end |
end |
||
-- level 1 hook |
|||
function State:getReferences(claim) |
function State:getReferences(claim) |
||
if self.conf.prefetchedRefs then |
if self.conf.prefetchedRefs then |
||
-- return references that have been prefetched by isSourced |
|||
return self.conf.prefetchedRefs |
return self.conf.prefetchedRefs |
||
end |
end |
||
if claim.references then |
if claim.references then |
||
return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) |
|||
-- iterate through claim's reference statements to collect their values; |
|||
-- 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 state with level 2 hook |
|||
else |
else |
||
return {} |
return {} |
||
end |
end |
||
end |
end |
||
-- level 2 hook |
|||
function State:getReference(statement) |
function State:getReference(statement) |
||
local key, citeWeb, citeQ, label |
local key, citeWeb, citeQ, label |
||
| Line 1,778: | Line 1,491: | ||
local value = "" |
local value = "" |
||
local ref = {} |
local ref = {} |
||
local version = 1 |
|||
local version = 1 -- increase this each time the below logic is changed to avoid conflict errors |
|||
if statement.snaks then |
if statement.snaks then |
||
if statement.snaks[aliasesP.importedFrom] then |
|||
-- don't include "imported from", which is added by a bot |
|||
statement.snaks[aliasesP.importedFrom] = nil |
|||
statement.snaks[p.aliasesP.importedFrom] = nil |
|||
end |
end |
||
if statement.snaks[aliasesP.inferredFrom] then |
|||
-- don't include "inferred from", which is added by a bot |
|||
statement.snaks[aliasesP.inferredFrom] = nil |
|||
statement.snaks[p.aliasesP.inferredFrom] = nil |
|||
end |
end |
||
if statement.snaks[aliasesP.typeOfReference] then |
|||
-- don't include "type of reference" |
|||
statement.snaks[aliasesP.typeOfReference] = nil |
|||
statement.snaks[p.aliasesP.typeOfReference] = nil |
|||
end |
end |
||
if statement.snaks[aliasesP.image] then |
|||
-- don't include "image" to prevent littering |
|||
statement.snaks[aliasesP.image] = nil |
|||
statement.snaks[p.aliasesP.image] = nil |
|||
end |
end |
||
if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then |
|||
-- don't include "language" if it is equal to the local one |
|||
statement.snaks[aliasesP.language] = nil |
|||
statement.snaks[p.aliasesP.language] = nil |
|||
end |
end |
||
-- retrieve all the parameters |
|||
for i in pairs(statement.snaks) do |
for i in pairs(statement.snaks) do |
||
label = "" |
label = "" |
||
if i == aliasesP.author then |
|||
-- multiple authors may be given |
|||
params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) |
|||
if i == p.aliasesP.author then |
|||
params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true |
|||
else |
else |
||
params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == |
params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} |
||
end |
end |
||
if #params[i] == 0 then |
if #params[i] == 0 then |
||
params[i] = nil |
params[i] = nil |
||
| Line 1,824: | Line 1,530: | ||
key = "external-id" |
key = "external-id" |
||
label = self.conf:getLabel(i) |
label = self.conf:getLabel(i) |
||
if label ~= "" then |
if label ~= "" then |
||
label = label .. " " |
label = label .. " " |
||
| Line 1,831: | Line 1,537: | ||
key = i |
key = i |
||
end |
end |
||
-- add the parameter to each matching type of citation |
|||
for j in pairs(citeParams) do |
for j in pairs(citeParams) do |
||
-- do so if there was no mismatch with a previous parameter |
|||
if not citeMismatch[j] then |
if not citeMismatch[j] then |
||
-- check if this parameter is not mismatching itself |
|||
if i18n['cite'][j][key] then |
if i18n['cite'][j][key] then |
||
-- continue if an option is available in the corresponding cite template |
|||
if i18n['cite'][j][key] ~= "" then |
if i18n['cite'][j][key] ~= "" then |
||
citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] |
citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] |
||
-- if there are multiple parameter values (authors), add those too |
|||
for k=2, #params[i] do |
for k=2, #params[i] do |
||
citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] |
citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] |
||
| Line 1,854: | Line 1,555: | ||
end |
end |
||
end |
end |
||
citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] |
|||
-- get title of general template for citing web references |
|||
citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front |
|||
citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] |
|||
-- get title of template that expands stated-in references into citations |
|||
if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then |
|||
citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front |
|||
-- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present |
|||
if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then |
|||
useCite = citeWeb |
useCite = citeWeb |
||
useParams = citeParams['web'] |
useParams = citeParams['web'] |
||
elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then |
|||
-- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present |
|||
citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) |
|||
-- we need the raw "stated in" Q-identifier for the this template |
|||
citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true |
|||
useCite = citeQ |
useCite = citeQ |
||
useParams = citeParams['q'] |
useParams = citeParams['q'] |
||
end |
end |
||
if useCite and useParams then |
if useCite and useParams then |
||
-- 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(useParams) do |
for i, v in pairs(useParams) do |
||
value = value .. "|" .. i .. "=" .. v |
value = value .. "|" .. i .. "=" .. v |
||
end |
end |
||
value = "{{" .. useCite .. value .. "}}" |
value = "{{" .. useCite .. value .. "}}" |
||
else |
else |
||
value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} |
value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} |
||
end |
end |
||
elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then |
|||
-- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present |
|||
elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then |
|||
citeParams['default'] = {} |
citeParams['default'] = {} |
||
if params[aliasesP.author] and #params[aliasesP.author] > 0 then |
|||
-- start by adding authors up front |
|||
citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") |
|||
citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") |
|||
end |
end |
||
if params[aliasesP.referenceURL] and params[aliasesP.title] then |
|||
-- combine "reference URL" and "title" into one link if both are present |
|||
citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' |
|||
elseif params[aliasesP.referenceURL] then |
|||
citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] |
|||
elseif params[aliasesP.title] then |
|||
citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' |
|||
citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' |
|||
end |
end |
||
if params[aliasesP.statedIn] then |
|||
-- then add "stated in" |
|||
citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" |
|||
citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" |
|||
end |
end |
||
params[aliasesP.author] = nil |
|||
-- remove previously added parameters so that they won't be added a second time |
|||
params[ |
params[aliasesP.referenceURL] = nil |
||
params[ |
params[aliasesP.title] = nil |
||
params[ |
params[aliasesP.statedIn] = nil |
||
params[p.aliasesP.statedIn] = nil |
|||
-- add the rest of the parameters |
|||
for i, v in pairs(params) do |
for i, v in pairs(params) do |
||
i = self.conf:getLabel(i) |
i = self.conf:getLabel(i) |
||
if i ~= "" then |
if i ~= "" then |
||
citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] |
citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] |
||
end |
end |
||
end |
end |
||
value = table.concat(citeParams['default'], "; ") |
value = table.concat(citeParams['default'], "; ") |
||
if value ~= "" then |
if value ~= "" then |
||
value = value .. "." |
value = value .. "." |
||
end |
end |
||
end |
end |
||
if value ~= "" then |
if value ~= "" then |
||
value = {value} |
value = {value} |
||
if not self.rawValue then |
if not self.rawValue then |
||
value.refHash = "pornbasedata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) |
|||
-- this should become a <ref> tag, so save the reference's hash for later |
|||
value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) |
|||
end |
end |
||
ref = {value} |
ref = {value} |
||
end |
end |
||
end |
end |
||
return ref |
return ref |
||
end |
end |
||
-- gets a detail of one particular type for a reference |
|||
function State:getReferenceDetail(snaks, dType, raw, link, anyLang) |
function State:getReferenceDetail(snaks, dType, raw, link, anyLang) |
||
local switchLang = anyLang |
local switchLang = anyLang |
||
local value = nil |
local value = nil |
||
if not snaks[dType] then |
if not snaks[dType] then |
||
return nil |
return nil |
||
end |
end |
||
-- if anyLang, first try the local language and otherwise any language |
|||
repeat |
repeat |
||
for |
for _, v in ipairs(snaks[dType]) do |
||
value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true |
value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true |
||
if value then |
if value then |
||
break |
break |
||
end |
end |
||
end |
end |
||
if value or not anyLang then |
if value or not anyLang then |
||
break |
break |
||
end |
end |
||
switchLang = not switchLang |
switchLang = not switchLang |
||
until anyLang and switchLang |
until anyLang and switchLang |
||
return value |
return value |
||
end |
end |
||
-- gets the details of one particular type for a reference |
|||
function State:getReferenceDetails(snaks, dType, raw, link, anyLang) |
function State:getReferenceDetails(snaks, dType, raw, link, anyLang) |
||
local values = {} |
local values = {} |
||
if not snaks[dType] then |
if not snaks[dType] then |
||
return {} |
return {} |
||
end |
end |
||
for |
for _, v in ipairs(snaks[dType]) do |
||
values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) |
|||
-- 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 |
end |
||
return values |
return values |
||
end |
end |
||
-- level 1 hook |
|||
function State:getAlias(object) |
function State:getAlias(object) |
||
local value = object.value |
local value = object.value |
||
local title = nil |
local title = nil |
||
if value and self.linked then |
if value and self.linked then |
||
if self.conf.entityID:sub(1,1) == "Q" then |
if self.conf.entityID:sub(1,1) == "Q" then |
||
title = mw.wikibase. |
title = mw.wikibase.getSitelink(self.conf.entityID) |
||
elseif self.conf.entityID:sub(1,1) == "P" then |
elseif self.conf.entityID:sub(1,1) == "P" then |
||
title = " |
title = "pbd:Property:" .. self.conf.entityID |
||
end |
end |
||
if title then |
if title then |
||
value = buildWikilink(title, value) |
value = buildWikilink(title, value) |
||
end |
end |
||
end |
end |
||
value = {value} |
value = {value} |
||
if #value > 0 then |
if #value > 0 then |
||
return {value} |
return {value} |
||
else |
else |
||
return {} |
|||
return {} -- return empty array if there was no value |
|||
end |
end |
||
end |
end |
||
-- level 1 hook |
|||
function State:getBadge(value) |
function State:getBadge(value) |
||
value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) |
value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) |
||
if value == "" then |
if value == "" then |
||
value = nil |
value = nil |
||
end |
end |
||
value = {value} |
value = {value} |
||
if #value > 0 then |
if #value > 0 then |
||
return {value} |
return {value} |
||
else |
else |
||
return {} |
|||
return {} -- return empty array if there was no value |
|||
end |
end |
||
end |
end |
||
| Line 2,037: | Line 1,719: | ||
function State:callHook(param, hooks, statement, result) |
function State:callHook(param, hooks, statement, result) |
||
local valuesArray, refHash |
local valuesArray, refHash |
||
-- call a parameter's hook if it has been defined and if it has not been called before |
|||
if not result[param] and hooks[param] then |
if not result[param] and hooks[param] then |
||
valuesArray = self[hooks[param]](self, statement, param, result, hooks) |
valuesArray = self[hooks[param]](self, statement, param, result, hooks) |
||
-- add to the result |
|||
if #valuesArray > 0 then |
if #valuesArray > 0 then |
||
result[param] = valuesArray |
result[param] = valuesArray |
||
result.count = result.count + 1 |
result.count = result.count + 1 |
||
else |
else |
||
result[param] = {} |
result[param] = {} |
||
return |
return true |
||
end |
end |
||
end |
end |
||
return false |
return false |
||
end |
end |
||
-- iterate through claims, claim's qualifiers or claim's references to collect values |
|||
function State:iterate(statements, hooks, matchHook) |
function State:iterate(statements, hooks, matchHook) |
||
matchHook = matchHook or alwaysTrue |
matchHook = matchHook or alwaysTrue |
||
local matches = false |
local matches = false |
||
local rankPos = nil |
local rankPos = nil |
||
local result, gotRequired |
local result, gotRequired |
||
for |
for _, v in ipairs(statements) do |
||
-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) |
|||
matches, rankPos = matchHook(self, v) |
matches, rankPos = matchHook(self, v) |
||
if matches then |
if matches then |
||
result = {count = 0} |
result = {count = 0} |
||
local function walk(formatTable) |
local function walk(formatTable) |
||
local miss |
local miss |
||
for i2, v2 in pairs(formatTable.req) do |
for i2, v2 in pairs(formatTable.req) do |
||
-- call a hook, adding its return value to the result |
|||
miss = self:callHook(i2, hooks, v, result) |
miss = self:callHook(i2, hooks, v, result) |
||
if miss then |
if miss then |
||
-- we miss a required value for this level, so return false |
|||
return false |
return false |
||
end |
end |
||
if result.count == hooks.count then |
if result.count == hooks.count then |
||
-- we're done if all hooks have been called; |
|||
-- returning at this point breaks the loop |
|||
return true |
return true |
||
end |
end |
||
end |
end |
||
for |
for _, v2 in ipairs(formatTable) do |
||
if result.count == hooks.count then |
if result.count == hooks.count then |
||
-- we're done if all hooks have been called; |
|||
-- returning at this point prevents further childs from being processed |
|||
return true |
return true |
||
end |
end |
||
if v2.child then |
if v2.child then |
||
walk(v2.child) |
walk(v2.child) |
||
end |
end |
||
end |
end |
||
return true |
return true |
||
end |
end |
||
gotRequired = walk(self.parsedFormat) |
gotRequired = walk(self.parsedFormat) |
||
-- only append the result if we got values for all required parameters on the root level |
|||
if gotRequired then |
if gotRequired then |
||
-- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank |
|||
if rankPos and self.conf.foundRank > rankPos then |
if rankPos and self.conf.foundRank > rankPos then |
||
self.conf.foundRank = rankPos |
self.conf.foundRank = rankPos |
||
end |
end |
||
-- append the result |
|||
self.results[#self.results + 1] = result |
self.results[#self.results + 1] = result |
||
-- break if we only need a single value |
|||
if self.singleValue then |
if self.singleValue then |
||
break |
break |
||
| Line 2,122: | Line 1,790: | ||
end |
end |
||
end |
end |
||
return self:out() |
return self:out() |
||
end |
end |
||
function getEntityId(arg, allowOmitPropPrefix) |
local function getEntityId(arg, eid, page, allowOmitPropPrefix) |
||
local id = nil |
|||
local prop = nil |
|||
elseif arg:sub(1,1):upper() == "Q" then |
|||
if arg then |
|||
return arg:upper() -- entity ID of an item was given |
|||
if arg:sub(1,1) == ":" then |
|||
page = arg |
|||
return replaceAlias(mw.text.trim(arg:sub(10))):upper() -- entity ID of a property was given |
|||
eid = nil |
|||
elseif allowOmitPropPrefix and arg ~= "" then |
|||
elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then |
|||
return replaceAlias(arg):upper() -- could be an entity ID of a property without "Property:" prefix |
|||
eid = arg |
|||
else |
|||
page = nil |
|||
else |
|||
prop = arg |
|||
end |
|||
end |
end |
||
if eid then |
|||
if eid:sub(1,9):lower() == "property:" then |
|||
id = replaceAlias(mw.text.trim(eid:sub(10))) |
|||
if id:sub(1,1):upper() ~= "P" then |
|||
id = "" |
|||
end |
|||
else |
|||
id = replaceAlias(eid) |
|||
end |
|||
elseif page then |
|||
if page:sub(1,1) == ":" then |
|||
page = mw.text.trim(page:sub(2)) |
|||
end |
|||
id = mw.wikibase.getEntityIdForTitle(page) or "" |
|||
end |
|||
if not id then |
|||
id = mw.wikibase.getEntityIdForCurrentPage() or "" |
|||
end |
|||
id = id:upper() |
|||
if not mw.wikibase.isValidEntityId(id) then |
|||
id = "" |
|||
end |
|||
return id, prop |
|||
end |
end |
||
function nextArg(args) |
local function nextArg(args) |
||
local arg = args[args.pointer] |
local arg = args[args.pointer] |
||
if arg then |
if arg then |
||
args.pointer = args.pointer + 1 |
args.pointer = args.pointer + 1 |
||
| Line 2,151: | Line 1,852: | ||
end |
end |
||
function claimCommand(args, funcName) |
local function claimCommand(args, funcName) |
||
local |
local cfg = Config:new() |
||
cfg:processFlagOrCommand(funcName) |
|||
local lastArg, parsedFormat, formatParams, claims, value |
local lastArg, parsedFormat, formatParams, claims, value |
||
local hooks = {count = 0} |
local hooks = {count = 0} |
||
if args[p.args.date] then |
|||
-- process flags and commands |
|||
cfg.atDate = {parseDate(args[p.args.date])} |
|||
cfg.periods = {false, true, false} |
|||
end |
|||
repeat |
repeat |
||
lastArg = nextArg(args) |
lastArg = nextArg(args) |
||
until not |
until not cfg:processFlagOrCommand(lastArg) |
||
cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) |
|||
-- use the entity ID from the positional arguments if given |
|||
_.entityID = getEntityId(lastArg, false) |
|||
if cfg.entityID == "" then |
|||
return "" |
|||
if not _.entityID or _.entityID == "" then |
|||
-- if no positional ID is given, use the eid argument if given |
|||
_.entityID = getEntityId(args[p.args.eid], true) |
|||
if _.entityID == "" then |
|||
-- if eid was explicitly set to empty, then this returns an empty string |
|||
return "" |
|||
elseif not _.entityID then |
|||
-- by default, use the item-entity connected to the current page |
|||
_.entityID = mw.wikibase.getEntityIdForCurrentPage() |
|||
end |
|||
else |
|||
lastArg = nextArg(args) |
|||
end |
end |
||
cfg.entity = mw.wikibase.getEntity(cfg.entityID) |
|||
_.propertyID = replaceAlias(lastArg) |
|||
if not cfg.propertyID then |
|||
cfg.propertyID = nextArg(args) |
|||
if not _.entity or not _.propertyID then |
|||
return "" -- we cannot continue without an entity or a property ID |
|||
end |
end |
||
cfg.propertyID = replaceAlias(cfg.propertyID) |
|||
if not |
if not cfg.entity or not cfg.propertyID then |
||
return "" |
|||
return "" -- there is no use to continue without any claims |
|||
end |
end |
||
cfg.propertyID = cfg.propertyID:upper() |
|||
if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then |
|||
if _.states.qualifiersCount > 0 then |
|||
return "" |
|||
-- do further processing if "qualifier(s)" command was given |
|||
end |
|||
if #args - args.pointer + 1 > _.states.qualifiersCount then |
|||
claims = cfg.entity.claims[cfg.propertyID] |
|||
-- claim ID or literal value has been given |
|||
if cfg.states.qualifiersCount > 0 then |
|||
_.propertyValue = nextArg(args) |
|||
if #args - args.pointer + 1 > cfg.states.qualifiersCount then |
|||
cfg.propertyValue = nextArg(args) |
|||
end |
end |
||
for i = 1, |
for i = 1, cfg.states.qualifiersCount do |
||
cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() |
|||
-- check if given qualifier ID is an alias and add it |
|||
_.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() |
|||
end |
end |
||
elseif |
elseif cfg.states[parameters.reference] then |
||
-- do further processing if "reference(s)" command was given |
|||
cfg.propertyValue = nextArg(args) |
|||
_.propertyValue = nextArg(args) |
|||
end |
end |
||
if cfg.propertyValue then |
|||
-- check for special property value 'somevalue' or 'novalue' |
|||
cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) |
|||
_.propertyValue = replaceSpecialChars(_.propertyValue) |
|||
if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then |
|||
cfg.propertyValue = " " |
|||
_.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' |
|||
else |
else |
||
cfg.propertyValue = mw.text.trim(cfg.propertyValue) |
|||
end |
end |
||
end |
end |
||
-- parse the desired format, or choose an appropriate format |
|||
if args["format"] then |
if args["format"] then |
||
parsedFormat, formatParams = parseFormat(args["format"]) |
parsedFormat, formatParams = parseFormat(args["format"]) |
||
elseif |
elseif cfg.states.qualifiersCount > 0 then |
||
if |
if cfg.states[parameters.property] then |
||
parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) |
parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) |
||
else |
else |
||
parsedFormat, formatParams = parseFormat(formats.qualifier) |
parsedFormat, formatParams = parseFormat(formats.qualifier) |
||
end |
end |
||
elseif |
elseif cfg.states[parameters.property] then |
||
parsedFormat, formatParams = parseFormat(formats.property) |
parsedFormat, formatParams = parseFormat(formats.property) |
||
else |
|||
else -- "reference(s)" command given |
|||
parsedFormat, formatParams = parseFormat(formats.reference) |
parsedFormat, formatParams = parseFormat(formats.reference) |
||
end |
end |
||
if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then |
|||
-- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon |
|||
cfg.separators["sep"..parameters.separator][1] = {";"} |
|||
if _.states.qualifiersCount > 0 and not _.states[parameters.property] then |
|||
_.separators["sep"..parameters.separator][1] = {";"} |
|||
end |
end |
||
if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 |
|||
-- if only "reference(s)" has been given, set the default separator to none (except when raw) |
|||
and not cfg.states[parameters.reference].rawValue then |
|||
cfg.separators["sep"][1] = nil |
|||
and not _.states[parameters.reference].rawValue then |
|||
_.separators["sep"][1] = nil |
|||
end |
end |
||
if cfg.states.qualifiersCount == 1 then |
|||
-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent |
|||
cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] |
|||
if _.states.qualifiersCount == 1 then |
|||
_.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] |
|||
end |
end |
||
cfg:processSeparators(args) |
|||
-- process overridden separator values; |
|||
-- must come AFTER tweaking the default separators |
|||
for i, v in pairs(cfg.states) do |
|||
_: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 |
|||
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 |
if formatParams[i] or formatParams[i:sub(1, 2)] then |
||
hooks[i] = getHookName(i, 1) |
hooks[i] = getHookName(i, 1) |
||
| Line 2,270: | Line 1,954: | ||
end |
end |
||
end |
end |
||
if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then |
|||
-- 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[parameters.qualifier] = getHookName(parameters.qualifier, 1) |
||
hooks.count = hooks.count + 1 |
hooks.count = hooks.count + 1 |
||
end |
end |
||
if not cfg.states[parameters.property] then |
|||
-- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; |
|||
cfg.states[parameters.property] = State:new(cfg, parameters.property) |
|||
-- must come AFTER defining the hooks |
|||
if not _.states[parameters.property] then |
|||
if cfg.singleClaim then |
|||
_.states[parameters.property] = State.new(_) |
|||
cfg.states[parameters.property].singleValue = true |
|||
-- 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 |
||
end |
end |
||
if cfg.sourcedOnly and not cfg.states[parameters.reference] then |
|||
-- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, |
|||
cfg:processFlagOrCommand(p.claimCommands.reference) |
|||
-- 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(p.claimCommands.reference) -- use singular "reference" to minimize overhead |
|||
end |
end |
||
cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) |
|||
-- set the parsed format and the separators (and optional punctuation mark); |
|||
-- must come AFTER creating the additonal states |
|||
_:setFormatAndSeparators(_.states[parameters.property], parsedFormat) |
|||
-- process qualifier matching values, analogous to _.propertyValue |
|||
for i, v in pairs(args) do |
for i, v in pairs(args) do |
||
i = tostring(i) |
i = tostring(i) |
||
if i:match('^[Pp]%d+$') or |
if i:match('^[Pp]%d+$') or aliasesP[i] then |
||
v = replaceSpecialChars(v) |
v = replaceSpecialChars(v) |
||
-- check for special qualifier value 'somevalue' |
|||
if v ~= "" and mw.text.trim(v) == "" then |
if v ~= "" and mw.text.trim(v) == "" then |
||
v = " " |
v = " " |
||
end |
end |
||
cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v |
|||
end |
end |
||
end |
end |
||
-- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) |
|||
claims = sortOnRank(claims) |
claims = sortOnRank(claims) |
||
value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) |
|||
-- then iterate through the claims to collect values |
|||
value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook |
|||
if cfg.editable and value ~= "" then |
|||
value = value .. cfg:getEditIcon() |
|||
-- if desired, add a clickable icon that may be used to edit the returned values on Wikidata |
|||
if _.editable and value ~= "" then |
|||
value = value .. _:getEditIcon() |
|||
end |
end |
||
return value |
return value |
||
end |
end |
||
function generalCommand(args, funcName) |
local function generalCommand(args, funcName) |
||
local |
local cfg = Config:new() |
||
cfg.curState = State:new(cfg) |
|||
local lastArg |
local lastArg |
||
local value = nil |
local value = nil |
||
repeat |
repeat |
||
lastArg = nextArg(args) |
lastArg = nextArg(args) |
||
until not |
until not cfg:processFlag(lastArg) |
||
cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) |
|||
-- use the entity ID from the positional arguments if given |
|||
_.entityID = getEntityId(lastArg, true) |
|||
if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then |
|||
return "" |
|||
if not _.entityID or _.entityID == "" then |
|||
-- if no positional ID is given, use the eid argument if given |
|||
_.entityID = getEntityId(args[p.args.eid], true) |
|||
if _.entityID == "" then |
|||
-- if eid was explicitly set to empty, then this returns an empty string |
|||
return "" |
|||
elseif not _.entityID then |
|||
-- by default, use the item-entity connected to the current page |
|||
_.entityID = mw.wikibase.getEntityIdForCurrentPage() |
|||
end |
|||
end |
end |
||
-- serve according to the given command |
|||
if funcName == p.generalCommands.label then |
if funcName == p.generalCommands.label then |
||
value = |
value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) |
||
elseif funcName == p.generalCommands.title then |
elseif funcName == p.generalCommands.title then |
||
cfg.inSitelinks = true |
|||
if |
if cfg.entityID:sub(1,1) == "Q" then |
||
value = mw.wikibase. |
value = mw.wikibase.getSitelink(cfg.entityID) |
||
end |
end |
||
if |
if cfg.curState.linked and value then |
||
value = buildWikilink(value) |
value = buildWikilink(value) |
||
end |
end |
||
elseif funcName == p.generalCommands.description then |
elseif funcName == p.generalCommands.description then |
||
value = mw.wikibase.getDescription(cfg.entityID) |
|||
if _.entity.descriptions[_.langCode] then |
|||
value = _.entity.descriptions[_.langCode].value |
|||
end |
|||
else |
else |
||
local parsedFormat, formatParams |
local parsedFormat, formatParams |
||
local hooks = {count = 0} |
local hooks = {count = 0} |
||
cfg.entity = mw.wikibase.getEntity(cfg.entityID) |
|||
if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then |
if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then |
||
cfg.curState.singleValue = true |
|||
end |
end |
||
_.entity = mw.wikibase.getEntity(_.entityID) |
|||
if not _.entity then |
|||
return "" -- we cannot continue without an entity |
|||
end |
|||
if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then |
if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then |
||
if not |
if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then |
||
return "" |
|||
return "" -- there is no use to continue without any aliasses |
|||
end |
end |
||
local aliases = |
local aliases = cfg.entity.aliases[cfg.langCode] |
||
-- parse the desired format, or parse the default aliases format |
|||
if args["format"] then |
if args["format"] then |
||
parsedFormat, formatParams = parseFormat(args["format"]) |
parsedFormat, formatParams = parseFormat(args["format"]) |
||
| Line 2,403: | Line 2,052: | ||
parsedFormat, formatParams = parseFormat(formats.alias) |
parsedFormat, formatParams = parseFormat(formats.alias) |
||
end |
end |
||
cfg:processSeparators(args) |
|||
-- process overridden separator values; |
|||
-- must come AFTER tweaking the default separators |
|||
_:processSeparators(args) |
|||
-- define the hook that should be called (getAlias); |
|||
-- only define the hook if the parameter ("%a") has been given |
|||
if formatParams[parameters.alias] then |
if formatParams[parameters.alias] then |
||
hooks[parameters.alias] = getHookName(parameters.alias, 1) |
hooks[parameters.alias] = getHookName(parameters.alias, 1) |
||
hooks.count = hooks.count + 1 |
hooks.count = hooks.count + 1 |
||
end |
end |
||
cfg:setFormatAndSeparators(cfg.curState, parsedFormat) |
|||
-- set the parsed format and the separators (and optional punctuation mark) |
|||
_:setFormatAndSeparators(_.curState, parsedFormat) |
|||
value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) |
|||
-- iterate to collect values |
|||
value = _:concatValues(_.curState:iterate(aliases, hooks)) |
|||
elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then |
elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then |
||
if not |
if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then |
||
return "" |
return "" |
||
end |
end |
||
local badges = |
local badges = cfg.entity.sitelinks[cfg.siteID].badges |
||
cfg.inSitelinks = true |
|||
-- parse the desired format, or parse the default aliases format |
|||
if args["format"] then |
if args["format"] then |
||
parsedFormat, formatParams = parseFormat(args["format"]) |
parsedFormat, formatParams = parseFormat(args["format"]) |
||
| Line 2,435: | Line 2,077: | ||
parsedFormat, formatParams = parseFormat(formats.badge) |
parsedFormat, formatParams = parseFormat(formats.badge) |
||
end |
end |
||
cfg:processSeparators(args) |
|||
-- process overridden separator values; |
|||
-- must come AFTER tweaking the default separators |
|||
_:processSeparators(args) |
|||
-- define the hook that should be called (getBadge); |
|||
-- only define the hook if the parameter ("%b") has been given |
|||
if formatParams[parameters.badge] then |
if formatParams[parameters.badge] then |
||
hooks[parameters.badge] = getHookName(parameters.badge, 1) |
hooks[parameters.badge] = getHookName(parameters.badge, 1) |
||
hooks.count = hooks.count + 1 |
hooks.count = hooks.count + 1 |
||
end |
end |
||
cfg:setFormatAndSeparators(cfg.curState, parsedFormat) |
|||
-- set the parsed format and the separators (and optional punctuation mark) |
|||
_:setFormatAndSeparators(_.curState, parsedFormat) |
|||
value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) |
|||
-- iterate to collect values |
|||
value = _:concatValues(_.curState:iterate(badges, hooks)) |
|||
end |
end |
||
end |
end |
||
value = value or "" |
value = value or "" |
||
if |
if cfg.editable and value ~= "" then |
||
value = value .. cfg:getEditIcon() |
|||
-- if desired, add a clickable icon that may be used to edit the returned value on Wikidata |
|||
value = value .. _:getEditIcon() |
|||
end |
end |
||
return value |
return value |
||
end |
end |
||
local function establishCommands(commandList, commandFunc) |
|||
-- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) |
|||
for _, commandName in pairs(commandList) do |
|||
function establishCommands(commandList, commandFunc) |
|||
for commandIndex, commandName in pairs(commandList) do |
|||
local function wikitextWrapper(frame) |
local function wikitextWrapper(frame) |
||
local args = copyTable(frame.args) |
local args = copyTable(frame.args) |
||
args.pointer = 1 |
args.pointer = 1 |
||
loadI18n(aliasesP, frame) |
|||
return commandFunc(args, commandName) |
return commandFunc(args, commandName) |
||
end |
end |
||
p[commandName] = wikitextWrapper |
p[commandName] = wikitextWrapper |
||
local function luaWrapper(args) |
local function luaWrapper(args) |
||
args = copyTable(args) |
args = copyTable(args) |
||
args.pointer = 1 |
args.pointer = 1 |
||
loadI18n(aliasesP) |
|||
loadSubmodules() |
|||
return commandFunc(args, commandName) |
return commandFunc(args, commandName) |
||
end |
end |
||
p["_" .. commandName] = luaWrapper |
p["_" .. commandName] = luaWrapper |
||
end |
end |
||
end |
end |
||
| Line 2,489: | Line 2,123: | ||
establishCommands(p.generalCommands, generalCommand) |
establishCommands(p.generalCommands, generalCommand) |
||
-- main function that is supposed to be used by wrapper templates |
|||
function p.main(frame) |
function p.main(frame) |
||
if not mw.wikibase then return nil end |
|||
local f, args, i, v |
|||
local f, args |
|||
loadSubmodules(frame) |
|||
loadI18n(aliasesP, frame) |
|||
-- get the parent frame to take the arguments that were passed to the wrapper template |
|||
frame = frame:getParent() or frame |
frame = frame:getParent() or frame |
||
if not frame.args[1] then |
if not frame.args[1] then |
||
throwError("no-function-specified") |
throwError("no-function-specified") |
||
end |
end |
||
f = mw.text.trim(frame.args[1]) |
f = mw.text.trim(frame.args[1]) |
||
if f == "main" then |
if f == "main" then |
||
throwError("main-called-twice") |
throwError("main-called-twice") |
||
end |
end |
||
assert(p["_"..f], errorText('no-such-function', f)) |
assert(p["_"..f], errorText('no-such-function', f)) |
||
-- copy arguments from immutable to mutable table |
|||
args = copyTable(frame.args) |
args = copyTable(frame.args) |
||
-- remove the function name from the list |
|||
table.remove(args, 1) |
table.remove(args, 1) |
||
return p["_"..f](args) |
return p["_"..f](args) |
||
end |
end |
||
Latest revision as of 01:55, 14 January 2025
Documentation for this module may be created at Module:PBD/doc
local p = {}
local arg = ...
local i18n
local function loadI18n(aliasesP, frame)
local title
if frame then
title = frame:getTitle()
else
title = arg
end
if not i18n then
i18n = require(title .. "/i18n").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"
}
local aliasesP = {
coord = "P48",
-----------------------
image = "P470",
author = "P215",
publisher = "P218",
importedFrom = "P36",
statedIn = "P15",
pages = "P219",
language = "P44",
hasPart = "P173",
publicationDate = "P19",
startTime = "P34",
endTime = "P41",
chapter = "P221",
retrieved = "P17",
referenceURL = "P35",
sectionVerseOrParagraph = "P223",
archiveURL = "P208",
title = "P225",
formatterURL = "P167",
quote = "P226",
shortName = "P203",
definingFormula = "P232",
archiveDate = "P227",
inferredFrom = "P71",
typeOfReference = "P228",
column = "P230"
}
local aliasesQ = {
percentage = "Q224",
prolepticJulianCalendar = "Q219",
citeWeb = "Q225",
citeQ = "Q227"
}
local parameters = {
property = "%p",
qualifier = "%q",
reference = "%r",
alias = "%a",
badge = "%b",
separator = "%s",
general = "%x"
}
local formats = {
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]"
}
local hookNames = {
[parameters.property] = {"getProperty"},
[parameters.reference] = {"getReferences", "getReference"},
[parameters.qualifier] = {"getAllQualifiers"},
[parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"},
[parameters.alias] = {"getAlias"},
[parameters.badge] = {"getBadge"}
}
local defaultSeparators = {
["sep"] = {" "},
["sep%s"] = {","},
["sep%q"] = {"; "},
["sep%q\\d"] = {", "},
["sep%r"] = nil,
["punc"] = nil
}
local rankTable = {
["preferred"] = 1,
["normal"] = 2,
["deprecated"] = 3
}
local Config = {}
function Config:new()
local cfg = {}
setmetatable(cfg, self)
self.__index = self
cfg.separators = {
["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}
cfg.foundRank = #cfg.ranks
cfg.flagBest = false
cfg.flagRank = false
cfg.periods = {true, true, true}
cfg.flagPeriod = false
cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))}
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
return cfg
end
local State = {}
function State:new(cfg, type)
local stt = {}
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
return stt
end
local function replaceAlias(id)
if aliasesP[id] then
id = aliasesP[id]
end
return id
end
local function errorText(code, param)
local text = i18n["errors"][code]
if param then text = mw.ustring.gsub(text, "$1", param) end
return text
end
local function throwError(errorMessage, param)
error(errorText(errorMessage, param))
end
local function replaceDecimalMark(num)
return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1)
end
local function padZeros(num, numDigits)
local numZeros
local negative = false
if num < 0 then
negative = true
num = num * -1
end
num = tostring(num)
numZeros = numDigits - num:len()
for _ = 1, numZeros do
num = "0"..num
end
if negative then
num = "-"..num
end
return num
end
local function replaceSpecialChar(chr)
if chr == '_' then
return ' '
else
return chr
end
end
local 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
local function buildWikilink(target, label)
if not label or target == label then
return "[[" .. target .. "]]"
else
return "[[" .. target .. "|" .. label .. "]]"
end
end
function copyTable(tIn)
if not tIn then
return nil
end
local tOut = {}
for i, v in pairs(tIn) do
tOut[i] = v
end
return tOut
end
local function mergeArrays(a1, a2)
for i = 1, #a2 do
a1[#a1 + 1] = a2[i]
end
return a1
end
local function split(str, del)
local out = {}
local i, j = str:find(del)
if i and j then
out[1] = str:sub(1, i - 1)
out[2] = str:sub(j + 1)
else
out[1] = str
end
return out
end
local function parsePornbasedataURL(url)
local id
if url:match('^http[s]?://') then
id = split(url, "Q")
if id[2] then
return "Q" .. id[2]
end
end
return nil
end
function parseDate(dateStr, precision)
precision = precision or "d"
local i, j, index, ptr
local parts = {nil, nil, nil}
if dateStr == nil then
return parts[1], parts[2], parts[3]
end
i, j = dateStr:find("[T/]")
if i then
dateStr = dateStr:sub(1, i-1)
end
local from = 1
if dateStr:sub(1,1) == "-" then
from = 2
end
index = 1
ptr = 1
i, j = dateStr:find("-", from)
if i then
parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10)
if parts[index] == -0 then
parts[index] = tonumber("0")
end
if precision == "y" then
return parts[1], parts[2], parts[3]
end
index = index + 1
ptr = i + 1
i, j = dateStr:find("-", ptr)
if i then
parts[index] = tonumber(dateStr:sub(ptr, i-1), 10)
if precision == "m" then
return parts[1], parts[2], parts[3]
end
index = index + 1
ptr = i + 1
end
end
if dateStr:sub(ptr) ~= "" then
parts[index] = tonumber(dateStr:sub(ptr), 10)
end
return parts[1], parts[2], parts[3]
end
local function datePrecedesDate(aY, aM, aD, bY, bM, bD)
if aY == nil or bY == nil then
return nil
end
aM = aM or 1
aD = aD or 1
bM = bM or 1
bD = bD or 1
if aY < bY then
return true
end
if aY > bY then
return false
end
if aM < bM then
return true
end
if aM > bM then
return false
end
if aD < bD then
return true
end
return false
end
local function getHookName(param, index)
if hookNames[param] then
return hookNames[param][index]
elseif param:len() > 2 then
return hookNames[param:sub(1, 2).."\\d"][index]
else
return nil
end
end
local function alwaysTrue()
return true
end
local function parseFormat(str)
local chr, esc, param, root, cur, prev, new
local params = {}
local function newObject(array)
local obj = {}
obj.str = ""
array[#array + 1] = obj
obj.parent = array
return obj
end
local function endParam()
if param > 0 then
if cur.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
for i = 1, #str do
chr = str:sub(i,i)
if not esc then
if chr == '\\' then
endParam()
esc = true
elseif chr == '%' then
endParam()
if cur.str ~= "" then
cur = newObject(cur.parent)
end
param = 2
elseif chr == '[' then
endParam()
if prev and cur.str == "" then
table.remove(cur.parent)
cur = prev
end
cur.child = {} -- new array
cur.child.req = {}
cur.child.parent = cur
cur = newObject(cur.child)
elseif chr == ']' then
endParam()
if cur.parent.parent then
new = newObject(cur.parent.parent.parent)
if cur.str == "" then
table.remove(cur.parent)
end
cur = new
end
else
if param > 1 then
param = param - 1
elseif param == 1 then
if not chr: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()
if not next(root.req) then
throwError("missing-required-parameter")
end
if root.req[parameters.separator] then
throwError("extra-required-parameter", parameters.separator)
end
return root, params
end
local function sortOnRank(claims)
local rankPos
local ranks = {{}, {}, {}, {}}
local sorted = {}
for _, v in ipairs(claims) do
rankPos = rankTable[v.rank] or 4
ranks[rankPos][#ranks[rankPos] + 1] = v
end
sorted = ranks[1]
sorted = mergeArrays(sorted, ranks[2])
sorted = mergeArrays(sorted, ranks[3])
return sorted
end
function Config:getLabel(id, raw, link, short)
local label = nil
local title = nil
local prefix= ""
if not id then
id = mw.wikibase.getEntityIdForCurrentPage()
if not id then
return ""
end
end
id = id:upper()
if raw then
if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then
label = id
if id:sub(1,1) == "P" then
prefix = "Property:"
end
end
prefix = "pbd:" .. prefix
title = label
else
if short then
label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name
if label == "" then
label = nil
end
end
-- get label
if not label then
label = mw.wikibase.getLabelByLang(id, self.langCode)
end
end
if not label then
label = ""
elseif link then
if not title then
if id:sub(1,1) == "Q" then
title = mw.wikibase.getSitelink(id)
if not title then
title = id
prefix = "pbd:Special:EntityPage/"
end
elseif id:sub(1,1) == "P" then
title = id
prefix = "pbd:Special:EntityPage/"
end
end
label = mw.text.nowiki(label)
if title then
label = buildWikilink(prefix .. title, mw.text.nowiki(label))
end
end
return label
end
function Config:getEditIcon()
local value = ""
local prefix = ""
local front = " "
local back = ""
if self.entityID:sub(1,1) == "P" then
prefix = "Property:"
end
if self.editAtEnd then
front = '<span style="float:'
if self.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-pornbasedata'] .. "|link=https://pornbasedata.com/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode
if self.propertyID then
value = value .. "#" .. self.propertyID
elseif self.inSitelinks then
value = value .. "#sitelinks-pbc"
end
value = value .. "|" .. i18n['info']['edit-on-pornbasedata'] .. "]]"
return front .. value .. back
end
function Config:concatValues(valuesArray)
local outString = ""
local j, skip
for i = 1, #valuesArray do
if valuesArray[i].refHash then
j = i - 1
skip = false
while valuesArray[j] and valuesArray[j].refHash do
if valuesArray[i].refHash == valuesArray[j].refHash then
skip = true
break
end
j = j - 1
end
if not skip then
outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash})
end
else
outString = outString .. valuesArray[i][1]
end
end
return outString
end
function Config:convertUnit(unit, raw, link, short, unitOnly)
local space = " "
local label = ""
local itemID
if unit == "" or unit == "1" then
return nil
end
if unitOnly then
space = ""
end
itemID = parsePornbasedataURL(unit)
if itemID then
if itemID == aliasesQ.percentage then
return "%"
else
label = self:getLabel(itemID, raw, link, short)
if label ~= "" then
return space .. label
end
end
end
return ""
end
function State:getValue(snak)
return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, true, self.unitOnly, false, self.type:sub(1,2))
end
function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type)
if snak.snaktype == 'value' then
local datatype = snak.datavalue.type
local subtype = snak.datatype
local datavalue = snak.datavalue.value
if datatype == 'string' then
if subtype == 'url' and link then
if raw then
return "[" .. datavalue .. "]"
else
return "[" .. datavalue .. " " .. datavalue .. "]"
end
elseif subtype == 'commonsMedia' then
if link then
return buildWikilink("pbc:File:" .. datavalue, datavalue)
elseif not raw then
return "[[File:" .. datavalue .. "]]"
else
return datavalue
end
elseif subtype == 'geo-shape' and link then
return buildWikilink("pbc:" .. datavalue, datavalue)
elseif subtype == 'math' and not raw then
local attribute = nil
if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then
attribute = {qid = self.entityID}
end
return mw.getCurrentFrame():extensionTag("math", datavalue, attribute)
elseif subtype == 'external-id' and link then
local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property}
if url ~= "" then
url = mw.ustring.gsub(url, "$1", datavalue)
return "[" .. url .. " " .. datavalue .. "]"
else
return datavalue
end
else
return datavalue
end
elseif datatype == 'monolingualtext' then
if anyLang or datavalue['language'] == self.langCode then
return datavalue['text']
else
return nil
end
elseif datatype == 'quantity' then
local value = ""
local unit
if not unitOnly then
value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1")
if raw then
return value
end
value = replaceDecimalMark(value)
value = i18n.addDelimiters(value)
end
unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly)
if unit then
value = value .. unit
end
return value
elseif datatype == 'time' then
local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr
local yFactor = 1
local sign = 1
local prefix = ""
local suffix = ""
local mayAddCalendar = false
local calendar = ""
local precision = datavalue['precision']
if precision == 11 then
p = "d"
elseif precision == 10 then
p = "m"
else
p = "y"
yFactor = 10^(9-precision)
end
y, m, d = parseDate(datavalue['time'], p)
if y < 0 then
sign = -1
y = y * sign
end
if precision <= 8 then
yDiv = y / yFactor
if precision >= 6 then
mayAddCalendar = true
if precision <= 7 then
yRound = math.ceil(yDiv)
if not raw then
if precision == 6 then
suffix = i18n['datetime']['suffixes']['millennium']
else
suffix = i18n['datetime']['suffixes']['century']
end
suffix = i18n.getOrdinalSuffix(yRound) .. suffix
else
yRound = (yRound - 1) * yFactor + 1
end
else
yRound = math.floor(yDiv) * yFactor
if not raw then
prefix = i18n['datetime']['prefixes']['decade-period']
suffix = i18n['datetime']['suffixes']['decade-period']
end
end
if raw and sign < 0 then
yRound = yRound + yFactor - 1
end
else
local yReFactor, yReDiv, yReRound
yRound = math.floor(yDiv + 0.5)
if yRound == 0 then
if precision <= 2 and y ~= 0 then
yReFactor = 1e6
yReDiv = y / yReFactor
yReRound = math.floor(yReDiv + 0.5)
if yReDiv == yReRound then
precision = 3
yFactor = yReFactor
yRound = yReRound
end
end
if yRound == 0 then
precision = 5
yFactor = 1
yRound = y
mayAddCalendar = true
end
end
if precision >= 1 and y ~= 0 then
yFull = yRound * yFactor
yReFactor = 1e9
yReDiv = yFull / yReFactor
yReRound = math.floor(yReDiv + 0.5)
if yReDiv == yReRound then
precision = 0
yFactor = yReFactor
yRound = yReRound
else
yReFactor = 1e6
yReDiv = yFull / yReFactor
yReRound = math.floor(yReDiv + 0.5)
if yReDiv == yReRound then
precision = 3
yFactor = yReFactor
yRound = yReRound
end
end
end
if not raw then
if precision == 3 then
suffix = i18n['datetime']['suffixes']['million-years']
elseif precision == 0 then
suffix = i18n['datetime']['suffixes']['billion-years']
else
yRound = yRound * yFactor
if yRound == 1 then
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
if mayAddCalendar then
calendarID = parsePornbasedataURL(datavalue['calendarmodel'])
if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then
if not raw then
if link then
calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")"
else
calendar = " ("..i18n['datetime']['julian']..")"
end
else
calendar = "/"..i18n['datetime']['julian']
end
end
end
if not raw then
local ce = nil
if sign < 0 then
ce = i18n['datetime']['BCE']
elseif precision <= 5 then
ce = i18n['datetime']['CE']
end
if ce then
if link then
ce = buildWikilink(i18n['datetime']['common-era'], ce)
end
suffix = suffix .. " " .. ce
end
value = tostring(yRound)
if m then
dateStr = self.langObj:formatDate("F", "1-"..m.."-1")
if d then
if self.mdyDate then
dateStr = dateStr .. " " .. d .. ","
else
dateStr = d .. " " .. dateStr
end
end
value = dateStr .. " " .. value
end
value = prefix .. value .. suffix .. calendar
else
value = padZeros(yRound * sign, 4)
if m then
value = value .. "-" .. padZeros(m, 2)
if d then
value = value .. "-" .. padZeros(d, 2)
end
end
value = value .. calendar
end
return value
elseif datatype == 'wikibase-entityid' then
local label
local itemID = datavalue['numeric-id']
if subtype == 'wikibase-item' then
itemID = "Q" .. itemID
elseif subtype == '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)
if label == "" then
label = nil
end
return label
else
return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>'
end
elseif snak.snaktype == 'somevalue' and not noSpecial then
if raw then
return " "
else
return i18n['values']['unknown']
end
elseif snak.snaktype == 'novalue' and not noSpecial then
if raw then
return ""
else
return i18n['values']['none']
end
else
return nil
end
end
function Config:getSingleRawQualifier(claim, qualifierID)
local qualifiers
if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end
if qualifiers and qualifiers[1] then
return self:getValue(qualifiers[1], true)
else
return nil
end
end
function Config:snakEqualsValue(snak, value)
local snakValue = self:getValue(snak, true)
if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end
return snakValue == value
end
function Config:setRank(rank)
local rankPos
if rank == p.flags.best then
self.bestRank = true
self.flagBest = true
return
end
if rank:sub(1,9) == p.flags.preferred then
rankPos = 1
elseif rank:sub(1,6) == p.flags.normal then
rankPos = 2
elseif rank:sub(1,10) == p.flags.deprecated then
rankPos = 3
else
return
end
if not self.flagRank then
self.ranks = {false, false, false}
self.bestRank = self.flagBest
self.flagRank = true
end
if rank:sub(-1) == "+" then
for i = rankPos, 1, -1 do
self.ranks[i] = true
end
elseif rank:sub(-1) == "-" then
for i = rankPos, #self.ranks do
self.ranks[i] = true
end
else
self.ranks[rankPos] = true
end
end
function Config:setPeriod(period)
local periodPos
if period == p.flags.future then
periodPos = 1
elseif period == p.flags.current then
periodPos = 2
elseif period == p.flags.former then
periodPos = 3
else
return
end
if not self.flagPeriod then
self.periods = {false, false, false}
self.flagPeriod = true
end
self.periods[periodPos] = true
end
function Config:qualifierMatches(claim, id, value)
local qualifiers
if claim.qualifiers then qualifiers = claim.qualifiers[id] end
if qualifiers then
for _, v in pairs(qualifiers) do
if self:snakEqualsValue(v, value) then
return true
end
end
elseif value == "" then
return true
end
return false
end
function Config:rankMatches(rankPos)
if self.bestRank then
return (self.ranks[rankPos] and self.foundRank >= rankPos)
else
return self.ranks[rankPos]
end
end
function Config:timeMatches(claim)
local startTime = nil
local startTimeY = nil
local startTimeM = nil
local startTimeD = nil
local endTime = nil
local endTimeY = nil
local endTimeM = nil
local endTimeD = nil
if self.periods[1] and self.periods[2] and self.periods[3] then
-- any time
return true
end
startTime = self:getSingleRawQualifier(claim, aliasesP.startTime)
if startTime and startTime ~= "" and startTime ~= " " then
startTimeY, startTimeM, startTimeD = parseDate(startTime)
end
endTime = self:getSingleRawQualifier(claim, aliasesP.endTime)
if endTime and endTime ~= "" and endTime ~= " " then
endTimeY, endTimeM, endTimeD = parseDate(endTime)
end
if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then
endTimeY = nil
endTimeM = nil
endTimeD = nil
end
if self.periods[1] then
if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then
return true
end
end
if self.periods[2] then
if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and
(endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then
return true
end
end
if self.periods[3] then
if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then
return true
end
end
return false
end
function Config:processFlag(flag)
if not flag then
return false
end
if flag == p.flags.linked then
self.curState.linked = true
return true
elseif flag == p.flags.raw then
self.curState.rawValue = true
if self.curState == self.states[parameters.reference] then
self.separators["sep%r"][1] = {" "}
end
return true
elseif flag == p.flags.short then
self.curState.shortName = true
return true
elseif flag == p.flags.multilanguage then
self.curState.anyLanguage = true
return true
elseif flag == p.flags.unit then
self.curState.unitOnly = true
return true
elseif flag == p.flags.mdy then
self.mdyDate = true
return true
elseif flag == p.flags.single then
self.singleClaim = true
return true
elseif flag == p.flags.sourced then
self.sourcedOnly = true
return true
elseif flag == p.flags.edit then
self.editable = true
return true
elseif flag == p.flags.editAtEnd then
self.editable = true
self.editAtEnd = true
return true
elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then
self:setRank(flag)
return true
elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then
self:setPeriod(flag)
return true
elseif flag == "" then
return true
else
return false
end
end
function Config:processFlagOrCommand(flag)
local param = ""
if not flag then
return false
end
if flag == p.claimCommands.property or flag == p.claimCommands.properties then
param = parameters.property
elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then
self.states.qualifiersCount = self.states.qualifiersCount + 1
param = parameters.qualifier .. self.states.qualifiersCount
self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])}
elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then
param = parameters.reference
else
return self:processFlag(flag)
end
if self.states[param] then
return false
end
self.states[param] = State:new(self, param)
self.states[param].parsedFormat = parseFormat(parameters.general)
self.states[param].separator = self.separators["sep"..param]
if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then
self.states[param].singleValue = true
end
self.curState = self.states[param]
return true
end
function Config:processSeparators(args)
local sep
for i, v in pairs(self.separators) do
if args[i] then
sep = replaceSpecialChars(args[i])
if sep ~= "" then
self.separators[i][1] = {sep}
else
self.separators[i][1] = nil
end
end
end
end
function Config:setFormatAndSeparators(state, parsedFormat)
state.parsedFormat = parsedFormat
state.separator = self.separators["sep"]
state.movSeparator = self.separators["sep"..parameters.separator]
state.puncMark = self.separators["punc"]
end
function State:isSourced(claim)
self.conf.prefetchedRefs = self:getReferences(claim)
return (#self.conf.prefetchedRefs > 0)
end
function State:resetCaches()
self.conf.prefetchedRefs = nil
end
function State:claimMatches(claim)
local matches, rankPos
self:resetCaches()
if self.conf.propertyValue then
matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue)
else
matches = true
end
for i, v in pairs(self.conf.qualifierIDsAndValues) do
matches = (matches and self.conf:qualifierMatches(claim, i, v))
end
rankPos = rankTable[claim.rank] or 4
matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim))
if self.conf.sourcedOnly then
matches = (matches and self:isSourced(claim))
end
return matches, rankPos
end
function State:out()
local result
local valuesArray
local sep = nil
local out = {}
local function walk(formatTable, result)
local valuesArray = {}
for i, v in pairs(formatTable.req) do
if not result[i] or not result[i][1] then
return {}
end
end
for _, v in ipairs(formatTable) do
if v.param then
valuesArray = mergeArrays(valuesArray, result[v.str])
elseif v.str ~= "" then
valuesArray[#valuesArray + 1] = {v.str}
end
if v.child then
valuesArray = mergeArrays(valuesArray, walk(v.child, result))
end
end
return valuesArray
end
for i = #self.results, 1, -1 do
result = self.results[i]
if #out > 0 then
sep = self.separator[1]
result[parameters.separator] = {self.movSeparator[1]}
else
sep = nil
result[parameters.separator] = {self.puncMark[1]}
end
valuesArray = walk(self.parsedFormat, result)
if #valuesArray > 0 then
if sep then
valuesArray[#valuesArray + 1] = sep
end
out = mergeArrays(valuesArray, out)
end
end
self.results = {}
return out
end
function State:getProperty(claim)
local value = {self:getValue(claim.mainsnak)}
if #value > 0 then
return {value}
else
return {}
end
end
function State:getQualifiers(claim, param)
local qualifiers
if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end
if qualifiers then
return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1})
else
return {}
end
end
function State:getQualifier(snak)
local value = {self:getValue(snak)}
if #value > 0 then
return {value}
else
return {}
end
end
function State:getAllQualifiers(claim, param, result, hooks)
local out = {}
local sep = self.conf.separators["sep"..parameters.qualifier][1]
for i = 1, self.conf.states.qualifiersCount do
if not result[parameters.qualifier..i] then
self:callHook(parameters.qualifier..i, hooks, claim, result)
end
if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then
if #out > 0 and sep then
out[#out + 1] = sep
end
out = mergeArrays(out, result[parameters.qualifier..i])
end
end
return out
end
function State:getReferences(claim)
if self.conf.prefetchedRefs then
return self.conf.prefetchedRefs
end
if claim.references then
return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1})
else
return {}
end
end
function State:getReference(statement)
local key, citeWeb, citeQ, label
local params = {}
local citeParams = {['web'] = {}, ['q'] = {}}
local citeMismatch = {}
local useCite = nil
local useParams = nil
local value = ""
local ref = {}
local version = 1
if statement.snaks then
if statement.snaks[aliasesP.importedFrom] then
statement.snaks[aliasesP.importedFrom] = nil
end
if statement.snaks[aliasesP.inferredFrom] then
statement.snaks[aliasesP.inferredFrom] = nil
end
if statement.snaks[aliasesP.typeOfReference] then
statement.snaks[aliasesP.typeOfReference] = nil
end
if statement.snaks[aliasesP.image] then
statement.snaks[aliasesP.image] = nil
end
if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then
statement.snaks[aliasesP.language] = nil
end
for i in pairs(statement.snaks) do
label = ""
if i == aliasesP.author then
params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true)
else
params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)}
end
if #params[i] == 0 then
params[i] = nil
else
if statement.snaks[i][1].datatype == 'external-id' then
key = "external-id"
label = self.conf:getLabel(i)
if label ~= "" then
label = label .. " "
end
else
key = i
end
for j in pairs(citeParams) do
if not citeMismatch[j] then
if i18n['cite'][j][key] then
if i18n['cite'][j][key] ~= "" then
citeParams[j][i18n['cite'][j][key]] = label .. params[i][1]
for k=2, #params[i] do
citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k]
end
end
else
citeMismatch[j] = true
end
end
end
end
end
citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2]
citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2]
if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then
useCite = citeWeb
useParams = citeParams['web']
elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then
citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)
useCite = citeQ
useParams = citeParams['q']
end
if useCite and useParams then
if mw.isSubsting() then
for i, v in pairs(useParams) do
value = value .. "|" .. i .. "=" .. v
end
value = "{{" .. useCite .. value .. "}}"
else
value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams}
end
elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then
citeParams['default'] = {}
if params[aliasesP.author] and #params[aliasesP.author] > 0 then
citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ")
end
if params[aliasesP.referenceURL] and params[aliasesP.title] then
citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]'
elseif params[aliasesP.referenceURL] then
citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1]
elseif params[aliasesP.title] then
citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"'
end
if params[aliasesP.statedIn] then
citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''"
end
params[aliasesP.author] = nil
params[aliasesP.referenceURL] = nil
params[aliasesP.title] = nil
params[aliasesP.statedIn] = nil
for i, v in pairs(params) do
i = self.conf:getLabel(i)
if i ~= "" then
citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1]
end
end
value = table.concat(citeParams['default'], "; ")
if value ~= "" then
value = value .. "."
end
end
if value ~= "" then
value = {value}
if not self.rawValue then
value.refHash = "pornbasedata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version)
end
ref = {value}
end
end
return ref
end
function State:getReferenceDetail(snaks, dType, raw, link, anyLang)
local switchLang = anyLang
local value = nil
if not snaks[dType] then
return nil
end
repeat
for _, v in ipairs(snaks[dType]) do
value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true
if value then
break
end
end
if value or not anyLang then
break
end
switchLang = not switchLang
until anyLang and switchLang
return value
end
function State:getReferenceDetails(snaks, dType, raw, link, anyLang)
local values = {}
if not snaks[dType] then
return {}
end
for _, v in ipairs(snaks[dType]) do
values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true)
end
return values
end
function State:getAlias(object)
local value = object.value
local title = nil
if value and self.linked then
if self.conf.entityID:sub(1,1) == "Q" then
title = mw.wikibase.getSitelink(self.conf.entityID)
elseif self.conf.entityID:sub(1,1) == "P" then
title = "pbd:Property:" .. self.conf.entityID
end
if title then
value = buildWikilink(title, value)
end
end
value = {value}
if #value > 0 then
return {value}
else
return {}
end
end
function State:getBadge(value)
value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName)
if value == "" then
value = nil
end
value = {value}
if #value > 0 then
return {value}
else
return {}
end
end
function State:callHook(param, hooks, statement, result)
local valuesArray, refHash
if not result[param] and hooks[param] then
valuesArray = self[hooks[param]](self, statement, param, result, hooks)
if #valuesArray > 0 then
result[param] = valuesArray
result.count = result.count + 1
else
result[param] = {}
return true
end
end
return false
end
function State:iterate(statements, hooks, matchHook)
matchHook = matchHook or alwaysTrue
local matches = false
local rankPos = nil
local result, gotRequired
for _, v in ipairs(statements) do
matches, rankPos = matchHook(self, v)
if matches then
result = {count = 0}
local function walk(formatTable)
local miss
for i2, v2 in pairs(formatTable.req) do
miss = self:callHook(i2, hooks, v, result)
if miss then
return false
end
if result.count == hooks.count then
return true
end
end
for _, v2 in ipairs(formatTable) do
if result.count == hooks.count then
return true
end
if v2.child then
walk(v2.child)
end
end
return true
end
gotRequired = walk(self.parsedFormat)
if gotRequired then
if rankPos and self.conf.foundRank > rankPos then
self.conf.foundRank = rankPos
end
self.results[#self.results + 1] = result
if self.singleValue then
break
end
end
end
end
return self:out()
end
local function getEntityId(arg, eid, page, allowOmitPropPrefix)
local id = nil
local prop = nil
if arg then
if arg:sub(1,1) == ":" then
page = arg
eid = nil
elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then
eid = arg
page = nil
else
prop = arg
end
end
if eid then
if eid:sub(1,9):lower() == "property:" then
id = replaceAlias(mw.text.trim(eid:sub(10)))
if id:sub(1,1):upper() ~= "P" then
id = ""
end
else
id = replaceAlias(eid)
end
elseif page then
if page:sub(1,1) == ":" then
page = mw.text.trim(page:sub(2))
end
id = mw.wikibase.getEntityIdForTitle(page) or ""
end
if not id then
id = mw.wikibase.getEntityIdForCurrentPage() or ""
end
id = id:upper()
if not mw.wikibase.isValidEntityId(id) then
id = ""
end
return id, prop
end
local function nextArg(args)
local arg = args[args.pointer]
if arg then
args.pointer = args.pointer + 1
return mw.text.trim(arg)
else
return nil
end
end
local function claimCommand(args, funcName)
local cfg = Config:new()
cfg:processFlagOrCommand(funcName)
local lastArg, parsedFormat, formatParams, claims, value
local hooks = {count = 0}
if args[p.args.date] then
cfg.atDate = {parseDate(args[p.args.date])}
cfg.periods = {false, true, false}
end
repeat
lastArg = nextArg(args)
until not cfg:processFlagOrCommand(lastArg)
cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page])
if cfg.entityID == "" then
return ""
end
cfg.entity = mw.wikibase.getEntity(cfg.entityID)
if not cfg.propertyID then
cfg.propertyID = nextArg(args)
end
cfg.propertyID = replaceAlias(cfg.propertyID)
if not cfg.entity or not cfg.propertyID then
return ""
end
cfg.propertyID = cfg.propertyID:upper()
if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then
return ""
end
claims = cfg.entity.claims[cfg.propertyID]
if cfg.states.qualifiersCount > 0 then
if #args - args.pointer + 1 > cfg.states.qualifiersCount then
cfg.propertyValue = nextArg(args)
end
for i = 1, cfg.states.qualifiersCount do
cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper()
end
elseif cfg.states[parameters.reference] then
cfg.propertyValue = nextArg(args)
end
if cfg.propertyValue then
cfg.propertyValue = replaceSpecialChars(cfg.propertyValue)
if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then
cfg.propertyValue = " "
else
cfg.propertyValue = mw.text.trim(cfg.propertyValue)
end
end
if args["format"] then
parsedFormat, formatParams = parseFormat(args["format"])
elseif cfg.states.qualifiersCount > 0 then
if cfg.states[parameters.property] then
parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier)
else
parsedFormat, formatParams = parseFormat(formats.qualifier)
end
elseif cfg.states[parameters.property] then
parsedFormat, formatParams = parseFormat(formats.property)
else
parsedFormat, formatParams = parseFormat(formats.reference)
end
if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then
cfg.separators["sep"..parameters.separator][1] = {";"}
end
if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0
and not cfg.states[parameters.reference].rawValue then
cfg.separators["sep"][1] = nil
end
if cfg.states.qualifiersCount == 1 then
cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"]
end
cfg:processSeparators(args)
for i, v in pairs(cfg.states) do
if formatParams[i] or formatParams[i:sub(1, 2)] then
hooks[i] = getHookName(i, 1)
hooks.count = hooks.count + 1
end
end
if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then
hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1)
hooks.count = hooks.count + 1
end
if not cfg.states[parameters.property] then
cfg.states[parameters.property] = State:new(cfg, parameters.property)
if cfg.singleClaim then
cfg.states[parameters.property].singleValue = true
end
end
if cfg.sourcedOnly and not cfg.states[parameters.reference] then
cfg:processFlagOrCommand(p.claimCommands.reference)
end
cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat)
for i, v in pairs(args) do
i = tostring(i)
if i:match('^[Pp]%d+$') or aliasesP[i] then
v = replaceSpecialChars(v)
if v ~= "" and mw.text.trim(v) == "" then
v = " "
end
cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v
end
end
claims = sortOnRank(claims)
value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches))
if cfg.editable and value ~= "" then
value = value .. cfg:getEditIcon()
end
return value
end
local function generalCommand(args, funcName)
local cfg = Config:new()
cfg.curState = State:new(cfg)
local lastArg
local value = nil
repeat
lastArg = nextArg(args)
until not cfg:processFlag(lastArg)
cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true)
if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then
return ""
end
if funcName == p.generalCommands.label then
value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName)
elseif funcName == p.generalCommands.title then
cfg.inSitelinks = true
if cfg.entityID:sub(1,1) == "Q" then
value = mw.wikibase.getSitelink(cfg.entityID)
end
if cfg.curState.linked and value then
value = buildWikilink(value)
end
elseif funcName == p.generalCommands.description then
value = mw.wikibase.getDescription(cfg.entityID)
else
local parsedFormat, formatParams
local hooks = {count = 0}
cfg.entity = mw.wikibase.getEntity(cfg.entityID)
if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then
cfg.curState.singleValue = true
end
if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then
if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then
return ""
end
local aliases = cfg.entity.aliases[cfg.langCode]
if args["format"] then
parsedFormat, formatParams = parseFormat(args["format"])
else
parsedFormat, formatParams = parseFormat(formats.alias)
end
cfg:processSeparators(args)
if formatParams[parameters.alias] then
hooks[parameters.alias] = getHookName(parameters.alias, 1)
hooks.count = hooks.count + 1
end
cfg:setFormatAndSeparators(cfg.curState, parsedFormat)
value = cfg:concatValues(cfg.curState:iterate(aliases, hooks))
elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then
if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then
return ""
end
local badges = cfg.entity.sitelinks[cfg.siteID].badges
cfg.inSitelinks = true
if args["format"] then
parsedFormat, formatParams = parseFormat(args["format"])
else
parsedFormat, formatParams = parseFormat(formats.badge)
end
cfg:processSeparators(args)
if formatParams[parameters.badge] then
hooks[parameters.badge] = getHookName(parameters.badge, 1)
hooks.count = hooks.count + 1
end
cfg:setFormatAndSeparators(cfg.curState, parsedFormat)
value = cfg:concatValues(cfg.curState:iterate(badges, hooks))
end
end
value = value or ""
if cfg.editable and value ~= "" then
value = value .. cfg:getEditIcon()
end
return value
end
local function establishCommands(commandList, commandFunc)
for _, commandName in pairs(commandList) do
local function wikitextWrapper(frame)
local args = copyTable(frame.args)
args.pointer = 1
loadI18n(aliasesP, frame)
return commandFunc(args, commandName)
end
p[commandName] = wikitextWrapper
local function luaWrapper(args)
args = copyTable(args)
args.pointer = 1
loadI18n(aliasesP)
return commandFunc(args, commandName)
end
p["_" .. commandName] = luaWrapper
end
end
establishCommands(p.claimCommands, claimCommand)
establishCommands(p.generalCommands, generalCommand)
function p.main(frame)
if not mw.wikibase then return nil end
local f, args
loadI18n(aliasesP, frame)
frame = frame:getParent() or frame
if not frame.args[1] then
throwError("no-function-specified")
end
f = mw.text.trim(frame.args[1])
if f == "main" then
throwError("main-called-twice")
end
assert(p["_"..f], errorText('no-such-function', f))
args = copyTable(frame.args)
table.remove(args, 1)
return p["_"..f](args)
end
return p