Database changes have finished applying - please report any issues you're (still) seeing to support@shoutwiki.com.
Module:Infobox
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Infobox/doc
1 --
2 -- This module implements {{Infobox}}
3 --
4
5 local p = {}
6
7 local navbar = require('Module:Navbar')._navbar
8
9 local args = {}
10 local origArgs
11 local root
12
13 local function notempty( s ) return s and s:match( '%S' ) end
14
15 local function fixChildBoxes(sval, tt)
16 if notempty(sval) then
17 local marker = '<span class=special_infobox_marker>'
18 local s = sval
19 s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
20 s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)
21 if s:match(marker) then
22 s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
23 s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
24 s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
25 s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
26 s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
27 s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
28 s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
29 s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
30 s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1')
31 end
32 if s:match(marker) then
33 local subcells = mw.text.split(s, marker)
34 s = ''
35 for k = 1, #subcells do
36 if k == 1 then
37 s = s .. subcells[k] .. '</' .. tt .. '></tr>'
38 elseif k == #subcells then
39 local rowstyle = ' style="display:none"'
40 if notempty(subcells[k]) then rowstyle = '' end
41 s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k]
42 elseif notempty(subcells[k]) then
43 if (k % 2) == 0 then
44 s = s .. subcells[k]
45 else
46 s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>'
47 end
48 end
49 end
50 end
51 -- the next two lines add a newline at the end of lists for the PHP parser
52 -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481
53 -- remove when [[:phab:T191516]] is fixed or OBE
54 s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
55 s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
56 s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
57 s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
58 return s
59 else
60 return sval
61 end
62 end
63
64 local function union(t1, t2)
65 -- Returns the union of the values of two tables, as a sequence.
66 local vals = {}
67 for k, v in pairs(t1) do
68 vals[v] = true
69 end
70 for k, v in pairs(t2) do
71 vals[v] = true
72 end
73 local ret = {}
74 for k, v in pairs(vals) do
75 table.insert(ret, k)
76 end
77 return ret
78 end
79
80 local function getArgNums(prefix)
81 -- Returns a table containing the numbers of the arguments that exist
82 -- for the specified prefix. For example, if the prefix was 'data', and
83 -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
84 local nums = {}
85 for k, v in pairs(args) do
86 local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$')
87 if num then table.insert(nums, tonumber(num)) end
88 end
89 table.sort(nums)
90 return nums
91 end
92
93 local function addRow(rowArgs)
94 -- Adds a row to the infobox, with either a header cell
95 -- or a label/data cell combination.
96 if rowArgs.header then
97 root
98 :tag('tr')
99 :addClass(rowArgs.rowclass)
100 :cssText(rowArgs.rowstyle)
101 :attr('id', rowArgs.rowid)
102 :tag('th')
103 :attr('colspan', 2)
104 :attr('id', rowArgs.headerid)
105 :addClass(rowArgs.class)
106 :addClass(args.headerclass)
107 :css('text-align', 'center')
108 :cssText(args.headerstyle)
109 :cssText(rowArgs.rowcellstyle)
110 :wikitext(fixChildBoxes(rowArgs.header, 'th'))
111 elseif rowArgs.data then
112 local row = root:tag('tr')
113 row:addClass(rowArgs.rowclass)
114 row:cssText(rowArgs.rowstyle)
115 row:attr('id', rowArgs.rowid)
116 if rowArgs.label then
117 row
118 :tag('th')
119 :attr('scope', 'row')
120 :attr('id', rowArgs.labelid)
121 :cssText(args.labelstyle)
122 :cssText(rowArgs.rowcellstyle)
123 :wikitext(rowArgs.label)
124 :done()
125 end
126
127 local dataCell = row:tag('td')
128 if not rowArgs.label then
129 dataCell
130 :attr('colspan', 2)
131 :css('text-align', 'center')
132 end
133 dataCell
134 :attr('id', rowArgs.dataid)
135 :addClass(rowArgs.class)
136 :cssText(rowArgs.datastyle)
137 :cssText(rowArgs.rowcellstyle)
138 :wikitext(fixChildBoxes(rowArgs.data, 'td'))
139 end
140 end
141
142 local function renderTitle()
143 if not args.title then return end
144
145 root
146 :tag('caption')
147 :addClass(args.titleclass)
148 :cssText(args.titlestyle)
149 :wikitext(args.title)
150 end
151
152 local function renderAboveRow()
153 if not args.above then return end
154
155 root
156 :tag('tr')
157 :tag('th')
158 :attr('colspan', 2)
159 :addClass(args.aboveclass)
160 :css('text-align', 'center')
161 :css('font-size', '125%')
162 :css('font-weight', 'bold')
163 :cssText(args.abovestyle)
164 :wikitext(fixChildBoxes(args.above,'th'))
165 end
166
167 local function renderBelowRow()
168 if not args.below then return end
169
170 root
171 :tag('tr')
172 :tag('td')
173 :attr('colspan', '2')
174 :addClass(args.belowclass)
175 :css('text-align', 'center')
176 :cssText(args.belowstyle)
177 :wikitext(fixChildBoxes(args.below,'td'))
178 end
179
180 local function renderSubheaders()
181 if args.subheader then
182 args.subheader1 = args.subheader
183 end
184 if args.subheaderrowclass then
185 args.subheaderrowclass1 = args.subheaderrowclass
186 end
187 local subheadernums = getArgNums('subheader')
188 for k, num in ipairs(subheadernums) do
189 addRow({
190 data = args['subheader' .. tostring(num)],
191 datastyle = args.subheaderstyle,
192 rowcellstyle = args['subheaderstyle' .. tostring(num)],
193 class = args.subheaderclass,
194 rowclass = args['subheaderrowclass' .. tostring(num)]
195 })
196 end
197 end
198
199 local function renderImages()
200 if args.image then
201 args.image1 = args.image
202 end
203 if args.caption then
204 args.caption1 = args.caption
205 end
206 local imagenums = getArgNums('image')
207 for k, num in ipairs(imagenums) do
208 local caption = args['caption' .. tostring(num)]
209 local data = mw.html.create():wikitext(args['image' .. tostring(num)])
210 if caption then
211 data
212 :tag('div')
213 :cssText(args.captionstyle)
214 :wikitext(caption)
215 end
216 addRow({
217 data = tostring(data),
218 datastyle = args.imagestyle,
219 class = args.imageclass,
220 rowclass = args['imagerowclass' .. tostring(num)]
221 })
222 end
223 end
224
225 local function renderRows()
226 -- Gets the union of the header and data argument numbers,
227 -- and renders them all in order using addRow.
228 local rownums = union(getArgNums('header'), getArgNums('data'))
229 table.sort(rownums)
230 for k, num in ipairs(rownums) do
231 addRow({
232 header = args['header' .. tostring(num)],
233 label = args['label' .. tostring(num)],
234 data = args['data' .. tostring(num)],
235 datastyle = args.datastyle,
236 class = args['class' .. tostring(num)],
237 rowclass = args['rowclass' .. tostring(num)],
238 rowstyle = args['rowstyle' .. tostring(num)],
239 rowcellstyle = args['rowcellstyle' .. tostring(num)],
240 dataid = args['dataid' .. tostring(num)],
241 labelid = args['labelid' .. tostring(num)],
242 headerid = args['headerid' .. tostring(num)],
243 rowid = args['rowid' .. tostring(num)]
244 })
245 end
246 end
247
248 local function renderNavBar()
249 if not args.name then return end
250
251 root
252 :tag('tr')
253 :tag('td')
254 :attr('colspan', '2')
255 :css('text-align', 'right')
256 :wikitext(navbar{
257 args.name,
258 mini = 1,
259 })
260 end
261
262 local function renderItalicTitle()
263 local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title'])
264 if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
265 root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'}))
266 end
267 end
268
269 local function renderTrackingCategories()
270 if args.decat ~= 'yes' then
271 if args.child == 'yes' then
272 if args.title then
273 root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]')
274 end
275 elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
276 root:wikitext('[[Category:Articles which use infobox templates with no data rows]]')
277 end
278 end
279 end
280
281 local function _infobox()
282 -- Specify the overall layout of the infobox, with special settings
283 -- if the infobox is used as a 'child' inside another infobox.
284 if args.child ~= 'yes' then
285 root = mw.html.create('table')
286
287 root
288 :addClass((args.subbox ~= 'yes') and 'infobox' or nil)
289 :addClass(args.bodyclass)
290
291 if args.subbox == 'yes' then
292 root
293 :css('padding', '0')
294 :css('border', 'none')
295 :css('margin', '-3px')
296 :css('width', 'auto')
297 :css('min-width', '100%')
298 :css('font-size', '100%')
299 :css('clear', 'none')
300 :css('float', 'none')
301 :css('background-color', 'transparent')
302 else
303 root
304 :css('width', '22em')
305 end
306 root
307 :cssText(args.bodystyle)
308
309 renderTitle()
310 renderAboveRow()
311 else
312 root = mw.html.create()
313
314 root
315 :wikitext(args.title)
316 end
317
318 renderSubheaders()
319 renderImages()
320 renderRows()
321 renderBelowRow()
322 renderNavBar()
323 renderItalicTitle()
324 renderTrackingCategories()
325
326 return tostring(root)
327 end
328
329 local function preprocessSingleArg(argName)
330 -- If the argument exists and isn't blank, add it to the argument table.
331 -- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
332 if origArgs[argName] and origArgs[argName] ~= '' then
333 args[argName] = origArgs[argName]
334 end
335 end
336
337 local function preprocessArgs(prefixTable, step)
338 -- Assign the parameters with the given prefixes to the args table, in order, in batches
339 -- of the step size specified. This is to prevent references etc. from appearing in the
340 -- wrong order. The prefixTable should be an array containing tables, each of which has
341 -- two possible fields, a "prefix" string and a "depend" table. The function always parses
342 -- parameters containing the "prefix" string, but only parses parameters in the "depend"
343 -- table if the prefix parameter is present and non-blank.
344 if type(prefixTable) ~= 'table' then
345 error("Non-table value detected for the prefix table", 2)
346 end
347 if type(step) ~= 'number' then
348 error("Invalid step value detected", 2)
349 end
350
351 -- Get arguments without a number suffix, and check for bad input.
352 for i,v in ipairs(prefixTable) do
353 if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then
354 error('Invalid input detected to preprocessArgs prefix table', 2)
355 end
356 preprocessSingleArg(v.prefix)
357 -- Only parse the depend parameter if the prefix parameter is present and not blank.
358 if args[v.prefix] and v.depend then
359 for j, dependValue in ipairs(v.depend) do
360 if type(dependValue) ~= 'string' then
361 error('Invalid "depend" parameter value detected in preprocessArgs')
362 end
363 preprocessSingleArg(dependValue)
364 end
365 end
366 end
367
368 -- Get arguments with number suffixes.
369 local a = 1 -- Counter variable.
370 local moreArgumentsExist = true
371 while moreArgumentsExist == true do
372 moreArgumentsExist = false
373 for i = a, a + step - 1 do
374 for j,v in ipairs(prefixTable) do
375 local prefixArgName = v.prefix .. tostring(i)
376 if origArgs[prefixArgName] then
377 moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones.
378 preprocessSingleArg(prefixArgName)
379 end
380 -- Process the depend table if the prefix argument is present and not blank, or
381 -- we are processing "prefix1" and "prefix" is present and not blank, and
382 -- if the depend table is present.
383 if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then
384 for j,dependValue in ipairs(v.depend) do
385 local dependArgName = dependValue .. tostring(i)
386 preprocessSingleArg(dependArgName)
387 end
388 end
389 end
390 end
391 a = a + step
392 end
393 end
394
395 function p.infobox(frame)
396 -- If called via #invoke, use the args passed into the invoking template.
397 -- Otherwise, for testing purposes, assume args are being passed directly in.
398 if frame == mw.getCurrentFrame() then
399 origArgs = frame:getParent().args
400 else
401 origArgs = frame
402 end
403
404 -- Parse the data parameters in the same order that the old {{infobox}} did, so that
405 -- references etc. will display in the expected places. Parameters that depend on
406 -- another parameter are only processed if that parameter is present, to avoid
407 -- phantom references appearing in article reference lists.
408 preprocessSingleArg('child')
409 preprocessSingleArg('bodyclass')
410 preprocessSingleArg('subbox')
411 preprocessSingleArg('bodystyle')
412 preprocessSingleArg('title')
413 preprocessSingleArg('titleclass')
414 preprocessSingleArg('titlestyle')
415 preprocessSingleArg('above')
416 preprocessSingleArg('aboveclass')
417 preprocessSingleArg('abovestyle')
418 preprocessArgs({
419 {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}}
420 }, 10)
421 preprocessSingleArg('subheaderstyle')
422 preprocessSingleArg('subheaderclass')
423 preprocessArgs({
424 {prefix = 'image', depend = {'caption', 'imagerowclass'}}
425 }, 10)
426 preprocessSingleArg('captionstyle')
427 preprocessSingleArg('imagestyle')
428 preprocessSingleArg('imageclass')
429 preprocessArgs({
430 {prefix = 'header'},
431 {prefix = 'data', depend = {'label'}},
432 {prefix = 'rowclass'},
433 {prefix = 'rowstyle'},
434 {prefix = 'rowcellstyle'},
435 {prefix = 'class'},
436 {prefix = 'dataid'},
437 {prefix = 'labelid'},
438 {prefix = 'headerid'},
439 {prefix = 'rowid'}
440 }, 50)
441 preprocessSingleArg('headerclass')
442 preprocessSingleArg('headerstyle')
443 preprocessSingleArg('labelstyle')
444 preprocessSingleArg('datastyle')
445 preprocessSingleArg('below')
446 preprocessSingleArg('belowclass')
447 preprocessSingleArg('belowstyle')
448 preprocessSingleArg('name')
449 args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent
450 preprocessSingleArg('decat')
451
452 return _infobox()
453 end
454
455 return p