Module:NavboxBuilder

-- local NBB = {}

-- Constants local EXPANDED, COLLAPSED = 1, 2

-- Easy access to all arguments local arguments = {}

-- Invokable - Navbox creation function NBB.create(frame) if frame then arguments = getArguments(frame) end if type(NBB.hlist) == 'boolean' then NBB.hlist = NBB.hlist and 'hlist' or '' end if type(NBB.vlist) == 'boolean' then NBB.vlist = NBB.vlist and 'vlist' or '' end

local main = prepMain local sections = prepSections(main) local navbox = '' if #sections.list > 0 then navbox = makeNavbox(main, sections) end return navbox end -- Invokable - Return parameter documentation function NBB.documentation(frame) local docs if not pcall(function -- Try specified lang code        local lang = mw.ustring.lower(mw.text.trim(tostring(frame.args[1])))        docs = require('Dev:NavboxBuilder/doc/' .. lang)   end) then pcall(function  -- Then wiki lang        local lang = mw.getContentLanguage:getCode        docs = require('Dev:NavboxBuilder/doc/' .. lang)   end) end if not docs then -- Then English docs = require('Dev:NavboxBuilder/doc/en') -- Then default end for k,v in pairs(NBB.params) do       v = mw.ustring.gsub(v, '#', tostring(NBB.n), 1) v = mw.ustring.gsub(v, '#', tostring(NBB.m), 1) docs = mw.ustring.gsub(docs, '%%'..k..'%%', v)   end docs = mw.ustring.gsub(docs, '%%n%%', NBB.n)   docs = mw.ustring.gsub(docs, '%%m%%', NBB.m)    return docs end

-- Customizing default parameters function NBB.changeParameters(params) if type(params) == 'table' then for k,v in pairs(params) do           if NBB.params[k] then if k:sub(1, 6) == 'value_' then v = mw.ustring.lower(mw.text.trim(tostring(v) or '')) end NBB.params[k] = v           end end end return NBB end

-- Default parameter names NBB.params = { -- Settings links = 'Links', state = 'State', -- Fields title = 'Title', above = 'Above', below = 'Below', limage = 'Left image', rimage = 'Right image', -- Sections header_n = 'Header #', layout_n = 'Layout #', state_n = 'State #', header_state = 'Header state', -- Table layout limage_n = 'Left image #', rimage_n = 'Right image #', -- Horizontal layout perrow_n = 'Per row #', span_n = 'Span #', -- Groups group_n = 'Group #', group_n_m = 'Group #.#', list_n = 'List #', list_n_m = 'List #.#', -- CSS navbox_class = 'Navbox class', navbox_style = 'Navbox style', title_class = 'Title class', title_style = 'Title style', base_class = 'Base class', base_style = 'Base style', above_class = 'Above class', above_style = 'Above style', below_class = 'Below class', below_style = 'Below style', image_class = 'Image class', image_style = 'Image style', limage_class = 'Left image class', limage_style = 'Left image style', rimage_class = 'Right image class', rimage_style = 'Right image style', header_class = 'Header class', header_style = 'Header style', header_n_class = 'Header # class', header_n_style = 'Header # style', limage_n_class = 'Left image # class', limage_n_style = 'Left image # style', rimage_n_class = 'Right image # class', rimage_n_style = 'Right image # style', group_class = 'Group class', group_style = 'Group style', subgroup_class = 'Subgroup class', subgroup_style = 'Subgroup style', group_n_class = 'Group # class', group_n_style = 'Group # style', group_n_m_class = 'Group #.# class', group_n_m_style = 'Group #.# style', list_class = 'List class', list_style = 'List style', list_n_class = 'List # class', list_n_style = 'List # style', list_n_m_class = 'List #.# class', list_n_m_style = 'List #.# style', -- Values value_expanded = 'expanded', value_collapsed = 'collapsed', value_table_layout = 'table', value_horizontal_layout = 'horizontal', }

-- How n's and m's are displayed in documentations NBB.n = ' n ' NBB.m = ' m '

-- Class names for horizontal and vertical lists NBB.hlist = 'hlist' NBB.vlist = 'vlist'

