Module:Infobox: Difference between revisions

From koreapedia
No edit summary
No edit summary
Line 1: Line 1:
-- Module:Infobox
-- Module:Infobox (robust)
-- Dependency-free, Lua-powered infobox builder.
-- Outputs HTML with shared CSS classes; auto-hides empty rows.
-- - Outputs HTML with CSS classes: .infobox, .infobox-title, .infobox-image, .infobox-caption, .infobox-label, .infobox-data
-- Tolerant to underscore/space mismatches and safe in previews.
-- - Auto-hides empty rows
-- - Safe in previews (guards getParent())
-- - Optional 'order' control in wrappers: | order = Label A|Label B|Label C


local p = {}
local p = {}


-- ========= helpers =========
-- ---------- helpers ----------
 
local function trim(s)
local function trim(s)
if type(s) ~= "string" then return s end
  if type(s) ~= "string" then return s end
return mw.text.trim(s)
  return mw.text.trim(s)
end
end


local function isNonEmpty(v)
local function isNonEmpty(v)
if v == nil then return false end
  if v == nil then return false end
if type(v) ~= "string" then return true end
  if type(v) ~= "string" then return true end
return mw.text.trim(v) ~= ""
  return mw.text.trim(v) ~= ""
end
end


local function renderImage(filename, size)
local function renderImage(filename, size)
if not isNonEmpty(filename) then return nil end
  if not isNonEmpty(filename) then return nil end
size = size or "280px"
  size = size or "280px"
return string.format("[[File:%s|%s|center]]", trim(filename), size)
  return string.format("[[File:%s|%s|center]]", trim(filename), size)
end
end


local function splitPipeList(s)
local function splitPipeList(s)
-- Split a pipe-separated string into an array; ignores empty segments
  local t = {}
local t = {}
  if not isNonEmpty(s) then return t end
if not isNonEmpty(s) then return t end
  for seg in tostring(s):gmatch("([^|]+)") do
for seg in tostring(s):gmatch("([^|]+)") do
    seg = mw.text.trim(seg)
seg = mw.text.trim(seg)
    if seg ~= "" then table.insert(t, seg) end
if seg ~= "" then table.insert(t, seg) end
  end
end
  return t
return t
end
end


-- ========= core table builder =========
-- try multiple key variants in user args: "birth_place" <-> "birth place"
local function getUserValue(user, key)
  if not isNonEmpty(key) then return nil end
  local k1 = key
  local k2 = key:gsub("_", " ")
  local k3 = key:gsub(" ", "_")
  return user[k1] or user[k2] or user[k3]
end


-- ---------- core builder ----------
local function buildTable(args)
local function buildTable(args)
local root = mw.html.create("table"):addClass("infobox")
  local root = mw.html.create("table"):addClass("infobox")


-- Title
  -- Title
local title = args.title or args.name or mw.title.getCurrentTitle().text
  local title = args.title or args.name or mw.title.getCurrentTitle().text
root:tag("tr")
  root:tag("tr")
:tag("th")
      :tag("th")
:attr("colspan", "2")
        :attr("colspan", "2")
:addClass("infobox-title")
        :addClass("infobox-title")
:wikitext(title)
        :wikitext(title)


-- Optional image
  -- Image
local image = renderImage(args.image, args.image_size)
  local image = renderImage(args.image, args.image_size)
if image then
  if image then
root:tag("tr")
    root:tag("tr")
:tag("td")
        :tag("td")
:attr("colspan", "2")
          :attr("colspan", "2")
:addClass("infobox-image")
          :addClass("infobox-image")
:wikitext(image)
          :wikitext(image)
end
  end


-- Optional caption
  -- Caption
if isNonEmpty(args.caption) then
  if isNonEmpty(args.caption) then
root:tag("tr")
    root:tag("tr")
:tag("td")
        :tag("td")
:attr("colspan", "2")
          :attr("colspan", "2")
:addClass("infobox-caption")
          :addClass("infobox-caption")
:wikitext(args.caption)
          :wikitext(args.caption)
end
  end


-- Numbered label/data pairs
  -- Numbered rows
local maxRows = tonumber(args.maxrows) or 50
  local maxRows = tonumber(args.maxrows) or 50
for i = 1, maxRows do
  for i = 1, maxRows do
local label = trim(args["label" .. i] or "")
    local label = trim(args["label"..i] or "")
local data  = trim(args["data" .. i] or "")
    local data  = trim(args["data"..i] or "")
