Jump to content

Module:Keywords: Difference between revisions

From Natural Philosophy Wiki
ClaudeBot (talk | contribs)
Render infobox keywords: link only curated concepts, no redlinks
 
ClaudeBot (talk | contribs)
Strip wiki markup before splitting: whole-list links were rejoining into one redlink
 
Line 28: Line 28:


-- Strip any stored wiki markup, keeping the display text of piped links.
-- Strip any stored wiki markup, keeping the display text of piped links.
--
-- This must run over the WHOLE field before it is split on commas. Some legacy
-- rows wrap the entire list in a single link -- [[Sw Canis Majoris, Binary
-- stars, Lacy, motion]] -- so splitting first would leave unbalanced fragments
-- ("[[Sw Canis Majoris" ... "motion]]") that rejoin into one giant redlink.
local function plainText(s)
local function plainText(s)
s = s:gsub('%[%[[^%]|]*|([^%]]*)%]%]', '%1')  -- [[Target|shown]] -> shown
s = s:gsub('%[%[[^%]|]*|([^%]]*)%]%]', '%1')  -- [[Target|shown]] -> shown
s = s:gsub('%[%[([^%]]*)%]%]', '%1')          -- [[Target]]      -> Target
s = s:gsub('%[%[([^%]]*)%]%]', '%1')          -- [[Target]]      -> Target
s = s:gsub("'''", ''):gsub("''", '')          -- stray bold/italic
s = s:gsub("'''", ''):gsub("''", '')          -- stray bold/italic
s = s:gsub('%[%[', ''):gsub('%]%]', '')        -- any unbalanced leftovers
return trim(s)
return trim(s)
end
end
Line 59: Line 65:
local ok, data = pcall(mw.loadData, 'Module:Keywords/data')
local ok, data = pcall(mw.loadData, 'Module:Keywords/data')
local map = (ok and type(data) == 'table' and data.map) or {}
local map = (ok and type(data) == 'table' and data.map) or {}
-- Remove markup first, then split -- see plainText above for why the order
-- matters.
raw = plainText(raw)


local out, seen = {}, {}
local out, seen = {}, {}
for chunk in mw.text.gsplit(raw, '%s*[,;]%s*') do
for chunk in mw.text.gsplit(raw, '%s*[,;]%s*') do
local display = plainText(chunk)
local display = trim(chunk)
if display ~= '' then
if display ~= '' then
local k = key(display)
local k = key(display)

Latest revision as of 07:59, 21 July 2026

This module renders the keywords field of {{Infobox paper}}. Recognised concepts become links; everything else stays plain text.

Why it exists

The keyword fields came from a legacy database in which the terms were already wrapped in [[...]]. Most of those targets had never been written, so about 8,900 keyword links across the paper pages rendered red. A second problem was subtler: some targets that did exist were the wrong kind of page. This wiki has papers titled Gravity, Time, Ether and Cosmology, so the keyword "gravity" pointed at one paper rather than at the subject. A further eleven terms — Cold Fusion, Anti-Gravity, Thermodynamics, Black Holes and others — were redirects to Index of Papers Without Abstracts.

So "link it if the page exists" is not a safe rule on this wiki. The module ignores whatever markup is stored on the page and decides the link itself, against a curated allowlist.

How it behaves

  • The whole field has its markup stripped before it is split on commas. Some rows wrap the
 entire list in one link ([[Sw Canis Majoris, Binary stars, Lacy, motion]]);
 splitting first would leave unbalanced fragments that rejoin into a single giant redlink.
  • Matching is case-insensitive and ignores trailing punctuation, so Aether,
 aether and aether. all resolve alike.
  • Duplicate terms within one field are dropped.
  • A term links only if it appears in Module:Keywords/data. Creating an article is
 therefore not enough to produce a link — the mapping must be added deliberately. That is what
 keeps papers and books out of the keyword links.
  • If the data page is missing or broken the keywords still render, just unlinked. The module is
 transcluded on thousands of pages and must not fail loudly.