-- Cleanup of arguments from frame and its parent function getArguments(frame, useFrame, useParent) local args = { keys = {}, frame = {}, parent = {}, }   if frame and frame.args then local keys = {} if useFrame or useFrame == nil then for k,v in pairs(frame.args) do               if type(k) == 'string' then k = mw.text.trim(k) end if type(v) == 'string' then v = mw.text.trim(v) end keys[k] = true args.frame[k] = v           end end if frame.getParent and (useParent or useParent == nil) then local parent = frame:getParent for k,v in pairs(parent.args) do               if type(k) == 'string' then k = mw.text.trim(k) end if type(v) == 'string' then v = mw.text.trim(v) end keys[k] = true args.parent[k] = v           end end for k,v in pairs(keys) do table.insert(args.keys, k) end end return args end

-- Returns value of a customized parameter function getValue(key, n, m)   local param = NBB.params[key] n, m = tonumber(n), tonumber(m) if n then param = mw.ustring.gsub(param, '#', tostring(n), 1) end if m then param = mw.ustring.gsub(param, '#', tostring(m), 1) end local val = arguments.parent[param] or arguments.frame[param] if val and val ~= '' then return val, arguments.frame[param], arguments.parent[param] end return nil end

-- Checks the input for customized values function checkValue(val, check) val = mw.ustring.lower(mw.text.trim(tostring(val) or '')) if type(check) == 'table' then for k,v in pairs(check) do           if NBB.params['value_' .. k] then local test = NBB.params['value_' .. k]               if val == test then return v end end end else check = tostring(check) if NBB.params['value_' .. check] then local test = NBB.params['value_' .. check] if val == test then return true end end end return nil end

-- Returns a list of values for parameters with variables function getList(...) local lists = {} local patterns = {} for i,v in ipairs(arg) do       if v then lists[i] = {} patterns[i] = '^'..mw.ustring.gsub(NBB.params[v], '#', '([0-9]+)')..'$' end end for i,k in ipairs(arguments.keys) do       local v = arguments.parent[k] or arguments.frame[k] if v and v ~= '' then for i,pattern in ipairs(patterns) do               local n, m = mw.ustring.match(k, pattern) if m then if not lists[i][tonumber(n)] then lists[i][tonumber(n)] = {} end lists[i][tonumber(n)][tonumber(m)] = v                   break elseif n then lists[i][tonumber(n)] = v                   break end end end end return unpack(lists) end

-- Determine which section the row belongs to function getSectionNo(test, list) for i=2,#list do       if test < list[i] then return list[i-1] end end return list[#list] or nil end

-- Reads parameters and prepares an object with settings for the navbox function prepMain local main = {} main.title = getValue('title') if main.title then main.links = getValue('links') main.state = checkValue(getValue('state'), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED}) end main.above = getValue('above') main.below = getValue('below') main.limage = getValue('limage') main.rimage = getValue('rimage') return main end

-- Reads parameters and prepares objects with necessary settings for each section function prepSections(main) local sections = {[0] = { rows = {} }} local headers, layouts, lists, sublists = getList('header_n', 'layout_n', 'list_n', 'list_n_m') for k,v in pairs(headers) do       sections[k] = { header = v, rows = {} } end for k,v in pairs(layouts) do       if not sections[k] then sections[k] = { layout = v, rows = {} } end end local numbers = getKeys(sections, true) for k,v in pairs(lists) do       local sec = getSectionNo(k, numbers) sections[sec].rows[k] = { list = v, group = getValue('group_n', k) } end for k,list in pairs(sublists) do       local sec = getSectionNo(k, numbers) local obj = { rows = {}, group = getValue('group_n', k) } for l,v in pairs(list) do           obj.rows[l] = { list = v, group = getValue('group_n_m', k, l) } end sections[sec].rows[k] = obj end for _,v in ipairs(numbers) do       local rows = getKeys(sections[v].rows) if #rows > 0 then sections[v].state = checkValue(getValue('state_n', v), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED}) or checkValue(getValue('header_state'), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED}) sections[v].layout = getValue('layout_n', v) or 'table' else sections[v] = nil end end sections.list = getKeys(sections, true) return sections end

-- Returns a list of keys in a table function getKeys(tab, sorted) local keys = {} if not tab then return keys end for k,v in pairs(tab) do       table.insert(keys, k)    end if sorted then table.sort(keys) end return keys end

