Module:HPB-SB authors

From Teopedia

Documentation for this module may be created at Module:HPB-SB authors/doc

local p = {}

-- Separators matching Template:HPB-SB authors raw row
local FS = mw.ustring.char(0x2063) -- field separator (INVISIBLE SEPARATOR)
local RS = mw.ustring.char(0x2064) -- record terminator (INVISIBLE PLUS)
local AS = mw.ustring.char(0x2062) -- author-value separator (INVISIBLE TIMES)

local function isempty(s)
	return s == nil or s == ''
end

-- plain-text split, no Lua pattern magic involved
local function split_plain(str, sep)
	local parts = {}
	local start = 1
	while true do
		local s, e = string.find(str, sep, start, true)
		if not s then
			table.insert(parts, string.sub(str, start))
			break
		end
		table.insert(parts, string.sub(str, start, s - 1))
		start = e + 1
	end
	return parts
end

local function trim(s)
	return (mw.ustring.gsub(s or '', '^%s*(.-)%s*$', '%1'))
end

-- Fetch one page (offset-limited) of raw item rows via the helper template.
local function fetchRows(frame, offset, limit)
	local query = table.concat({
		'{{#ask: [[HPB SB item author::+]]',
		'|?HPB SB item author=a|+sep=' .. AS,
		'|?HPB SB item ID=id',
		'|?HPB SB volume=vol',
		'|?HPB SB volume page=pg',
		'|?HPB SB item title=t',
		'|?HPB SB item untitled=u',
		'|?HPB SB item source title=s',
		'|?HPB SB item source details=d',
		'|?HPB SB item publication date=p',
		'|?#=page',
		'| mainlabel=-',
		'| link=none',
		'| headers=hide',
		'| named args=yes',
		'| format=template',
		'| template=HPB-SB authors raw row',
		'| sort=HPB SB item author',
		'| order=asc',
		'| limit=' .. tostring(limit),
		'| offset=' .. tostring(offset),
		'}}'
	}, '\n')
	local text = frame:preprocess(query)
	local records = split_plain(text, RS)
	local rows = {}
	for _, rec in ipairs(records) do
		if rec ~= '' then
			local f = split_plain(rec, FS)
			if #f >= 9 then
				table.insert(rows, {
					a = f[1], page = f[2], t = f[3], u = f[4],
					vol = f[5], pg = f[6], s = f[7], d = f[8], p = f[9]
				})
			end
		end
	end
	return rows
end

-- Collect ALL matching item rows across the whole dataset, working around
-- Semantic MediaWiki's server-side per-query result cap ($smwgQMaxLimit)
-- by chaining several offset-paginated #ask calls. A page can legitimately
-- come back shorter than requested without being the last one, so a short
-- page is NOT treated as a stop condition -- only a truly empty page is.
local function fetchAllRows(frame)
	local allRows = {}
	local step = 500
	local offset = 0
	local maxIterations = 20 -- safety stop, far above what's needed today
	for i = 1, maxIterations do
		local rows = fetchRows(frame, offset, step)
		if #rows == 0 then
			break
		end
		for _, r in ipairs(rows) do
			table.insert(allRows, r)
		end
		offset = offset + step
	end
	return allRows
end

-- Build a name -> {items...} map, splitting any multi-valued author field.
local function groupByAuthor(rows)
	local map = {}
	local order = {}
	for _, r in ipairs(rows) do
		local names = split_plain(r.a, AS)
		for _, name in ipairs(names) do
			name = trim(name)
			if name ~= '' then
				if not map[name] then
					map[name] = {}
					table.insert(order, name)
				end
				table.insert(map[name], r)
			end
		end
	end
	return map, order
end

local function renderItem(frame, r)
	return frame:expandTemplate{ title = 'HPB-SB author items', args = {
		['untitled'] = r.u, ['wiki page'] = r.page, ['title'] = r.t,
		['source'] = r.s, ['details'] = r.d, ['publication'] = r.p,
		['volume'] = r.vol, ['page'] = r.pg
	}}
end

local function showName(frame, name)
	local ok, result = pcall(function()
		return frame:expandTemplate{ title = 'CTD show name', args = { [1] = name, ['mode'] = 'full LF' } }
	end)
	if ok and not isempty(result) then
		return result
	end
	return name
end

-- Main entry point: {{#invoke: HPB-SB authors | list }}
function p.list(frame)
	local rows = fetchAllRows(frame)
	local map, order = groupByAuthor(rows)
	table.sort(order)

	local out = {}
	for _, name in ipairs(order) do
		table.insert(out, '<h3>' .. showName(frame, name) .. '</h3>')
		table.insert(out, '<ol>')
		local items = map[name]
		table.sort(items, function(a, b) return (a.t or '') < (b.t or '') end)
		for _, r in ipairs(items) do
			table.insert(out, renderItem(frame, r))
		end
		table.insert(out, '</ol>')
	end
	return table.concat(out, '\n')
end

-- Diagnostic helper: {{#invoke: HPB-SB authors | debug }}
function p.debug(frame)
	local rows = fetchAllRows(frame)
	local map, order = groupByAuthor(rows)
	table.sort(order)
	local out = {}
	table.insert(out, 'Total rows fetched: ' .. tostring(#rows))
	table.insert(out, 'Unique authors: ' .. tostring(#order))
	table.insert(out, 'Has "Kingsford, Anna": ' .. tostring(map['Kingsford, Anna'] ~= nil))
	table.insert(out, 'Has "Kislingbury, Emily": ' .. tostring(map['Kislingbury, Emily'] ~= nil))
	return table.concat(out, '<br>\n')
end

-- TEMPORARY diagnostic helper for the author-name cleanup project: dumps every
-- unique raw author string with the wiki pages it appears on, tab/semicolon
-- separated, for offline analysis. Safe to remove once that project is done.
function p.rawnames(frame)
	local rows = fetchAllRows(frame)
	local map, order = groupByAuthor(rows)
	table.sort(order)
	local out = {}
	for _, name in ipairs(order) do
		local pages = {}
		local seen = {}
		for _, r in ipairs(map[name]) do
			if not seen[r.page] then
				seen[r.page] = true
				table.insert(pages, r.page)
			end
		end
		table.insert(out, name .. '\t' .. table.concat(pages, ';'))
	end
	return '<pre>' .. table.concat(out, '\n') .. '</pre>'
end

return p