Module:Table des isotopes
La documentation de ce module est générée par le modèle {{Documentation module}}.
Les éditeurs peuvent travailler dans le bac à sable (créer).
Voir les statistiques d'appel depuis le wikicode sur l'outil wstat et les appels depuis d'autres modules.
require("strict")
local listeria_datas = require("Module:DonnéesListeria")
local json = require("Module:Jf-JSON")
local p = {}
-- utilities (table manipulation) ------------------------------------
-- trouver l’indice minimal dans un tableau indexé par des entiers
local function find_min_idx(tabl, cmp)
local min_idx
cmp = cmp or math.min
for i,v in pairs(tabl) do
min_idx = min_idx or i
min_idx = cmp(i, min_idx)
end
return min_idx
end
local function find_max_idx(tabl, cmp)
local max_idx
cmp = cmp or math.max
for i,v in pairs(tabl) do
max_idx = max_idx or i
max_idx = cmp(i, max_idx)
end
assert(max_idx, mw.dumpObject(tabl))
return max_idx
end
-- like the usual "map" function except the role of the mapped function is taken by a dictionary
local function mapdic(tbl, d)
local t = {}
for k,v in pairs(tbl) do
t[k] = d[v]
end
return t
end
table.reduce = function (list, fn, init)
local acc = init
for k, v in ipairs(list) do
if 1 == k then
acc = v
else
acc = fn(acc, v)
end
end
return acc
end
-- time unit conversion
local function to_minutes(seconds)
return seconds/60
end
local function from_minutes(minutes)
return minutes*60
end
local function to_days(seconds)
return to_minutes(seconds)/(24*60)
end
local function from_days(days)
return days*24*3600
end
local function to_years(seconds)
return to_days(seconds)/365.2422
end
local function from_years(years)
return years*from_days(365.2422)
end
local function from_seconds(seconds)
return seconds
end
--------------------------------------------------------------------------------
-- module datas
--------------------------------------------------------------------------------
p.classes_fmtdata = {
{from_seconds(1), "unstable_atom",
"instable",
{
"background-color: #ffffff",
"color:black"
},
},
{from_days(1), "between_seconds_and_days",
"demie vie > 1 s",
{
"background-color: #f2f2f2",
"color:black",
},
},
{from_days(10), "days_stable_atom",
"demie vie <10 jours",
{
"background-color: #993399",
"color:white"
},
},
{from_days(100), "months_stable_atom",
"demie vie <100 jours",
{
"background-color: #6600cc",
"color:white"
},
{
"color:white",
"text-decoration: underline"
}
},
{from_years(10), "day100_to_year10_stable_atom",
"demie vie < 10 ans",
{
"background-color: #3333ff",
"color:white"
},
{
"color:white",
"text-decoration: underline"
}
},
{from_years(10000), "year100_to_year10000_stable_atom",
"demie vie < 10 000 ans",
{
"background-color: #33ff33",
"color:black"
},
},
{from_years(1000000), "year10000_to_year1000000_stable_atom",
"demie vie < 1 000 000 ans",
{
"background-color: #ffff00",
"color:black"
},
},
{from_years(1000000000000), "stable_radioisotope",
"radioisotope stable",
{
"background-color: #ff9900",
"color:black"
},
},
{nil, "stable_atom" ,
"stable",
{
"background-color: #A9A9A9",
"color:black"
},
}
}
--------------------------------------------------------------------------------
-- data loading
function p.data_from_listeria_json(wikipage)
local datas = mw.title.new(wikipage):getContent()
datas = datas and listeria_datas.extraction(datas)
datas=json:decode(datas)
return datas
end
--preprocessing : compute the cells of the htmltable content type
-- compute in which cell the isotopes go
local function insert_in_matrix(isotable, row, col, val)
if not isotable.min then
isotable.min = row
else
isotable.min = math.min(isotable.min, row)
end
if not isotable[row] then
isotable[row]={}
end
isotable[row][col] = val
end
local Matrix = {}
Matrix.__index = Matrix
--local RevMatrix = {}
--Matrix.__index = RevMatrix
function Matrix:create()
local matr = {m = {},max_col=0} -- our new object
setmetatable(matr, Matrix) -- make Account handle lookup
-- self.__index = self
-- matr.max_col = 0
return matr
end
function Matrix:insert(proton, neutron, val)
insert_in_matrix(self.m, neutron, proton, val)
self.max_col=math.max(self.max_col, proton)
end
function Matrix:get(proton, neutron)
assert(proton, "the proton number is nil")
assert(neutron, "the neutron number is nil")
assert(self.m[neutron], "nothing at row " .. neutron)
return self.m[neutron][proton]
end
function Matrix:min_col_idx(col)
local min = nil
if self.m[col][0] ~= nil then -- special case of Hydrogen without neutrons
min = 0
return min
end
for i, line in pairs(self.m) do
if type(i) == "number" then -- big hack as the min is stored in the object at key "min" TODO : FIX
if line[col] then
min = (min and math.min(i, min)) or i
end
end
end
assert(min, "column seems empty")
return min
end
function Matrix:prot_neutron_at_index(row, col)
return col, row
end
local RevMatrix = Matrix:create()
--function RevMatrix:create()
-- local matr = {} -- our new object
-- setmetatable(matr,RevMatrix) -- make Account handle lookup
-- matr.max_col = 0
-- return matr
--end
function RevMatrix:insert(proton, neutron, val)
insert_in_matrix(self.m, proton, neutron, val)
self.max_col=math.max(self.max_col, row)
end
function RevMatrix:get(proton, neutron)
return self.m[proton][neutron]
end
function RevMatrix:prot_neutron_at_index(row, col)
return row, col
end
-- inserts the isotopes into an integer indexed bidimensional matrix (without the headers)
local function matrix_from_datas(elements, isomatrix)
for num_ato, element in pairs(elements) do
for num_neutrons, iso in pairs(element.iso) do
isomatrix:insert(tonumber(num_ato), tonumber(num_neutrons), iso)
end
end
return isomatrix
end
-- computes in which row of the html table the atomic number and the atom symbol should go
local function compute_rows_of_column_headers(isotopematrix, elements, rows_matrix, headers_function)
local prev = 1
for col_idx=1, isotopematrix.max_col do
local element = elements[tostring(col_idx)]
local header_row_base = isotopematrix:min_col_idx(col_idx)
assert(header_row_base, "nothing in col " .. col_idx .. "/" .. isotopematrix.max_col)
-- décallage négatif si on est dans un trou
if elements[tostring(col_idx+1)] and header_row_base > 2 then
if (elements[tostring(col_idx+1)].iso[tostring(header_row_base-1)])
then
header_row_base = prev
end
end
local headers = headers_function(elements, col_idx)
for n, header in ipairs(headers) do
insert_in_matrix(rows_matrix, header_row_base - #headers + n - 1, col_idx, header)
end
prev=header_row_base
end
return rows_matrix
end
local function isotope_cell_content(iso, element, num_nucleons)
local label = "<small><sup>" .. num_nucleons .. "</sup></small>" .. element.s
if iso.lien then
return "[["..iso.lien .. "|" .. label.. "]]"
end
return label
end
-- table formatting
local function class_by_halflife(dv, range_map)
if not(dv) then
return range_map[1]["class"]
end
for i, bound_data in ipairs(range_map) do
local bound = bound_data["bound"]
if not(bound) or dv < bound then
return bound_data["class"]
end
end
return range_map[#bound_data]["class"]
end
local function is_stable(isotope_variant)
return isotope_variant and isotope_variant.stable
end
-- compare isotopes according to their half life
local function is_stabler_or_equal(iso1, iso2)
if is_stable(iso1) and is_stable(iso2) then
return iso1.init_key > iso2.init_key
elseif is_stable(iso1) then
return true
elseif is_stable(iso2) then
return false
else
if iso1.dv and not iso2.dv then
return true
elseif iso2.dv and not iso1.dv then
return false
elseif iso1.dv ~= nil and iso2.dv ~= nil then
return iso1.dv > iso2.dv
end
end
return iso1.init_key < iso2.init_key
end
-- computes the css classes relevant for some isotope
local function isotope_cell_classes(fmt_datas, isotopes_in_cell, classes)
local sort = true
-- force a total ordering for indishinguishible elements
for i = 1, #isotopes_in_cell do
isotopes_in_cell[i].init_key = i
end
-- tri des variants isotopiques par stabilité
table.sort(isotopes_in_cell, is_stabler_or_equal)
local classes = {}
local last
for i, iso in ipairs(isotopes_in_cell) do
local class
if is_stable(iso) then
class="stable_atom"
else
class=class_by_halflife(iso.dv, fmt_datas.bounds)
end
if class ~= last then
classes[#classes+1] = class
last = class
end
end
return classes
end
local function html_isotope_cell(fmt_datas, html_line, isotopematrix, elements, row_num, col_num)
local isos = isotopematrix:get(row_num, col_num)
local atomic_num, neutron_num = isotopematrix:prot_neutron_at_index(row_num, col_num)
if not(isos) then
html_line:tag("td"):done()
else
local isotope_sym = isotope_cell_content(isos[1], elements[tostring(neutron_num)], atomic_num+neutron_num)
local classes = isotope_cell_classes(fmt_datas, isos)
local html_cell =
html_line
:tag("td")
-- help to debug
local hls = {}
for x, iso in pairs(isos) do
if iso.dv then
hls[#hls+1] = tostring(iso.dv) .. " s"
end
end
local heading = table.concat(hls, " ; ")
if #isos == 1 then
heading = heading .. " "
else
heading = heading .. tostring(" " .. #isos .. " variantes: ")
end
if #isos == 1 or #classes == 1 then
html_cell:addClass(classes[1])
:attr("title", heading .. fmt_datas.messages[classes[1]])
:wikitext(isotope_sym)
else
html_cell:addClass(classes[2])
:tag("div")
:addClass(classes[1])
:attr("title", heading .. table.concat(mapdic(classes, fmt_datas.messages), " ; " ))
:wikitext(isotope_sym)
:done()
end
html_line:done()
end
end
local function insert_needed_colspan(html, last, current)
if current > last + 1 then
html:tag("td")
:attr("colspan", current - last - 1)
:addClass("empty_cell")
:done()
end
return html
end
local function wikilink(title, text)
return "[[" .. title .. "|" .. text .. "]]"
end
local function empty_cell(hrow, size)
if size > 0 then
hrow
:tag("td")
:attr("colspan", size)
:addClass("empty_cell")
:done()
end
end
local function wikitext_element(element)
if element.lien then
return wikilink(element.lien, element.s)
else
return element.s
end
end
local function element_headers_cells(elements, num_ato)
local element = elements[tostring(num_ato)]
local element_number_string = num_ato
if element and element.isolist then
element_number_string = wikilink(element.isolist, num_ato)
end
return {
mw.html.create("th")
:wikitext(num_ato)
:done(),
mw.html.create("th")
:wikitext(wikitext_element(element))
:done()
}
end
local function neutron_header_cells(neutron_num)
return {
mw.html.create("th")
:wikitext(neutron_num)
:done()
}
end
local function insert_headers_in_row(hrow, headers, col_decay, first)
if first then
hrow:node(first)
empty_cell(hrow, col_decay - 1)
else
empty_cell(hrow, col_decay)
end
if headers then
for _, header in ipairs(headers) do
hrow:node(header)
end
end
end
-- génère un tableau html d’isotopes, à partir
---- fmt_datas : les informations de style
---- isomatrix : la table des isotopes
---- headermatrix : les en-tête
local function to_html_table(fmt_datas, isomatrix, headermatrix, elements)
local htable = mw.html.create("table")
htable:addClass("wikitable")
local row_ref = headermatrix.min
local headers_ref = neutron_header_cells(1)
local col_decay = #headers_ref
local row_header_title = mw.html.create("th"):wikitext(wikilink("Proton" ,"p")):done()
local col_header_title = mw.html.create("th"):wikitext(wikilink("Neutron" ,"n")):done()
local hrow = htable:tag("tr")
insert_headers_in_row(hrow, headermatrix[row_ref], col_decay, row_header_title)
hrow:done()
row_ref = row_ref + 1
-- find and store the table data first row number
local min_row_idx = 1
if isomatrix.m[0] then
min_row_idx = 0
end
-- fill the space between the highest column header and the first row with the needed headers
while(row_ref < min_row_idx ) do
local hrow = htable:tag("tr")
local first = nil
if row_ref == min_row_idx - 1 then
first = col_header_title
end
insert_headers_in_row(hrow, headermatrix[row_ref], col_decay, first)
htable:done()
row_ref = row_ref +1
end
for row_num = min_row_idx, #isomatrix.m do
local hrow = mw.html.create("tr")
local data_row = isomatrix.m[row_num]
local minidx = math.max(find_min_idx(data_row), 1)
local maxidx = find_max_idx(data_row)
local headers_in_row = headermatrix[row_num]
local headers = neutron_header_cells(row_num)
empty_cell(hrow, - col_decay + minidx)
for _, header in ipairs(headers) do
hrow:node(header)
end
assert (next(data_row), "data row is empty for row" .. row_num)
-- populate table rows
for col_num = minidx, maxidx do
html_isotope_cell(
fmt_datas, hrow, isomatrix, elements,
col_num, row_num
)
end
local last_full_cell_index = maxidx
if headers_in_row then
local str=tostring(maxidx)
for i,x in ipairs(headers_in_row) do
str = str .. " " .. i
end
mw.log(str)
end
if headers_in_row then
assert (next(headers_in_row), "header row is empty for row " .. row_num)
for column = maxidx, find_max_idx(headers_in_row) do
local cell_content = headers_in_row[column]
if cell_content then
insert_needed_colspan(hrow, last_full_cell_index, column)
-- adding columns headers (element symbol, element atomic
-- number and links to the element and its isotope articles)
hrow:node(cell_content)
last_full_cell_index = column
end
end
end
htable:node(
hrow
)
--end --
end
return htable
end
function p.preprocess_fmtdatas(fmt_datatable)
local fmt_datas = {classes = {}, bounds={}, messages={}, styles={}, linkstyles={}}
for i, class_datas in pairs (p.classes_fmtdata) do
local class_lbl = class_datas[2]
fmt_datas.classes[i] = class_lbl
fmt_datas.bounds[i] = { bound = class_datas[1], class=class_lbl }
fmt_datas.messages[class_lbl] = class_datas[3]
fmt_datas.styles[class_lbl] = class_datas[4]
fmt_datas.linkstyles[class_lbl] = class_datas[5]
end
return fmt_datas
end
local function logmatrix(datamatrix)
for x, row in ipairs(isomatrix.m) do
local str=""
for y, c in pairs(row) do
str = str.. "(" .. x .. ",".. y .. ")"
end
mw.log(str)
end
end
function p.tableau(frame)
local elements = p.data_from_listeria_json("Modèle:Table des isotopes/données")
local fmt_datas = p.preprocess_fmtdatas(p.classes_fmtdata)
local isomatrix = matrix_from_datas(elements, Matrix:create())
local headermatrix = compute_rows_of_column_headers(isomatrix, elements, Matrix:create(), element_headers_cells)
local htmltable = to_html_table(fmt_datas, isomatrix, headermatrix, elements)
return p.legend(frame) .. tostring(htmltable)
end
function p.legend(frame)
local fmt_datas = p.preprocess_fmtdatas(p.classes_fmtdata)
local htable = mw.html.create("table")
htable:addClass("wikitable")
htable:tag("caption"):wikitext("légende"):done()
for i, cell in pairs(fmt_datas.bounds) do
htable:tag("tr")
:tag("td")
:addClass(cell.class)
:wikitext("isotope")
:done()
:tag("td")
:wikitext(fmt_datas.messages[cell.class])
:done()
:done()
end
return tostring(htable:done())
end
local function css_class(classes, props, selector_prefix, selector_suffix)
local res = "."
selector_prefix = selector_prefix or ""
selector_suffix = selector_suffix or ""
return selector_prefix .. "." .. table.concat(classes," .") .. selector_suffix .. "{\n\t" .. table.concat(props, ";\n\t") .. ";\n}\n"
end
-- utilisation pour générer la CSS, dans la console lua d’édition de modules :
-- taper « = p.gen_css(p.classes_fmtdata) »
p.gen_css = function(fmtdatas)
fmtdatas = p.preprocess_fmtdatas(fmtdatas)
local styles = fmtdatas.styles
local linkstyles = fmtdatas.linkstyles
local classes = fmtdatas.classes
for i, class in ipairs(classes) do
mw.log(css_class({class}, styles[class]))
if linkstyles[class] then
mw.log(css_class({class},linkstyles[class], "td", ">a"))
mw.log(css_class({class},linkstyles[class], "td>div" ,">a"))
end
end
end
return p