-- Applies styles and classes from parameters to the element function applyCSS(elem, ...) if not elem then return nil end local classes, styles = {}, {} for i,v in ipairs(arg) do       local c, cP, cF, s, sP, sF        if type(v) == 'table' then local key = v[1] v[1] = key .. '_class' c, cF, cP = getValue(unpack(v)) v[1] = key .. '_style' s, sF, sP = getValue(unpack(v)) else v = tostring(v) c, cF, cP = getValue(v .. '_class') s, sF, sP = getValue(v .. '_style') end if c then table.insert(classes, cF) table.insert(classes, cP) end if s then table.insert(styles, sF) table.insert(styles, sP) end end if #classes > 0 then classes = mw.ustring.gsub(table.concat(classes, ' '), ' +', ' ') elem:addClass(classes) end if #styles > 0 then styles = mw.ustring.gsub(table.concat(styles, ';'), ';;+', ';') styles = mw.ustring.gsub(styles, ';$', '') elem:cssText(styles) end return elem end

-- Makes an element collapsible function collapsible(elem, header, state) if state then elem:addClass('mw-collapsible') if state == COLLAPSED then elem:addClass('mw-collapsed') end if header then header:addClass('mw-collapsible-toggle') end return true end return false end

-- Creates a navbox with prepared settings function makeNavbox(main, sections) -- Structure local box, title, wrapper, tab, row, links, above, below, limage, rimage, content box = mw.html.create('div') if main.title then title = box:tag('div') if main.links then links = title:tag('div'):wikitext(main.links .. ' ') end title:tag('span'):addClass('navbox-title-text'):wikitext(main.title) end wrapper = box:tag('div') tab = wrapper:tag('table') if main.above then above = tab:tag('tr'):tag('td'):newline:wikitext(main.above) above:done:newline end row = tab:tag('tr') if main.limage then limage = row:tag('td'):newline:wikitext(main.limage) limage:done:newline end content = row:tag('td') for i,v in ipairs(sections.list) do content:node(makeSection(sections[v], v)) end if #content.nodes == 0 then return nil end if main.rimage then rimage = row:tag('td'):newline:wikitext(main.rimage) rimage:done:newline end if main.below then below = tab:tag('tr'):tag('td'):newline:wikitext(main.below) below:done:newline end -- Appearance box:addClass('navbox') applyCSS(box, 'navbox') wrapper:addClass('navbox-table-wrapper') tab:addClass('navbox-table') content:addClass('navbox-content') if title then title:addClass('navbox-title') applyCSS(title, 'title') if collapsible(box, title, main.state) then wrapper:addClass('mw-collapsible-content') end end if links then links:addClass('navbox-template-links') end if above then above:addClass('navbox-above navbox-base navbox-padding') applyCSS(above, 'base', 'above') end if below then below:addClass('navbox-below navbox-base navbox-padding') applyCSS(below, 'base', 'below') end if limage then limage:addClass('navbox-image') applyCSS(limage, 'image', 'limage') end if rimage then rimage:addClass('navbox-image') applyCSS(rimage, 'image', 'rimage') end if limage or rimage then local cols = 1 + (limage and 1 or 0) + (rimage and 1 or 0) if cols > 1 then if above then above:attr('colspan', cols) end if below then below:attr('colspan', cols) end end end return box end

-- Creates a single section function makeSection(section, no) -- Structure local sec, header, wrapper sec = mw.html.create('div') if section.header then header = sec:tag('div'):wikitext(section.header) end wrapper = sec:tag('div') -- Appearance sec:addClass('navbox-section') wrapper:addClass('navbox-section-wrapper') if header then header:addClass('navbox-header navbox-base') if collapsible(sec, header, section.state) then wrapper:addClass('mw-collapsible-content') end applyCSS(header, 'base', 'header', {'header_n', no}) end -- Layout local layout for k,v in pairs(NBB.formats) do       if checkValue(section.layout, k .. '_layout') then layout = k           break else if k == section.layout then layout = k               break end end end layout = layout or 'table' section.layout = layout local res = NBB.formats[layout](section, no, wrapper) if res == nil then return nil end wrapper:node(res) wrapper:addClass('navbox-' .. layout .. '-layout') return sec end

NBB.formats = {}