Adding a keyword

Edit Module:Keywords/data and add a line to the map table:

['relativity principle'] = 'Principle of relativity',

The key must be lower case. The value must be the exact title of a topic, theory or person page. Before adding one, open the target and check it is not a paper or a book — that is the mistake this module exists to prevent.

See also


-- Module:Keywords
--
-- Renders the |keywords= field of the infobox templates as a comma-separated
-- list in which recognised concepts link to their article and everything else
-- stays plain text.
--
-- WHY THIS EXISTS
-- The keyword fields were populated from a legacy database in which the terms
-- were already wrapped in [[...]]. Most of those targets have never existed, so
-- roughly 8,900 keyword links across the paper pages rendered red. Worse, a
-- number of the targets that DO exist are papers rather than concepts -- the
-- wiki has papers titled "Gravity", "Time", "Ether" and "Cosmology" -- so the
-- keyword "gravity" pointed at a single paper instead of the subject.
--
-- The fix is to ignore the markup stored on the page and decide the link here,
-- against the curated allowlist in [[Module:Keywords/data]]. A term links only
-- if it is in that list; otherwise it renders as plain text. Adding a page to
-- the wiki is therefore not enough to create a link -- the mapping has to be
-- added deliberately, which is what keeps bad targets out.
--
-- Usage: {{#invoke:Keywords|render|{{{keywords|}}}}}

local p = {}

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

-- Strip any stored wiki markup, keeping the display text of piped links.
--
-- This must run over the WHOLE field before it is split on commas. Some legacy
-- rows wrap the entire list in a single link -- [[Sw Canis Majoris, Binary
-- stars, Lacy, motion]] -- so splitting first would leave unbalanced fragments
-- ("[[Sw Canis Majoris" ... "motion]]") that rejoin into one giant redlink.
local function plainText(s)
	s = s:gsub('%[%[[^%]|]*|([^%]]*)%]%]', '%1')   -- [[Target|shown]] -> shown
	s = s:gsub('%[%[([^%]]*)%]%]', '%1')           -- [[Target]]       -> Target
	s = s:gsub("'''", ''):gsub("''", '')           -- stray bold/italic
	s = s:gsub('%[%[', ''):gsub('%]%]', '')        -- any unbalanced leftovers
	return trim(s)
end

-- Lookup key: lower case, collapsed whitespace, no trailing punctuation.
local function key(s)
	s = trim(s):lower()
	s = s:gsub('%s+', ' ')
	s = s:gsub('[%.%s]+$', '')
	return s
end

function p.render(frame)
	local raw = frame.args[1]
	if raw == nil or trim(raw) == '' then
		local parent = frame:getParent()
		raw = parent and parent.args and parent.args.keywords or ''
	end
	raw = trim(raw or '')
	if raw == '' then
		return ''
	end

	-- Load the map defensively: if the data page is missing or broken the
	-- keywords must still render, just without links. This template is
	-- transcluded on thousands of pages and must not fail loudly.
	local ok, data = pcall(mw.loadData, 'Module:Keywords/data')
	local map = (ok and type(data) == 'table' and data.map) or {}

	-- Remove markup first, then split -- see plainText above for why the order
	-- matters.
	raw = plainText(raw)

	local out, seen = {}, {}
	for chunk in mw.text.gsplit(raw, '%s*[,;]%s*') do
		local display = trim(chunk)
		if display ~= '' then
			local k = key(display)
			-- Drop duplicates left over from the legacy data (e.g. a term
			-- appearing both linked and unlinked in the same field).
			if not seen[k] then
				seen[k] = true
				local target = map[k]
				if target and target ~= '' then
					if target == display then
						table.insert(out, '[[' .. target .. ']]')
					else
						table.insert(out, '[[' .. target .. '|' .. display .. ']]')
					end
				else
					table.insert(out, display)
				end
			end
		end
	end

	return table.concat(out, ', ')
end

return p