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