<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.jads.stream/history/Module:Navbox?feed=atom</id>
	<title>Module:Navbox - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.jads.stream/history/Module:Navbox?feed=atom"/>
	<link rel="alternate" type="text/html" href="https://wiki.jads.stream/history/Module:Navbox"/>
	<updated>2026-04-20T08:18:02Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.3</generator>
	<entry>
		<id>https://wiki.jads.stream/index.php?title=Module:Navbox&amp;diff=82&amp;oldid=prev</id>
		<title>Latty at 23:03, 20 January 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.jads.stream/index.php?title=Module:Navbox&amp;diff=82&amp;oldid=prev"/>
		<updated>2025-01-20T23:03:14Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://wiki.jads.stream/index.php?title=Module:Navbox&amp;amp;diff=82&amp;amp;oldid=76&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Latty</name></author>
	</entry>
	<entry>
		<id>https://wiki.jads.stream/index.php?title=Module:Navbox&amp;diff=76&amp;oldid=prev</id>
		<title>Latty: Created page with &quot;-- version 1.1.3  -- config table for RANGER. -- If you want to change the default config, DO NOT change it here, -- please do it via the `onLoadConfig` hook in Module:Navbox/Hooks. local config = { 	default_navbox_class = &quot;navigation-not-searchable&quot;,   -- Base value of the `class` parameter. 	default_title_class = nil,    -- Base value of the `title_class` parameter. 	default_above_class = nil,    -- Base value of the `above_class` parameter. 	default_below_class =...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.jads.stream/index.php?title=Module:Navbox&amp;diff=76&amp;oldid=prev"/>
		<updated>2025-01-20T22:34:00Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;-- version 1.1.3  -- config table for RANGER. -- If you want to change the default config, DO NOT change it here, -- please do it via the `onLoadConfig` hook in &lt;a href=&quot;/edit/Module:Navbox/Hooks?redlink=1&quot; class=&quot;new&quot; title=&quot;Module:Navbox/Hooks (page does not exist)&quot;&gt;Module:Navbox/Hooks&lt;/a&gt;. local config = { 	default_navbox_class = &amp;quot;navigation-not-searchable&amp;quot;,   -- Base value of the `class` parameter. 	default_title_class = nil,    -- Base value of the `title_class` parameter. 	default_above_class = nil,    -- Base value of the `above_class` parameter. 	default_below_class =...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- version 1.1.3&lt;br /&gt;
&lt;br /&gt;
-- config table for RANGER.&lt;br /&gt;
-- If you want to change the default config, DO NOT change it here,&lt;br /&gt;
-- please do it via the `onLoadConfig` hook in [[Module:Navbox/Hooks]].&lt;br /&gt;
local config = {&lt;br /&gt;
	default_navbox_class = &amp;quot;navigation-not-searchable&amp;quot;,   -- Base value of the `class` parameter.&lt;br /&gt;
	default_title_class = nil,    -- Base value of the `title_class` parameter.&lt;br /&gt;
	default_above_class = nil,    -- Base value of the `above_class` parameter.&lt;br /&gt;
	default_below_class = nil,    -- Base value of the `below_class` parameter.&lt;br /&gt;
	default_section_class =nil,   -- Base value of the `section_class` parameter.&lt;br /&gt;
	default_header_class = nil,   -- Base value of the `header_class` parameter.&lt;br /&gt;
	default_group_class = nil,    -- Base value of the `group_class` parameter.&lt;br /&gt;
	default_list_class = &amp;#039;hlist&amp;#039;, -- Base value of the `list_class` parameter.&lt;br /&gt;
	&lt;br /&gt;
	default_header_state = nil, -- Base value of the `state` parameter.&lt;br /&gt;
&lt;br /&gt;
	editlink_hover_message_key = &amp;#039;Navbox-edit-hover&amp;#039;, -- The system message name for hover text of the edit icon. &lt;br /&gt;
	&lt;br /&gt;
	auto_flatten_top_level = true, -- If true, when a section has only one list with no content and no corresponding group but has sublists, these sublists will be moved to top level.&lt;br /&gt;
	-- This helps make the hierarchy of sections and content clearer.&lt;br /&gt;
	-- An example:&lt;br /&gt;
	-- {{navbox&lt;br /&gt;
	-- ...&lt;br /&gt;
	--   |header-1 = Items&lt;br /&gt;
	--   | group-1.1 = Weapons&lt;br /&gt;
	--   |  list-1.1 = Swords · Guns · Wands&lt;br /&gt;
	--   | group-1.2 = Armors&lt;br /&gt;
	--   |  list-1.2 = Head pieces · Capes&lt;br /&gt;
	--   |header-2 = NPCs&lt;br /&gt;
	--   | group-2.1 = Town NPCs&lt;br /&gt;
	--   |  list-2.1 = Guide · Witch&lt;br /&gt;
	-- ...&lt;br /&gt;
	-- }}&lt;br /&gt;
	-- will be equal to:&lt;br /&gt;
	-- {{navbox&lt;br /&gt;
	-- ...&lt;br /&gt;
	--   |header-1 = Items&lt;br /&gt;
	--   | group-2 = Weapons&lt;br /&gt;
	--   |  list-2 = Swords · Guns · Wands&lt;br /&gt;
	--   | group-3 = Armors&lt;br /&gt;
	--   |  list-3 = Head pieces · Capes&lt;br /&gt;
	--   |header-5 = NPCs&lt;br /&gt;
	--   | group-6 = Town NPCs&lt;br /&gt;
	--   |  list-6 = Guide · Witch&lt;br /&gt;
	-- ...&lt;br /&gt;
	-- }}&lt;br /&gt;
	&lt;br /&gt;
	custom_render_handle = nil, -- usually for debugging purposes only. if set, it should be a function accept 2 parameters: `dataTree` and `args`, and return a string as module output.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
---------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Argument alias.&lt;br /&gt;
local CANONICAL_NAMES = {&lt;br /&gt;
	[&amp;#039;titlestyle&amp;#039;] = &amp;#039;title_style&amp;#039;,&lt;br /&gt;
	[&amp;#039;listclass&amp;#039;] = &amp;#039;list_class&amp;#039;,&lt;br /&gt;
	[&amp;#039;groupstyle&amp;#039;] = &amp;#039;group_style&amp;#039;,&lt;br /&gt;
	[&amp;#039;collapsible&amp;#039;] = &amp;#039;state&amp;#039;,&lt;br /&gt;
	[&amp;#039;editlink&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;editlinks&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;editicon&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;edit_link&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;edit_links&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;edit_icon&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;navbar&amp;#039;] = &amp;#039;meta&amp;#039;,&lt;br /&gt;
	[&amp;#039;evenodd&amp;#039;] = &amp;#039;striped&amp;#039;,&lt;br /&gt;
	[&amp;#039;class&amp;#039;] = &amp;#039;navbox_class&amp;#039;,&lt;br /&gt;
	[&amp;#039;css&amp;#039;] = &amp;#039;navbox_style&amp;#039;,&lt;br /&gt;
	[&amp;#039;style&amp;#039;] = &amp;#039;navbox_style&amp;#039;,&lt;br /&gt;
	[&amp;#039;group&amp;#039;] = &amp;#039;1:group&amp;#039;,&lt;br /&gt;
	[&amp;#039;list&amp;#039;] = &amp;#039;1:list&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STATES = {&lt;br /&gt;
	[&amp;#039;no&amp;#039;] = &amp;#039;&amp;#039;,&lt;br /&gt;
	[&amp;#039;off&amp;#039;] = &amp;#039;&amp;#039;,&lt;br /&gt;
	[&amp;#039;plain&amp;#039;] = &amp;#039;&amp;#039;,&lt;br /&gt;
	[&amp;#039;collapsed&amp;#039;] = &amp;#039;mw-collapsible mw-collapsed&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local BOOL_FALSE = {&lt;br /&gt;
	[&amp;#039;no&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;off&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;false&amp;#039;] = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STRIPED = {&lt;br /&gt;
	[&amp;#039;odd&amp;#039;] = &amp;#039;striped-odd&amp;#039;,&lt;br /&gt;
	[&amp;#039;swap&amp;#039;] = &amp;#039;striped-odd&amp;#039;,&lt;br /&gt;
	[&amp;#039;y&amp;#039;] = &amp;#039;striped-even&amp;#039;,&lt;br /&gt;
	[&amp;#039;yes&amp;#039;] = &amp;#039;striped-even&amp;#039;,&lt;br /&gt;
	[&amp;#039;on&amp;#039;] = &amp;#039;striped-even&amp;#039;,&lt;br /&gt;
	[&amp;#039;even&amp;#039;] = &amp;#039;striped-even&amp;#039;,&lt;br /&gt;
	[&amp;#039;striped&amp;#039;] = &amp;#039;striped-even&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local DEFAULT_ARGS = {&lt;br /&gt;
	[&amp;#039;meta&amp;#039;] = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local NAVBOX_CHILD_INDICATOR = &amp;#039;!!C$H$I$L$D!!&amp;#039;&lt;br /&gt;
local NAVBOX_CHILD_INDICATOR_LENGTH = string.len( NAVBOX_CHILD_INDICATOR )&lt;br /&gt;
&lt;br /&gt;
local CLASS_PREFIX = &amp;#039;ranger-&amp;#039;&lt;br /&gt;
&lt;br /&gt;
---------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
local h = {} -- non-public&lt;br /&gt;
local hooks = mw.title.new(&amp;#039;Module:Navbox/Hooks&amp;#039;).exists and require(&amp;#039;Module:Navbox/Hooks&amp;#039;) or {}&lt;br /&gt;
&lt;br /&gt;
---------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- For templates: {{#invoke:navbox|main|...}}&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = p.mergeArgs(frame)&lt;br /&gt;
	args = h.parseArgs(args)&lt;br /&gt;
	return p.build(args)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For modules: return require(&amp;#039;module:navbox&amp;#039;).build(args)&lt;br /&gt;
-- By default this method will skip the arguments sanitizing phase &lt;br /&gt;
-- (and onSanitizeArgsStart/onSanitizeArgsEnd hooks).&lt;br /&gt;
-- Set `doParseArgs` to true to do arguments sanitizing.&lt;br /&gt;
-- If `customConfig` table is provided, it will be merged into default config table.&lt;br /&gt;
-- If `customHooks` table is provided, all default hook handles will be overrided, unprovided hooks will be empty.&lt;br /&gt;
function p.build(args, doParseArgs, customConfig, customHooks)&lt;br /&gt;
	if customHooks then&lt;br /&gt;
		hooks = customHooks&lt;br /&gt;
	end&lt;br /&gt;
	if customConfig then&lt;br /&gt;
		for k,v in pairs(customConfig) do&lt;br /&gt;
			config[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if doParseArgs then &lt;br /&gt;
		args = h.parseArgs(args)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	h.runHook(&amp;#039;onLoadConfig&amp;#039;, config, args)&lt;br /&gt;
	&lt;br /&gt;
	--merge default args&lt;br /&gt;
	for k,v in pairs(DEFAULT_ARGS) do&lt;br /&gt;
		if args[k] == nil then&lt;br /&gt;
			args[k] = DEFAULT_ARGS[k]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	h.runHook(&amp;#039;onBuildTreeStart&amp;#039;, args)&lt;br /&gt;
	local dataTree = h.buildDataTree(args)&lt;br /&gt;
	h.runHook(&amp;#039;onBuildTreeEnd&amp;#039;, dataTree, args)&lt;br /&gt;
	&lt;br /&gt;
	if type(config.custom_render_handle) == &amp;#039;function&amp;#039; then&lt;br /&gt;
		return config.custom_render_handle(dataTree, args)&lt;br /&gt;
	else&lt;br /&gt;
		return h.render(dataTree)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- merge args from frame and frame:getParent()&lt;br /&gt;
-- It may be used when creating custom wrapping navbox module.&lt;br /&gt;
--&lt;br /&gt;
-- For example, Module:PillNavbox&lt;br /&gt;
--&lt;br /&gt;
-- local RANGER = require(&amp;#039;Module:Navbox&amp;#039;)&lt;br /&gt;
-- local p = {}&lt;br /&gt;
-- function p.main(frame)&lt;br /&gt;
--     return RANGER.build(RANGER.mergeArgs(frame), true, {&lt;br /&gt;
--         default_navbox_class = &amp;#039;pill&amp;#039;, -- use &amp;quot;pill&amp;quot; style by default.&lt;br /&gt;
--     })&lt;br /&gt;
-- end&lt;br /&gt;
-- return p&lt;br /&gt;
--&lt;br /&gt;
function p.mergeArgs(frame)&lt;br /&gt;
	local inputArgs = {}&lt;br /&gt;
	&lt;br /&gt;
	for k, v in pairs(frame.args) do&lt;br /&gt;
		v = mw.text.trim(tostring(v))&lt;br /&gt;
		if v ~= &amp;#039;&amp;#039; then&lt;br /&gt;
			inputArgs[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	for k, v in pairs(frame:getParent().args) do&lt;br /&gt;
		v = mw.text.trim(v)&lt;br /&gt;
		if v ~= &amp;#039;&amp;#039; then&lt;br /&gt;
			inputArgs[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return inputArgs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
function h.parseArgs(inputArgs)&lt;br /&gt;
	&lt;br /&gt;
	h.runHook(&amp;#039;onSanitizeArgsStart&amp;#039;, inputArgs)&lt;br /&gt;
	&lt;br /&gt;
	local args = {}&lt;br /&gt;
	&lt;br /&gt;
	for k, v in pairs(inputArgs) do&lt;br /&gt;
		-- all args have already been trimmed&lt;br /&gt;
		if type(k) == &amp;#039;string&amp;#039; then&lt;br /&gt;
			local key = h.normalizeKey(k)&lt;br /&gt;
			args[key] = h.normalizeValue(key, v)&lt;br /&gt;
		else&lt;br /&gt;
			args[k] = mw.text.trim(v) -- keep number-index arguments (for {{navbox|child|...}})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	h.runHook(&amp;#039;onSanitizeArgsEnd&amp;#039;, args, inputArgs)&lt;br /&gt;
	&lt;br /&gt;
	return args&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Normalize the name string of arguments.&lt;br /&gt;
-- the normalized form is (index:)?name, in which:&lt;br /&gt;
-- index is number index such as 1, 1.3, 1.2.45,&lt;br /&gt;
-- name is in lowercase underscore-case, such as group, group_style&lt;br /&gt;
-- e.g: header_state, 1.3:list_style&lt;br /&gt;
-- the input argument name can be:&lt;br /&gt;
-- * camel-case: listStyle, ListStyle&lt;br /&gt;
-- * space separated: list style&lt;br /&gt;
-- * prefix+index+postfix?, and can be in camel-case or space/hyphen separated or mixed: list 1 style, list1, list1Style, list1_style&lt;br /&gt;
-- * index.name: 1.3.list&lt;br /&gt;
-- * index_name: 1.3_list (Space separated are treated as underscore separated, therefore 1.3 list are vaild too)&lt;br /&gt;
function h.normalizeKey(s)&lt;br /&gt;
	-- camel-case to lowercase underscore-case&lt;br /&gt;
	s = s:gsub(&amp;#039;%l%f[%u]&amp;#039;, &amp;#039;%0_&amp;#039;) -- listStyle to list_style&lt;br /&gt;
	s = (s:gsub(&amp;#039; &amp;#039;, &amp;#039;_&amp;#039;)):lower() -- space to underscore &lt;br /&gt;
	s = s:gsub(&amp;#039;%l%f[%d]&amp;#039;, &amp;#039;%0_&amp;#039;) -- group1* to group_1*&lt;br /&gt;
	s = s:gsub(&amp;#039;%d%f[%l]&amp;#039;, &amp;#039;%0_&amp;#039;) -- *1style to *1_style&lt;br /&gt;
	&lt;br /&gt;
	-- number format x_y_z to x.y.z&lt;br /&gt;
	s = s:gsub(&amp;#039;(%d)_%f[%d]&amp;#039;, &amp;#039;%1%.&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	-- move index to the beginning:&lt;br /&gt;
	-- group_1.2_style to 1.2:group_style&lt;br /&gt;
	-- group_1 to 1:group&lt;br /&gt;
	s = s:gsub(&amp;#039;^([%l_]+)_([%d%.]+)&amp;#039;, &amp;#039;%2:%1&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	-- support index.name and index_name:&lt;br /&gt;
	-- 1.2.group / 1.2_group to 1.2:group&lt;br /&gt;
	s = s:gsub(&amp;#039;^([%d%.]+)[%._]%f[%l]&amp;#039;, &amp;#039;%1:&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	-- now the key should be in normalized form, if the origin key is vaild&lt;br /&gt;
&lt;br /&gt;
	-- standardize *_css to *_style&lt;br /&gt;
	s = s:gsub(&amp;#039;_css$&amp;#039;, &amp;#039;_style&amp;#039;)&lt;br /&gt;
	-- standardize *collapsible to *state&lt;br /&gt;
	s = s:gsub(&amp;#039;collapsible$&amp;#039;, &amp;#039;state&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	&lt;br /&gt;
	-- standardize all aliases to the canonical name&lt;br /&gt;
	return CANONICAL_NAMES[s] or s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.normalizeValue(k, v)&lt;br /&gt;
	k = tostring(k)&lt;br /&gt;
	if k:find(&amp;#039;_style$&amp;#039;) then&lt;br /&gt;
		v = (v .. &amp;#039;;&amp;#039;):gsub(&amp;#039;;;&amp;#039;, &amp;#039;;&amp;#039;)&lt;br /&gt;
		return v&lt;br /&gt;
	elseif k == &amp;#039;striped&amp;#039; then&lt;br /&gt;
		return STRIPED[v]&lt;br /&gt;
	elseif v:sub(1, 2) == &amp;#039;{|&amp;#039; or v:match(&amp;#039;^[*:;#]&amp;#039;) then&lt;br /&gt;
		-- Applying nowrap to lines in a table does not make sense.&lt;br /&gt;
		-- Add newlines to compensate for trim of x in |parm=x in a template.&lt;br /&gt;
		return &amp;#039;\n&amp;#039; .. v ..&amp;#039;\n&amp;#039;&lt;br /&gt;
	elseif k == &amp;#039;meta&amp;#039; then&lt;br /&gt;
		return not BOOL_FALSE[v]&lt;br /&gt;
	end&lt;br /&gt;
	return v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- we need a default value for all empty state_* arguments, so we can not do this in h.normalizeValue()&lt;br /&gt;
function h.normalizeStateValue(v)&lt;br /&gt;
	return STATES[v] or &amp;#039;mw-collapsible&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- parse arguments, convert them to structured data tree&lt;br /&gt;
function h.buildDataTree(args)&lt;br /&gt;
	local data = {&lt;br /&gt;
		state = h.normalizeStateValue(args.state),&lt;br /&gt;
		striped = args.striped,&lt;br /&gt;
		class = h.mergeAttrs(args.navbox_class, config.default_navbox_class),&lt;br /&gt;
		style = args.navbox_style,&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	if args.title or args.meta or data.state ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		data.title = {&lt;br /&gt;
			content = args.title,&lt;br /&gt;
			class = h.mergeAttrs(args.title_class, config.default_title_class),&lt;br /&gt;
			style = args.title_style,&lt;br /&gt;
		}&lt;br /&gt;
		if args.meta then&lt;br /&gt;
			data.metaLinks = {&lt;br /&gt;
				template = args.name or mw.getCurrentFrame():getParent():getTitle()&lt;br /&gt;
			}&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if args.above then&lt;br /&gt;
		data.above = {&lt;br /&gt;
			content = args.above,&lt;br /&gt;
			class= h.mergeAttrs(args.above_class, config.default_above_class),&lt;br /&gt;
			style = args.above_style,&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if args.below then&lt;br /&gt;
		data.below = {&lt;br /&gt;
			content = args.below,&lt;br /&gt;
			class= h.mergeAttrs(args.below_class, config.default_below_class),&lt;br /&gt;
			style = args.below_style,&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local tree = h.buildTree(args, {&lt;br /&gt;
		listClass = h.mergeAttrs(args.list_class, config.default_list_class),&lt;br /&gt;
		listStyle =  args.list_style,&lt;br /&gt;
		groupClass = h.mergeAttrs(args.group_class, config.default_group_class),&lt;br /&gt;
		groupStyle = args.group_style,&lt;br /&gt;
		headerClass = h.mergeAttrs(args.header_class, config.default_header_class),&lt;br /&gt;
		headerStyle = args.header_style,&lt;br /&gt;
	})&lt;br /&gt;
	&lt;br /&gt;
	-- handle {{navbox|child|...}} syntax:&lt;br /&gt;
	if args[1] == &amp;#039;child&amp;#039; then&lt;br /&gt;
		return NAVBOX_CHILD_INDICATOR..mw.text.jsonEncode(tree)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- normal case&lt;br /&gt;
	local sectionClass = h.mergeAttrs(args.section_class, config.default_section_class)&lt;br /&gt;
	local sectionStyle = args.section_style&lt;br /&gt;
	local headerState = args.header_state or config.default_header_state&lt;br /&gt;
	data.sections = {}&lt;br /&gt;
	local section&lt;br /&gt;
	for k, v in h.orderedPairs(tree or {}) do&lt;br /&gt;
		if v.header or not section then&lt;br /&gt;
			--start a new section&lt;br /&gt;
			section = { &lt;br /&gt;
				class = h.mergeAttrs(args[k..&amp;#039;:section_class&amp;#039;], sectionClass),&lt;br /&gt;
				style = h.mergeAttrs(args[k..&amp;#039;:section_style&amp;#039;], sectionStyle),&lt;br /&gt;
				body = {},&lt;br /&gt;
			}&lt;br /&gt;
			-- Section header if needed.&lt;br /&gt;
			-- If the value of a `|header_n=` is two or more consecutive &amp;quot;-&amp;quot; characters (e.g. --, -----), &lt;br /&gt;
			-- it means start a new section without header, and the new section will be not collapsable.&lt;br /&gt;
			if v.header and not string.match(v.header.content, &amp;#039;^%-%-+$&amp;#039;) then&lt;br /&gt;
				section.header =v.header&lt;br /&gt;
				section.state = h.normalizeStateValue(args[k..&amp;#039;:state&amp;#039;] or headerState)&lt;br /&gt;
			end&lt;br /&gt;
			v.header = nil&lt;br /&gt;
			data.sections[#data.sections+1] = section&lt;br /&gt;
		end&lt;br /&gt;
		-- check for section above/below areas&lt;br /&gt;
		if v.above then&lt;br /&gt;
			section.above = v.above&lt;br /&gt;
			v.above = nil&lt;br /&gt;
		end&lt;br /&gt;
		if v.below then&lt;br /&gt;
			section.below = v.below&lt;br /&gt;
			v.below = nil&lt;br /&gt;
		end&lt;br /&gt;
		if next(v) then -- v is not empty (with group/list/sub)&lt;br /&gt;
			section.body[#section.body+1] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if config.auto_flatten_top_level then&lt;br /&gt;
		for _, sect in ipairs(data.sections) do&lt;br /&gt;
			if #sect.body == 1 then&lt;br /&gt;
				local node = sect.body[1]&lt;br /&gt;
				if not node.group and not node.list and node.sub then&lt;br /&gt;
					sect.body = node.sub&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.buildTree(args, defaults)&lt;br /&gt;
	local tree = {}&lt;br /&gt;
	local check = function(key, value)&lt;br /&gt;
		local index, name = string.match(key, &amp;#039;^([%d%.]+):(.+)$&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
		if not index then return end -- no number index found&lt;br /&gt;
		if name ~= &amp;#039;list&amp;#039; and name ~= &amp;#039;group&amp;#039; and name ~= &amp;#039;header&amp;#039; and name ~= &amp;#039;above&amp;#039; and name ~= &amp;#039;below&amp;#039; then return end -- check only the names we are interested in&lt;br /&gt;
		if string.match(index, &amp;#039;^%.&amp;#039;) or string.match(index, &amp;#039;%.$&amp;#039;) or string.match(index, &amp;#039;%.%.&amp;#039;) then return end -- invalid number index&lt;br /&gt;
		&lt;br /&gt;
		-- find the node that matches the index in the tree&lt;br /&gt;
		local arr = mw.text.split(index, &amp;#039;.&amp;#039;, true)&lt;br /&gt;
		n = tonumber(table.remove(arr))&lt;br /&gt;
		local node = tree&lt;br /&gt;
		for _, v in ipairs(arr) do&lt;br /&gt;
			v = tonumber(v)&lt;br /&gt;
			if not node[v] then &lt;br /&gt;
				node[v] = {[&amp;#039;sub&amp;#039;] = {}} &lt;br /&gt;
			elseif not node[v][&amp;#039;sub&amp;#039;] then&lt;br /&gt;
				node[v][&amp;#039;sub&amp;#039;] = {}&lt;br /&gt;
			end&lt;br /&gt;
			node = node[v][&amp;#039;sub&amp;#039;]&lt;br /&gt;
		end&lt;br /&gt;
		if not node[n] then node[n] = {} end&lt;br /&gt;
		&lt;br /&gt;
		if name == &amp;#039;list&amp;#039; and string.sub(value, 1, NAVBOX_CHILD_INDICATOR_LENGTH) == NAVBOX_CHILD_INDICATOR then&lt;br /&gt;
			-- it is from {{navbox|child| ... }}&lt;br /&gt;
			node[n][&amp;#039;sub&amp;#039;] = mw.text.jsonDecode(string.sub(value, NAVBOX_CHILD_INDICATOR_LENGTH+1))&lt;br /&gt;
		else&lt;br /&gt;
			node[n][name] = {&lt;br /&gt;
				content = value,&lt;br /&gt;
				class= h.mergeAttrs(args[key..&amp;#039;_class&amp;#039;], defaults[name..&amp;#039;Class&amp;#039;]),&lt;br /&gt;
				style = h.mergeAttrs(args[key..&amp;#039;_style&amp;#039;], defaults[name..&amp;#039;Style&amp;#039;])&lt;br /&gt;
			}&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	for k,v in pairs(args) do&lt;br /&gt;
		check(k, v)&lt;br /&gt;
	end&lt;br /&gt;
	return tree&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.render(data)&lt;br /&gt;
	-- handle {{navbox|child|...}} syntax&lt;br /&gt;
	if type(data) == &amp;#039;string&amp;#039; then&lt;br /&gt;
		return data&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-----  normal case -----&lt;br /&gt;
	&lt;br /&gt;
	local out = mw.html.create()&lt;br /&gt;
	&lt;br /&gt;
	-- build navbox container&lt;br /&gt;
	local navbox = out:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
		:attr(&amp;#039;role&amp;#039;, &amp;#039;navigation&amp;#039;):attr(&amp;#039;aria-label&amp;#039;, &amp;#039;Navbox&amp;#039;)&lt;br /&gt;
		:addClass(CLASS_PREFIX..&amp;#039;navbox&amp;#039;)&lt;br /&gt;
		:addClass(data.class)&lt;br /&gt;
		:addClass(data.striped)&lt;br /&gt;
		:addClass(data.state)&lt;br /&gt;
		:cssText(data.style)&lt;br /&gt;
&lt;br /&gt;
	--title bar&lt;br /&gt;
	if data.title then&lt;br /&gt;
		local titlebar = navbox:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;title&amp;#039;)&lt;br /&gt;
		titlebar:tag(&amp;#039;div&amp;#039;):addClass(&amp;#039;mw-collapsible-toggle-placeholder&amp;#039;)&lt;br /&gt;
		if data.metaLinks then&lt;br /&gt;
			titlebar:node(h.renderMetaLinks(data.metaLinks))&lt;br /&gt;
		end&lt;br /&gt;
		if data.title then&lt;br /&gt;
			titlebar:addClass(data.title.class):tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
			:addClass(CLASS_PREFIX..&amp;#039;title-text&amp;#039;)&lt;br /&gt;
			:addClass(data.title.class)&lt;br /&gt;
			:cssText(data.title.style)&lt;br /&gt;
			:wikitext(data.title.content)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	--above&lt;br /&gt;
	if data.above then&lt;br /&gt;
		navbox:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
		:addClass(CLASS_PREFIX..&amp;#039;above mw-collapsible-content&amp;#039;)&lt;br /&gt;
		:addClass(data.above.class)&lt;br /&gt;
		:cssText(data.above.style)&lt;br /&gt;
		:wikitext(data.above.content)&lt;br /&gt;
		:attr(&amp;#039;id&amp;#039;, (not data.title) and mw.uri.anchorEncode(data.above.content) or nil) -- id for aria-labelledby attribute, if no title&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- sections&lt;br /&gt;
	for i,sect in ipairs(data.sections) do&lt;br /&gt;
		--section box&lt;br /&gt;
		local section = navbox:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
			:addClass(CLASS_PREFIX..&amp;#039;section mw-collapsible-content&amp;#039;)&lt;br /&gt;
			:addClass(sect.class)&lt;br /&gt;
			:addClass(sect.state)&lt;br /&gt;
			:cssText(sect.style)&lt;br /&gt;
		-- section header&lt;br /&gt;
		if sect.header then&lt;br /&gt;
			section:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
			:addClass(CLASS_PREFIX..&amp;#039;header&amp;#039;)&lt;br /&gt;
			:addClass(sect.header.class)&lt;br /&gt;
			:cssText(sect.header.style)&lt;br /&gt;
			:tag(&amp;#039;div&amp;#039;):addClass(&amp;#039;mw-collapsible-toggle-placeholder&amp;#039;):done()&lt;br /&gt;
			:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;header-text&amp;#039;):wikitext(sect.header.content)&lt;br /&gt;
		end&lt;br /&gt;
		-- above:&lt;br /&gt;
		if sect.above then&lt;br /&gt;
			section:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
			:addClass(CLASS_PREFIX..&amp;#039;above mw-collapsible-content&amp;#039;)&lt;br /&gt;
			:addClass(sect.above.class)&lt;br /&gt;
			:cssText(sect.above.style)&lt;br /&gt;
			:wikitext(sect.above.content)&lt;br /&gt;
		end&lt;br /&gt;
		-- body: groups&amp;amp;lists&lt;br /&gt;
		local box = section:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;section-body mw-collapsible-content&amp;#039;)&lt;br /&gt;
		h.renderBody(sect.body, box, 0, true) -- reset even status each section&lt;br /&gt;
		-- below:&lt;br /&gt;
		if sect.below then&lt;br /&gt;
			section:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
			:addClass(CLASS_PREFIX..&amp;#039;below mw-collapsible-content&amp;#039;)&lt;br /&gt;
			:addClass(sect.below.class)&lt;br /&gt;
			:cssText(sect.below.style)&lt;br /&gt;
			:wikitext(sect.below.content)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- Insert a blank section for completely empty navbox to ensure it behaves correctly when collapsed.&lt;br /&gt;
	if #data.sections == 0 and not data.above and not data.below then &lt;br /&gt;
		navbox:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;section mw-collapsible-content&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	--below&lt;br /&gt;
	if data.below then&lt;br /&gt;
		navbox:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
		:addClass(CLASS_PREFIX..&amp;#039;below mw-collapsible-content&amp;#039;)&lt;br /&gt;
		:addClass(data.below.class)&lt;br /&gt;
		:cssText(data.below.style)&lt;br /&gt;
		:wikitext(data.below.content)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.renderMetaLinks(info)&lt;br /&gt;
	local title = mw.title.new(mw.text.trim(info.template), &amp;#039;Template&amp;#039;)&lt;br /&gt;
	if not title then&lt;br /&gt;
		error(&amp;#039;Invalid title &amp;#039; .. info.template)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local msg = mw.message.new(config.editlink_hover_message_key)&lt;br /&gt;
	local hoverText = msg:exists() and msg:plain() or &amp;#039;View or edit this template&amp;#039;&lt;br /&gt;
	&lt;br /&gt;
	return mw.html.create(&amp;#039;span&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;meta&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;span&amp;#039;):addClass(&amp;#039;nv nv-view&amp;#039;)&lt;br /&gt;
			:wikitext(&amp;#039;[[&amp;#039;..title.fullText..&amp;#039;|&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;span&amp;#039;):wikitext(hoverText):attr(&amp;#039;title&amp;#039;, hoverText):done()&lt;br /&gt;
			:wikitext(&amp;#039;]]&amp;#039;)&lt;br /&gt;
		:done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.renderBody(info, box, level, even)&lt;br /&gt;
	local count = 0&lt;br /&gt;
	for _,v in h.orderedPairs(info) do&lt;br /&gt;
		if v.group or v.list or v.sub then&lt;br /&gt;
			count = count + 1&lt;br /&gt;
			-- row container&lt;br /&gt;
			local row = box:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;row&amp;#039;)&lt;br /&gt;
			-- group cell&lt;br /&gt;
			if v.group or (v.sub and level &amp;gt; 0 and not v.list) then&lt;br /&gt;
				local groupCell = row:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
					:addClass(CLASS_PREFIX..&amp;#039;group level-&amp;#039;..level)&lt;br /&gt;
					:addClass((level &amp;gt; 0) and CLASS_PREFIX..&amp;#039;subgroup&amp;#039; or nil)&lt;br /&gt;
				local groupContentWrap = groupCell:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;wrap&amp;#039;)&lt;br /&gt;
				if v.group then&lt;br /&gt;
					groupCell:addClass(v.group.class):cssText(v.group.style)&lt;br /&gt;
					groupContentWrap:wikitext(v.group.content)&lt;br /&gt;
				else&lt;br /&gt;
					groupCell:addClass(&amp;#039;empty&amp;#039;)&lt;br /&gt;
					row:addClass(&amp;#039;empty-group-list&amp;#039;)&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				row:addClass(&amp;#039;empty-group&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
			-- list cell&lt;br /&gt;
			local listCell = row:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;listbox&amp;#039;)&lt;br /&gt;
			if not v.list and not v.sub then&lt;br /&gt;
				listCell:addClass(&amp;#039;empty&amp;#039;)&lt;br /&gt;
				row:addClass(&amp;#039;empty-list&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
			if v.list or (v.group and not v.sub) then&lt;br /&gt;
				--listCell:node(h.renderList(v[&amp;#039;list&amp;#039;] or &amp;#039;&amp;#039;, k, level, args))&lt;br /&gt;
				even = not even -- flip even/odd status&lt;br /&gt;
				local cell = listCell:tag(&amp;#039;div&amp;#039;)&lt;br /&gt;
				:addClass(CLASS_PREFIX..&amp;#039;wrap&amp;#039;)&lt;br /&gt;
				:addClass(even and CLASS_PREFIX..&amp;#039;even&amp;#039; or CLASS_PREFIX..&amp;#039;odd&amp;#039;)&lt;br /&gt;
				if v.list then&lt;br /&gt;
					cell:addClass(v.list.class):cssText(v.list.style)&lt;br /&gt;
					:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;list&amp;#039;):wikitext(v.list.content)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if v.sub then&lt;br /&gt;
				local sublistBox = listCell:tag(&amp;#039;div&amp;#039;):addClass(CLASS_PREFIX..&amp;#039;sublist level-&amp;#039;..level)&lt;br /&gt;
				even = h.renderBody(v.sub, sublistBox, level+1, even)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if count &amp;gt; 0 then&lt;br /&gt;
		box:css(&amp;#039;--count&amp;#039;, count) -- for flex-grow&lt;br /&gt;
	end&lt;br /&gt;
	return even&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- pairs, but sort the keys alphabetically&lt;br /&gt;
function h.orderedPairs(t, f)&lt;br /&gt;
	local a = {}&lt;br /&gt;
	for n in pairs(t) do table.insert(a, n) end&lt;br /&gt;
	table.sort(a, f)&lt;br /&gt;
	local i = 0      -- iterator variable&lt;br /&gt;
	local iter = function ()   -- iterator function&lt;br /&gt;
		i = i + 1&lt;br /&gt;
		if a[i] == nil then return nil&lt;br /&gt;
		else return a[i], t[a[i]]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return iter&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For cascading parameters, such as style or class, they are merged in exact order (from general to specific). &lt;br /&gt;
-- Any parameter starting with multiple hyphens(minus signs) will terminate the cascade.&lt;br /&gt;
-- An example:&lt;br /&gt;
-- For group_1.1, its style is affected by parameters |group_1.1_style=... , |subgroup_level_1_style=... , and |subgroup_style=... .&lt;br /&gt;
-- If we have |group_1.1_style= color:red; |subgroup_level_1_style= font-weight: bold; and |subgroup_style= color: green; ,&lt;br /&gt;
-- the style of group_1.1 will be style=&amp;quot;color:green; font-weight: bold; color: red;&amp;quot; ;&lt;br /&gt;
-- if we have |group_1.1_style= -- color:red; |subgroup_level_1_style= font-weight: bold; and |subgroup_style= color: green; ,&lt;br /&gt;
-- the style of group_1.1 will be style=&amp;quot;color: red;&amp;quot; only, and the cascade is no longer performed for |subgroup_level_1_style and |subgroup_style.&lt;br /&gt;
function h.mergeAttrs(...)&lt;br /&gt;
	local trim = mw.text.trim&lt;br /&gt;
	local s = &amp;#039;&amp;#039;&lt;br /&gt;
	for i=1, select(&amp;#039;#&amp;#039;, ...) do&lt;br /&gt;
		local v = trim(select(i, ...) or &amp;#039;&amp;#039;)&lt;br /&gt;
		local str = string.match(v, &amp;#039;^%-%-+(.*)$&amp;#039;)&lt;br /&gt;
		if str then&lt;br /&gt;
			s = trim(str..&amp;#039; &amp;#039;..s)&lt;br /&gt;
			break&lt;br /&gt;
		else&lt;br /&gt;
			s = trim(v..&amp;#039; &amp;#039;..s)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if s == &amp;#039;&amp;#039; then s = nil end&lt;br /&gt;
	return s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.runHook(key, ...)&lt;br /&gt;
	if hooks[key] then&lt;br /&gt;
		hooks[key](...)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-----------------------------------------------&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Latty</name></author>
	</entry>
</feed>