モジュール:Navbox
このモジュールについての説明文ページを モジュール:Navbox/doc に作成できます
1 require('strict')
2 local p = {}
3 local cfg = mw.loadData('Module:Navbox/configuration')
4 local inArray = require("Module:TableTools").inArray
5 local getArgs -- lazily initialized
6 local hiding_templatestyles = {}
7
8 -- global passthrough variables
9 local passthrough = {
10 [cfg.arg.above]=true,[cfg.arg.aboveclass]=true,[cfg.arg.abovestyle]=true,
11 [cfg.arg.basestyle]=true,
12 [cfg.arg.below]=true,[cfg.arg.belowclass]=true,[cfg.arg.belowstyle]=true,
13 [cfg.arg.bodyclass]=true,
14 [cfg.arg.groupclass]=true,
15 [cfg.arg.image]=true,[cfg.arg.imageclass]=true,[cfg.arg.imagestyle]=true,
16 [cfg.arg.imageleft]=true,[cfg.arg.imageleftstyle]=true,
17 [cfg.arg.listclass]=true,
18 [cfg.arg.name]=true,
19 [cfg.arg.navbar]=true,
20 [cfg.arg.state]=true,
21 [cfg.arg.title]=true,[cfg.arg.titleclass]=true,[cfg.arg.titlestyle]=true,
22 argHash=true
23 }
24
25 -- helper functions
26 local andnum = function(s, n) return string.format(cfg.arg[s .. '_and_num'], n) end
27 local isblank = function(v) return (v or '') == '' end
28
29 local function concatstrings(s)
30 local r = table.concat(s, '')
31 if r:match('^%s*$') then return nil end
32 return r
33 end
34
35 local function concatstyles(s)
36 local r = ''
37 for _, v in ipairs(s) do
38 v = mw.text.trim(v, "%s;")
39 if not isblank(v) then r = r .. v .. ';' end
40 end
41 if isblank(r) then return nil end
42 return r
43 end
44
45 local function getSubgroup(args, listnum, listText, prefix)
46 local subArgs = {
47 [cfg.arg.border] = cfg.keyword.border_subgroup,
48 [cfg.arg.navbar] = cfg.keyword.navbar_plain,
49 argHash = 0
50 }
51 local hasSubArgs = false
52 local subgroups_and_num = prefix and {prefix} or cfg.arg.subgroups_and_num
53 for k, v in pairs(args) do
54 k = tostring(k)
55 for _, w in ipairs(subgroups_and_num) do
56 w = string.format(w, listnum) .. "_"
57 if (#k > #w) and (k:sub(1, #w) == w) then
58 subArgs[k:sub(#w + 1)] = v
59 hasSubArgs = true
60 subArgs.argHash = subArgs.argHash + (v and #v or 0)
61 end
62 end
63 end
64 return hasSubArgs and p._navbox(subArgs) or listText
65 end
66
67 -- Main functions
68 function p._navbox(args)
69 if args.type == cfg.keyword.with_collapsible_groups then
70 return p._withCollapsibleGroups(args)
71 elseif args.type == cfg.keyword.with_columns then
72 return p._withColumns(args)
73 end
74
75 local function striped(wikitext, border)
76 -- Return wikitext with markers replaced for odd/even striping.
77 -- Child (subgroup) navboxes are flagged with a category that is removed
78 -- by parent navboxes. The result is that the category shows all pages
79 -- where a child navbox is not contained in a parent navbox.
80 local orphanCat = cfg.category.orphan
81 if border == cfg.keyword.border_subgroup and args[cfg.arg.orphan] ~= cfg.keyword.orphan_yes then
82 -- No change; striping occurs in outermost navbox.
83 return wikitext .. orphanCat
84 end
85 local first, second = cfg.class.navbox_odd_part, cfg.class.navbox_even_part
86 if args[cfg.arg.evenodd] then
87 if args[cfg.arg.evenodd] == cfg.keyword.evenodd_swap then
88 first, second = second, first
89 else
90 first = args[cfg.arg.evenodd]
91 second = first
92 end
93 end
94 local changer
95 if first == second then
96 changer = first
97 else
98 local index = 0
99 changer = function (code)
100 if code == '0' then
101 -- Current occurrence is for a group before a nested table.
102 -- Set it to first as a valid although pointless class.
103 -- The next occurrence will be the first row after a title
104 -- in a subgroup and will also be first.
105 index = 0
106 return first
107 end
108 index = index + 1
109 return index % 2 == 1 and first or second
110 end
111 end
112 local regex = orphanCat:gsub('([%[%]])', '%%%1')
113 return (wikitext:gsub(regex, ''):gsub(cfg.marker.regex, changer)) -- () omits gsub count
114 end
115
116 local function processItem(item, nowrapitems)
117 if item:sub(1, 2) == '{|' then
118 -- Applying nowrap to lines in a table does not make sense.
119 -- Add newlines to compensate for trim of x in |parm=x in a template.
120 return '\n' .. item .. '\n'
121 end
122 if nowrapitems == cfg.keyword.nowrapitems_yes then
123 local lines = {}
124 for line in (item .. '\n'):gmatch('([^\n]*)\n') do
125 local prefix, content = line:match('^([*:;#]+)%s*(.*)')
126 if prefix and not content:match(cfg.pattern.nowrap) then
127 line = string.format(cfg.nowrap_item, prefix, content)
128 end
129 table.insert(lines, line)
130 end
131 item = table.concat(lines, '\n')
132 end
133 if item:match('^[*:;#]') then
134 return '\n' .. item .. '\n'
135 end
136 return item
137 end
138
139 local function has_navbar()
140 return args[cfg.arg.navbar] ~= cfg.keyword.navbar_off
141 and args[cfg.arg.navbar] ~= cfg.keyword.navbar_plain
142 and (
143 args[cfg.arg.name]
144 or mw.getCurrentFrame():getParent():getTitle():gsub(cfg.pattern.sandbox, '')
145 ~= cfg.pattern.navbox
146 )
147 end
148
149 -- extract text color from css, which is the only permitted inline CSS for the navbar
150 local function extract_color(css_str)
151 -- return nil because navbar takes its argument into mw.html which handles
152 -- nil gracefully, removing the associated style attribute
153 return mw.ustring.match(';' .. css_str .. ';', '.*;%s*([Cc][Oo][Ll][Oo][Rr]%s*:%s*.-)%s*;') or nil
154 end
155
156 local function renderNavBar(titleCell)
157 if has_navbar() then
158 local navbar = require('Module:Navbar')._navbar
159 titleCell:wikitext(navbar{
160 [cfg.navbar.name] = args[cfg.arg.name],
161 [cfg.navbar.mini] = 1,
162 [cfg.navbar.fontstyle] = extract_color(
163 (args[cfg.arg.basestyle] or '') .. ';' .. (args[cfg.arg.titlestyle] or '')
164 )
165 })
166 end
167
168 end
169
170 local function renderTitleRow(tbl)
171 if not args[cfg.arg.title] then return end
172
173 local titleRow = tbl:tag('tr')
174
175 local titleCell = titleRow:tag('th'):attr('scope', 'col')
176
177 local titleColspan = 2
178 if args[cfg.arg.imageleft] then titleColspan = titleColspan + 1 end
179 if args[cfg.arg.image] then titleColspan = titleColspan + 1 end
180
181 titleCell
182 :cssText(args[cfg.arg.basestyle])
183 :cssText(args[cfg.arg.titlestyle])
184 :addClass(cfg.class.navbox_title)
185 :attr('colspan', titleColspan)
186
187 renderNavBar(titleCell)
188
189 titleCell
190 :tag('div')
191 -- id for aria-labelledby attribute
192 :attr('id', mw.uri.anchorEncode(args[cfg.arg.title]) .. args.argHash)
193 :addClass(args[cfg.arg.titleclass])
194 :css('font-size', '114%')
195 :css('margin', '0 4em')
196 :wikitext(processItem(args[cfg.arg.title]))
197 end
198
199 local function getAboveBelowColspan()
200 local ret = 2
201 if args[cfg.arg.imageleft] then ret = ret + 1 end
202 if args[cfg.arg.image] then ret = ret + 1 end
203 return ret
204 end
205
206 local function renderAboveRow(tbl)
207 if not args[cfg.arg.above] then return end
208
209 tbl:tag('tr')
210 :tag('td')
211 :addClass(cfg.class.navbox_abovebelow)
212 :addClass(args[cfg.arg.aboveclass])
213 :cssText(args[cfg.arg.basestyle])
214 :cssText(args[cfg.arg.abovestyle])
215 :attr('colspan', getAboveBelowColspan())
216 :tag('div')
217 -- id for aria-labelledby attribute, if no title
218 :attr('id', (not args[cfg.arg.title]) and
219 (mw.uri.anchorEncode(args[cfg.arg.above]) .. args.argHash)
220 or nil)
221 :wikitext(processItem(args[cfg.arg.above], args[cfg.arg.nowrapitems]))
222 end
223
224 local function renderBelowRow(tbl)
225 if not args[cfg.arg.below] then return end
226
227 tbl:tag('tr')
228 :tag('td')
229 :addClass(cfg.class.navbox_abovebelow)
230 :addClass(args[cfg.arg.belowclass])
231 :cssText(args[cfg.arg.basestyle])
232 :cssText(args[cfg.arg.belowstyle])
233 :attr('colspan', getAboveBelowColspan())
234 :tag('div')
235 :wikitext(processItem(args[cfg.arg.below], args[cfg.arg.nowrapitems]))
236 end
237
238 local function renderListRow(tbl, index, listnum, listnums_size)
239 local row = tbl:tag('tr')
240
241 if index == 1 and args[cfg.arg.imageleft] then
242 row
243 :tag('td')
244 :addClass(cfg.class.noviewer)
245 :addClass(cfg.class.navbox_image)
246 :addClass(args[cfg.arg.imageclass])
247 :css('width', '1px') -- Minimize width
248 :css('padding', '0 2px 0 0')
249 :cssText(args[cfg.arg.imageleftstyle])
250 :attr('rowspan', listnums_size)
251 :tag('div')
252 :wikitext(processItem(args[cfg.arg.imageleft]))
253 end
254
255 local group_and_num = andnum('group', listnum)
256 local groupstyle_and_num = andnum('groupstyle', listnum)
257 if args[group_and_num] then
258 local groupCell = row:tag('th')
259
260 -- id for aria-labelledby attribute, if lone group with no title or above
261 if listnum == 1 and not (args[cfg.arg.title] or args[cfg.arg.above] or args[cfg.arg.group2]) then
262 groupCell
263 :attr('id', mw.uri.anchorEncode(args[cfg.arg.group1]) .. args.argHash)
264 end
265
266 groupCell
267 :attr('scope', 'row')
268 :addClass(cfg.class.navbox_group)
269 :addClass(args[cfg.arg.groupclass])
270 :cssText(args[cfg.arg.basestyle])
271 -- If groupwidth not specified, minimize width
272 :css('width', args[cfg.arg.groupwidth] or '1%')
273
274 groupCell
275 :cssText(args[cfg.arg.groupstyle])
276 :cssText(args[groupstyle_and_num])
277 :wikitext(args[group_and_num])
278 end
279
280 local listCell = row:tag('td')
281
282 if args[group_and_num] then
283 listCell
284 :addClass(cfg.class.navbox_list_with_group)
285 else
286 listCell:attr('colspan', 2)
287 end
288
289 if not args[cfg.arg.groupwidth] then
290 listCell:css('width', '100%')
291 end
292
293 local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing
294 if index % 2 == 1 then
295 rowstyle = args[cfg.arg.oddstyle]
296 else
297 rowstyle = args[cfg.arg.evenstyle]
298 end
299
300 local list_and_num = andnum('list', listnum)
301 local listText = inArray(cfg.keyword.subgroups, args[list_and_num])
302 and getSubgroup(args, listnum, args[list_and_num]) or args[list_and_num]
303
304 local oddEven = cfg.marker.oddeven
305 if listText:sub(1, 12) == '</div><table' then
306 -- Assume list text is for a subgroup navbox so no automatic striping for this row.
307 oddEven = listText:find(cfg.pattern.navbox_title) and cfg.marker.restart or cfg.class.navbox_odd_part
308 end
309
310 local liststyle_and_num = andnum('liststyle', listnum)
311 local listclass_and_num = andnum('listclass', listnum)
312 listCell
313 :css('padding', '0')
314 :cssText(args[cfg.arg.liststyle])
315 :cssText(rowstyle)
316 :cssText(args[liststyle_and_num])
317 :addClass(cfg.class.navbox_list)
318 :addClass(cfg.class.navbox_part .. oddEven)
319 :addClass(args[cfg.arg.listclass])
320 :addClass(args[listclass_and_num])
321 :tag('div')
322 :css('padding',
323 (index == 1 and args[cfg.arg.list1padding]) or args[cfg.arg.listpadding] or '0 0.25em'
324 )
325 :wikitext(processItem(listText, args[cfg.arg.nowrapitems]))
326
327 if index == 1 and args[cfg.arg.image] then
328 row
329 :tag('td')
330 :addClass(cfg.class.noviewer)
331 :addClass(cfg.class.navbox_image)
332 :addClass(args[cfg.arg.imageclass])
333 :css('width', '1px') -- Minimize width
334 :css('padding', '0 0 0 2px')
335 :cssText(args[cfg.arg.imagestyle])
336 :attr('rowspan', listnums_size)
337 :tag('div')
338 :wikitext(processItem(args[cfg.arg.image]))
339 end
340 end
341
342 local function has_list_class(htmlclass)
343 local patterns = {
344 '^' .. htmlclass .. '$',
345 '%s' .. htmlclass .. '$',
346 '^' .. htmlclass .. '%s',
347 '%s' .. htmlclass .. '%s'
348 }
349
350 for arg, _ in pairs(args) do
351 if type(arg) == 'string' and mw.ustring.find(arg, cfg.pattern.class) then
352 for _, pattern in ipairs(patterns) do
353 if mw.ustring.find(args[arg] or '', pattern) then
354 return true
355 end
356 end
357 end
358 end
359 return false
360 end
361
362 -- there are a lot of list classes in the wild, so we add their TemplateStyles
363 local function add_list_styles()
364 local frame = mw.getCurrentFrame()
365 local function add_list_templatestyles(htmlclass, templatestyles)
366 if has_list_class(htmlclass) then
367 return frame:extensionTag{
368 name = 'templatestyles', args = { src = templatestyles }
369 }
370 else
371 return ''
372 end
373 end
374
375 local hlist_styles = add_list_templatestyles('hlist', cfg.hlist_templatestyles)
376 local plainlist_styles = add_list_templatestyles('plainlist', cfg.plainlist_templatestyles)
377
378 -- a second workaround for [[phab:T303378]]
379 -- when that issue is fixed, we can actually use has_navbar not to emit the
380 -- tag here if we want
381 if has_navbar() and hlist_styles == '' then
382 hlist_styles = frame:extensionTag{
383 name = 'templatestyles', args = { src = cfg.hlist_templatestyles }
384 }
385 end
386
387 -- hlist -> plainlist is best-effort to preserve old Common.css ordering.
388 -- this ordering is not a guarantee because most navboxes will emit only
389 -- one of these classes [hlist_note]
390 return hlist_styles .. plainlist_styles
391 end
392
393 local function needsHorizontalLists(border)
394 if border == cfg.keyword.border_subgroup or args[cfg.arg.tracking] == cfg.keyword.tracking_no then
395 return false
396 end
397 return not has_list_class(cfg.pattern.hlist) and not has_list_class(cfg.pattern.plainlist)
398 end
399
400 local function hasBackgroundColors()
401 for _, key in ipairs({cfg.arg.titlestyle, cfg.arg.groupstyle,
402 cfg.arg.basestyle, cfg.arg.abovestyle, cfg.arg.belowstyle}) do
403 if tostring(args[key]):find('background', 1, true) then
404 return true
405 end
406 end
407 return false
408 end
409
410 local function hasBorders()
411 for _, key in ipairs({cfg.arg.groupstyle, cfg.arg.basestyle,
412 cfg.arg.abovestyle, cfg.arg.belowstyle}) do
413 if tostring(args[key]):find('border', 1, true) then
414 return true
415 end
416 end
417 return false
418 end
419
420 local function isIllegible()
421 local styleratio = require('Module:Color contrast')._styleratio
422 for key, style in pairs(args) do
423 if tostring(key):match(cfg.pattern.style) then
424 if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then
425 return true
426 end
427 end
428 end
429 return false
430 end
431
432 local function getTrackingCategories(border)
433 local cats = {}
434 if needsHorizontalLists(border) then table.insert(cats, cfg.category.horizontal_lists) end
435 if hasBackgroundColors() then table.insert(cats, cfg.category.background_colors) end
436 if isIllegible() then table.insert(cats, cfg.category.illegible) end
437 if hasBorders() then table.insert(cats, cfg.category.borders) end
438 return cats
439 end
440
441 local function renderTrackingCategories(builder, border)
442 local title = mw.title.getCurrentTitle()
443 if title.namespace ~= 10 then return end -- not in template space
444 local subpage = title.subpageText
445 if subpage == cfg.keyword.subpage_doc or subpage == cfg.keyword.subpage_sandbox
446 or subpage == cfg.keyword.subpage_testcases then return end
447
448 for _, cat in ipairs(getTrackingCategories(border)) do
449 builder:wikitext('[[Category:' .. cat .. ']]')
450 end
451 end
452
453 local function renderMainTable(border, listnums)
454 local tbl = mw.html.create('table')
455 :addClass(cfg.class.nowraplinks)
456 :addClass(args[cfg.arg.bodyclass])
457
458 local state = args[cfg.arg.state]
459 if args[cfg.arg.title] and state ~= cfg.keyword.state_plain and state ~= cfg.keyword.state_off then
460 if state == cfg.keyword.state_collapsed then
461 state = cfg.class.collapsed
462 end
463 tbl
464 :addClass(cfg.class.collapsible)
465 :addClass(state or cfg.class.autocollapse)
466 end
467
468 tbl:css('border-spacing', 0)
469 if border == cfg.keyword.border_subgroup or border == cfg.keyword.border_none then
470 tbl
471 :addClass(cfg.class.navbox_subgroup)
472 :cssText(args[cfg.arg.bodystyle])
473 :cssText(args[cfg.arg.style])
474 else -- regular navbox - bodystyle and style will be applied to the wrapper table
475 tbl
476 :addClass(cfg.class.navbox_inner)
477 :css('background', 'transparent')
478 :css('color', 'inherit')
479 end
480 tbl:cssText(args[cfg.arg.innerstyle])
481
482 renderTitleRow(tbl)
483 renderAboveRow(tbl)
484 local listnums_size = #listnums
485 for i, listnum in ipairs(listnums) do
486 renderListRow(tbl, i, listnum, listnums_size)
487 end
488 renderBelowRow(tbl)
489
490 return tbl
491 end
492
493 local function add_navbox_styles(hiding_templatestyles)
494 local frame = mw.getCurrentFrame()
495 -- This is a lambda so that it doesn't need the frame as a parameter
496 local function add_user_styles(templatestyles)
497 if not isblank(templatestyles) then
498 return frame:extensionTag{
499 name = 'templatestyles', args = { src = templatestyles }
500 }
501 end
502 return ''
503 end
504
505 -- get templatestyles. load base from config so that Lua only needs to do
506 -- the work once of parser tag expansion
507 local base_templatestyles = cfg.templatestyles
508 local templatestyles = add_user_styles(args[cfg.arg.templatestyles])
509 local child_templatestyles = add_user_styles(args[cfg.arg.child_templatestyles])
510
511 -- The 'navbox-styles' div exists to wrap the styles to work around T200206
512 -- more elegantly. Instead of combinatorial rules, this ends up being linear
513 -- number of CSS rules.
514 return mw.html.create('div')
515 :addClass(cfg.class.navbox_styles)
516 :wikitext(
517 add_list_styles() .. -- see [hlist_note] applied to 'before base_templatestyles'
518 base_templatestyles ..
519 templatestyles ..
520 child_templatestyles ..
521 table.concat(hiding_templatestyles)
522 )
523 :done()
524 end
525
526 -- work around [[phab:T303378]]
527 -- for each arg: find all the templatestyles strip markers, insert them into a
528 -- table. then remove all templatestyles markers from the arg
529 local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
530 local argHash = 0
531 for k, arg in pairs(args) do
532 if type(arg) == 'string' then
533 for marker in string.gfind(arg, strip_marker_pattern) do
534 table.insert(hiding_templatestyles, marker)
535 end
536 argHash = argHash + #arg
537 args[k] = string.gsub(arg, strip_marker_pattern, '')
538 end
539 end
540
541 if not args.argHash then args.argHash = argHash end
542
543 local listnums = {}
544
545 for k, _ in pairs(args) do
546 if type(k) == 'string' then
547 local listnum = k:match(cfg.pattern.listnum)
548 if listnum and args[andnum('list', tonumber(listnum))] then
549 table.insert(listnums, tonumber(listnum))
550 end
551 end
552 end
553 table.sort(listnums)
554
555 local border = mw.text.trim(args[cfg.arg.border] or args[1] or '')
556 if border == cfg.keyword.border_child then
557 border = cfg.keyword.border_subgroup
558 end
559
560 -- render the main body of the navbox
561 local tbl = renderMainTable(border, listnums)
562
563 local res = mw.html.create()
564 -- render the appropriate wrapper for the navbox, based on the border param
565
566 if border == cfg.keyword.border_none then
567 res:node(add_navbox_styles(hiding_templatestyles))
568 local nav = res:tag('div')
569 :attr('role', 'navigation')
570 :node(tbl)
571 -- aria-labelledby title, otherwise above, otherwise lone group
572 if args[cfg.arg.title] or args[cfg.arg.above] or (args[cfg.arg.group1]
573 and not args[cfg.arg.group2]) then
574 nav:attr(
575 'aria-labelledby',
576 mw.uri.anchorEncode(
577 args[cfg.arg.title] or args[cfg.arg.above] or args[cfg.arg.group1]
578 ) .. args.argHash
579 )
580 else
581 nav:attr('aria-label', cfg.aria_label)
582 end
583 elseif border == cfg.keyword.border_subgroup then
584 -- We assume that this navbox is being rendered in a list cell of a
585 -- parent navbox, and is therefore inside a div with padding:0em 0.25em.
586 -- We start with a </div> to avoid the padding being applied, and at the
587 -- end add a <div> to balance out the parent's </div>
588 res
589 :wikitext('</div>')
590 :node(tbl)
591 :wikitext('<div>')
592 else
593 res:node(add_navbox_styles(hiding_templatestyles))
594 local nav = res:tag('div')
595 :attr('role', 'navigation')
596 :addClass(cfg.class.navbox)
597 :addClass(args[cfg.arg.navboxclass])
598 :cssText(args[cfg.arg.bodystyle])
599 :cssText(args[cfg.arg.style])
600 :css('padding', '3px')
601 :node(tbl)
602 -- aria-labelledby title, otherwise above, otherwise lone group
603 if args[cfg.arg.title] or args[cfg.arg.above]
604 or (args[cfg.arg.group1] and not args[cfg.arg.group2]) then
605 nav:attr(
606 'aria-labelledby',
607 mw.uri.anchorEncode(
608 args[cfg.arg.title] or args[cfg.arg.above] or args[cfg.arg.group1]
609 ) .. args.argHash
610 )
611 else
612 nav:attr('aria-label', cfg.aria_label .. args.argHash)
613 end
614 end
615
616 if (args[cfg.arg.nocat] or cfg.keyword.nocat_false):lower() == cfg.keyword.nocat_false then
617 renderTrackingCategories(res, border)
618 end
619 return striped(tostring(res), border)
620 end --p._navbox
621
622 function p._withCollapsibleGroups(pargs)
623 -- table for args passed to navbox
624 local targs = {}
625
626 -- process args
627 local passthroughLocal = {
628 [cfg.arg.bodystyle] = true,
629 [cfg.arg.border] = true,
630 [cfg.arg.style] = true,
631 }
632 for k,v in pairs(pargs) do
633 if k and type(k) == 'string' then
634 if passthrough[k] or passthroughLocal[k] then
635 targs[k] = v
636 elseif (k:match(cfg.pattern.num)) then
637 local n = k:match(cfg.pattern.num)
638 local list_and_num = andnum('list', n)
639 if ((k:match(cfg.pattern.listnum) or k:match(cfg.pattern.contentnum))
640 and targs[list_and_num] == nil
641 and pargs[andnum('group', n)] == nil
642 and pargs[andnum('sect', n)] == nil
643 and pargs[andnum('section', n)] == nil) then
644 targs[list_and_num] = concatstrings({
645 pargs[list_and_num] or '',
646 pargs[andnum('content', n)] or ''
647 })
648 if (targs[list_and_num] and inArray(cfg.keyword.subgroups, targs[list_and_num])) then
649 targs[list_and_num] = getSubgroup(pargs, n, targs[list_and_num])
650 end
651 elseif ((k:match(cfg.pattern.groupnum) or k:match(cfg.pattern.sectnum) or k:match(cfg.pattern.sectionnum))
652 and targs[list_and_num] == nil) then
653 local titlestyle = concatstyles({
654 pargs[cfg.arg.groupstyle] or '',
655 pargs[cfg.arg.secttitlestyle] or '',
656 pargs[andnum('groupstyle', n)] or '',
657 pargs[andnum('sectiontitlestyle', n)] or ''
658 })
659 local liststyle = concatstyles({
660 pargs[cfg.arg.liststyle] or '',
661 pargs[cfg.arg.contentstyle] or '',
662 pargs[andnum('liststyle', n)] or '',
663 pargs[andnum('contentstyle', n)] or ''
664 })
665 local title = concatstrings({
666 pargs[andnum('group', n)] or '',
667 pargs[andnum('sect', n)] or '',
668 pargs[andnum('section', n)] or ''
669 })
670 local list = concatstrings({
671 pargs[list_and_num] or '',
672 pargs[andnum('content', n)] or ''
673 })
674 if list and inArray(cfg.keyword.subgroups, list) then
675 list = getSubgroup(pargs, n, list)
676 end
677 local abbr_and_num = andnum('abbr', n)
678 local state = (pargs[abbr_and_num] and pargs[abbr_and_num] == pargs[cfg.arg.selected])
679 and cfg.keyword.state_uncollapsed
680 or (pargs[andnum('state', n)] or cfg.keyword.state_collapsed)
681
682 targs[list_and_num] =p._navbox({
683 cfg.keyword.border_child,
684 [cfg.arg.navbar] = cfg.keyword.navbar_plain,
685 [cfg.arg.state] = state,
686 [cfg.arg.basestyle] = pargs[cfg.arg.basestyle],
687 [cfg.arg.title] = title,
688 [cfg.arg.titlestyle] = titlestyle,
689 [andnum('list', 1)] = list,
690 [cfg.arg.liststyle] = liststyle,
691 [cfg.arg.listclass] = pargs[andnum('listclass', n)],
692 [cfg.arg.image] = pargs[andnum('image', n)],
693 [cfg.arg.imageleft] = pargs[andnum('imageleft', n)],
694 [cfg.arg.listpadding] = pargs[cfg.arg.listpadding],
695 argHash = pargs.argHash
696 })
697 end
698 end
699 end
700 end
701 -- ordering of style and bodystyle
702 targs[cfg.arg.style] = concatstyles({targs[cfg.arg.style] or '', targs[cfg.arg.bodystyle] or ''})
703 targs[cfg.arg.bodystyle] = nil
704
705 -- child or subgroup
706 if targs[cfg.arg.border] == nil then targs[cfg.arg.border] = pargs[1] end
707
708 return p._navbox(targs)
709 end --p._withCollapsibleGroups
710
711 function p._withColumns(pargs)
712 -- table for args passed to navbox
713 local targs = {}
714
715 -- tables of column numbers
716 local colheadernums = {}
717 local colnums = {}
718 local colfooternums = {}
719
720 -- process args
721 local passthroughLocal = {
722 [cfg.arg.evenstyle]=true,
723 [cfg.arg.groupstyle]=true,
724 [cfg.arg.liststyle]=true,
725 [cfg.arg.oddstyle]=true,
726 [cfg.arg.state]=true,
727 }
728 for k,v in pairs(pargs) do
729 if passthrough[k] or passthroughLocal[k] then
730 targs[k] = v
731 elseif type(k) == 'string' then
732 if k:match(cfg.pattern.listnum) then
733 local n = k:match(cfg.pattern.listnum)
734 targs[andnum('liststyle', n + 2)] = pargs[andnum('liststyle', n)]
735 targs[andnum('group', n + 2)] = pargs[andnum('group', n)]
736 targs[andnum('groupstyle', n + 2)] = pargs[andnum('groupstyle', n)]
737 if v and inArray(cfg.keyword.subgroups, v) then
738 targs[andnum('list', n + 2)] = getSubgroup(pargs, n, v)
739 else
740 targs[andnum('list', n + 2)] = v
741 end
742 elseif (k:match(cfg.pattern.colheadernum) and v ~= '') then
743 table.insert(colheadernums, tonumber(k:match(cfg.pattern.colheadernum)))
744 elseif (k:match(cfg.pattern.colnum) and v ~= '') then
745 table.insert(colnums, tonumber(k:match(cfg.pattern.colnum)))
746 elseif (k:match(cfg.pattern.colfooternum) and v ~= '') then
747 table.insert(colfooternums, tonumber(k:match(cfg.pattern.colfooternum)))
748 end
749 end
750 end
751 table.sort(colheadernums)
752 table.sort(colnums)
753 table.sort(colfooternums)
754
755 -- HTML table for list1
756 local coltable = mw.html.create( 'table' ):addClass('navbox-columns-table')
757 local row, col
758
759 local tablestyle = ( (#colheadernums > 0) or (not isblank(pargs[cfg.arg.fullwidth])) )
760 and 'width:100%'
761 or 'width:auto; margin-left:auto; margin-right:auto'
762
763 coltable:cssText(concatstyles({
764 'border-spacing: 0px; text-align:left',
765 tablestyle,
766 pargs[cfg.arg.coltablestyle] or ''
767 }))
768
769 --- Header row ---
770 if (#colheadernums > 0) then
771 row = coltable:tag('tr')
772 for k, n in ipairs(colheadernums) do
773 col = row:tag('td'):addClass('navbox-abovebelow')
774 col:cssText(concatstyles({
775 (k > 1) and 'border-left:2px solid #fdfdfd' or '',
776 'font-weight:bold',
777 pargs[cfg.arg.colheaderstyle] or '',
778 pargs[andnum('colheaderstyle', n)] or ''
779 }))
780 col:attr('colspan', tonumber(pargs[andnum('colheadercolspan', n)]))
781 col:wikitext(pargs[andnum('colheader', n)])
782 end
783 end
784
785 --- Main columns ---
786 row = coltable:tag('tr'):css('vertical-align', 'top')
787 for k, n in ipairs(colnums) do
788 if k == 1 and isblank(pargs[andnum('colheader', 1)])
789 and isblank(pargs[andnum('colfooter', 1)])
790 and isblank(pargs[cfg.arg.fullwidth]) then
791 local nopad = inArray(
792 {'off', '0', '0em', '0px'},
793 mw.ustring.gsub(pargs[cfg.arg.padding] or '', '[;%%]', ''))
794 if not nopad then
795 row:tag('td'):wikitext(' ')
796 :css('width', (pargs[cfg.arg.padding] or '5em'))
797 end
798 end
799 col = row:tag('td'):addClass('navbox-list')
800 col:cssText(concatstyles({
801 (k > 1) and 'border-left:2px solid #fdfdfd' or '',
802 'padding:0px',
803 pargs[cfg.arg.colstyle] or '',
804 ((n%2 == 0) and pargs[cfg.arg.evencolstyle] or pargs[cfg.arg.oddcolstyle]) or '',
805 pargs[andnum('colstyle', n)] or '',
806 'width:' .. (pargs[andnum('colwidth', n)] or pargs[cfg.arg.colwidth] or '10em')
807 }))
808 local wt = pargs[andnum('col', n)]
809 if wt and inArray(cfg.keyword.subgroups, wt) then
810 wt = getSubgroup(pargs, n, wt, cfg.arg.col_and_num)
811 end
812 col:tag('div'):newline():wikitext(wt):newline()
813 end
814
815 --- Footer row ---
816 if (#colfooternums > 0) then
817 row = coltable:tag('tr')
818 for k, n in ipairs(colfooternums) do
819 col = row:tag('td'):addClass('navbox-abovebelow')
820 col:cssText(concatstyles({
821 (k > 1) and 'border-left:2px solid #fdfdfd' or '',
822 'font-weight:bold',
823 pargs[cfg.arg.colfooterstyle] or '',
824 pargs[andnum('colfooterstyle', n)] or ''
825 }))
826 col:attr('colspan', tonumber(pargs[andnum('colfootercolspan', n)]))
827 col:wikitext(pargs[andnum('colfooter', n)])
828 end
829 end
830
831 -- assign table to list1
832 targs[andnum('list', 1)] = tostring(coltable)
833 if isblank(pargs[andnum('colheader', 1)])
834 and isblank(pargs[andnum('col', 1)])
835 and isblank(pargs[andnum('colfooter', 1)]) then
836 targs[andnum('list', 1)] = targs[andnum('list', 1)] ..
837 cfg.category.without_first_col
838 end
839
840 -- Other parameters
841 targs[cfg.arg.border] = pargs[cfg.arg.border] or pargs[1]
842 targs[cfg.arg.evenodd] = (not isblank(pargs[cfg.arg.evenodd])) and pargs[cfg.arg.evenodd] or nil
843 targs[cfg.arg.list1padding] = '0px'
844 targs[andnum('liststyle', 1)] = 'background:transparent;color:inherit;'
845 targs[cfg.arg.style] = concatstyles({pargs[cfg.arg.style], pargs[cfg.arg.bodystyle]})
846 targs[cfg.arg.tracking] = 'no'
847
848 return p._navbox(targs)
849 end --p._withColumns
850
851 -- Template entry points
852 function p.navbox (frame, boxtype)
853 local function readArgs(args, prefix)
854 -- Read the arguments in the order they'll be output in, to make references
855 -- number in the right order.
856 local _ = 0
857 _ = _ + (args[prefix .. cfg.arg.title] and #args[prefix .. cfg.arg.title] or 0)
858 _ = _ + (args[prefix .. cfg.arg.above] and #args[prefix .. cfg.arg.above] or 0)
859 -- Limit this to 20 as covering 'most' cases (that's a SWAG) and because
860 -- iterator approach won't work here
861 for i = 1, 20 do
862 _ = _ + (args[prefix .. andnum('group', i)] and #args[prefix .. andnum('group', i)] or 0)
863 if inArray(cfg.keyword.subgroups, args[prefix .. andnum('list', i)]) then
864 for _, v in ipairs(cfg.arg.subgroups_and_num) do
865 readArgs(args, prefix .. string.format(v, i) .. "_")
866 end
867 readArgs(args, prefix .. andnum('col', i) .. "_")
868 end
869 end
870 _ = _ + (args[prefix .. cfg.arg.below] and #args[prefix .. cfg.arg.below] or 0)
871 return _
872 end
873
874 if not getArgs then
875 getArgs = require('Module:Arguments').getArgs
876 end
877 local args = getArgs(frame, {wrappers = {cfg.pattern[boxtype or 'navbox']}})
878 args.argHash = readArgs(args, "")
879 args.type = args.type or cfg.keyword[boxtype]
880 return p['_navbox'](args)
881 end
882
883 p[cfg.keyword.with_collapsible_groups] = function (frame)
884 return p.navbox(frame, 'with_collapsible_groups')
885 end
886
887 p[cfg.keyword.with_columns] = function (frame)
888 return p.navbox(frame, 'with_columns')
889 end
890
891 return p