Content deleted Content added
mNo edit summary |
Added option to use multiple qualifier(s) commands, and fixed issue with references |
||
Line 30:
qualifier = "%q",
reference = "%r",
separator = "%s",
general = "%x"
}
Line 40 ⟶ 41:
}
local hookNames = { -- {level_1, level_2}
[parameters.property] = {"getProperty"},
[parameters.
[parameters.qualifier] = {"
[parameters.
}
local defaultSeparators = {
["sep"] = " " ,
["sep%s"] = "," ,
["sep%q"] = "; " ,
["sep%q\\d"] = ", " ,
["sep%r"] = "" ,
["punc"] = ""
}
Line 57 ⟶ 67:
cfg.separators = {
-- use tables so that we can pass by reference
["sep"] = {defaultSeparators["
["sep%s"] = {defaultSeparators["
["sep%q"] = {defaultSeparators["
["sep%r"] = {defaultSeparators["sep%r"]},
["punc"] = {defaultSeparators["punc"]}
}
Line 67 ⟶ 77:
cfg.propertyID = nil
cfg.propertyValue = nil
cfg.
cfg.bestRank = true
Line 87 ⟶ 97:
cfg.states = {}
cfg.states.qualifiersCount = 0
cfg.curState = nil
Line 128 ⟶ 139:
function extraRequiredParameterError(param)
return "Parameter '" .. param .. "' must be defined as optional"
end
-- used to make frame.args mutable and to replace #frame.args (which is always 0) with the actual amount
function copyTable(tIn)
local tOut = {}
for i, v in pairs(tIn) do
tOut[i] = v
end
return tOut
end
-- used to merge output tables together;
-- note that it currently mutates the first input table
function mergeTables(t1, t2)
for i = 1, #t2 do
t1[#t1 + 1] = t2[i]
end
return t1
end
-- used to create the final output string when it's all done,
-- so that extensionTag("ref") is only called when it's really in the final output
function concatValues(valuesArray)
local outString = ""
for i = 1, #valuesArray do
if valuesArray[i].refHash 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 = "wikidata-" .. valuesArray[i].refHash})
else
outString = outString .. valuesArray[i][1]
end
end
return outString
end
function getHookName(param, index)
if hookNames[param] then
return hookNames[param][index]
elseif string.len(param) > 2 then
return hookNames[string.sub(param, 1, 2).."\\d"][index]
else
return nil
end
end
Line 321 ⟶ 380:
end
-- The following function parses a format string.
--
-- The example below shows how a parsed string is structured in memory.
-- Variables other than 'str' and 'child' are left out for clarity's sake.
--
-- Example:
-- "A %p B [%s[%q1]] C [%r] D"
--
-- Structure:
-- [
-- {
-- str = "A "
-- },
-- {
-- str = "%p"
-- },
-- {
-- str = " B ",
-- child =
-- [
-- {
-- str = "%s",
-- child =
-- [
-- {
-- str = "%q1"
-- }
-- ]
-- }
-- ]
-- },
-- {
-- str = " C ",
-- child =
-- [
-- {
-- str = "%r"
-- }
-- ]
-- },
-- {
-- str = " D"
-- }
-- ]
--
function parseFormat(str)
local chr, esc, param, root, cur, prev, new
local params = {}
Line 333 ⟶ 437:
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
Line 338 ⟶ 456:
root.req = {}
cur = newObject(root)
prev = nil
esc = false
param =
for i = 1, #str do
Line 347 ⟶ 466:
if not esc then
if chr == '\\' then
endParam()
esc = true
elseif chr == '%' then
endParam()
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
if not string.match(chr, '%d') then
end
end
end
else
cur.str = cur.str .. chr
esc = false
end
prev = nil
end
endParam()
return root, params
Line 921 ⟶ 1,045:
param = parameters.property
elseif flag:match('^qualifier[s]?$') then
self.states.qualifiersCount = self.states.qualifiersCount + 1
param = parameters.qualifier .. self.states.qualifiersCount
self.separators["sep"..param] = {defaultSeparators["sep%q\\d"]}
elseif flag:match('^reference[s]?$') then
param = parameters.reference
Line 936 ⟶ 1,062:
-- use "%x" as the general parameter name
self.states[param].parsedFormat = parseFormat(
-- set the separator
Line 1,009 ⟶ 1,135:
if (startTimeY == nil or not datePrecedesDate(now['year'], now['month'], now['day'], startTimeY, startTimeM, startTimeD)) and
(endTimeY == nil or datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD)) then
end
end
Line 1,090 ⟶ 1,216:
function State:out()
local out =
local function walk(formatTable, result)
local
for i, v in pairs(formatTable.req) do
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
return
end
end
Line 1,105 ⟶ 1,231:
for i, v in ipairs(formatTable) do
if v.param then
else
end
if v.child then
end
end
return
end
local function prepend(results)
local sep = ""
local result
local valuesArray -- array with value objects
-- iterate from back to front, so that we know when to add separators
Line 1,127 ⟶ 1,254:
-- if there is already some output, then add the separators
if #out
sep = self.separator[1] -- fixed separator
result[parameters.separator] = { {self.movSeparator[1]} } -- movable separator
else
sep = ""
result[parameters.separator] = { {self.puncMark[1]} } -- optional punctuation mark
end
if
out = mergeTables(valuesArray, out)
end
end
Line 1,157 ⟶ 1,285:
-- level 1 hook
function State:getProperty(claim)
if #value > 0 then
return {value} -- wrap the value object in an array and return it
else
return {} -- return empty array if there was no value
end
end
-- level 1 hook
function State:getQualifiers(claim, param)
local qualifiers
if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.
if qualifiers then
-- 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
return
end
end
Line 1,175 ⟶ 1,310:
-- level 2 hook
function State:getQualifier(snak)
if #value > 0 then
return {value} -- wrap the value object in an array and return it
else
return {} -- return empty array if there was no value
end
end
-- level 1 hook
function State:getAllQualifiers(claim, param, result, hooks)
local out = {} -- array with value objects
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
-- if a hook has not been called yet, call it now
if not result[parameters.qualifier..i] then
self:callHook(parameters.qualifier..i, hooks, claim, result)
end
-- if there is output for this particular "qualifier(s)" command, then add it
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 then
out[#out + 1] = {sep}
end
out = mergeTables(out, result[parameters.qualifier..i])
end
end
return out
end
Line 1,181 ⟶ 1,350:
function State:getReferences(claim)
if claim.references then
-- 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
return
end
end
Line 1,193 ⟶ 1,363:
local snakValue, lang, property
local value = ""
local ref = {}
local snaks = {}
local params = {}
Line 1,264 ⟶ 1,435:
if value ~= "" then
ref = {value} -- create one value object
if not self.rawValue then
--
end
ref = {ref} -- wrap the value object in an array
end
end
return ref
end
function State:callHook(param, hooks, statement, result)
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
valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects
-- add to the result
if #valuesArray > 0 then
result[param] = valuesArray
result.count = result.count + 1
else
result[param] = {} -- an empty array to indicate that we've tried this hook already
return true -- miss == true
end
end
return
end
Line 1,291 ⟶ 1,484:
if matches then
result = {count = 0} -- collection of arrays with value objects
doAppend = true
Line 1,311 ⟶ 1,504:
if doAppend then
local function walk(formatTable)
local
for i2, v2 in pairs(formatTable.req) do
-- call a hook, adding its return value to the result
end
Line 1,364 ⟶ 1,553:
function p.property(frame)
return p._property(copyTable(frame.args))
end
Line 1,372 ⟶ 1,561:
function p.properties(frame)
return p._properties(copyTable(frame.args))
end
Line 1,380 ⟶ 1,569:
function p.qualifier(frame)
return p._qualifier(copyTable(frame.args))
end
Line 1,388 ⟶ 1,577:
function p.qualifiers(frame)
return p._qualifiers(copyTable(frame.args))
end
Line 1,396 ⟶ 1,585:
function p.reference(frame)
return p._reference(copyTable(frame.args))
end
Line 1,404 ⟶ 1,593:
function p.references(frame)
return p._references(copyTable(frame.args))
end
Line 1,450 ⟶ 1,639:
_.propertyID = _.propertyID:upper()
if _.states
-- do further processing if "qualifier(s)" command was given
-- claim ID or literal value has been given
nextArg = args[nextIndex] -- don't trim because might be single space representing 'somevalue'
nextIndex = nextIndex + 1
_.propertyValue = nextArg
end
for i = 1, _.states.qualifiersCount do
nextArg = mw.text.trim(args[nextIndex] or "") -- is a qualifierID
nextIndex = nextIndex + 1
-- check if given qualifier ID is an alias
if aliasesP[nextArg] then
nextArg = aliasesP[nextArg]
end
_.qualifierIDs[parameters.qualifier..i] = nextArg:upper()
end
elseif _.states[parameters.reference] then
-- do further processing if "reference(s)" command was given
Line 1,498 ⟶ 1,683:
if args["format"] then
parsedFormat, formatParams = parseFormat(mw.text.trim(args["format"]))
elseif _.states
if _.states[parameters.property] then
parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier)
Line 1,513 ⟶ 1,698:
_.separators["sep"][1] = ""
end
end
-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent
-- must come BEFORE overriding the separator values
if _.states.qualifiersCount == 1 then
_.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"]
end
Line 1,534 ⟶ 1,725:
-- define the hooks that should be called (getProperty, getQualifiers, getReferences);
-- only define a hook if both its command ("propert(y|ies)", "
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[string.sub(i, 1, 2)] then
hooks[i] = getHookName(i, 1)
hooks.count = hooks.count + 1
end
end
-- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...);
-- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given
if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then
hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1)
hooks.count = hooks.count + 1
end
Line 1,557 ⟶ 1,756:
if claims then
-- iterate through the claims to collect values
return concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property State with level 1 hooks and matchHook
else
return ""
Line 1,564 ⟶ 1,763:
function p.label(frame)
return p._label(copyTable(frame.args))
end
Line 1,684 ⟶ 1,883:
function p.title(frame)
return p._title(copyTable(frame.args))
end
Line 1,693 ⟶ 1,892:
end
-- main function that is supposed to be used by
function p.main(frame)
local f, args, i, v
Line 1,707 ⟶ 1,906:
assert(p["_"..f], 'The function "' .. f .. '" does not exist')
-- copy arguments from immutable to mutable table
-- remove the function name from the list
|