Database changes have finished applying - please report any issues you're (still) seeing to support@shoutwiki.com.

Module:Infobox

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