Module:FamilyTree: Difference between revisions
From KB Lexicon
(Created page with "local p = {} local cargo = mw.ext.cargo local function esc(value) if not value then return '' end value = tostring(value) value = value:gsub('\\', '\\\\') value = value:gsub('"', '\\"') return value end local function cargoQuery(tables, fields, args) args = args or {} local ok, result = pcall(function() return cargo.query(tables, fields, args) end) if ok and result then return result end return {} end local function trim(s) if not s then return...") |
No edit summary |
||
| Line 38: | Line 38: | ||
end | end | ||
local function | local function addUnique(list, seen, value) | ||
value = trim(value) | value = trim(value) | ||
if value then | if value and not seen[value] then | ||
seen[value] = true | |||
table.insert(list, value) | |||
end | end | ||
end | |||
local function sorted(list) | |||
table.sort(list, function(a, b) | |||
return a:lower() < b:lower() | |||
end) | |||
return list | |||
end | end | ||
| Line 67: | Line 75: | ||
end | end | ||
local function | local function getCharacter(pageName) | ||
local | pageName = trim(pageName) | ||
if not pageName then | |||
return nil | |||
end | |||
local rows = cargoQuery( | |||
'Characters', | |||
'Page,DisplayName,Gender,BirthDate,DeathDate,Status', | |||
{ | |||
where = 'Page="' .. esc(pageName) .. '"', | |||
limit = 1 | |||
} | |||
) | |||
return rows[1] | |||
end | |||
local function getParents(person) | |||
local rows = cargoQuery( | |||
'ParentChild', | |||
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder', | |||
{ | |||
where = 'Child="' .. esc(person) .. '"', | |||
limit = 20 | |||
} | |||
) | |||
local parents = {} | |||
local seen = {} | |||
for _, row in ipairs(rows) do | |||
addUnique(parents, seen, row.Parent1) | |||
addUnique(parents, seen, row.Parent2) | |||
end | end | ||
local | return sorted(parents), rows[1] | ||
end | |||
local function getChildren(person) | |||
local rows = cargoQuery( | |||
'ParentChild', | |||
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder', | |||
{ | |||
where = 'Parent1="' .. esc(person) .. '" OR Parent2="' .. esc(person) .. '"', | |||
limit = 200 | |||
} | |||
) | |||
local children = {} | |||
local seen = {} | |||
table.sort(rows, function(a, b) | |||
local aOrder = tonumber(a.BirthOrder) or 9999 | |||
local bOrder = tonumber(b.BirthOrder) or 9999 | |||
if aOrder == bOrder then | |||
return tostring(a.Child):lower() < tostring(b.Child):lower() | |||
end | |||
return aOrder < bOrder | |||
end) | |||
for _, row in ipairs(rows) do | |||
addUnique(children, seen, row.Child) | |||
end | |||
return children | |||
end | |||
local function getPartners(person) | |||
local rows = cargoQuery( | |||
'Unions', | 'Unions', | ||
'UnionID,Partner1,Partner2', | 'UnionID,Partner1,Partner2,UnionType,Status,MarriageDate,DivorceDate,EngagementDate', | ||
{ | { | ||
where = 'Partner1="' .. esc(person) .. '" OR Partner2="' .. esc(person) .. '"', | where = 'Partner1="' .. esc(person) .. '" OR Partner2="' .. esc(person) .. '"', | ||
limit = | limit = 50 | ||
} | } | ||
) | ) | ||
for _, row in ipairs( | local partners = {} | ||
local seen = {} | |||
for _, row in ipairs(rows) do | |||
if trim(row.Partner1) == person then | |||
addUnique(partners, seen, row.Partner2) | |||
elseif trim(row.Partner2) == person then | |||
addUnique(partners, seen, row.Partner1) | |||
end | |||
end | end | ||
local | return sorted(partners), rows | ||
end | |||
local function getSiblings(person) | |||
local rows = cargoQuery( | |||
'ParentChild', | |||
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder', | |||
{ | |||
where = 'Parent1 HOLDS ""', | |||
limit = 1000 | |||
} | |||
) | |||
local targetRows = cargoQuery( | |||
'ParentChild', | 'ParentChild', | ||
'Child,Parent1,Parent2,UnionID', | 'Child,Parent1,Parent2,UnionID', | ||
{ | { | ||
where = 'Child | where = 'Child="' .. esc(person) .. '"', | ||
limit = | limit = 10 | ||
} | } | ||
) | ) | ||
for _, row in ipairs( | if not targetRows[1] then | ||
return {} | |||
end | |||
local targetUnion = trim(targetRows[1].UnionID) | |||
local targetP1 = trim(targetRows[1].Parent1) | |||
local targetP2 = trim(targetRows[1].Parent2) | |||
local siblings = {} | |||
local seen = {} | |||
for _, row in ipairs(rows) do | |||
local sameUnion = targetUnion and trim(row.UnionID) == targetUnion | |||
local sameParents = trim(row.Parent1) == targetP1 and trim(row.Parent2) == targetP2 | |||
if (sameUnion or sameParents) and trim(row.Child) ~= person then | |||
addUnique(siblings, seen, row.Child) | |||
end | |||
end | end | ||
return sorted(siblings) | |||
end | |||
return | local function makeLinkedName(pageName) | ||
local displayName = getDisplayName(pageName) | |||
return '[[' .. pageName .. '|' .. displayName .. ']]' | |||
end | end | ||
function p.connected(frame) | |||
local | local args = frame.args | ||
local | local parentArgs = frame:getParent() and frame:getParent().args or {} | ||
local | local root = trim(args.root or parentArgs.root or args[1] or parentArgs[1]) | ||
if not root then | if not root then | ||
return | return 'Error: no root provided. Use root=Character Name' | ||
end | end | ||
local visited = {} | |||
local queue = { root } | |||
local head = 1 | |||
visited[root] = true | visited[root] = true | ||
while head <= #queue do | while head <= #queue do | ||
| Line 126: | Line 232: | ||
head = head + 1 | head = head + 1 | ||
local | local parents = getParents(current) | ||
local children = getChildren(current) | |||
local partners = getPartners(current) | |||
for | for _, person in ipairs(parents) do | ||
if not visited[ | if not visited[person] then | ||
visited[ | visited[person] = true | ||
table.insert(queue, | table.insert(queue, person) | ||
end | end | ||
end | end | ||
for _, person in ipairs(children) do | |||
end | if not visited[person] then | ||
visited[person] = true | |||
table.insert(queue, person) | |||
end | |||
end | |||
for _, person in ipairs(partners) do | |||
if not visited[person] then | |||
visited[person] = true | |||
table.insert(queue, person) | |||
end | |||
end | |||
end | |||
for | local people = {} | ||
table.insert( | for name, _ in pairs(visited) do | ||
table.insert(people, name) | |||
end | end | ||
sorted(people) | |||
table. | local lines = {} | ||
table.insert(lines, "'''Connected component for " .. getDisplayName(root) .. "'''") | |||
end | table.insert(lines, '* Total people found: ' .. tostring(#people)) | ||
for _, person in ipairs(people) do | |||
table.insert(lines, '* ' .. makeLinkedName(person)) | |||
end | |||
return | return table.concat(lines, '\n') | ||
end | end | ||
function p. | function p.profile(frame) | ||
local args = frame.args | local args = frame.args | ||
local parentArgs = frame:getParent() and frame:getParent().args or {} | local parentArgs = frame:getParent() and frame:getParent().args or {} | ||
local root = trim(args.root or parentArgs.root or args[1] or parentArgs[1]) | local root = trim(args.root or parentArgs.root or args[1] or parentArgs[1]) | ||
| Line 163: | Line 283: | ||
end | end | ||
local | local parents = getParents(root) | ||
local | local siblings = getSiblings(root) | ||
local partners = getPartners(root) | |||
local children = getChildren(root) | |||
local lines = {} | |||
table.insert(lines, '{| class="wikitable" style="width:100%; max-width:900px;"') | |||
table.insert(lines, '|-') | |||
table.insert(lines, '! colspan="2" | Family profile for ' .. getDisplayName(root)) | |||
table.insert(lines, '|-') | |||
table.insert(lines, '! style="width:20%;" | Person') | |||
table.insert(lines, '| ' .. makeLinkedName(root)) | |||
if # | table.insert(lines, '|-') | ||
table.insert(lines, '! Parents') | |||
if #parents > 0 then | |||
table.insert(lines, '| ' .. table.concat(mw.text.map(parents, makeLinkedName), '<br>')) | |||
else | |||
table.insert(lines, '| —') | |||
end | end | ||
table.insert(lines, '|-') | |||
table.insert(lines, | table.insert(lines, '! Siblings') | ||
table.insert(lines, ' | if #siblings > 0 then | ||
table.insert(lines, '| ' .. table.concat(mw.text.map(siblings, makeLinkedName), '<br>')) | |||
else | |||
table.insert(lines, '| —') | |||
end | |||
table.insert(lines, '|-') | |||
table.insert(lines, '! Partners') | |||
if #partners > 0 then | |||
table.insert(lines, '| ' .. table.concat(mw.text.map(partners, makeLinkedName), '<br>')) | |||
else | |||
table.insert(lines, '| —') | |||
end | |||
table.insert(lines, '|-') | |||
table.insert(lines, '! Children') | |||
table.insert(lines, ' | if #children > 0 then | ||
table.insert(lines, '| ' .. table.concat(mw.text.map(children, makeLinkedName), '<br>')) | |||
else | |||
table.insert(lines, '| —') | |||
end | end | ||
table.insert(lines, '|}') | |||
return table.concat(lines, '\n') | return table.concat(lines, '\n') | ||
Revision as of 21:02, 27 March 2026
Documentation for this module may be created at Module:FamilyTree/doc
local p = {}
local cargo = mw.ext.cargo
local function esc(value)
if not value then
return ''
end
value = tostring(value)
value = value:gsub('\\', '\\\\')
value = value:gsub('"', '\\"')
return value
end
local function cargoQuery(tables, fields, args)
args = args or {}
local ok, result = pcall(function()
return cargo.query(tables, fields, args)
end)
if ok and result then
return result
end
return {}
end
local function trim(s)
if not s then
return nil
end
s = tostring(s)
s = mw.text.trim(s)
if s == '' then
return nil
end
return s
end
local function addUnique(list, seen, value)
value = trim(value)
if value and not seen[value] then
seen[value] = true
table.insert(list, value)
end
end
local function sorted(list)
table.sort(list, function(a, b)
return a:lower() < b:lower()
end)
return list
end
local function getDisplayName(pageName)
pageName = trim(pageName)
if not pageName then
return nil
end
local rows = cargoQuery(
'Characters',
'Page,DisplayName',
{
where = 'Page="' .. esc(pageName) .. '"',
limit = 1
}
)
if rows[1] and trim(rows[1].DisplayName) then
return trim(rows[1].DisplayName)
end
return pageName
end
local function getCharacter(pageName)
pageName = trim(pageName)
if not pageName then
return nil
end
local rows = cargoQuery(
'Characters',
'Page,DisplayName,Gender,BirthDate,DeathDate,Status',
{
where = 'Page="' .. esc(pageName) .. '"',
limit = 1
}
)
return rows[1]
end
local function getParents(person)
local rows = cargoQuery(
'ParentChild',
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder',
{
where = 'Child="' .. esc(person) .. '"',
limit = 20
}
)
local parents = {}
local seen = {}
for _, row in ipairs(rows) do
addUnique(parents, seen, row.Parent1)
addUnique(parents, seen, row.Parent2)
end
return sorted(parents), rows[1]
end
local function getChildren(person)
local rows = cargoQuery(
'ParentChild',
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder',
{
where = 'Parent1="' .. esc(person) .. '" OR Parent2="' .. esc(person) .. '"',
limit = 200
}
)
local children = {}
local seen = {}
table.sort(rows, function(a, b)
local aOrder = tonumber(a.BirthOrder) or 9999
local bOrder = tonumber(b.BirthOrder) or 9999
if aOrder == bOrder then
return tostring(a.Child):lower() < tostring(b.Child):lower()
end
return aOrder < bOrder
end)
for _, row in ipairs(rows) do
addUnique(children, seen, row.Child)
end
return children
end
local function getPartners(person)
local rows = cargoQuery(
'Unions',
'UnionID,Partner1,Partner2,UnionType,Status,MarriageDate,DivorceDate,EngagementDate',
{
where = 'Partner1="' .. esc(person) .. '" OR Partner2="' .. esc(person) .. '"',
limit = 50
}
)
local partners = {}
local seen = {}
for _, row in ipairs(rows) do
if trim(row.Partner1) == person then
addUnique(partners, seen, row.Partner2)
elseif trim(row.Partner2) == person then
addUnique(partners, seen, row.Partner1)
end
end
return sorted(partners), rows
end
local function getSiblings(person)
local rows = cargoQuery(
'ParentChild',
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder',
{
where = 'Parent1 HOLDS ""',
limit = 1000
}
)
local targetRows = cargoQuery(
'ParentChild',
'Child,Parent1,Parent2,UnionID',
{
where = 'Child="' .. esc(person) .. '"',
limit = 10
}
)
if not targetRows[1] then
return {}
end
local targetUnion = trim(targetRows[1].UnionID)
local targetP1 = trim(targetRows[1].Parent1)
local targetP2 = trim(targetRows[1].Parent2)
local siblings = {}
local seen = {}
for _, row in ipairs(rows) do
local sameUnion = targetUnion and trim(row.UnionID) == targetUnion
local sameParents = trim(row.Parent1) == targetP1 and trim(row.Parent2) == targetP2
if (sameUnion or sameParents) and trim(row.Child) ~= person then
addUnique(siblings, seen, row.Child)
end
end
return sorted(siblings)
end
local function makeLinkedName(pageName)
local displayName = getDisplayName(pageName)
return '[[' .. pageName .. '|' .. displayName .. ']]'
end
function p.connected(frame)
local args = frame.args
local parentArgs = frame:getParent() and frame:getParent().args or {}
local root = trim(args.root or parentArgs.root or args[1] or parentArgs[1])
if not root then
return 'Error: no root provided. Use root=Character Name'
end
local visited = {}
local queue = { root }
local head = 1
visited[root] = true
while head <= #queue do
local current = queue[head]
head = head + 1
local parents = getParents(current)
local children = getChildren(current)
local partners = getPartners(current)
for _, person in ipairs(parents) do
if not visited[person] then
visited[person] = true
table.insert(queue, person)
end
end
for _, person in ipairs(children) do
if not visited[person] then
visited[person] = true
table.insert(queue, person)
end
end
for _, person in ipairs(partners) do
if not visited[person] then
visited[person] = true
table.insert(queue, person)
end
end
end
local people = {}
for name, _ in pairs(visited) do
table.insert(people, name)
end
sorted(people)
local lines = {}
table.insert(lines, "'''Connected component for " .. getDisplayName(root) .. "'''")
table.insert(lines, '* Total people found: ' .. tostring(#people))
for _, person in ipairs(people) do
table.insert(lines, '* ' .. makeLinkedName(person))
end
return table.concat(lines, '\n')
end
function p.profile(frame)
local args = frame.args
local parentArgs = frame:getParent() and frame:getParent().args or {}
local root = trim(args.root or parentArgs.root or args[1] or parentArgs[1])
if not root then
return 'Error: no root provided. Use root=Character Name'
end
local parents = getParents(root)
local siblings = getSiblings(root)
local partners = getPartners(root)
local children = getChildren(root)
local lines = {}
table.insert(lines, '{| class="wikitable" style="width:100%; max-width:900px;"')
table.insert(lines, '|-')
table.insert(lines, '! colspan="2" | Family profile for ' .. getDisplayName(root))
table.insert(lines, '|-')
table.insert(lines, '! style="width:20%;" | Person')
table.insert(lines, '| ' .. makeLinkedName(root))
table.insert(lines, '|-')
table.insert(lines, '! Parents')
if #parents > 0 then
table.insert(lines, '| ' .. table.concat(mw.text.map(parents, makeLinkedName), '<br>'))
else
table.insert(lines, '| —')
end
table.insert(lines, '|-')
table.insert(lines, '! Siblings')
if #siblings > 0 then
table.insert(lines, '| ' .. table.concat(mw.text.map(siblings, makeLinkedName), '<br>'))
else
table.insert(lines, '| —')
end
table.insert(lines, '|-')
table.insert(lines, '! Partners')
if #partners > 0 then
table.insert(lines, '| ' .. table.concat(mw.text.map(partners, makeLinkedName), '<br>'))
else
table.insert(lines, '| —')
end
table.insert(lines, '|-')
table.insert(lines, '! Children')
if #children > 0 then
table.insert(lines, '| ' .. table.concat(mw.text.map(children, makeLinkedName), '<br>'))
else
table.insert(lines, '| —')
end
table.insert(lines, '|}')
return table.concat(lines, '\n')
end
return p