Documentation for this module may be created at Module:Infobox/doc

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