Module:FamilyTree: Difference between revisions
From KB Lexicon
No edit summary |
No edit summary |
||
| Line 79: | Line 79: | ||
return nil | return nil | ||
end | end | ||
local function ensurePerson(people, name) | local function ensurePerson(people, name) | ||
| Line 106: | Line 93: | ||
birthDate = nil, | birthDate = nil, | ||
deathDate = nil, | deathDate = nil, | ||
status = nil, | |||
birthFamily = nil, | birthFamily = nil, | ||
currentFamily = nil, | currentFamily = nil, | ||
father = nil, | |||
mother = nil, | |||
adoptiveFather = nil, | |||
adoptiveMother = nil, | |||
bloodStatus = nil, | |||
title = nil, | |||
heir = nil, | |||
illegitimate = nil, | |||
adopted = nil, | |||
parents = {}, | parents = {}, | ||
children = {}, | children = {}, | ||
| Line 118: | Line 114: | ||
return people[name] | return people[name] | ||
end | 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 | |||
local function yesNo(val) | |||
if val == nil then | |||
return nil | |||
end | |||
local s = mw.ustring.lower(tostring(val)) | |||
if s == '1' or s == 'true' or s == 'yes' then | |||
return 'Yes' | |||
end | |||
if s == '0' or s == 'false' or s == 'no' then | |||
return 'No' | |||
end | |||
return tostring(val) | |||
end | |||
-- ========================================= | |||
-- Data loading | |||
-- ========================================= | |||
local function queryCharacters() | local function queryCharacters() | ||
local results = cargo.query( | local results = cargo.query( | ||
'Characters', | 'Characters', | ||
' | 'Page,DisplayName,Gender,BirthDate,DeathDate,Status,BirthFamily,CurrentFamily,Father,Mother,AdoptiveFather,AdoptiveMother,BloodStatus,Title,Heir,Illegitimate,Adopted', | ||
{ limit = 5000 } | { limit = 5000 } | ||
) | ) | ||
| Line 130: | Line 152: | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
local | local page = trim(row.Page) | ||
if isRealValue( | if isRealValue(page) then | ||
people[ | people[page] = { | ||
name = | name = page, | ||
displayName = | displayName = trim(row.DisplayName) or page, | ||
gender = | gender = trim(row.Gender), | ||
birthDate = | birthDate = trim(row.BirthDate), | ||
deathDate = | deathDate = trim(row.DeathDate), | ||
birthFamily = | status = trim(row.Status), | ||
currentFamily = | birthFamily = trim(row.BirthFamily), | ||
currentFamily = trim(row.CurrentFamily), | |||
father = trim(row.Father), | |||
mother = trim(row.Mother), | |||
adoptiveFather = trim(row.AdoptiveFather), | |||
adoptiveMother = trim(row.AdoptiveMother), | |||
bloodStatus = trim(row.BloodStatus), | |||
title = trim(row.Title), | |||
heir = row.Heir, | |||
illegitimate = row.Illegitimate, | |||
adopted = row.Adopted, | |||
parents = {}, | parents = {}, | ||
children = {}, | children = {}, | ||
| Line 180: | Line 210: | ||
addUnique(people[p2].children, child) | addUnique(people[p2].children, child) | ||
end | end | ||
end | |||
end | |||
end | |||
local function loadCharacterParentFallbacks(people) | |||
for _, person in pairs(people) do | |||
if isRealValue(person.father) then | |||
ensurePerson(people, person.father) | |||
addUnique(person.parents, person.father) | |||
addUnique(people[person.father].children, person.name) | |||
end | |||
if isRealValue(person.mother) then | |||
ensurePerson(people, person.mother) | |||
addUnique(person.parents, person.mother) | |||
addUnique(people[person.mother].children, person.name) | |||
end | |||
if isRealValue(person.adoptiveFather) then | |||
ensurePerson(people, person.adoptiveFather) | |||
addUnique(person.parents, person.adoptiveFather) | |||
addUnique(people[person.adoptiveFather].children, person.name) | |||
end | |||
if isRealValue(person.adoptiveMother) then | |||
ensurePerson(people, person.adoptiveMother) | |||
addUnique(person.parents, person.adoptiveMother) | |||
addUnique(people[person.adoptiveMother].children, person.name) | |||
end | end | ||
end | end | ||
| Line 187: | Line 245: | ||
local results = cargo.query( | local results = cargo.query( | ||
'Unions', | 'Unions', | ||
'UnionID,Partner1,Partner2,UnionType,Status, | 'UnionID,Partner1,Partner2,UnionType,Status,StartDate,EndDate,MarriageDate,DivorceDate,EngagementDate', | ||
{ limit = 5000 } | { limit = 5000 } | ||
) | ) | ||
| Line 196: | Line 254: | ||
local p2 = trim(row.Partner2) | local p2 = trim(row.Partner2) | ||
if isRealValue(p1) then ensurePerson(people, p1) end | if isRealValue(p1) then | ||
if isRealValue(p2) then ensurePerson(people, p2) end | ensurePerson(people, p1) | ||
end | |||
if isRealValue(p2) then | |||
ensurePerson(people, p2) | |||
end | |||
if isRealValue(p1) and isRealValue(p2) then | if isRealValue(p1) and isRealValue(p2) then | ||
| Line 208: | Line 270: | ||
unionType = trim(row.UnionType), | unionType = trim(row.UnionType), | ||
status = trim(row.Status), | status = trim(row.Status), | ||
startDate = trim(row.StartDate), | |||
endDate = trim(row.EndDate), | |||
marriageDate = trim(row.MarriageDate), | marriageDate = trim(row.MarriageDate), | ||
divorceDate = trim(row.DivorceDate) | divorceDate = trim(row.DivorceDate), | ||
engagementDate = trim(row.EngagementDate) | |||
}) | }) | ||
| Line 218: | Line 282: | ||
unionType = trim(row.UnionType), | unionType = trim(row.UnionType), | ||
status = trim(row.Status), | status = trim(row.Status), | ||
startDate = trim(row.StartDate), | |||
endDate = trim(row.EndDate), | |||
marriageDate = trim(row.MarriageDate), | marriageDate = trim(row.MarriageDate), | ||
divorceDate = trim(row.DivorceDate) | divorceDate = trim(row.DivorceDate), | ||
engagementDate = trim(row.EngagementDate) | |||
}) | }) | ||
end | end | ||
| Line 226: | Line 292: | ||
end | end | ||
local function | local function finalizePeople(people) | ||
for _, person in pairs(people) do | for _, person in pairs(people) do | ||
person.parents = uniq(person.parents) | person.parents = uniq(person.parents) | ||
| Line 236: | Line 298: | ||
person.partners = uniq(person.partners) | person.partners = uniq(person.partners) | ||
end | end | ||
end | |||
local function loadData() | |||
local people = queryCharacters() | |||
loadParentChild(people) | |||
loadCharacterParentFallbacks(people) | |||
loadUnions(people) | |||
finalizePeople(people) | |||
return people | return people | ||
end | end | ||
| Line 243: | Line 312: | ||
-- Relationship helpers | -- Relationship helpers | ||
-- ========================================= | -- ========================================= | ||
local function getGrandparents(people, personName) | local function getGrandparents(people, personName) | ||
| Line 316: | Line 377: | ||
-- ========================================= | -- ========================================= | ||
-- Rendering | -- Rendering helpers | ||
-- ========================================= | -- ========================================= | ||
| Line 464: | Line 525: | ||
addField('Display Name', person.displayName ~= person.name and person.displayName or nil) | addField('Display Name', person.displayName ~= person.name and person.displayName or nil) | ||
addField('Title', person.title) | |||
addField('Gender', person.gender) | addField('Gender', person.gender) | ||
addField('Birth Date', person.birthDate) | addField('Birth Date', person.birthDate) | ||
addField('Death Date', person.deathDate) | addField('Death Date', person.deathDate) | ||
addField('Status', person.status) | |||
addField('Blood Status', person.bloodStatus) | |||
addField('Birth Family', person.birthFamily and makeLink(person.birthFamily) or nil) | addField('Birth Family', person.birthFamily and makeLink(person.birthFamily) or nil) | ||
addField('Current Family', person.currentFamily and makeLink(person.currentFamily) or nil) | addField('Current Family', person.currentFamily and makeLink(person.currentFamily) or nil) | ||
addField(' | addField('Heir', yesNo(person.heir)) | ||
addField('Illegitimate', yesNo(person.illegitimate)) | |||
addField('Adopted', yesNo(person.adopted)) | |||
if #person.parents > 0 then | if #person.parents > 0 then | ||
Revision as of 21:38, 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 makeLink(name)
if not isRealValue(name) then
return ''
end
return string.format('[[%s|%s]]', name, name)
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 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,
gender = nil,
birthDate = nil,
deathDate = nil,
status = nil,
birthFamily = nil,
currentFamily = nil,
father = nil,
mother = nil,
adoptiveFather = nil,
adoptiveMother = nil,
bloodStatus = nil,
title = nil,
heir = nil,
illegitimate = nil,
adopted = nil,
parents = {},
children = {},
partners = {},
unions = {}
}
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
local function yesNo(val)
if val == nil then
return nil
end
local s = mw.ustring.lower(tostring(val))
if s == '1' or s == 'true' or s == 'yes' then
return 'Yes'
end
if s == '0' or s == 'false' or s == 'no' then
return 'No'
end
return tostring(val)
end
-- =========================================
-- Data loading
-- =========================================
local function queryCharacters()
local results = cargo.query(
'Characters',
'Page,DisplayName,Gender,BirthDate,DeathDate,Status,BirthFamily,CurrentFamily,Father,Mother,AdoptiveFather,AdoptiveMother,BloodStatus,Title,Heir,Illegitimate,Adopted',
{ limit = 5000 }
)
local people = {}
for _, row in ipairs(results) do
local page = trim(row.Page)
if isRealValue(page) then
people[page] = {
name = page,
displayName = trim(row.DisplayName) or page,
gender = trim(row.Gender),
birthDate = trim(row.BirthDate),
deathDate = trim(row.DeathDate),
status = trim(row.Status),
birthFamily = trim(row.BirthFamily),
currentFamily = trim(row.CurrentFamily),
father = trim(row.Father),
mother = trim(row.Mother),
adoptiveFather = trim(row.AdoptiveFather),
adoptiveMother = trim(row.AdoptiveMother),
bloodStatus = trim(row.BloodStatus),
title = trim(row.Title),
heir = row.Heir,
illegitimate = row.Illegitimate,
adopted = row.Adopted,
parents = {},
children = {},
partners = {},
unions = {}
}
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 loadCharacterParentFallbacks(people)
for _, person in pairs(people) do
if isRealValue(person.father) then
ensurePerson(people, person.father)
addUnique(person.parents, person.father)
addUnique(people[person.father].children, person.name)
end
if isRealValue(person.mother) then
ensurePerson(people, person.mother)
addUnique(person.parents, person.mother)
addUnique(people[person.mother].children, person.name)
end
if isRealValue(person.adoptiveFather) then
ensurePerson(people, person.adoptiveFather)
addUnique(person.parents, person.adoptiveFather)
addUnique(people[person.adoptiveFather].children, person.name)
end
if isRealValue(person.adoptiveMother) then
ensurePerson(people, person.adoptiveMother)
addUnique(person.parents, person.adoptiveMother)
addUnique(people[person.adoptiveMother].children, person.name)
end
end
end
local function loadUnions(people)
local results = cargo.query(
'Unions',
'UnionID,Partner1,Partner2,UnionType,Status,StartDate,EndDate,MarriageDate,DivorceDate,EngagementDate',
{ limit = 5000 }
)
for _, row in ipairs(results) do
local unionID = trim(row.UnionID)
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)
table.insert(people[p1].unions, {
unionID = unionID,
partner = p2,
unionType = trim(row.UnionType),
status = trim(row.Status),
startDate = trim(row.StartDate),
endDate = trim(row.EndDate),
marriageDate = trim(row.MarriageDate),
divorceDate = trim(row.DivorceDate),
engagementDate = trim(row.EngagementDate)
})
table.insert(people[p2].unions, {
unionID = unionID,
partner = p1,
unionType = trim(row.UnionType),
status = trim(row.Status),
startDate = trim(row.StartDate),
endDate = trim(row.EndDate),
marriageDate = trim(row.MarriageDate),
divorceDate = trim(row.DivorceDate),
engagementDate = trim(row.EngagementDate)
})
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 = queryCharacters()
loadParentChild(people)
loadCharacterParentFallbacks(people)
loadUnions(people)
finalizePeople(people)
return people
end
-- =========================================
-- Relationship helpers
-- =========================================
local function getGrandparents(people, personName)
local out = {}
local person = people[personName]
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, personName)
local out = {}
local seen = {}
local person = people[personName]
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 ~= personName and not seen[childName] then
seen[childName] = true
table.insert(out, childName)
end
end
end
end
return uniq(out)
end
local function getConnectedPeople(people, personName)
local out = {}
local person = people[personName]
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, personName)
for _, v in ipairs(siblings) do addUnique(out, v) end
local grandparents = getGrandparents(people, personName)
for _, v in ipairs(grandparents) do addUnique(out, v) end
return uniq(out)
end
-- =========================================
-- Rendering helpers
-- =========================================
local function renderPersonBox(name, extraClass)
if not isRealValue(name) then
return nil
end
local box = html.create('div')
box:addClass('familytree-person')
if isRealValue(extraClass) then
box:addClass(extraClass)
end
box:wikitext(makeLink(name))
return box
end
local function renderRow(label, names, rowClass)
names = uniq(names)
if #names == 0 then
return nil
end
local row = html.create('div')
row:addClass('familytree-row')
if isRealValue(rowClass) then
row:addClass(rowClass)
end
row:tag('div')
:addClass('familytree-row-label')
:wikitext(label)
local items = row:tag('div')
:addClass('familytree-row-items')
for _, name in ipairs(names) do
local node = renderPersonBox(name)
if node then
items:node(node)
end
end
return row
end
local function renderTreeForPerson(people, personName)
local person = people[personName]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(personName) .. '".'
end
local grandparents = getGrandparents(people, personName)
local parents = uniq(person.parents)
local siblings = getSiblings(people, personName)
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 root = html.create('div')
root:addClass('familytree-wrapper')
local gpRow = renderRow('Grandparents', grandparents, 'familytree-grandparents')
if gpRow then root:node(gpRow) end
local pRow = renderRow('Parents', parents, 'familytree-parents')
if pRow then root:node(pRow) end
local selfRow = html.create('div')
selfRow:addClass('familytree-row')
selfRow:addClass('familytree-focus-row')
selfRow:tag('div')
:addClass('familytree-row-label')
:wikitext('Focus')
selfRow:tag('div')
:addClass('familytree-row-items')
:node(renderPersonBox(personName, 'familytree-focus-person'))
root:node(selfRow)
local sRow = renderRow('Siblings', siblings, 'familytree-siblings')
if sRow then root:node(sRow) end
local partnerRow = renderRow('Partners', partners, 'familytree-partners')
if partnerRow then root:node(partnerRow) end
local childRow = renderRow('Children', children, 'familytree-children')
if childRow then root:node(childRow) end
return tostring(root)
end
local function renderConnectedForPerson(people, personName)
local person = people[personName]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(personName) .. '".'
end
local connected = getConnectedPeople(people, personName)
sortNames(people, connected)
local root = html.create('div')
root:addClass('familytree-connected')
root:tag('div')
:addClass('familytree-connected-title')
:wikitext('Connected to ' .. makeLink(personName))
local items = root:tag('div')
:addClass('familytree-row-items')
for _, name in ipairs(connected) do
items:node(renderPersonBox(name))
end
return tostring(root)
end
local function renderProfileForPerson(people, personName)
local person = people[personName]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(personName) .. '".'
end
local root = html.create('div')
root:addClass('familytree-profile')
root:tag('div')
:addClass('familytree-profile-name')
:wikitext(makeLink(person.name))
local dl = root:tag('dl')
local function addField(label, value)
if isRealValue(value) then
dl:tag('dt'):wikitext(label)
dl:tag('dd'):wikitext(value)
end
end
addField('Display Name', person.displayName ~= person.name and person.displayName or nil)
addField('Title', person.title)
addField('Gender', person.gender)
addField('Birth Date', person.birthDate)
addField('Death Date', person.deathDate)
addField('Status', person.status)
addField('Blood Status', person.bloodStatus)
addField('Birth Family', person.birthFamily and makeLink(person.birthFamily) or nil)
addField('Current Family', person.currentFamily and makeLink(person.currentFamily) or nil)
addField('Heir', yesNo(person.heir))
addField('Illegitimate', yesNo(person.illegitimate))
addField('Adopted', yesNo(person.adopted))
if #person.parents > 0 then
local links = {}
for _, name in ipairs(person.parents) do
table.insert(links, makeLink(name))
end
addField('Parents', table.concat(links, ', '))
end
if #person.partners > 0 then
local links = {}
for _, name in ipairs(person.partners) do
table.insert(links, makeLink(name))
end
addField('Partners', table.concat(links, ', '))
end
if #person.children > 0 then
local links = {}
for _, name in ipairs(person.children) do
table.insert(links, makeLink(name))
end
addField('Children', table.concat(links, ', '))
end
return tostring(root)
end
-- =========================================
-- Public functions
-- =========================================
function p.tree(frame)
local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text
local people = loadData()
return renderTreeForPerson(people, personName)
end
function p.profile(frame)
local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text
local people = loadData()
return renderProfileForPerson(people, personName)
end
function p.connected(frame)
local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text
local people = loadData()
return renderConnectedForPerson(people, personName)
end
return p