Module:FamilyTree

From KB Lexicon
Revision as of 20:28, 27 March 2026 by Wylder Merrow (talk | contribs) (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...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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 addToSet(set, value)
	value = trim(value)
	if value then
		set[value] = true
	end
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 getNeighbors(person)
	local neighbors = {}

	person = trim(person)
	if not person then
		return neighbors
	end

	local unionRows = cargoQuery(
		'Unions',
		'UnionID,Partner1,Partner2',
		{
			where = 'Partner1="' .. esc(person) .. '" OR Partner2="' .. esc(person) .. '"',
			limit = 500
		}
	)

	for _, row in ipairs(unionRows) do
		addToSet(neighbors, row.Partner1)
		addToSet(neighbors, row.Partner2)
	end

	local parentChildRows = cargoQuery(
		'ParentChild',
		'Child,Parent1,Parent2,UnionID',
		{
			where = 'Child="' .. esc(person) .. '" OR Parent1="' .. esc(person) .. '" OR Parent2="' .. esc(person) .. '"',
			limit = 1000
		}
	)

	for _, row in ipairs(parentChildRows) do
		addToSet(neighbors, row.Child)
		addToSet(neighbors, row.Parent1)
		addToSet(neighbors, row.Parent2)
	end

	neighbors[person] = nil

	return neighbors
end

local function collectConnectedComponent(root)
	local visited = {}
	local queue = {}
	local head = 1

	root = trim(root)
	if not root then
		return visited
	end

	visited[root] = true
	table.insert(queue, root)

	while head <= #queue do
		local current = queue[head]
		head = head + 1

		local neighbors = getNeighbors(current)

		for neighbor, _ in pairs(neighbors) do
			if not visited[neighbor] then
				visited[neighbor] = true
				table.insert(queue, neighbor)
			end
		end
	end

	return visited
end

local function sortedKeys(set)
	local arr = {}

	for key, _ in pairs(set) do
		table.insert(arr, key)
	end

	table.sort(arr, function(a, b)
		return a:lower() < b:lower()
	end)

	return arr
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 component = collectConnectedComponent(root)
	local people = sortedKeys(component)

	if #people == 0 then
		return 'No connected people found for ' .. root
	end

	local lines = {}
	table.insert(lines, "'''Connected component for " .. getDisplayName(root) .. "'''")
	table.insert(lines, '* Total people found: ' .. tostring(#people))

	for _, pageName in ipairs(people) do
		local displayName = getDisplayName(pageName)
		table.insert(lines, '* [[' .. pageName .. '|' .. displayName .. ']]')
	end

	return table.concat(lines, '\n')
end

return p