Documentation for this module may be created at Module:Data/doc
-- <nowiki>
local p = {}
local h = {}
local cargo = require('Module:CargoUtil')
local yesno = require('Module:Yesno')
local dataModel = require("Module:Data/Model")
local util_text = require('Module:TextUtil')
local util_time = require('Module:TimeUtil')
local split = util_text.split
local gsplit = util_text.gsplit
local trim = util_text.trim
local langs = {'english', 'japanese'}
local locPrefixes = {
['Ability'] = true,
['Artifact'] = true,
['ConceptCard'] = true,
['Item'] = true,
['Job'] = true,
['Skill'] = true,
['Unit'] = true,
}
function h.getEntityType(args)
local name = args.name or mw.title.getCurrentTitle().prefixedText
return name:match('Data:Game/MasterParam/([^/]+)/.+')
end
local hasOtherdesc = false
local hasCharadesc = false
function p.displayLocs(data)
local locs = data.loc
if not locs then return end
local entityType = h.getEntityType(data)
if not entityType or not locPrefixes[entityType] then return end
local definitions = mw.loadData('Module:CargoDeclare/'..entityType..'Loc')
-- HOTFIX: "otherdesc"
local temp = {}
for param, obj in pairs(locs) do
if param == 'otherdesc' then
hasOtherdesc = true
param = 'desc_ot'
end
if param == 'charadesc' then
hasCharadesc = true
end
for lang, value in pairs(obj) do
temp[param] = temp[param] or {}
temp[param][lang] = value
end
end
locs = temp
-- Build localization wikitable
local wikitable = mw.html.create('table'):addClass('wikitable')
local tr = wikitable:tag('tr')
tr:tag('th'):wikitext('param')
for _, lang in ipairs(langs) do
tr:tag('th'):wikitext(lang)
end
for _, definition in ipairs(definitions) do
local field = definition.field
if field and locs[field] then
tr = wikitable:tag('tr')
tr:tag('th'):wikitext(field)
for _, lang in ipairs(langs) do
tr:tag('td'):wikitext(locs[field][lang])
end
end
end
return tostring(wikitable)
end
function p.storeLocs(nocargo, data)
local locs = data.loc
if not locs then return end
local entityType = h.getEntityType(data)
if not entityType or not locPrefixes[entityType] then return end
local iname = (data.gl or data.jp or {}).iname
local definitions = mw.loadData('Module:CargoDeclare/'..entityType..'Loc')
-- HOTFIX: "otherdesc"
local temp = {}
for param, obj in pairs(locs) do
if param == 'otherdesc' then
param = 'desc_ot'
end
for lang, value in pairs(obj) do
temp[param] = temp[param] or {}
temp[param][lang] = value
end
end
locs = temp
local rows = {}
for _, definition in ipairs(definitions) do
local field = definition.field
if field and locs[field] then
for lang, value in pairs(locs[field]) do
rows[lang] = rows[lang] or {
_table = entityType .. 'Loc',
iname = iname,
lang = lang,
}
rows[lang][field] = value
end
end
end
local result = {}
-- Store localization
if not yesno(nocargo) then
for _, row in pairs(rows) do
mw.logObject(row)
cargo.store(row)
end
end
return table.concat(result)
end
function h.getTitlePath()
local curTitle = mw.title.getCurrentTitle()
--if curTitle.prefixedText == "Module:Data" then curTitle = mw.title.new("Game/MasterParam/Unit/UN_V2_LOGI", "Data") end
local titleNodes = {curTitle.subpageText}
while curTitle.baseText ~= curTitle.text do
curTitle = curTitle.basePageTitle
table.insert(titleNodes, 1, curTitle.subpageText)
end
return titleNodes
end
-- Generates table for data page
function h.insertQueryResult(result, title, tbl)
local fields = {}
local found = {}
local count = 0
local hideCols = true
for i, row in pairs(tbl) do
count = count + 1
for col in ipairs(row) do
if found[col] == nil then
table.insert(fields, col)
found[col] = true
end
end
for col in pairs(row) do
if found[col] == nil then
table.insert(fields, col)
found[col] = true
hideCols = false
end
end
end
table.sort(fields, function(a, b) return a == "server" or a == "lang" or (b ~= "server" and b ~= "lang" and (a or '') < (b or '')) end)
if count == 0 then return nil end
if title ~= nil then table.insert(result, "==="..title.."===\n") end
table.insert(result, '{| class="wikitable"')
for i, col in ipairs(fields) do
table.insert(result, "\n|-")
if not hideCols then
table.insert(result, "\n! ")
table.insert(result, col)
end
-- Merge matching rows
local allSame = true
local testVal = tbl[next(tbl)][col]
for j, row in pairs(tbl) do
if type(testVal) == 'table' and type(row[col]) == 'table' then
for k, v in pairs(testVal) do if v ~= row[col][k] then allSame = false end end
for k, v in pairs(row[col]) do if v ~= testVal[k] then allSame = false end end
elseif row[col] ~= testVal then allSame = false end
end
local function writeVal(val)
if type(val) == "table" then
table.insert(result, "\n")
h.insertQueryResult(result, nil, {val})
else
table.insert(result, tostring(val or ''))
end
end
if allSame then
table.insert(result, '\n| colspan="'..count..'" | ')
writeVal(testVal)
else
for j, row in pairs(tbl) do
table.insert(result, "\n| ")
writeVal(row[col])
end
end
end
table.insert(result, "\n|}\n")
end
p.insertQueryResult = h.insertQueryResult
-- Will only actually perform the declares on the CargoTable template pages.
function p.init(frame)
local args = require('Module:Arguments').getArgs(frame, {parentFirst=true})
return p._init(args)
end
function p._init(args)
local declareStatements = {}
local recFunc
function recFunc(node)
if node.nodes ~= nil then for k, child in pairs(node.nodes) do recFunc(child) end end
if node.model ~= nil then for tbl, model in pairs(node.model) do
local statement = {"", _table = tbl, server = "String"}
for col, colModel in pairs(model) do
if type(colModel) == "string" then colModel = {colModel} end
local modelType = colModel[1]
if colModel.list ~= nil then modelType = "List ("..colModel.list..") of "..modelType end
statement[col] = modelType
end
declareStatements[tbl] = statement
end end
end
recFunc(dataModel)
local curTitle = mw.title.getCurrentTitle()
if args[1] then
curTitle = mw.title.new(args[1])
end
if curTitle.basePageTitle.prefixedText == "Template:CargoTable" then
local statement = declareStatements[curTitle.subpageText]
if statement == nil then return "Invalid table "..curTitle.subpageText end
local tbl = {'{{#cargo_declare:', '}}'}
for k,v in pairs(statement) do
if k ~= 1 then
tbl[#tbl+1] = '| ' .. k..' = '..v
end
end
table.sort(tbl)
local declare = table.concat(tbl, '\n')
if yesno(args.debug) then
return tostring(mw.html.create('pre'):wikitext(declare))
else
mw.log(declare)
return mw.getCurrentFrame():callParserFunction('#cargo_declare', statement)
end
end
if mw.getCurrentFrame():getParent() == nil then
return nil
end
return "asdf"
end
-- Called from Data pages
function p.insert(frame)
local args = require('Module:Arguments').getArgs(frame, {parentFirst = true})
return p._insert(args)
end
function p._insert(args)
local nocargo = args.nocargo
local curNode = {nodes = {Game = dataModel}}
local titlePath = h.getTitlePath()
for i, pathItem in ipairs(titlePath) do
if curNode == nil then return nil end
if i == #titlePath then break end
if curNode.nodes == nil then return nil end
curNode = curNode.nodes[pathItem]
end
if curNode.model == nil then return nil end
-- Now that we know this is a valid page
local result = {}
if next(curNode.model) ~= nil then table.insert(result, "==Tables==\nThe following tables have these rows defined by this data entry.\n") end
-- Decode JSON
local jsonSets = {}
for k, v in pairs(args) do
if type(v) == 'string' then
v = mw.text.decode(mw.text.unstripNoWiki(v))
local success, result = pcall(mw.text.jsonDecode, v)
if success then
v = result
end
args[k] = v
end
end
local content_sha256s = {}
local suffix = '_sha256'
for server, json in pairs(args) do
if server == 'gl' or server == 'jp' or server == 'loc' then
jsonSets[server] = json
elseif string.sub(server, -string.len(suffix)) == suffix then
-- TODO: Store this in a cargo table somewhere.
content_sha256s[string.sub(server, 1, -string.len(suffix))] = json
end
end
-- Convert Functions
local convertFrom = {
table = function(val, model)
if model.list ~= nil then
return table.concat(val, model.list)
end
error("JSON data for "..model[1].." is a table but is not modeled to handle it.")
end,
}
-- Insert Rows
local where = '_pageId='..mw.title.getCurrentTitle().id
local errors = {}
-- Do loc first
result[#result+1] = p.storeLocs(nocargo, args)
result[#result+1] = p.displayLocs(args)
local locSets = jsonSets.loc or {}
jsonSets.loc = nil
local expected = 0
for param, json in pairs(locSets) do
if type(json) == "table" then
for lang, value in pairs(json) do
cargo.store{
_table = 'Loc',
lang = lang,
param = param,
value = value,
}
expected = expected + 1
end
end
end
-- Query loc table
local rows = cargo.query{
tables = 'Loc',
fields = 'lang, param, value',
where = where,
orderBy = 'lang'
}
-- Check if expected row count matches actual row count.
local actual = #rows
if actual ~= expected then
table.insert(result, string.format('Entry count mismatch in table %q (%s expected, got %s)\n', 'Loc', expected, actual))
errors.cargoStore = true
end
-- Generate loc tables from queried data
local queryResult = {}
for i, row in ipairs(rows) do
if row.lang == 'english' and row.param == 'name' then
local pagename = string.gsub(row.value, "[%[%]]", {
["["] = "【",
["]"] = "】",
})
if pagename ~= row.value then
row.value = '[[' .. pagename .. '|' .. row.value .. ']]'
else
row.value = '[[' .. row.value .. ']]'
end
end
queryResult[i] = row
end
h.insertQueryResult(result, tbl, queryResult)
local iname
for server, json in pairs(jsonSets) do
if not iname and json.iname then
iname = json.iname
end
end
local actualTitle = table.concat(titlePath, '/', 1, 3)
mw.logObject(actualTitle, 'actualTitle')
if actualTitle == 'Game/MasterParam/Buff' then
local definitions = mw.loadData('Module:CargoDeclare/BuffDetail')
for server, json in pairs(jsonSets) do
for i=1,11 do
local values = {
_table = 'BuffDetail',
}
for _, definition in ipairs(definitions) do
local field = definition.field
if definition.type == 'Integer' then
values[field] = tonumber(json[field..i])
else
values[field] = json[field..i]
end
end
values.server = server
values.buff_iname = iname
values.idx = i
if (values.type or 0) <= 0 then
mw.logObject(values, "Skipped insert into BuffDetail values")
else
mw.logObject(values, "#cargo_store")
cargo.store(values)
end
end
end
end
if actualTitle == 'Game/MasterParam/Job' then
local definitions = mw.loadData('Module:CargoDeclare/JobRank')
local jobRanks = {}
for server, json in pairs(jsonSets) do
for i, rank in ipairs(json.ranks) do
local values = {
_table = 'JobRank',
}
for _, definition in ipairs(definitions) do
local field = definition.field
if rank[field] ~= nil then
if definition.type == 'Integer' then
values[field] = tonumber(rank[field])
else
values[field] = rank[field]
end
rank[field] = nil -- remove
end
end
values.server = server
values.job_iname = iname
values.idx = i
mw.logObject(values, "#cargo_store")
cargo.store(values)
values._table = nil
jobRanks[i] = jobRanks[i] or {}
jobRanks[i][#jobRanks[i]+1] = values
end
json.ranks = nil -- done
end
table.insert(result, '=== JobRank ===\n')
for i, rank in ipairs(jobRanks) do
h.insertQueryResult(result, nil, rank)
end
end
-- Now get the data
for tbl, model in pairs(curNode.model) do
expected = 0
for server, json in pairs(jsonSets) do
local statement = {_table = tbl, server = server}
for col, colModel in pairs(model) do
if type(colModel) == "string" then colModel = {colModel} end
-- Extract value
local jsonVal = json[colModel.from or colModel.alias or col]
json[colModel.from or colModel.alias or col] = nil
-- Convert datatypes here
local converter = convertFrom[type(jsonVal)]
if converter ~= nil then jsonVal = converter(jsonVal, colModel) end
if colModel[1] == 'Datetime' then
jsonVal = util_time.worldTimeToUTC(jsonVal, server) or jsonVal
end
-- Append to statement
statement[col] = jsonVal
end
mw.logObject(statement, 'statement')
cargo.store(statement)
expected = expected + 1
end
-- Query the data
local query = {
tables = tbl,
fields = {"server"},
where = where,
orderBy = 'server',
}
for col in pairs(model) do
-- Required to query only existing columns.
local ok, _ = pcall(cargo.query, {tables=tbl, fields=col, where=1, limit=1})
if ok then
table.insert(query.fields, col)
end
end
local rows = cargo.query(query)
local actual = #rows
if actual ~= expected then
table.insert(result, string.format('Entry count mismatch in table %q (%s expected, got %s)\n', tbl, expected, actual))
errors.cargoStore = true
end
-- Generate tables from queried data
queryResult = {}
for i, row in ipairs(rows) do
for col, val in pairs(row) do
if type(model[col]) == "table" then
if model[col].list ~= nil then
val = #val == 0 and {} or split(val, model[col].list, true)
end
if model[col].dataRef ~= nil then
if type(val) == "table" then
for k, v in pairs(val) do
if not v:find('__HIDE__$') then
val[k] = "[[Data:Game/"..model[col].dataRef.."/"..v.."|"..v.."]]"
else
val[k] = v
end
end
elseif #val > 0 and not val:find('__HIDE__$') then
val = "[[Data:Game/"..model[col].dataRef.."/"..val.."|"..val.."]]"
end
end
end
row[col] = val
end
queryResult[i] = row
end
h.insertQueryResult(result, tbl, queryResult)
end
local hasEffects = false
local jsonEffects = {}
for server, json in pairs(jsonSets) do
if json.effects then
hasEffects = true
jsonEffects[server] = {effects = {}}
for i,effect in ipairs(json.effects) do
local values = {}
values._table = 'ConceptCardEffect'
values.server = server
values.cc_iname = iname
jsonEffects[server].effects[i] = {}
for k,v in pairs(effect) do
values[k] = v
jsonEffects[server].effects[i][k] = v
effect[k] = nil
end
cargo.store(values)
jsonEffects[server].server = server
end
-- A table of failed effect insertions.
local errorEffects = {}
for i, effect in ipairs(json.effects) do
local empty = true
for k, v in pairs(effect) do empty = false end
if not empty then
errorEffects[#errorEffects+1] = effect
end
end
json.effects = #errorEffects > 0 and errorEffects or nil
end
end
if next(jsonEffects) then
table.insert(result, '===ConceptCardEffect===\n')
local toInsert = {}
for k in pairs(jsonEffects) do table.insert(toInsert, k) end
table.sort(toInsert)
for i, k in ipairs(toInsert) do toInsert[i] = jsonEffects[k] end
h.insertQueryResult(result, nil, toInsert)
end
local buffDetailRows = cargo.query{
tables = 'BuffDetail',
fields = 'buff_iname, server, idx, calc, type, tktag, vini, vmax, vone',
where = where,
orderBy = 'idx'
}
h.insertQueryResult(result, 'BuffDetail', buffDetailRows)
-- Display unused keys here
local unused = false
for server, json in pairs(jsonSets) do if next(json) ~= nil then
unused = true
json.server = server
end end
if unused then
table.insert(result, "===Unused Keys===\nThe following keys were not used by inserted into any table and are thus unused.\n")
local toInsert = {}
for k in pairs(jsonSets) do table.insert(toInsert, k) end
table.sort(toInsert)
for i, k in ipairs(toInsert) do toInsert[i] = jsonSets[k] end
h.insertQueryResult(result, nil, toInsert)
end
table.insert(result, '[[Category:Data pages]]') -- Tracking category.
-- Handle errors encountered
if errors.cargoStore then
table.insert(result, '[[Category:Data pages that failed a table insert]]' )
end
if hasEffects then
table.insert(result, '[[Category:Data pages with effects]]')
end
if hasOtherdesc then
table.insert(result, '[[Category:Data pages with otherdesc]]')
end
if hasCharadesc then
table.insert(result, '[[Category:Data pages with charadesc]]')
end
return table.concat(result)
end
-- Add functions to model
p.model = (function()
dataModel.tables = {}
local recFunc
function recFunc(node)
if node.nodes ~= nil then for k, child in pairs(node.nodes) do recFunc(child) end end
if node.model ~= nil then for tbl, model in pairs(node.model) do dataModel.tables[tbl] = model end end
end
recFunc(dataModel)
-----------
-- Query --
-----------
dataModel.query = function(tables, fields, args)
local query = args
query.tables = tables
query.fields = fields
local result = cargo.query(query)
local toCast = {}
if type(query.tables) == 'string' then
query.tables = split(query.tables, ',', true)
end
if type(query.fields) == 'string' then
query.fields = split(query.fields, ',', true)
end
for _, field in ipairs(query.fields) do
-- Extract alias
local t = split(field, '=', true)
local alias = trim(t[2] or t[1])
local fnMatch = string.match(t[1], "([^()]*)[(](.*)[)](.*)")
if fnMatch ~= nil then
mw.log(fnMatch)
toCast[alias] = {"String"}
else
-- Extract table
local t2 = split(t[1], '.', true)
local tbl = #t2 > 1 and trim(t2[1]) or nil
local key = trim(t2[#t2])
if tbl == nil then
for _, tblName in ipairs(query.tables) do
tblName = trim(tblName)
if (dataModel.tables[tblName] or {})[key] ~= nil then
tbl = tblName
break
end
end
end
local model = (dataModel.tables[tbl] or {})[key]
toCast[alias] = type(model) == "string" and {model} or model
end
end
local castFuncs = {
Integer = tonumber
}
for i, row in ipairs(result) do
for k, castTo in pairs(toCast) do
local castFunc = castFuncs[castTo[1]] or function(v) return v end
if castTo.list ~= nil then
local list = {}
if row[k] and row[k] ~= '' then
for s in gsplit(row[k], castTo.list, true) do
list[#list+1] = castFunc(s)
end
end
row[k] = list
else
row[k] = castFunc(row[k])
end
end
end
return result
end
------------
-- getLoc --
------------
dataModel.getLoc = function(_pageName, param, lang)
if _pageName == nil then return '???' end
local where = "_pageName='".._pageName.."' and param='"..param.."' and value<>'' and lang='"
local thisLang = cargo.query{tables='Loc',fields='value',where=where..(lang or "english'")}
if #thisLang == 0 then thisLang = cargo.query{tables='Loc',fields='value',where=where.."english'"} end
if #thisLang == 0 then thisLang = cargo.query{tables='Loc',fields='value',where=where.."japanese'"} end
if #thisLang == 0 then thisLang = {{value = ''}} end
return thisLang[1].value
end
-------------
-- Sources --
-------------
dataModel.sources = require("Module:Data/Sources")(dataModel)
return dataModel
end)()
return p