Module:Tree chart: Difference between revisions
Jump to navigation
Jump to search
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 1: | Line 1: | ||
-- Module:Tree chart | -- Module:Tree chart | ||
-- Defensive renderer | -- Defensive renderer that emits a scoped inline stylesheet next to the table. | ||
pcall(function() require('strict') end) | pcall(function() require('strict') end) | ||
local p = {} | local p = {} | ||
local cells = mw.loadData('Module:Tree chart/data') | |||
-- safe wrapper to insert raw HTML fragments into mw.html nodes | |||
local function safe_wikitext(node, html) | |||
node:wikitext(html) | |||
end | |||
function p._main(cell_args) | function p._main(cell_args) | ||
local | local out = mw.html.create() | ||
-- | |||
local tbl = | -- Inline style block scoped to .tree-chart-inline-style (highly specific, uses !important) | ||
local style = [[ | |||
<style type="text/css"> | |||
/* Scoped tree styles that override global rules */ | |||
.tree-chart-inline-style { border-collapse: collapse !important; border-spacing: 0 !important; vertical-align: top !important; display: inline-table !important; font-family: inherit !important; } | |||
.tree-chart-inline-style td { padding: 0 !important; margin: 0 !important; vertical-align: top !important; box-sizing: border-box !important; white-space: nowrap !important; line-height: 1 !important; font-size: 0.9rem !important; } | |||
.tree-chart-inline-style td.connector { padding: 0 !important; width:1em !important; height:1em !important; min-width:1em !important; min-height:1em !important; max-width:1em !important; max-height:1em !important; line-height:1 !important; } | |||
.tree-chart-inline-style td.box { padding: 0.35em 0.9em !important; white-space: normal !important; font-size: 1rem !important; text-align: center !important; } | |||
.tree-chart-inline-style td[style*="border-bottom"], .tree-chart-inline-style td[style*="border-right"], .tree-chart-inline-style td[style*="border"] { box-sizing: border-box !important; } | |||
</style> | |||
]] | |||
out:wikitext(style) | |||
-- Build table with explicit style attribute (avoid :css camelCase pitfalls) | |||
local tbl = out:tag('table') | |||
:addClass('tree-chart-inline-style') | |||
:attr{ style = 'border-collapse: collapse; border-spacing: 0; vertical-align: top;' } | |||
local top = tbl:tag('tr'): | local top = tbl:tag('tr'):attr{ style = 'height:1px; text-align:center;' } | ||
local bottom = tbl:tag('tr'): | local bottom = tbl:tag('tr'):attr{ style = 'height:1px; text-align:center;' } | ||
for _, v in ipairs(cell_args) do | for _, v in ipairs(cell_args) do | ||
| Line 27: | Line 39: | ||
local celldef = cells[v] | local celldef = cells[v] | ||
if celldef then | if celldef then | ||
-- celldef.t / celldef.b are fragments produced by Module:Tree chart/data (strings) | |||
-- Tag their <td> elements as connector cells by adding class="connector" | |||
if celldef.t then | if celldef.t then | ||
local tstr = tostring(celldef.t) | |||
-- add class only if not already present | |||
tstr = tstr:gsub('<td(.-)>', function(attrs) | |||
-- if class attribute exists, append connector; otherwise add it | |||
if attrs:match('class%s*=') then | |||
return '<td' .. attrs:gsub('class%s*=%s*"(.-)"', function(c) return string.format(' class="%s connector"', c) end) .. '>' | |||
else | |||
return '<td' .. attrs .. ' class="connector">' | |||
end | |||
end) | |||
safe_wikitext(top, tstr) | |||
else | else | ||
top:tag('td'): | top:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' } | ||
end | end | ||
if celldef.b then | if celldef.b then | ||
local bstr = tostring(celldef.b) | |||
bstr = bstr:gsub('<td(.-)>', function(attrs) | |||
if attrs:match('class%s*=') then | |||
return '<td' .. attrs:gsub('class%s*=%s*"(.-)"', function(c) return string.format(' class="%s connector"', c) end) .. '>' | |||
else | |||
return '<td' .. attrs .. ' class="connector">' | |||
end | |||
end) | |||
safe_wikitext(bottom, bstr) | |||
else | else | ||
bottom:tag('td'): | bottom:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' } | ||
end | end | ||
else | else | ||
top:tag('td'): | -- unknown symbol: render empty connector td | ||
bottom:tag('td'): | top:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' } | ||
bottom:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' } | |||
end | end | ||
else | else | ||
-- custom cell with text (a boxed cell) | |||
local colspan = v.colspan or cell_args.colspan or 6 | local colspan = v.colspan or cell_args.colspan or 6 | ||
local rowspan = v.rowspan or cell_args.rowspan or 2 | local rowspan = v.rowspan or cell_args.rowspan or 2 | ||
| Line 48: | Line 83: | ||
top:tag('td') | top:tag('td') | ||
:attr{ colspan = tostring(colspan), rowspan = tostring(rowspan) | :addClass('box') | ||
:attr{ | |||
colspan = tostring(colspan), | |||
rowspan = tostring(rowspan), | |||
style = 'padding:0.35em 0.9em; border: ' .. border .. '; box-sizing: border-box;' | |||
} | |||
:wikitext(v.text or '') | :wikitext(v.text or '') | ||
if tonumber(rowspan) | -- if rowspan < 2 we need a bottom placeholder; most boxes use rowspan=2 so bottom is empty | ||
bottom:tag('td'): | if not tonumber(rowspan) or tonumber(rowspan) < 2 then | ||
bottom:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' } | |||
end | end | ||
end | end | ||
| Line 64: | Line 102: | ||
function p.main(frame) | function p.main(frame) | ||
local args = require('Module:Arguments').getArgs(frame, | local args = require('Module:Arguments').getArgs(frame, | ||
wrappers = 'Template:Tree chart', | { wrappers = 'Template:Tree chart', trim = false, removeBlanks = false }) | ||
local cell_args = { | local cell_args = { | ||
| Line 79: | Line 114: | ||
for _, val in ipairs(args) do | for _, val in ipairs(args) do | ||
local trimmedVal = val:match('^%s*(.-)%s*$') | local trimmedVal = val:match('^%s*(.-)%s*$') | ||
if trimmedVal == '' then | if trimmedVal == '' then trimmedVal = '$' end | ||
if cells[trimmedVal] then | if cells[trimmedVal] then | ||
Revision as of 14:35, 11 November 2025
Documentation for this module may be created at Module:Tree chart/doc
-- Module:Tree chart
-- Defensive renderer that emits a scoped inline stylesheet next to the table.
pcall(function() require('strict') end)
local p = {}
local cells = mw.loadData('Module:Tree chart/data')
-- safe wrapper to insert raw HTML fragments into mw.html nodes
local function safe_wikitext(node, html)
node:wikitext(html)
end
function p._main(cell_args)
local out = mw.html.create()
-- Inline style block scoped to .tree-chart-inline-style (highly specific, uses !important)
local style = [[
<style type="text/css">
/* Scoped tree styles that override global rules */
.tree-chart-inline-style { border-collapse: collapse !important; border-spacing: 0 !important; vertical-align: top !important; display: inline-table !important; font-family: inherit !important; }
.tree-chart-inline-style td { padding: 0 !important; margin: 0 !important; vertical-align: top !important; box-sizing: border-box !important; white-space: nowrap !important; line-height: 1 !important; font-size: 0.9rem !important; }
.tree-chart-inline-style td.connector { padding: 0 !important; width:1em !important; height:1em !important; min-width:1em !important; min-height:1em !important; max-width:1em !important; max-height:1em !important; line-height:1 !important; }
.tree-chart-inline-style td.box { padding: 0.35em 0.9em !important; white-space: normal !important; font-size: 1rem !important; text-align: center !important; }
.tree-chart-inline-style td[style*="border-bottom"], .tree-chart-inline-style td[style*="border-right"], .tree-chart-inline-style td[style*="border"] { box-sizing: border-box !important; }
</style>
]]
out:wikitext(style)
-- Build table with explicit style attribute (avoid :css camelCase pitfalls)
local tbl = out:tag('table')
:addClass('tree-chart-inline-style')
:attr{ style = 'border-collapse: collapse; border-spacing: 0; vertical-align: top;' }
local top = tbl:tag('tr'):attr{ style = 'height:1px; text-align:center;' }
local bottom = tbl:tag('tr'):attr{ style = 'height:1px; text-align:center;' }
for _, v in ipairs(cell_args) do
if type(v) == 'string' then
local celldef = cells[v]
if celldef then
-- celldef.t / celldef.b are fragments produced by Module:Tree chart/data (strings)
-- Tag their <td> elements as connector cells by adding class="connector"
if celldef.t then
local tstr = tostring(celldef.t)
-- add class only if not already present
tstr = tstr:gsub('<td(.-)>', function(attrs)
-- if class attribute exists, append connector; otherwise add it
if attrs:match('class%s*=') then
return '<td' .. attrs:gsub('class%s*=%s*"(.-)"', function(c) return string.format(' class="%s connector"', c) end) .. '>'
else
return '<td' .. attrs .. ' class="connector">'
end
end)
safe_wikitext(top, tstr)
else
top:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' }
end
if celldef.b then
local bstr = tostring(celldef.b)
bstr = bstr:gsub('<td(.-)>', function(attrs)
if attrs:match('class%s*=') then
return '<td' .. attrs:gsub('class%s*=%s*"(.-)"', function(c) return string.format(' class="%s connector"', c) end) .. '>'
else
return '<td' .. attrs .. ' class="connector">'
end
end)
safe_wikitext(bottom, bstr)
else
bottom:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' }
end
else
-- unknown symbol: render empty connector td
top:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' }
bottom:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' }
end
else
-- custom cell with text (a boxed cell)
local colspan = v.colspan or cell_args.colspan or 6
local rowspan = v.rowspan or cell_args.rowspan or 2
local border_prop = v.border or cell_args.border or '2'
local border = tostring(border_prop) .. 'px solid'
top:tag('td')
:addClass('box')
:attr{
colspan = tostring(colspan),
rowspan = tostring(rowspan),
style = 'padding:0.35em 0.9em; border: ' .. border .. '; box-sizing: border-box;'
}
:wikitext(v.text or '')
-- if rowspan < 2 we need a bottom placeholder; most boxes use rowspan=2 so bottom is empty
if not tonumber(rowspan) or tonumber(rowspan) < 2 then
bottom:tag('td'):addClass('connector'):attr{ style = 'width:1em; height:1em;' }
end
end
end
return tostring(tbl)
end
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame,
{ wrappers = 'Template:Tree chart', trim = false, removeBlanks = false })
local cell_args = {
colspan = args.colspan,
rowspan = args.rowspan,
border = args.border,
boxstyle = args.boxstyle
}
for _, val in ipairs(args) do
local trimmedVal = val:match('^%s*(.-)%s*$')
if trimmedVal == '' then trimmedVal = '$' end
if cells[trimmedVal] then
table.insert(cell_args, trimmedVal)
else
local rightTrimmedVal = val:gsub('%s+$','')
local custom = {
text = args[trimmedVal] or ('{{{'..trimmedVal..'}}}'),
colspan = args['colspan_'..rightTrimmedVal],
rowspan = args['rowspan_'..rightTrimmedVal],
border = args['border_'..rightTrimmedVal],
boxstyle = args['boxstyle_'..rightTrimmedVal]
}
table.insert(cell_args, custom)
end
end
return p._main(cell_args)
end
return p