if isNonEmpty(label) or isNonEmpty(data) then
    if isNonEmpty(label) or isNonEmpty(data) then
local tr = root:tag("tr")
      local tr = root:tag("tr")
tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
      tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
tr:tag("td"):addClass("infobox-data") :wikitext(data  or "")
      tr:tag("td"):addClass("infobox-data") :wikitext(data  or "")
end
    end
end
  end


return tostring(root)
  return tostring(root)
end
end


-- Public: direct builder; merges parent + frame args
-- Direct builder for numbered label/data pairs
function p.build(frame)
function p.build(frame)
local args = {}
  local args = {}
-- parent args
 
if type(frame.getParent) == "function" then
  -- parent args
local parent = frame:getParent()
  if type(frame.getParent) == "function" then
if parent and parent.args then
    local parent = frame:getParent()
for k, v in pairs(parent.args) do args[k] = v end
    if parent and parent.args then
end
      for k, v in pairs(parent.args) do args[k] = v end
end
    end
-- direct args
  end
if frame and frame.args then
 
for k, v in pairs(frame.args) do args[k] = v end
  -- direct args
end
  if frame and frame.args then
return buildTable(args)
    for k, v in pairs(frame.args) do args[k] = v end
  end
 
  return buildTable(args)
end
end


-- Public: smart wrapper builder
-- Smart builder: wrappers pass Label=paramName; only non-empty values render.
-- Wrappers pass a mapping of "Label = paramName" in frame.args,
-- and end-users supply values via those paramNames.
-- Optional: wrappers can set | order = Label A|Label B|Label C to control row order.
function p.smart(frame)
function p.smart(frame)
-- Safe guards for parent
  -- safe parent
local parent = nil
  local parent = nil
if type(frame.getParent) == "function" then
  if type(frame.getParent) == "function" then
parent = frame:getParent()
    parent = frame:getParent()
end
  end


local user = (parent and parent.args) or {}    -- end-user provided values
  -- values supplied by the article transclusion
local map  = frame.args or {}                   -- wrapper's label->param map (plus optional 'order')
  local user = (parent and parent.args) or frame.args or {}


-- Build numbered pairs only for non-empty values
  -- wrapper map (Label=paramKey), plus optional "order"
local newArgs = {}
  local map = frame.args or {}


-- Preserve order if wrapper provided it
  local newArgs, added = {}, 0
local orderList = splitPipeList(map.order)
local added = 0


local function addRow(label, key)
  local function addRow(label, key)
local value = user[key]
    local value = getUserValue(user, key)
if isNonEmpty(value) then
    if isNonEmpty(value) then
added = added + 1
      added = added + 1
newArgs["label" .. added] = label
      newArgs["label"..added] = label
newArgs["data" .. added] = value
      newArgs["data"..added] = value
end
    end
end
  end


-- 1) Ordered labels first (if any)
  -- explicit order first
for _, label in ipairs(orderList) do
  local orderList = splitPipeList(map.order)
local key = map[label]
  for _, label in ipairs(orderList) do
if isNonEmpty(key) then
    local key = map[label]
addRow(label, key)
    if isNonEmpty(key) then addRow(label, key) end
end
  end
end


-- 2) Any remaining labels not covered by 'order'
  -- remaining labels
for label, key in pairs(map) do
  for label, key in pairs(map) do
-- skip control keys
    if label ~= "order" and type(label) == "string" then
if label ~= "order" and type(label) == "string" then
      local already = false
-- If it wasn't already added via order
      for _, ol in ipairs(orderList) do if ol == label then already = true; break end end
local already = false
      if not already and isNonEmpty(key) then addRow(label, key) end
for _, olab in ipairs(orderList) do
    end
if olab == label then already = true; break end
  end
end
if not already and isNonEmpty(key) then
addRow(label, key)
end
end
end


-- Pass through common header fields
  -- headers
newArgs.title      = user.title or user.name or mw.title.getCurrentTitle().text
  newArgs.title      = user.title or user.name or mw.title.getCurrentTitle().text
newArgs.image      = user.image
  newArgs.image      = getUserValue(user, "image")
newArgs.image_size = user.image_size
  newArgs.image_size = getUserValue(user, "image_size")
newArgs.caption    = user.caption
  newArgs.caption    = getUserValue(user, "caption")
newArgs.maxrows    = user.maxrows or 50
  newArgs.maxrows    = user.maxrows or 50


-- Build via the core function
  return buildTable(newArgs)
local fakeFrame = { args = newArgs }
return buildTable(fakeFrame)
end
end


return p
return p

