Module:Infobox
Jump to navigation
Jump to search
![]() | This Lua module is used on approximately 3,870,000 pages, or roughly 291635% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This module depends on the following other modules: |
Module:Infobox is a module that implements the {{Infobox}} template. Please see the template page for usage instructions.
Tracking categories
- Category:Articles which use infobox templates with no data rows (0)
- Category:Pages which use embedded infobox templates with the title parameter (0)
1 local p = {}
2 local args = {}
3 local origArgs = {}
4 local root
5 local empty_row_categories = {}
6 local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]'
7 local has_rows = false
8
9 local function fixChildBoxes(sval, tt)
10 local function notempty( s ) return s and s:match( '%S' ) end
11
12 if notempty(sval) then
13 local marker = '<span class=special_infobox_marker>'
14 local s = sval
15 -- start moving templatestyles and categories inside of table rows
16 local slast = ''
17 while slast ~= s do
18 slast = s
19 s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1')
20 s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1')
21 end
22 -- end moving templatestyles and categories inside of table rows
23 s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
24 s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)
25 if s:match(marker) then
26 s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
27 s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
28 s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
29 s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
30 s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
31 s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
32 s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
33 s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
34 s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1')
35 end
36 if s:match(marker) then
37 local subcells = mw.text.split(s, marker)
38 s = ''
39 for k = 1, #subcells do
40 if k == 1 then
41 s = s .. subcells[k] .. '</' .. tt .. '></tr>'
42 elseif k == #subcells then
43 local rowstyle = ' style="display:none"'
44 if notempty(subcells[k]) then rowstyle = '' end
45 s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' ..
46 subcells[k]
47 elseif notempty(subcells[k]) then
48 if (k % 2) == 0 then
49 s = s .. subcells[k]
50 else
51 s = s .. '<tr><' .. tt .. ' colspan=2>\n' ..
52 subcells[k] .. '</' .. tt .. '></tr>'
53 end
54 end
55 end
56 end
57 -- the next two lines add a newline at the end of lists for the PHP parser
58 -- [[Special:Diff/849054481]]
59 -- remove when [[:phab:T191516]] is fixed or OBE
60 s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
61 s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
62 s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
63 s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
64 return s
65 else
66 return sval
67 end
68 end
69
70 -- Cleans empty tables
71 local function cleanInfobox()
72 root = tostring(root)
73 if has_rows == false then
74 root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '')
75 end
76 end
77
78 -- Returns the union of the values of two tables, as a sequence.
79 local function union(t1, t2)
80
81 local vals = {}
82 for k, v in pairs(t1) do
83 vals[v] = true
84 end
85 for k, v in pairs(t2) do
86 vals[v] = true
87 end
88 local ret = {}
89 for k, v in pairs(vals) do
90 table.insert(ret, k)
91 end
92 return ret
93 end
94
95 -- Returns a table containing the numbers of the arguments that exist
96 -- for the specified prefix. For example, if the prefix was 'data', and
97 -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
98 local function getArgNums(prefix)
99 local nums = {}
100 for k, v in pairs(args) do
101 local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$')
102 if num then table.insert(nums, tonumber(num)) end
103 end
104 table.sort(nums)
105 return nums
106 end
107
108 -- Adds a row to the infobox, with either a header cell
109 -- or a label/data cell combination.
110 local function addRow(rowArgs)
111
112 if rowArgs.header and rowArgs.header ~= '_BLANK_' then
113 has_rows = true
114 root
115 :tag('tr')
116 :addClass(rowArgs.rowclass)
117 :cssText(rowArgs.rowstyle)
118 :tag('th')
119 :attr('colspan', '2')
120 :addClass('infobox-header')
121 :addClass(rowArgs.class)
122 :addClass(args.headerclass)
123 -- @deprecated next; target .infobox-<name> .infobox-header
124 :cssText(args.headerstyle)
125 :cssText(rowArgs.rowcellstyle)
126 :wikitext(fixChildBoxes(rowArgs.header, 'th'))
127 if rowArgs.data then
128 root:wikitext(
129 '[[Category:Pages using infobox templates with ignored data cells]]'
130 )
131 end
132 elseif rowArgs.data and rowArgs.data:gsub(
133 category_in_empty_row_pattern, ''
134 ):match('^%S') then
135 has_rows = true
136 local row = root:tag('tr')
137 row:addClass(rowArgs.rowclass)
138 row:cssText(rowArgs.rowstyle)
139 if rowArgs.label then
140 row
141 :tag('th')
142 :attr('scope', 'row')
143 :addClass('infobox-label')
144 -- @deprecated next; target .infobox-<name> .infobox-label
145 :cssText(args.labelstyle)
146 :cssText(rowArgs.rowcellstyle)
147 :wikitext(rowArgs.label)
148 :done()
149 end
150
151 local dataCell = row:tag('td')
152 dataCell
153 :attr('colspan', not rowArgs.label and '2' or nil)
154 :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data')
155 :addClass(rowArgs.class)
156 -- @deprecated next; target .infobox-<name> .infobox(-full)-data
157 :cssText(rowArgs.datastyle)
158 :cssText(rowArgs.rowcellstyle)
159 :wikitext(fixChildBoxes(rowArgs.data, 'td'))
160 else
161 table.insert(empty_row_categories, rowArgs.data or '')
162 end
163 end
164
165 local function renderTitle()
166 if not args.title then return end
167
168 has_rows = true
169 root
170 :tag('caption')
171 :addClass('infobox-title')
172 :addClass(args.titleclass)
173 -- @deprecated next; target .infobox-<name> .infobox-title
174 :cssText(args.titlestyle)
175 :wikitext(args.title)
176 end
177
178 local function renderAboveRow()
179 if not args.above then return end
180
181 has_rows = true
182 root
183 :tag('tr')
184 :tag('th')
185 :attr('colspan', '2')
186 :addClass('infobox-above')
187 :addClass(args.aboveclass)
188 -- @deprecated next; target .infobox-<name> .infobox-above
189 :cssText(args.abovestyle)
190 :wikitext(fixChildBoxes(args.above,'th'))
191 end
192
193 local function renderBelowRow()
194 if not args.below then return end
195
196 has_rows = true
197 root
198 :tag('tr')
199 :tag('td')
200 :attr('colspan', '2')
201 :addClass('infobox-below')
202 :addClass(args.belowclass)
203 -- @deprecated next; target .infobox-<name> .infobox-below
204 :cssText(args.belowstyle)
205 :wikitext(fixChildBoxes(args.below,'td'))
206 end
207
208 local function addSubheaderRow(subheaderArgs)
209 if subheaderArgs.data and
210 subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
211 has_rows = true
212 local row = root:tag('tr')
213 row:addClass(subheaderArgs.rowclass)
214
215 local dataCell = row:tag('td')
216 dataCell
217 :attr('colspan', '2')
218 :addClass('infobox-subheader')
219 :addClass(subheaderArgs.class)
220 :cssText(subheaderArgs.datastyle)
221 :cssText(subheaderArgs.rowcellstyle)
222 :wikitext(fixChildBoxes(subheaderArgs.data, 'td'))
223 else
224 table.insert(empty_row_categories, subheaderArgs.data or '')
225 end
226 end
227
228 local function renderSubheaders()
229 if args.subheader then
230 args.subheader1 = args.subheader
231 end
232 if args.subheaderrowclass then
233 args.subheaderrowclass1 = args.subheaderrowclass
234 end
235 local subheadernums = getArgNums('subheader')
236 for k, num in ipairs(subheadernums) do
237 addSubheaderRow({
238 data = args['subheader' .. tostring(num)],
239 -- @deprecated next; target .infobox-<name> .infobox-subheader
240 datastyle = args.subheaderstyle,
241 rowcellstyle = args['subheaderstyle' .. tostring(num)],
242 class = args.subheaderclass,
243 rowclass = args['subheaderrowclass' .. tostring(num)]
244 })
245 end
246 end
247
248 local function addImageRow(imageArgs)
249
250 if imageArgs.data and
251 imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
252
253 has_rows = true
254 local row = root:tag('tr')
255 row:addClass(imageArgs.rowclass)
256
257 local dataCell = row:tag('td')
258 dataCell
259 :attr('colspan', '2')
260 :addClass('infobox-image')
261 :addClass(imageArgs.class)
262 :cssText(imageArgs.datastyle)
263 :wikitext(fixChildBoxes(imageArgs.data, 'td'))
264 else
265 table.insert(empty_row_categories, imageArgs.data or '')
266 end
267 end
268
269 local function renderImages()
270 if args.image then
271 args.image1 = args.image
272 end
273 if args.caption then
274 args.caption1 = args.caption
275 end
276 local imagenums = getArgNums('image')
277 for k, num in ipairs(imagenums) do
278 local caption = args['caption' .. tostring(num)]
279 local data = mw.html.create():wikitext(args['image' .. tostring(num)])
280 if caption then
281 data
282 :tag('div')
283 :addClass('infobox-caption')
284 -- @deprecated next; target .infobox-<name> .infobox-caption
285 :cssText(args.captionstyle)
286 :wikitext(caption)
287 end
288 addImageRow({
289 data = tostring(data),
290 -- @deprecated next; target .infobox-<name> .infobox-image
291 datastyle = args.imagestyle,
292 class = args.imageclass,
293 rowclass = args['imagerowclass' .. tostring(num)]
294 })
295 end
296 end
297
298 -- When autoheaders are turned on, preprocesses the rows
299 local function preprocessRows()
300 if not args.autoheaders then return end
301
302 local rownums = union(getArgNums('header'), getArgNums('data'))
303 table.sort(rownums)
304 local lastheader
305 for k, num in ipairs(rownums) do
306 if args['header' .. tostring(num)] then
307 if lastheader then
308 args['header' .. tostring(lastheader)] = nil
309 end
310 lastheader = num
311 elseif args['data' .. tostring(num)] and
312 args['data' .. tostring(num)]:gsub(
313 category_in_empty_row_pattern, ''
314 ):match('^%S') then
315 local data = args['data' .. tostring(num)]
316 if data:gsub(category_in_empty_row_pattern, ''):match('%S') then
317 lastheader = nil
318 end
319 end
320 end
321 if lastheader then
322 args['header' .. tostring(lastheader)] = nil
323 end
324 end
325
326 -- Gets the union of the header and data argument numbers,
327 -- and renders them all in order
328 local function renderRows()
329
330 local rownums = union(getArgNums('header'), getArgNums('data'))
331 table.sort(rownums)
332 for k, num in ipairs(rownums) do
333 addRow({
334 header = args['header' .. tostring(num)],
335 label = args['label' .. tostring(num)],
336 data = args['data' .. tostring(num)],
337 datastyle = args.datastyle,
338 class = args['class' .. tostring(num)],
339 rowclass = args['rowclass' .. tostring(num)],
340 -- @deprecated next; target .infobox-<name> rowclass
341 rowstyle = args['rowstyle' .. tostring(num)],
342 rowcellstyle = args['rowcellstyle' .. tostring(num)]
343 })
344 end
345 end
346
347 local function renderNavBar()
348 if not args.name then return end
349
350 has_rows = true
351 root
352 :tag('tr')
353 :tag('td')
354 :attr('colspan', '2')
355 :addClass('infobox-navbar')
356 :wikitext(require('Module:Navbar')._navbar{
357 args.name,
358 mini = 1,
359 })
360 end
361
362 local function renderItalicTitle()
363 local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title'])
364 if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
365 root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'}))
366 end
367 end
368
369 -- Categories in otherwise empty rows are collected in empty_row_categories.
370 -- This function adds them to the module output. It is not affected by
371 -- args.decat because this module should not prevent module-external categories
372 -- from rendering.
373 local function renderEmptyRowCategories()
374 for _, s in ipairs(empty_row_categories) do
375 root:wikitext(s)
376 end
377 end
378
379 -- Render tracking categories. args.decat == turns off tracking categories.
380 local function renderTrackingCategories()
381 if args.decat == 'yes' then return end
382 if args.child == 'yes' then
383 if args.title then
384 root:wikitext(
385 '[[Category:Pages using embedded infobox templates with the title parameter]]'
386 )
387 end
388 elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
389 root:wikitext('[[Category:Articles using infobox templates with no data rows]]')
390 end
391 end
392
393 --[=[
394 Loads the templatestyles for the infobox.
395
396 TODO: FINISH loading base templatestyles here rather than in
397 MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables.
398 See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :).
399 When we do this we should clean up the inline CSS below too.
400 Will have to do some bizarre conversion category like with sidebar.
401
402 ]=]
403 local function loadTemplateStyles()
404 local frame = mw.getCurrentFrame()
405
406 -- See function description
407 local base_templatestyles = frame:extensionTag{
408 name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' }
409 }
410
411 local templatestyles = ''
412 if args['templatestyles'] then templatestyles = frame:extensionTag{
413 name = 'templatestyles', args = { src = args['templatestyles'] }
414 }
415 end
416
417 local child_templatestyles = ''
418 if args['child templatestyles'] then child_templatestyles = frame:extensionTag{
419 name = 'templatestyles', args = { src = args['child templatestyles'] }
420 }
421 end
422
423 local grandchild_templatestyles = ''
424 if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{
425 name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
426 }
427 end
428
429 return table.concat({
430 base_templatestyles, -- see function description
431 templatestyles,
432 child_templatestyles,
433 grandchild_templatestyles
434 })
435 end
436
437 -- common functions between the child and non child cases
438 local function structure_infobox_common()
439 renderSubheaders()
440 renderImages()
441 preprocessRows()
442 renderRows()
443 renderBelowRow()
444 renderNavBar()
445 renderItalicTitle()
446 renderEmptyRowCategories()
447 renderTrackingCategories()
448 cleanInfobox()
449 end
450
451 -- Specify the overall layout of the infobox, with special settings if the
452 -- infobox is used as a 'child' inside another infobox.
453 local function _infobox()
454 if args.child ~= 'yes' then
455 root = mw.html.create('table')
456
457 root
458 :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox')
459 :addClass(args.bodyclass)
460 -- @deprecated next; target .infobox-<name>
461 :cssText(args.bodystyle)
462
463 renderTitle()
464 renderAboveRow()
465 else
466 root = mw.html.create()
467
468 root
469 :wikitext(args.title)
470 end
471 structure_infobox_common()
472
473 return loadTemplateStyles() .. root
474 end
475
476 -- If the argument exists and isn't blank, add it to the argument table.
477 -- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
478 local function preprocessSingleArg(argName)
479 if origArgs[argName] and origArgs[argName] ~= '' then
480 args[argName] = origArgs[argName]
481 end
482 end
483
484 -- Assign the parameters with the given prefixes to the args table, in order, in
485 -- batches of the step size specified. This is to prevent references etc. from
486 -- appearing in the wrong order. The prefixTable should be an array containing
487 -- tables, each of which has two possible fields, a "prefix" string and a
488 -- "depend" table. The function always parses parameters containing the "prefix"
489 -- string, but only parses parameters in the "depend" table if the prefix
490 -- parameter is present and non-blank.
491 local function preprocessArgs(prefixTable, step)
492 if type(prefixTable) ~= 'table' then
493 error("Non-table value detected for the prefix table", 2)
494 end
495 if type(step) ~= 'number' then
496 error("Invalid step value detected", 2)
497 end
498
499 -- Get arguments without a number suffix, and check for bad input.
500 for i,v in ipairs(prefixTable) do
501 if type(v) ~= 'table' or type(v.prefix) ~= "string" or
502 (v.depend and type(v.depend) ~= 'table') then
503 error('Invalid input detected to preprocessArgs prefix table', 2)
504 end
505 preprocessSingleArg(v.prefix)
506 -- Only parse the depend parameter if the prefix parameter is present
507 -- and not blank.
508 if args[v.prefix] and v.depend then
509 for j, dependValue in ipairs(v.depend) do
510 if type(dependValue) ~= 'string' then
511 error('Invalid "depend" parameter value detected in preprocessArgs')
512 end
513 preprocessSingleArg(dependValue)
514 end
515 end
516 end
517
518 -- Get arguments with number suffixes.
519 local a = 1 -- Counter variable.
520 local moreArgumentsExist = true
521 while moreArgumentsExist == true do
522 moreArgumentsExist = false
523 for i = a, a + step - 1 do
524 for j,v in ipairs(prefixTable) do
525 local prefixArgName = v.prefix .. tostring(i)
526 if origArgs[prefixArgName] then
527 -- Do another loop if any arguments are found, even blank ones.
528 moreArgumentsExist = true
529 preprocessSingleArg(prefixArgName)
530 end
531 -- Process the depend table if the prefix argument is present
532 -- and not blank, or we are processing "prefix1" and "prefix" is
533 -- present and not blank, and if the depend table is present.
534 if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then
535 for j,dependValue in ipairs(v.depend) do
536 local dependArgName = dependValue .. tostring(i)
537 preprocessSingleArg(dependArgName)
538 end
539 end
540 end
541 end
542 a = a + step
543 end
544 end
545
546 -- Parse the data parameters in the same order that the old {{infobox}} did, so
547 -- that references etc. will display in the expected places. Parameters that
548 -- depend on another parameter are only processed if that parameter is present,
549 -- to avoid phantom references appearing in article reference lists.
550 local function parseDataParameters()
551
552 preprocessSingleArg('autoheaders')
553 preprocessSingleArg('child')
554 preprocessSingleArg('bodyclass')
555 preprocessSingleArg('subbox')
556 preprocessSingleArg('bodystyle')
557 preprocessSingleArg('title')
558 preprocessSingleArg('titleclass')
559 preprocessSingleArg('titlestyle')
560 preprocessSingleArg('above')
561 preprocessSingleArg('aboveclass')
562 preprocessSingleArg('abovestyle')
563 preprocessArgs({
564 {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}}
565 }, 10)
566 preprocessSingleArg('subheaderstyle')
567 preprocessSingleArg('subheaderclass')
568 preprocessArgs({
569 {prefix = 'image', depend = {'caption', 'imagerowclass'}}
570 }, 10)
571 preprocessSingleArg('captionstyle')
572 preprocessSingleArg('imagestyle')
573 preprocessSingleArg('imageclass')
574 preprocessArgs({
575 {prefix = 'header'},
576 {prefix = 'data', depend = {'label'}},
577 {prefix = 'rowclass'},
578 {prefix = 'rowstyle'},
579 {prefix = 'rowcellstyle'},
580 {prefix = 'class'}
581 }, 50)
582 preprocessSingleArg('headerclass')
583 preprocessSingleArg('headerstyle')
584 preprocessSingleArg('labelstyle')
585 preprocessSingleArg('datastyle')
586 preprocessSingleArg('below')
587 preprocessSingleArg('belowclass')
588 preprocessSingleArg('belowstyle')
589 preprocessSingleArg('name')
590 -- different behaviour for italics if blank or absent
591 args['italic title'] = origArgs['italic title']
592 preprocessSingleArg('decat')
593 preprocessSingleArg('templatestyles')
594 preprocessSingleArg('child templatestyles')
595 preprocessSingleArg('grandchild templatestyles')
596 end
597
598 -- If called via #invoke, use the args passed into the invoking template.
599 -- Otherwise, for testing purposes, assume args are being passed directly in.
600 function p.infobox(frame)
601 if frame == mw.getCurrentFrame() then
602 origArgs = frame:getParent().args
603 else
604 origArgs = frame
605 end
606
607 parseDataParameters()
608
609 return _infobox()
610 end
611
612 -- For calling via #invoke within a template
613 function p.infoboxTemplate(frame)
614 origArgs = {}
615 for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
616
617 parseDataParameters()
618
619 return _infobox()
620 end
621 return p