NBB.formats.table = function(section, no) local lists, deep, count, keys = {}, false, 0, getKeys(section.rows, true) if #keys == 0 then return nil end -- Structure local tab, limage, rimage section.limage = getValue('limage_n', no) section.rimage = getValue('rimage_n', no) tab = mw.html.create('table') for i1,v1 in ipairs(keys) do       count = count + 1 local row1 = section.rows[v1] -- Structure local tr, group, list tr = tab:tag('tr'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd')) if i1 == 1 and section.limage then limage = tr:tag('td'):newline:wikitext(section.limage) limage:done:newline end if row1.group then group = tr:tag('th'):wikitext(row1.group) end local keys = getKeys(row1.rows, true) if #keys > 0 then for i2,v2 in ipairs(keys) do               local row2 = row1.rows[v2] -- Structure local tr, group, list = tr               if i2 > 1 then count = count + 1 tr = tab:tag('tr'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd')) end if row2.group then group = tr:tag('th'):wikitext(row2.group) deep = true end list = tr:tag('td'):newline:wikitext(row2.list) list:done:newline -- Appearance list:addClass('navbox-list navbox-padding'):addClass(NBB.hlist or '') applyCSS(list, 'list', {'list_n_m', v1, v2}) if not row2.group then list:attr('colspan', 2) end if not row1.group then table.insert(lists, list) end if group then group:addClass('navbox-subgroup navbox-base navbox-padding') applyCSS(group, 'base', 'subgroup', {'group_n_m', v1, v2}) end end else -- Structure list = tr:tag('td'):newline:wikitext(row1.list) list:done:newline table.insert(lists, list) -- Appearance list:addClass('navbox-list navbox-padding'):addClass(NBB.hlist or '') if not row1.group then list:attr('colspan', 2):addClass('navbox-nogroup') end applyCSS(list, 'list', {'list_n', v1}) end if i1 == 1 and section.rimage then rimage = tr:tag('td'):newline:wikitext(section.rimage) rimage:done:newline end -- Appearance if group then group:addClass('navbox-group navbox-base navbox-padding') if #keys > 1 then group:attr('rowspan', #keys) end applyCSS(group, 'base', 'group', {'group_n', v1}) end end -- Appearance tab:addClass('navbox-table') for i,v in ipairs(lists) do       local span = v:getAttr('colspan') or 1 if deep then span = span + 1 end if span == 1 then span = nil end v:attr('colspan', span) end if limage then limage:addClass('navbox-image') applyCSS(limage, 'image', {'limage_n', no}) if count > 1 then limage:attr('rowspan', count) end end if rimage then rimage:addClass('navbox-image') applyCSS(rimage, 'image', {'rimage_n', no}) if count > 1 then rimage:attr('rowspan', count) end end return tab end NBB.formats.horizontal = function(section, no, wrapper) local lists, deep, count, keys = {}, false, 0, getKeys(section.rows, true) if #keys == 0 then return nil end -- Structure local res, wrap wrap = tonumber(getValue('perrow_n', no)) if wrap then wrap = math.max(1, wrap) -- if wrap > 0 then --    wrapper:addClass('navbox-static'):css('--wrap', wrap) -- end end res = mw.html.create('') for i1,v1 in ipairs(keys) do       local row1 = section.rows[v1] -- Structure local keys = getKeys(row1.rows, true) if #keys > 0 then for i2,v2 in ipairs(keys) do               count = count + 1 local row2 = row1.rows[v2] -- Structure local col, group, list col = res:tag('div'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd')) if row2.group then group = col:tag('div'):wikitext(row2.group) end if wrap then -- delete in case of css vars col:css('flex-basis', 100/wrap .. '%') end list = col:tag('div'):newline:wikitext(row2.list) list:done:newline table.insert(lists, list) -- Appearance col:addClass('navbox-col') list:addClass('navbox-list navbox-padding'):addClass(NBB.vlist or '') applyCSS(list, 'list', {'list_n', v1}) if group then group:addClass('navbox-group navbox-base navbox-padding') applyCSS(group, 'base', 'group', {'group_n', v1}) end end else count = count + 1 -- Structure local col, group, list col = res:tag('div'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd')) if row1.group then group = col:tag('div'):wikitext(row1.group) end if wrap then local span = math.max(0, math.min(wrap, tonumber(getValue('span_n', v1)) or 1)) col:css('flex-basis', span/wrap*100 .. '%') -- delete in case of css vars -- if span ~= 1 then --    col:css('--span', span) -- end else col._span = tonumber(getValue('span_n', v1)) end list = col:tag('div'):newline:wikitext(row1.list) list:done:newline table.insert(lists, list) -- Appearance col:addClass('navbox-col') list:addClass('navbox-list navbox-padding'):addClass(NBB.vlist or '') applyCSS(list, 'list', {'list_n', v1}) if group then group:addClass('navbox-group navbox-base navbox-padding') applyCSS(group, 'base', 'group', {'group_n', v1}) end end end if not wrap then wrap = #lists for i,v in ipairs(lists) do           local col = v.parent col:css('flex-basis', math.max(0, math.min(wrap, col._span or 1))/wrap*100 .. '%') end end return res end

return NBB