Revision as of 18:06, 8 October 2025

Documentation for this module may be created at Module:Infobox/doc

-- Module:Infobox (robust)
-- Outputs HTML with shared CSS classes; auto-hides empty rows.
-- Tolerant to underscore/space mismatches and safe in previews.

local p = {}

-- ---------- helpers ----------
local function trim(s)
  if type(s) ~= "string" then return s end
  return mw.text.trim(s)
end

local function isNonEmpty(v)
  if v == nil then return false end
  if type(v) ~= "string" then return true end
  return mw.text.trim(v) ~= ""
end

local function renderImage(filename, size)
  if not isNonEmpty(filename) then return nil end
  size = size or "280px"
  return string.format("[[File:%s|%s|center]]", trim(filename), size)
end

local function splitPipeList(s)
  local t = {}
  if not isNonEmpty(s) then return t end
  for seg in tostring(s):gmatch("([^|]+)") do
    seg = mw.text.trim(seg)
    if seg ~= "" then table.insert(t, seg) end
  end
  return t
end

-- try multiple key variants in user args: "birth_place" <-> "birth place"
local function getUserValue(user, key)
  if not isNonEmpty(key) then return nil end
  local k1 = key
  local k2 = key:gsub("_", " ")
  local k3 = key:gsub(" ", "_")
  return user[k1] or user[k2] or user[k3]
end

-- ---------- core builder ----------
local function buildTable(args)
  local root = mw.html.create("table"):addClass("infobox")

  -- Title
  local title = args.title or args.name or mw.title.getCurrentTitle().text
  root:tag("tr")
      :tag("th")
        :attr("colspan", "2")
        :addClass("infobox-title")
        :wikitext(title)

  -- Image
  local image = renderImage(args.image, args.image_size)
  if image then
    root:tag("tr")
        :tag("td")
          :attr("colspan", "2")
          :addClass("infobox-image")
          :wikitext(image)
  end

  -- Caption
  if isNonEmpty(args.caption) then
    root:tag("tr")
        :tag("td")
          :attr("colspan", "2")
          :addClass("infobox-caption")
          :wikitext(args.caption)
  end

  -- Numbered rows
  local maxRows = tonumber(args.maxrows) or 50
  for i = 1, maxRows do
    local label = trim(args["label"..i] or "")
    local data  = trim(args["data"..i]  or "")
    if isNonEmpty(label) or isNonEmpty(data) then
      local tr = root:tag("tr")
      tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
      tr:tag("td"):addClass("infobox-data") :wikitext(data  or "")
    end
  end

  return tostring(root)
end

-- Direct builder for numbered label/data pairs
function p.build(frame)
  local args = {}

  -- parent args
  if type(frame.getParent) == "function" then
    local parent = frame:getParent()
    if parent and parent.args then
      for k, v in pairs(parent.args) do args[k] = v end
    end
  end

  -- direct args
  if frame and frame.args then
    for k, v in pairs(frame.args) do args[k] = v end
  end

  return buildTable(args)
end

-- Smart builder: wrappers pass Label=paramName; only non-empty values render.
function p.smart(frame)
  -- safe parent
  local parent = nil
  if type(frame.getParent) == "function" then
    parent = frame:getParent()
  end

  -- values supplied by the article transclusion
  local user = (parent and parent.args) or frame.args or {}

  -- wrapper map (Label=paramKey), plus optional "order"
  local map = frame.args or {}

  local newArgs, added = {}, 0

  local function addRow(label, key)
    local value = getUserValue(user, key)
    if isNonEmpty(value) then
      added = added + 1
      newArgs["label"..added] = label
      newArgs["data"..added]  = value
    end
  end

  -- explicit order first
  local orderList = splitPipeList(map.order)
  for _, label in ipairs(orderList) do
    local key = map[label]
    if isNonEmpty(key) then addRow(label, key) end
  end

  -- remaining labels
  for label, key in pairs(map) do
    if label ~= "order" and type(label) == "string" then
      local already = false
      for _, ol in ipairs(orderList) do if ol == label then already = true; break end end
      if not already and isNonEmpty(key) then addRow(label, key) end
    end
  end

  -- headers
  newArgs.title      = user.title or user.name or mw.title.getCurrentTitle().text
  newArgs.image      = getUserValue(user, "image")
  newArgs.image_size = getUserValue(user, "image_size")
  newArgs.caption    = getUserValue(user, "caption")
  newArgs.maxrows    = user.maxrows or 50

  return buildTable(newArgs)
end

return p