Difference between revisions of "Module:Userbox/sandbox"

From blackwiki
Jump to navigation Jump to search
blackwiki>Mr. Stradivarius
(don't categorise User:UBX subpages, and fix variable name)
m (65 revisions imported)
 
(48 intermediate revisions by 8 users not shown)
Line 1: Line 1:
 
-- This module implements {{userbox}}.
 
-- This module implements {{userbox}}.
  
local calculateContrastRatio = require('Module:Color contrast')._ratio
+
local categoryHandler = require('Module:Category handler').main
local yesno = require('Module:Yesno')
 
  
 
local p = {}
 
local p = {}
Line 43: Line 42:
 
return mw.ustring.format('[[Category:%s]]', cat)
 
return mw.ustring.format('[[Category:%s]]', cat)
 
end
 
end
end
 
 
local function hasLegibleContrast(data)
 
local function trim(s)
 
return s:match('^%s*(.-)%s*$')
 
end
 
 
local function hasText(wikitext)
 
if not wikitext then
 
return false
 
end
 
 
local function getAlt(text)
 
return text:match('|alt=([^|]*)') or ''
 
end
 
 
wikitext = wikitext:gsub(']]', '|]]')
 
wikitext = wikitext:gsub('%[%[%s*[Mm][Ee][Dd][Ii][Aa]%s*:[^|]-(|.-)]]', getAlt)
 
wikitext = wikitext:gsub('%[%[%s*[Ii][Mm][Aa][Gg][Ee]%s*:[^|]-(|.-)]]', getAlt)
 
wikitext = wikitext:gsub('%[%[%s*[Ff][Ii][Ll][Ee]%s*:[^|]-(|.-)]]', getAlt)
 
return trim(wikitext) ~= ''
 
end
 
 
local function isLegible(color1, color2)
 
return calculateContrastRatio{color1, color2, error = 0} >= 4.5
 
end
 
 
local function idIsLegible(isShown, id, color1, color2)
 
return not isShown or not hasText(id) or isLegible(color1, color2)
 
end
 
 
return isLegible(data.infoColor, data.backgroundColor) and
 
idIsLegible(data.showId, data.id, data.idColor, data.idBackgroundColor) and
 
idIsLegible(data.showId2, data.id2, data.id2Color, data.id2BackgroundColor)
 
 
end
 
end
  
Line 105: Line 70:
 
local userboxData = p[funcName](args)
 
local userboxData = p[funcName](args)
 
local userbox = p.render(userboxData)
 
local userbox = p.render(userboxData)
local cats = p.categories(args, userboxData)
+
local cats = p.categories(args)
return (userbox or '') .. (cats or '')
+
return userbox .. (cats or '')
 
end
 
end
  
Line 117: Line 82:
 
local borderWidthNum = checkNum(args['border-width'] or args['border-s'], 1) -- Used to calculate width.
 
local borderWidthNum = checkNum(args['border-width'] or args['border-s'], 1) -- Used to calculate width.
 
data.borderWidth = addSuffix(borderWidthNum, 'px')
 
data.borderWidth = addSuffix(borderWidthNum, 'px')
data.borderColor = args['border-color'] or args[1] or args['border-c'] or args['id-c'] or '#999'
+
data.borderColor = args['border-color'] or args['border-c'] or args[1] or args['id-c'] or '#999'
 
data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
 
data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
 
data.bodyClass = args.bodyclass
 
data.bodyClass = args.bodyclass
Line 159: Line 124:
 
-- Get div tag values.
 
-- Get div tag values.
 
data.float = args.float or 'left'
 
data.float = args.float or 'left'
local borderWidthNum = checkNum(args[9] or args['border-s'], 1) -- Used to calculate width.
+
local borderWidthNum = checkNum(args['border-s'] or args[9], 1) -- Used to calculate width.
 
data.borderWidth = addSuffix(borderWidthNum, 'px')
 
data.borderWidth = addSuffix(borderWidthNum, 'px')
data.borderColor = args[1] or args['border-c'] or args['id1-c'] or '#999999'
+
data.borderColor = args['border-c'] or args[6] or args['id1-c'] or args[1] or '#999999'
 
data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
 
data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
 
data.bodyClass = args.bodyclass
 
data.bodyClass = args.bodyclass
  
 
-- Get table tag values.
 
-- Get table tag values.
data.backgroundColor = args[2] or args['info-c'] or '#eeeeee'
+
data.backgroundColor = args['info-c'] or args[2] or '#eeeeee'
  
 
-- Get info values.
 
-- Get info values.
data.info = args[4] or args.info or "<code>{{{info}}}</code>"
+
data.info = args.info or args[4] or "<code>{{{info}}}</code>"
 
data.infoTextAlign = args['info-a'] or 'left'
 
data.infoTextAlign = args['info-a'] or 'left'
 
data.infoFontSize = checkNumAndAddSuffix(args['info-s'], 8, 'pt')
 
data.infoFontSize = checkNumAndAddSuffix(args['info-s'], 8, 'pt')
data.infoColor = args[8] or args['info-fc'] or 'black'
+
data.infoColor = args['info-fc'] or args[8] or 'black'
 
data.infoPadding = args['info-p'] or '0 4px 0 4px'
 
data.infoPadding = args['info-p'] or '0 4px 0 4px'
 
data.infoLineHeight = args['info-lh'] or '1.25em'
 
data.infoLineHeight = args['info-lh'] or '1.25em'
Line 182: Line 147:
 
data.idWidth = checkNumAndAddSuffix(args['id1-w'], 45, 'px')
 
data.idWidth = checkNumAndAddSuffix(args['id1-w'], 45, 'px')
 
data.idHeight = checkNumAndAddSuffix(args['id-h'], 45, 'px')
 
data.idHeight = checkNumAndAddSuffix(args['id-h'], 45, 'px')
data.idBackgroundColor = args[1] or args['id1-c'] or '#dddddd'
+
data.idBackgroundColor = args['id1-c'] or args[1] or '#dddddd'
 
data.idTextAlign = 'center'
 
data.idTextAlign = 'center'
 
data.idFontSize = checkNumAndAddSuffix(args['id1-s'], 14, 'pt')
 
data.idFontSize = checkNumAndAddSuffix(args['id1-s'], 14, 'pt')
Line 195: Line 160:
 
data.id2Width = checkNumAndAddSuffix(args['id2-w'], 45, 'px')
 
data.id2Width = checkNumAndAddSuffix(args['id2-w'], 45, 'px')
 
data.id2Height = data.idHeight
 
data.id2Height = data.idHeight
data.id2BackgroundColor = args[7] or args['id2-c'] or args[1] or '#dddddd'
+
data.id2BackgroundColor = args['id2-c'] or args[7] or args[1] or '#dddddd'
 
data.id2TextAlign = 'center'
 
data.id2TextAlign = 'center'
 
data.id2FontSize = checkNumAndAddSuffix(args['id2-s'], 14, 'pt')
 
data.id2FontSize = checkNumAndAddSuffix(args['id2-s'], 14, 'pt')
Line 214: Line 179:
 
local borderWidthNum = checkNum(args['border-width'] or args['border-s'], 1) -- Used to calculate width.
 
local borderWidthNum = checkNum(args['border-width'] or args['border-s'], 1) -- Used to calculate width.
 
data.borderWidth = addSuffix(borderWidthNum, 'px')
 
data.borderWidth = addSuffix(borderWidthNum, 'px')
data.borderColor = args['border-color'] or args[1] or args['border-c'] or args['id-c'] or '#999'
+
data.borderColor = args['border-color'] or args['border-c'] or args[1] or args['id-c'] or '#999'
 
data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
 
data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
 
data.bodyClass = args.bodyclass
 
data.bodyClass = args.bodyclass
Line 264: Line 229:
 
local tableroot = root:tag('table')
 
local tableroot = root:tag('table')
 
tableroot
 
tableroot
 +
:attr('role', 'presentation')
 
:css('border-collapse', 'collapse')
 
:css('border-collapse', 'collapse')
 
:css('width', data.width)
 
:css('width', data.width)
 
:css('margin-bottom', '0')
 
:css('margin-bottom', '0')
 +
:css('margin-top', '0')
 
:css('background', data.backgroundColor)
 
:css('background', data.backgroundColor)
 
 
Line 272: Line 239:
 
local tablerow = tableroot:tag('tr')
 
local tablerow = tableroot:tag('tr')
 
if data.showId then
 
if data.showId then
tablerow:tag('th')
+
tablerow:tag('td')
 
:css('border', '0')
 
:css('border', '0')
 
:css('width', data.idWidth)
 
:css('width', data.idWidth)
Line 279: Line 246:
 
:css('text-align', data.idTextAlign)
 
:css('text-align', data.idTextAlign)
 
:css('font-size', data.idFontSize)
 
:css('font-size', data.idFontSize)
 +
:css('font-weight', 'bold')
 
:css('color', data.idColor)
 
:css('color', data.idColor)
 
:css('padding', data.idPadding)
 
:css('padding', data.idPadding)
Line 304: Line 272:
 
-- Render the second id html.
 
-- Render the second id html.
 
if data.showId2 then
 
if data.showId2 then
tablerow:tag('th')
+
tablerow:tag('td')
 
:css('border', '0')
 
:css('border', '0')
 
:css('width', data.id2Width)
 
:css('width', data.id2Width)
Line 311: Line 279:
 
:css('text-align', data.id2TextAlign)
 
:css('text-align', data.id2TextAlign)
 
:css('font-size', data.id2FontSize)
 
:css('font-size', data.id2FontSize)
 +
:css('font-weight', 'bold')
 
:css('color', data.id2Color)
 
:css('color', data.id2Color)
 
:css('padding', data.id2Padding)
 
:css('padding', data.id2Padding)
Line 317: Line 286:
 
:cssText(data.id2OtherParams)
 
:cssText(data.id2OtherParams)
 
:wikitext(data.id2)
 
:wikitext(data.id2)
 +
end
 +
 +
local title = mw.title.getCurrentTitle()
 +
if (title.namespace == 2) and not title.text:match("/") then
 +
return tostring(root) -- regular user page
 +
elseif title.namespace == 14 then
 +
return tostring(root) -- category
 +
elseif title.isTalkPage then
 +
return tostring(root) -- talk page
 +
end
 +
 +
local legible = true
 +
local contrast = require('Module:Color contrast')._ratio
 +
 +
local function has_text(wikitext)
 +
local function get_alt(text)
 +
return text:match("|alt=([^|]*)") or ""
 +
end
 +
 +
wikitext = wikitext:gsub("]]", "|]]")
 +
wikitext = wikitext:gsub("%[%[%s*[Mm][Ee][Dd][Ii][Aa]%s*:[^|]-(|.-)]]", get_alt)
 +
wikitext = wikitext:gsub("%[%[%s*[Ii][Mm][Aa][Gg][Ee]%s*:[^|]-(|.-)]]", get_alt)
 +
wikitext = wikitext:gsub("%[%[%s*[Ff][Ii][Ll][Ee]%s*:[^|]-(|.-)]]", get_alt)
 +
return mw.text.trim(wikitext) ~= ""
 +
end
 +
 +
if contrast { data.infoColor, data.backgroundColor, error = 0 } < 4.5 then
 +
legible = false
 +
end
 +
 +
if data.showId and contrast { data.idColor, data.idBackgroundColor, error = 0 } < 4.5 then
 +
if has_text(data.id or "") then
 +
legible = false
 +
end
 +
end
 +
 +
if data.showId2 and contrast { data.id2Color, data.id2BackgroundColor, error = 0 } < 4.5 then
 +
if has_text(data.id2 or "") then
 +
legible = false
 +
end
 +
end
 +
 +
if not legible then
 +
root:wikitext('[[Category:Userboxes with insufficient color contrast]]')
 
end
 
end
  
Line 322: Line 335:
 
end
 
end
  
function p.categories(args, data, page)
+
function p.categories(args, page)
-- Render tracking categories.
+
-- Gets categories from [[Module:Category handler]].
-- The page parameter makes the function act as though the module was being
+
-- The page parameter makes the function act as though the module was being called from that page.
-- called from that page. It is included for testing purposes.
+
-- It is included for testing purposes.
 +
local cats = {}
 +
cats[#cats + 1] = args.usercategory
 +
cats[#cats + 1] = args.usercategory2
 +
cats[#cats + 1] = args.usercategory3
 +
cats[#cats + 1] = args.usercategory4
 +
cats[#cats + 1] = args.usercategory5
 +
-- Get the title object
 
local title
 
local title
 
if page then
 
if page then
Line 332: Line 352:
 
title = mw.title.getCurrentTitle()
 
title = mw.title.getCurrentTitle()
 
end
 
end
 
+
-- Build category handler arguments.
-- Exit if we are on a blacklisted page or categorization is disabled.
+
local chargs = {}
if title.prefixedText:find('/[aA]rchive') or yesno(args.nocat) then
+
chargs.page = page
return nil
+
chargs.nocat = args.nocat
end
+
chargs.main = '[[Category:Pages with misplaced templates]]'
 
+
if args.notcatsubpages then
local ret = {}
+
chargs.subpage = 'no'
 
 
-- User-specified categories
 
if not args.notcatsubpages or not title.isSubpage then
 
local userCats = {}
 
userCats[#userCats + 1] = args.usercategory
 
userCats[#userCats + 1] = args.usercategory2
 
userCats[#userCats + 1] = args.usercategory3
 
if #userCats > 0 then
 
if title.namespace == 2 and title.rootText ~= 'UBX' then
 
-- We are in the user namespace, and this is not the "UBX"
 
-- userbox pseudo-namespace.
 
for i, cat in ipairs(userCats) do
 
ret[#ret + 1] = makeCat(cat)
 
end
 
elseif title.namespace == 10 then
 
-- We are in the template namespace.
 
local basepage = title.baseText
 
for i, cat in ipairs(userCats) do
 
ret[#ret + 1] = makeCat(cat, ' ' .. basepage)
 
end
 
end
 
end
 
 
end
 
end
 
+
-- User namespace.
-- Tracking categories
+
local user = ''
local function maybeUserboxTemplate(title)
+
for i, cat in ipairs(cats) do
-- Whether this title could be a userbox template, rather than just a
+
user = user .. makeCat(cat)
-- transclusion of a userbox.
 
return title.namespace == 2 and title.isSubpage or -- In user space, but not a user page
 
title.namespace == 4 or -- Project namespace
 
title.namespace == 10 -- Template namespace
 
 
end
 
end
if title.namespace == 0 then -- Main namespace
+
chargs.user = user
ret[#ret + 1] = makeCat('Pages with misplaced templates')
+
-- Template namespace.
elseif maybeUserboxTemplate(title) and not hasLegibleContrast(data) then
+
local basepage = title.baseText
ret[#ret + 1] = makeCat('Potentially illegible userboxes')
+
local template = ''
 +
for i, cat in ipairs(cats) do
 +
template = template .. makeCat(cat, ' ' .. basepage)
 
end
 
end
 
+
chargs.template = template
return table.concat(ret)
+
return categoryHandler(chargs)
 
end
 
end
  
 
return p
 
return p

Latest revision as of 05:04, 30 September 2020

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

-- This module implements {{userbox}}.

local categoryHandler = require('Module:Category handler').main

local p = {}

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

local function checkNum(val, default)
	-- Checks whether a value is a number greater than or equal to zero. If so,
	-- returns it as a number. If not, returns a default value.
	val = tonumber(val)
	if val and val >= 0 then
		return val
	else
		return default
	end
end

local function addSuffix(num, suffix)
	-- Turns a number into a string and adds a suffix.
	if num then
		return tostring(num) .. suffix
	else
		return nil
	end
end

local function checkNumAndAddSuffix(num, default, suffix)
	-- Checks a value with checkNum and adds a suffix.
	num = checkNum(num, default)
	return addSuffix(num, suffix)
end

local function makeCat(cat, sort)
	-- Makes a category link.
	if sort then
		return mw.ustring.format('[[Category:%s|%s]]', cat, sort)
	else
		return mw.ustring.format('[[Category:%s]]', cat)
	end
end

--------------------------------------------------------------------------------
-- Argument processing
--------------------------------------------------------------------------------

local function makeInvokeFunc(funcName)
	return function (frame)
		local origArgs = require('Module:Arguments').getArgs(frame)
		local args = {}
		for k, v in pairs(origArgs) do
			args[k] = v
		end
		return p.main(funcName, args)
	end
end

p.userbox = makeInvokeFunc('_userbox')
p['userbox-2'] = makeInvokeFunc('_userbox-2')
p['userbox-r'] = makeInvokeFunc('_userbox-r')

--------------------------------------------------------------------------------
-- Main functions
--------------------------------------------------------------------------------

function p.main(funcName, args)
	local userboxData = p[funcName](args)
	local userbox = p.render(userboxData)
	local cats = p.categories(args)
	return userbox .. (cats or '')
end

function p._userbox(args)
	-- Does argument processing for {{userbox}}.
	local data = {}

	-- Get div tag values.
	data.float = args.float or 'left'
	local borderWidthNum = checkNum(args['border-width'] or args['border-s'], 1) -- Used to calculate width.
	data.borderWidth = addSuffix(borderWidthNum, 'px')
	data.borderColor = args['border-color'] or args['border-c'] or args[1] or args['id-c'] or '#999'
	data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
	data.bodyClass = args.bodyclass

	-- Get table tag values.
	data.backgroundColor = args['info-background'] or args[2] or args['info-c'] or '#eee'

	-- Get info values.
	data.info = args.info or args[4] or "<code>{{{info}}}</code>"
	data.infoTextAlign = args['info-a'] or 'left'
	data.infoFontSize = checkNumAndAddSuffix(args['info-size'] or args['info-s'], 8, 'pt')
	data.infoHeight = checkNumAndAddSuffix(args['logo-height'] or args['id-h'], 45, 'px')
	data.infoPadding = args['info-padding'] or args['info-p'] or '0 4px 0 4px'
	data.infoLineHeight = args['info-line-height'] or args['info-lh'] or '1.25em'
	data.infoColor = args['info-color'] or args['info-fc'] or 'black'
	data.infoOtherParams = args['info-other-param'] or args['info-op']
	data.infoClass = args['info-class']

	-- Get id values.
	local id = args.logo or args[3] or args.id
	data.id = id
	data.showId = id and true or false
	data.idWidth = checkNumAndAddSuffix(args['logo-width'] or args['id-w'], 45, 'px')
	data.idHeight = checkNumAndAddSuffix(args['logo-height'] or args['id-h'], 45, 'px')
	data.idBackgroundColor = args['logo-background'] or args[1] or args['id-c'] or '#ddd'
	data.idTextAlign = args['id-a'] or 'center'
	data.idFontSize = checkNumAndAddSuffix(args['logo-size'] or args[5] or args['id-s'], 14, 'pt')
	data.idColor = args['logo-color'] or args['id-fc'] or data.infoColor
	data.idPadding = args['logo-padding'] or args['id-p'] or '0 1px 0 0'
	data.idLineHeight = args['logo-line-height'] or args['id-lh'] or '1.25em'
	data.idOtherParams = args['logo-other-param'] or args['id-op']
	data.idClass = args['id-class']

	return data
end

p['_userbox-2'] = function (args)
	-- Does argument processing for {{userbox-2}}.
	local data = {}

	-- Get div tag values.
	data.float = args.float or 'left'
	local borderWidthNum = checkNum(args['border-s'] or args[9], 1) -- Used to calculate width.
	data.borderWidth = addSuffix(borderWidthNum, 'px')
	data.borderColor = args['border-c'] or args[6] or args['id1-c'] or args[1] or '#999999'
	data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
	data.bodyClass = args.bodyclass

	-- Get table tag values.
	data.backgroundColor = args['info-c'] or args[2] or '#eeeeee'

	-- Get info values.
	data.info = args.info or args[4] or "<code>{{{info}}}</code>"
	data.infoTextAlign = args['info-a'] or 'left'
	data.infoFontSize = checkNumAndAddSuffix(args['info-s'], 8, 'pt')
	data.infoColor = args['info-fc'] or args[8] or 'black'
	data.infoPadding = args['info-p'] or '0 4px 0 4px'
	data.infoLineHeight = args['info-lh'] or '1.25em'
	data.infoOtherParams = args['info-op']

	-- Get id values.
	data.showId = true
	data.id = args.logo or args[3] or args.id1 or 'id1'
	data.idWidth = checkNumAndAddSuffix(args['id1-w'], 45, 'px')
	data.idHeight = checkNumAndAddSuffix(args['id-h'], 45, 'px')
	data.idBackgroundColor = args['id1-c'] or args[1] or '#dddddd'
	data.idTextAlign = 'center'
	data.idFontSize = checkNumAndAddSuffix(args['id1-s'], 14, 'pt')
	data.idLineHeight = args['id1-lh'] or '1.25em'
	data.idColor = args['id1-fc'] or data.infoColor
	data.idPadding = args['id1-p'] or '0 1px 0 0'
	data.idOtherParams = args['id1-op']

	-- Get id2 values.
	data.showId2 = true
	data.id2 = args.logo or args[5] or args.id2 or 'id2'
	data.id2Width = checkNumAndAddSuffix(args['id2-w'], 45, 'px')
	data.id2Height = data.idHeight
	data.id2BackgroundColor = args['id2-c'] or args[7] or args[1] or '#dddddd'
	data.id2TextAlign = 'center'
	data.id2FontSize = checkNumAndAddSuffix(args['id2-s'], 14, 'pt')
	data.id2LineHeight = args['id2-lh'] or '1.25em'
	data.id2Color = args['id2-fc'] or data.infoColor
	data.id2Padding = args['id2-p'] or '0 0 0 1px'
	data.id2OtherParams = args['id2-op']

	return data
end

p['_userbox-r'] = function (args)
	-- Does argument processing for {{userbox-r}}.
	local data = {}

	-- Get div tag values.
	data.float = args.float or 'left'
	local borderWidthNum = checkNum(args['border-width'] or args['border-s'], 1) -- Used to calculate width.
	data.borderWidth = addSuffix(borderWidthNum, 'px')
	data.borderColor = args['border-color'] or args['border-c'] or args[1] or args['id-c'] or '#999'
	data.width = addSuffix(240 - 2 * borderWidthNum, 'px') -- Also used in the table tag.
	data.bodyClass = args.bodyclass
	
	-- Get table tag values.
	data.backgroundColor = args['info-background'] or args[2] or args['info-c'] or '#eee'

	-- Get id values.
	data.showId = false -- We only show id2 in userbox-r.

	-- Get info values.
	data.info = args.info or args[4] or "<code>{{{info}}}</code>"
	data.infoTextAlign = args['info-align'] or args['info-a'] or 'left'
	data.infoFontSize = checkNumAndAddSuffix(args['info-size'] or args['info-s'], 8, 'pt')
	data.infoPadding = args['info-padding'] or args['info-p'] or '0 4px 0 4px'
	data.infoLineHeight = args['info-line-height'] or args['info-lh'] or '1.25em'
	data.infoColor = args['info-color'] or args['info-fc'] or 'black'
	data.infoOtherParams = args['info-other-param'] or args['info-op']
	
	-- Get id2 values.
	data.showId2 = true
	data.id2 = args.logo or args[3] or args.id or 'id'
	data.id2Width = checkNumAndAddSuffix(args['logo-width'] or args['id-w'], 45, 'px')
	data.id2Height = checkNumAndAddSuffix(args['logo-height'] or args['id-h'], 45, 'px')
	data.id2BackgroundColor = args['logo-background'] or args[1] or args['id-c'] or '#ddd'
	data.id2TextAlign = args['id-a'] or 'center'
	data.id2FontSize = checkNumAndAddSuffix(args['logo-size'] or args[5] or args['id-s'], 14, 'pt')
	data.id2Color = args['logo-color'] or args['id-fc'] or data.infoColor
	data.id2Padding = args['logo-padding'] or args['id-p'] or '0 0 0 1px'
	data.id2LineHeight = args['logo-line-height'] or args['id-lh'] or '1.25em'
	data.id2OtherParams = args['logo-other-param'] or args['id-op']

	return data
end

function p.render(data)
	-- Renders the userbox html using the content of the data table. 
	-- Render the div tag html.
	local root = mw.html.create('div')
	root
		:css('float', data.float)
		:css('border', (data.borderWidth or '') .. ' solid ' .. (data.borderColor or ''))
		:css('margin', '1px')
		:css('width', data.width)
		:addClass('wikipediauserbox')
		:addClass(data.bodyClass)

	-- Render the table tag html.
	local tableroot = root:tag('table')
	tableroot
		:attr('role', 'presentation')
		:css('border-collapse', 'collapse')
		:css('width', data.width)
		:css('margin-bottom', '0')
		:css('margin-top', '0')
		:css('background', data.backgroundColor)
	
	-- Render the id html.
	local tablerow = tableroot:tag('tr')
	if data.showId then
		tablerow:tag('td')
			:css('border', '0')
			:css('width', data.idWidth)
			:css('height', data.idHeight)
			:css('background', data.idBackgroundColor)
			:css('text-align', data.idTextAlign)
			:css('font-size', data.idFontSize)
			:css('font-weight', 'bold')
			:css('color', data.idColor)
			:css('padding', data.idPadding)
			:css('line-height', data.idLineHeight)
			:css('vertical-align', 'middle')
			:cssText(data.idOtherParams)
			:addClass(data.idClass)
			:wikitext(data.id)
	end

	-- Render the info html.
	tablerow:tag('td')
		:css('border', '0')
		:css('text-align', data.infoTextAlign)
		:css('font-size', data.infoFontSize)
		:css('padding', data.infoPadding)
		:css('height', data.infoHeight)
		:css('line-height', data.infoLineHeight)
		:css('color', data.infoColor)
		:css('vertical-align', 'middle')
		:cssText(data.infoOtherParams)
		:addClass(data.infoClass)
		:wikitext(data.info)
	
	-- Render the second id html.
	if data.showId2 then
		tablerow:tag('td')
			:css('border', '0')
			:css('width', data.id2Width)
			:css('height', data.id2Height)
			:css('background', data.id2BackgroundColor)
			:css('text-align', data.id2TextAlign)
			:css('font-size', data.id2FontSize)
			:css('font-weight', 'bold')
			:css('color', data.id2Color)
			:css('padding', data.id2Padding)
			:css('line-height', data.id2LineHeight)
			:css('vertical-align', 'middle')
			:cssText(data.id2OtherParams)
			:wikitext(data.id2)
	end

	local title = mw.title.getCurrentTitle()
	if (title.namespace == 2) and not title.text:match("/") then
		return tostring(root) -- regular user page
	elseif title.namespace == 14 then
		return tostring(root) -- category
	elseif title.isTalkPage then
		return tostring(root) -- talk page
	end

	local legible = true
	local contrast = require('Module:Color contrast')._ratio

	local function has_text(wikitext)
		local function get_alt(text)
			return text:match("|alt=([^|]*)") or ""
		end
	
		wikitext = wikitext:gsub("]]", "|]]")
		wikitext = wikitext:gsub("%[%[%s*[Mm][Ee][Dd][Ii][Aa]%s*:[^|]-(|.-)]]", get_alt)
		wikitext = wikitext:gsub("%[%[%s*[Ii][Mm][Aa][Gg][Ee]%s*:[^|]-(|.-)]]", get_alt)
		wikitext = wikitext:gsub("%[%[%s*[Ff][Ii][Ll][Ee]%s*:[^|]-(|.-)]]", get_alt)
		return mw.text.trim(wikitext) ~= ""
	end

	if contrast { data.infoColor, data.backgroundColor, error = 0 } < 4.5 then
		legible = false
	end

	if data.showId and contrast { data.idColor, data.idBackgroundColor, error = 0 } < 4.5 then
		if has_text(data.id or "") then
			legible = false
		end
	end

	if data.showId2 and contrast { data.id2Color, data.id2BackgroundColor, error = 0 } < 4.5 then
		if has_text(data.id2 or "") then
			legible = false
		end
	end

	if not legible then
		root:wikitext('[[Category:Userboxes with insufficient color contrast]]')
	end

	return tostring(root)
end

function p.categories(args, page)
	-- Gets categories from [[Module:Category handler]].
	-- The page parameter makes the function act as though the module was being called from that page.
	-- It is included for testing purposes.
	local cats = {}
	cats[#cats + 1] = args.usercategory
	cats[#cats + 1] = args.usercategory2
	cats[#cats + 1] = args.usercategory3
	cats[#cats + 1] = args.usercategory4
	cats[#cats + 1] = args.usercategory5
	-- Get the title object
	local title
	if page then
		title = mw.title.new(page)
	else
		title = mw.title.getCurrentTitle()
	end
	-- Build category handler arguments.
	local chargs = {}
	chargs.page = page
	chargs.nocat = args.nocat
	chargs.main = '[[Category:Pages with misplaced templates]]'
	if args.notcatsubpages then
		chargs.subpage = 'no'
	end
	-- User namespace.
	local user = ''
	for i, cat in ipairs(cats) do
		user = user .. makeCat(cat)
	end
	chargs.user = user
	-- Template namespace.
	local basepage = title.baseText
	local template = ''
	for i, cat in ipairs(cats) do
		template = template .. makeCat(cat, ' ' .. basepage)
	end
	chargs.template = template
	return categoryHandler(chargs)
end

return p