Module:FamilyTree: Difference between revisions
From KB Lexicon
No edit summary |
No edit summary |
||
| Line 25: | Line 25: | ||
return false | return false | ||
end | end | ||
local lowered = mw.ustring.lower(v) | local lowered = mw.ustring.lower(v) | ||
return lowered ~= 'unknown' and lowered ~= 'none' and lowered ~= 'n/a' | return lowered ~= 'unknown' and lowered ~= 'none' and lowered ~= 'n/a' | ||
| Line 45: | Line 44: | ||
local out = {} | local out = {} | ||
local seen = {} | local seen = {} | ||
for _, v in ipairs(list or {}) do | for _, v in ipairs(list or {}) do | ||
if isRealValue(v) and not seen[v] then | if isRealValue(v) and not seen[v] then | ||
| Line 52: | Line 50: | ||
end | end | ||
end | end | ||
return out | return out | ||
end | end | ||
| Line 82: | Line 72: | ||
local function getRoot(frame) | local function getRoot(frame) | ||
return getArg(frame, 'root') or getArg(frame, 'person') or mw.title.getCurrentTitle().text | return getArg(frame, 'root') or getArg(frame, 'person') or mw.title.getCurrentTitle().text | ||
end | |||
local function makeLink(name, displayName) | |||
if not isRealValue(name) then | |||
return '' | |||
end | |||
displayName = trim(displayName) or name | |||
return string.format('[[%s|%s]]', name, displayName) | |||
end | end | ||
| Line 281: | Line 279: | ||
-- ========================================= | -- ========================================= | ||
-- Rendering | -- Rendering helpers | ||
-- ========================================= | -- ========================================= | ||
local function | local function renderCard(people, name) | ||
if not isRealValue(name) then | if not isRealValue(name) then | ||
return nil | return nil | ||
end | end | ||
local | local person = people[name] or { name = name, displayName = name } | ||
local card = html.create('div') | |||
card:addClass('kbft-card') | |||
card:wikitext(makeLink(person.name, person.displayName)) | |||
return card | |||
return | |||
end | end | ||
local function | local function renderSingle(people, name) | ||
local wrap = html.create('div') | |||
wrap:addClass('kbft-single') | |||
wrap:node(renderCard(people, name)) | |||
return wrap | |||
end | |||
local function renderRow(people, names) | |||
local row = html.create('div') | local row = html.create('div') | ||
row:addClass(' | row:addClass('kbft-row') | ||
for _, name in ipairs(names) do | for _, name in ipairs(names) do | ||
row:node(renderSingle(people, name)) | |||
end | end | ||
| Line 327: | Line 313: | ||
end | end | ||
local function | -- ========================================= | ||
-- Public renderers | |||
-- ========================================= | |||
local function renderConnectedForRoot(people, root) | |||
local person = people[root] | local person = people[root] | ||
if not person then | if not person then | ||
| Line 333: | Line 323: | ||
end | end | ||
local | local connected = getConnectedPeople(people, root) | ||
sortNames(people, connected) | |||
local node = html.create('div') | |||
node:addClass('kbft-tree') | |||
node:tag('div') | |||
:addClass('kbft-title') | |||
:wikitext('Connected to ' .. makeLink(person.name, person.displayName)) | |||
local | local row = node:tag('div') | ||
row:addClass('kbft-row') | |||
for _, name in ipairs(connected) do | |||
row:node(renderSingle(people, name)) | |||
end | |||
return tostring(node) | |||
return tostring( | |||
end | end | ||
| Line 386: | Line 349: | ||
end | end | ||
local | local node = html.create('div') | ||
node:addClass('kbft-tree') | |||
node:tag('div') | |||
:addClass(' | :addClass('kbft-title') | ||
:wikitext(makeLink(person.name)) | :wikitext(makeLink(person.name, person.displayName)) | ||
local function addSection(label, values) | |||
values = uniq(values) | |||
local function | if #values == 0 then | ||
return | |||
end | end | ||
node:tag('div') | |||
:addClass('kbft-generation') | |||
:tag('div') | |||
:addClass('kbft-title') | |||
:wikitext(label) | |||
node:node(renderRow(people, values)) | |||
end | end | ||
addSection('Parents', person.parents) | |||
addSection('Partners', person.partners) | |||
addSection('Children', person.children) | |||
return tostring( | return tostring(node) | ||
end | end | ||
local function | local function renderTreeForRoot(people, root) | ||
local person = people[root] | local person = people[root] | ||
if not person then | if not person then | ||
| Line 437: | Line 384: | ||
end | end | ||
local | local grandparents = getGrandparents(people, root) | ||
sortNames(people, | local parents = uniq(person.parents) | ||
local siblings = getSiblings(people, root) | |||
local partners = uniq(person.partners) | |||
local children = uniq(person.children) | |||
sortNames(people, grandparents) | |||
sortNames(people, parents) | |||
sortNames(people, siblings) | |||
sortNames(people, partners) | |||
sortNames(people, children) | |||
local node = html.create('div') | |||
node:addClass('kbft-tree') | |||
node:tag('div') | |||
:addClass('kbft-title') | |||
:wikitext('Family Tree: ' .. makeLink(person.name, person.displayName)) | |||
if #grandparents > 0 then | |||
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, grandparents)) | |||
node:tag('div'):addClass('kbft-connector') | |||
end | |||
if #parents > 0 then | |||
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, parents)) | |||
node:tag('div'):addClass('kbft-connector') | |||
end | |||
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, { root })) | |||
if #siblings > 0 then | |||
node:tag('div'):addClass('kbft-connector') | |||
: | node:tag('div'):addClass('kbft-generation'):node(renderRow(people, siblings)) | ||
end | |||
if #partners > 0 then | |||
:addClass(' | node:tag('div'):addClass('kbft-connector') | ||
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, partners)) | |||
end | |||
if #children > 0 then | |||
node:tag('div'):addClass('kbft-connector') | |||
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, children)) | |||
end | end | ||
return tostring( | return tostring(node) | ||
end | end | ||
Revision as of 21:47, 29 March 2026
Documentation for this module may be created at Module:FamilyTree/doc
local p = {}
local cargo = mw.ext.cargo
local html = mw.html
-- =========================================
-- Helpers
-- =========================================
local function trim(s)
if s == nil then
return nil
end
s = tostring(s)
s = mw.text.trim(s)
if s == '' then
return nil
end
return s
end
local function isRealValue(v)
v = trim(v)
if not v then
return false
end
local lowered = mw.ustring.lower(v)
return lowered ~= 'unknown' and lowered ~= 'none' and lowered ~= 'n/a'
end
local function addUnique(list, value)
if not isRealValue(value) then
return
end
for _, existing in ipairs(list) do
if existing == value then
return
end
end
table.insert(list, value)
end
local function uniq(list)
local out = {}
local seen = {}
for _, v in ipairs(list or {}) do
if isRealValue(v) and not seen[v] then
seen[v] = true
table.insert(out, v)
end
end
return out
end
local function getArg(frame, key)
local v = frame.args[key]
if isRealValue(v) then
return trim(v)
end
local parent = frame:getParent()
if parent then
v = parent.args[key]
if isRealValue(v) then
return trim(v)
end
end
return nil
end
local function getRoot(frame)
return getArg(frame, 'root') or getArg(frame, 'person') or mw.title.getCurrentTitle().text
end
local function makeLink(name, displayName)
if not isRealValue(name) then
return ''
end
displayName = trim(displayName) or name
return string.format('[[%s|%s]]', name, displayName)
end
local function ensurePerson(people, name)
name = trim(name)
if not isRealValue(name) then
return nil
end
if not people[name] then
people[name] = {
name = name,
displayName = name,
parents = {},
children = {},
partners = {}
}
end
return people[name]
end
local function sortNames(people, names)
table.sort(names, function(a, b)
local ad = (people[a] and people[a].displayName) or a
local bd = (people[b] and people[b].displayName) or b
return mw.ustring.lower(ad) < mw.ustring.lower(bd)
end)
end
-- =========================================
-- Data loading
-- =========================================
local function loadCharacters()
local results = cargo.query(
'Characters',
'Page,DisplayName',
{ limit = 5000 }
)
local people = {}
for _, row in ipairs(results) do
local page = trim(row.Page)
local displayName = trim(row.DisplayName)
if isRealValue(page) then
people[page] = {
name = page,
displayName = displayName or page,
parents = {},
children = {},
partners = {}
}
end
end
return people
end
local function loadParentChild(people)
local results = cargo.query(
'ParentChild',
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder',
{ limit = 5000 }
)
for _, row in ipairs(results) do
local child = trim(row.Child)
local p1 = trim(row.Parent1)
local p2 = trim(row.Parent2)
if isRealValue(child) then
ensurePerson(people, child)
if isRealValue(p1) then
ensurePerson(people, p1)
addUnique(people[child].parents, p1)
addUnique(people[p1].children, child)
end
if isRealValue(p2) then
ensurePerson(people, p2)
addUnique(people[child].parents, p2)
addUnique(people[p2].children, child)
end
end
end
end
local function loadUnions(people)
local results = cargo.query(
'Unions',
'UnionID,Partner1,Partner2,UnionType,Status',
{ limit = 5000 }
)
for _, row in ipairs(results) do
local p1 = trim(row.Partner1)
local p2 = trim(row.Partner2)
if isRealValue(p1) then
ensurePerson(people, p1)
end
if isRealValue(p2) then
ensurePerson(people, p2)
end
if isRealValue(p1) and isRealValue(p2) then
addUnique(people[p1].partners, p2)
addUnique(people[p2].partners, p1)
end
end
end
local function finalizePeople(people)
for _, person in pairs(people) do
person.parents = uniq(person.parents)
person.children = uniq(person.children)
person.partners = uniq(person.partners)
end
end
local function loadData()
local people = loadCharacters()
loadParentChild(people)
loadUnions(people)
finalizePeople(people)
return people
end
-- =========================================
-- Relationship helpers
-- =========================================
local function getGrandparents(people, root)
local out = {}
local person = people[root]
if not person then
return out
end
for _, parentName in ipairs(person.parents) do
local parent = people[parentName]
if parent then
for _, gp in ipairs(parent.parents) do
addUnique(out, gp)
end
end
end
return uniq(out)
end
local function getSiblings(people, root)
local out = {}
local seen = {}
local person = people[root]
if not person then
return out
end
for _, parentName in ipairs(person.parents) do
local parent = people[parentName]
if parent then
for _, childName in ipairs(parent.children) do
if childName ~= root and not seen[childName] then
seen[childName] = true
table.insert(out, childName)
end
end
end
end
return uniq(out)
end
local function getConnectedPeople(people, root)
local out = {}
local person = people[root]
if not person then
return out
end
for _, v in ipairs(person.parents) do addUnique(out, v) end
for _, v in ipairs(person.children) do addUnique(out, v) end
for _, v in ipairs(person.partners) do addUnique(out, v) end
local siblings = getSiblings(people, root)
for _, v in ipairs(siblings) do addUnique(out, v) end
local grandparents = getGrandparents(people, root)
for _, v in ipairs(grandparents) do addUnique(out, v) end
return uniq(out)
end
-- =========================================
-- Rendering helpers
-- =========================================
local function renderCard(people, name)
if not isRealValue(name) then
return nil
end
local person = people[name] or { name = name, displayName = name }
local card = html.create('div')
card:addClass('kbft-card')
card:wikitext(makeLink(person.name, person.displayName))
return card
end
local function renderSingle(people, name)
local wrap = html.create('div')
wrap:addClass('kbft-single')
wrap:node(renderCard(people, name))
return wrap
end
local function renderRow(people, names)
local row = html.create('div')
row:addClass('kbft-row')
for _, name in ipairs(names) do
row:node(renderSingle(people, name))
end
return row
end
-- =========================================
-- Public renderers
-- =========================================
local function renderConnectedForRoot(people, root)
local person = people[root]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(root) .. '".'
end
local connected = getConnectedPeople(people, root)
sortNames(people, connected)
local node = html.create('div')
node:addClass('kbft-tree')
node:tag('div')
:addClass('kbft-title')
:wikitext('Connected to ' .. makeLink(person.name, person.displayName))
local row = node:tag('div')
row:addClass('kbft-row')
for _, name in ipairs(connected) do
row:node(renderSingle(people, name))
end
return tostring(node)
end
local function renderProfileForRoot(people, root)
local person = people[root]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(root) .. '".'
end
local node = html.create('div')
node:addClass('kbft-tree')
node:tag('div')
:addClass('kbft-title')
:wikitext(makeLink(person.name, person.displayName))
local function addSection(label, values)
values = uniq(values)
if #values == 0 then
return
end
node:tag('div')
:addClass('kbft-generation')
:tag('div')
:addClass('kbft-title')
:wikitext(label)
node:node(renderRow(people, values))
end
addSection('Parents', person.parents)
addSection('Partners', person.partners)
addSection('Children', person.children)
return tostring(node)
end
local function renderTreeForRoot(people, root)
local person = people[root]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(root) .. '".'
end
local grandparents = getGrandparents(people, root)
local parents = uniq(person.parents)
local siblings = getSiblings(people, root)
local partners = uniq(person.partners)
local children = uniq(person.children)
sortNames(people, grandparents)
sortNames(people, parents)
sortNames(people, siblings)
sortNames(people, partners)
sortNames(people, children)
local node = html.create('div')
node:addClass('kbft-tree')
node:tag('div')
:addClass('kbft-title')
:wikitext('Family Tree: ' .. makeLink(person.name, person.displayName))
if #grandparents > 0 then
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, grandparents))
node:tag('div'):addClass('kbft-connector')
end
if #parents > 0 then
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, parents))
node:tag('div'):addClass('kbft-connector')
end
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, { root }))
if #siblings > 0 then
node:tag('div'):addClass('kbft-connector')
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, siblings))
end
if #partners > 0 then
node:tag('div'):addClass('kbft-connector')
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, partners))
end
if #children > 0 then
node:tag('div'):addClass('kbft-connector')
node:tag('div'):addClass('kbft-generation'):node(renderRow(people, children))
end
return tostring(node)
end
-- =========================================
-- Public functions
-- =========================================
function p.tree(frame)
local root = getRoot(frame)
local people = loadData()
return renderTreeForRoot(people, root)
end
function p.profile(frame)
local root = getRoot(frame)
local people = loadData()
return renderProfileForRoot(people, root)
end
function p.connected(frame)
local root = getRoot(frame)
local people = loadData()
return renderConnectedForRoot(people, root)
end
return p