Module:College color
--
 -- This module implements {{CollegePrimaryHex}}, {{CollegePrimaryStyle}},
 -- {{CollegePrimaryColorLink}}, {{CollegeSecondaryHex}},
 -- {{CollegeSecondaryStyle}}, {{CollegeSecondaryColorLink}}, and {{NCAA color}}
 --
 local p = {}
 
 local data_module = &quot;Module:College color/data&quot;
 
 local function stripwhitespace(text)
 	return text:match(&quot;^%s*(.-)%s*$&quot;)
 end
 local function sRGB ( v )
 	if (v &lt;= 0.03928) then
 		v = v / 12.92
 	else
 		v = math.pow((v+0.055)/1.055, 2.4)
 	end
 	return v
 end
 local function color2lum( origc )
 	local c = stripwhitespace(origc or ''):lower()
 
 	-- remove leading # (if there is one)
 	c = mw.ustring.match(c, '^[#]*([a-f0-9]*)$')
 
 	-- split into rgb
 	local cs = mw.text.split(c or '', '')
 	if( #cs == 6 ) then
 		local R = sRGB( (16*tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[2]))/255 )
 		local G = sRGB( (16*tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[4]))/255 )
 		local B = sRGB( (16*tonumber('0x' .. cs[5]) + tonumber('0x' .. cs[6]))/255 )
 
 		return 0.2126 * R + 0.7152 * G + 0.0722 * B
 	elseif ( #cs == 3 ) then
 		local R = sRGB( (16*tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[1]))/255 )
 		local G = sRGB( (16*tonumber('0x' .. cs[2]) + tonumber('0x' .. cs[2]))/255 )
 		local B = sRGB( (16*tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[3]))/255 )
 
 		return 0.2126 * R + 0.7152 * G + 0.0722 * B
 	end
 
 	-- failure
 	error('Invalid hex color ' .. origc, 2)
 end
 
 local function remove_sport(team)
 	team = mw.ustring.gsub(team, &quot;%s*&lt;[Bb][Rr][^&lt;&gt;]*&gt;%s*&quot;, ' ');
 	team = mw.ustring.gsub(team, &quot; [Tt]eam$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Bb]asketball$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Bb]aseball$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Cc]ross [Cc]ountry$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ff]ield [Hh]ockey$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ff]ootball$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Gg]olf$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Gg]ymnastics$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ii]ce [Hh]ockey$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ll]acrosse$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Rr]owing$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ss]ki$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ss]occer$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ss]oftball$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ss]wim$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Tt]ennis$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Tt]rack [Aa]nd [Ff]ield$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Vv]olleyball$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ww]restling$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Ww]omen's$&quot;, '')
 	team = mw.ustring.gsub(team, &quot; [Mm]en's$&quot;, '')
 
 	return team
 end
 local function get_colors(team, unknown)
 	team = stripwhitespace(team or '')
 	unknown = unknown or {&quot;DCDCDC&quot;, &quot;000000&quot;}
 
 	local use_default = {
 		[&quot;&quot;] = 1,
 		[&quot;retired&quot;] = 1,
 		[&quot;free agent&quot;] = 1,
 	}
 
 	local colors = nil
 
 	if ( team and use_default[team:lower()] ) then
 		colors = {&quot;DCDCDC&quot;, &quot;000000&quot;}
 	else
 		local all_colors = mw.loadData(data_module)
 		colors = all_colors[team]
 		if ( colors and type(colors) == 'string' ) then
 			colors = all_colors[colors]
 		end
 	end
 
 	return colors or unknown
 end
 
 local function team_color(team, num, num2)
 	local colors = get_colors(team, nil)
 
 	num = tonumber(num:match('[1-3]') or '0')
 	num2 = tonumber(num2:match('[1-3]') or '0')
 	if ( num ) then
 		return colors[num] or colors[num2] or ''
 	else
 		return ''
 	end
 end
 
 local function team_style1(team, borderwidth, fontcolor)
 	local colors = get_colors(team, nil)
 
 	local color = '#' .. (colors[3] or colors[2] or '')
 	local style = 'background-color:#' .. (colors[1] or '') .. ';color:' .. (fontcolor or color) .. ';'
 	-- remove the border if it's nearly white
 	if ((1 + 0.05)/(color2lum(color) + 0.05) &lt; 1.25) then
 		borderwidth = '0'
 	end
 	borderwidth = tonumber(borderwidth or '2') or 0
 	if (borderwidth &gt; 0 and color ~= '#FFFFFF') then
 		style = style .. ' border:' .. borderwidth .. 'px solid ' .. color .. ';'
 	end
 
 	return style
 end
 
 local function team_style2(team, borderwidth, fontcolor)
 	local colors = get_colors(team, nil)
 
 	local color = '#' .. (colors[1] or '')
 	local style = 'background-color:#' .. (colors[3] or colors[2] or '') .. ';color:' .. (fontcolor or color) .. ';'
 	-- remove the border if it's nearly white
 	if ((1 + 0.05)/(color2lum(color) + 0.05) &lt; 1.25) then
 		borderwidth = '0'
 	end
 	borderwidth = tonumber(borderwidth or '2') or 0
 	if (borderwidth &gt; 0 and color ~= '#FFFFFF') then
 		style = style .. ' border:' .. borderwidth .. 'px solid ' .. color .. ';'
 	end
 
 	return style
 end
 
 local function team_header1(team, borderwidth)
 	local colors = get_colors(team, nil)
 	-- set the default background
 	local background = (colors[1] or 'FFFFFF'):upper()
 	-- set background to white if it's nearly white
 	if ((1 + 0.05)/(color2lum(background) + 0.05) &lt; 1.25) then
 		background = 'FFFFFF'
 	end
 	-- now pick a font color
 	local fontcolor = '000000'
 	-- compute the luminosity of the background
 	local lum = color2lum(background)
 	-- compute the contrast with white and black
 	local wcontrast = (1 + 0.05)/(lum + 0.05)
 	local bcontrast = (lum + 0.05)/(0 + 0.05)
 	-- select the text color with the best contrast
 	if( bcontrast &gt; wcontrast + 1.25 ) then
 		fontcolor = '000000'
 	else
 		fontcolor = 'FFFFFF'
 	end
 
 	local style
 	if( background == 'FFFFFF' ) then
 		style = 'background-color:none;color:#' .. fontcolor .. ';'
 	else
 		style = 'background-color:#' .. background .. ';color:#' .. fontcolor .. ';'
 	end
 
 	if borderwidth then
 		borderwidth = tonumber(borderwidth or '2') or 0
 		local bordercolor = (colors[3] or colors[2] or 'FFFFFF'):upper()
 		if (borderwidth &gt; 0 and bordercolor ~= 'FFFFFF') then
 			-- do not add a border if it's nearly white
 			if ((1 + 0.05)/(color2lum(bordercolor) + 0.05) &gt;= 1.25) then
 				style = style .. ' border:' .. borderwidth .. 'px solid #' .. bordercolor .. ';'
 			end
 		end
 	end
 	return style
 end
 
 local function team_header2(team)
 	local colors = get_colors(team, nil)
 	-- set the default background
 	local background = (colors[3] or colors[2] or 'FFFFFF'):upper()
 	-- set background to white if it's nearly white
 	if ((1 + 0.05)/(color2lum(background) + 0.05) &lt; 1.25) then
 		background = 'FFFFFF'
 	end
 	-- if the background is white, then use the primary background instead
 	if( background == 'FFFFFF' ) then
 		background = (colors[1] or 'FFFFFF'):upper()
 	end
 	-- now pick a font color
 	local fontcolor = '000000'
 	-- compute the luminosity of the background
 	local lum = color2lum(background)
 	-- compute the contrast with white and black
 	local wcontrast = (1 + 0.05)/(lum + 0.05)
 	local bcontrast = (lum + 0.05)/(0 + 0.05)
 	-- select the text color with the best contrast
 	if( bcontrast &gt; wcontrast + 1.25 ) then
 		fontcolor = '000000'
 	else
 		fontcolor = 'FFFFFF'
 	end
 	if( background == 'FFFFFF' ) then
 		return 'background-color:none;color:#' .. fontcolor .. ';'
 	else
 		return 'background-color:#' .. background .. ';color:#' .. fontcolor .. ';'
 	end
 end
 
 local function team_table_head(args, team, ctype)
 	local colors = get_colors(team, nil)
 	-- set the default background
 	local background = (ctype == 'p') and
 		(colors[1] or 'FFFFFF'):upper() or
 		(colors[3] or colors[2] or 'FFFFFF'):upper()
 	-- now pick a font color
 	local fontcolor = '000000'
 	-- compute the luminosity of the background
 	local lum = color2lum(background)
 	-- compute the contrast with white and black
 	local wcontrast = (1 + 0.05)/(lum + 0.05)
 	local bcontrast = (lum + 0.05)/(0 + 0.05)
 	-- select the text color with the best contrast
 	if( bcontrast &gt; wcontrast + 1.25 ) then
 		fontcolor = '000000'
 	else
 		fontcolor = 'FFFFFF'
 	end
 	local s = 'background-color:#' .. background .. ';color:#' .. fontcolor .. ';'
 
 	local res = '|-\n'
 	for i=1,50 do
 		if( args[i] ~= nil ) then
 			local cstyle = 'scope=&quot;col&quot; style=&quot;' .. s .. '&quot;'
 			if args['col' .. i .. 'span'] ~= nil then
 				cstyle = cstyle .. ' colspan=' .. args['col' .. i .. 'span']
 			end
 			res = res .. '! ' .. cstyle .. ' |' .. args[i] .. '\n'
 		else
 			return res .. '|-\n'
 		end
 	end
 	return res .. '&lt;span class=&quot;error&quot;&gt;Error!&lt;/span&gt;\n|-\n'
 
 end
 
 local function team_stripe1(team, borderwidth)
 	local colors = get_colors(team, nil)
 
 	-- set the default scheme
 	local background = colors[1] or ''
 	local fontcolor = colors[2] or ''
 	local bordercolor = (colors[3] or colors[2] or ''):upper()
 	borderwidth = tonumber(borderwidth or '3') or 0
 
 	-- if there is no tertiary color, then pick a font color
 	if (colors[3] == nil) then
 		-- compute the luminosity of the background
 		local lum = color2lum(colors[1])
 		-- compute the contrast with white and black
 		local wcontrast = (1 + 0.05)/(lum + 0.05)
 		local bcontrast = (lum + 0.05)/(0 + 0.05)
 		-- select the text color with the best contrast
 		if( bcontrast &gt; wcontrast + 1.25 ) then
 			fontcolor = '000000'
 		else
 			fontcolor = 'FFFFFF'
 		end
 	end
 
 	-- finally build the style string
 	local style = ''
 	if (borderwidth &gt; 0) then
 		-- use the primary as the border if the border is white or close to white
 		local bordercontrast = (1 + 0.05)/(color2lum(bordercolor) + 0.05)
 		if (bordercontrast &lt; 1.25) then
 			bordercolor = background
 			local fontcontrast = (1 + 0.05)/(color2lum(colors[2] or 'FFFFFF') + 0.05)
 			if (fontcontrast &lt; 1.25) then
 				fontcolor = colors[2] or 'FFFFFF'
 			end
 		end
 		style = style .. ' border:' .. borderwidth .. 'px solid #' .. bordercolor .. ';'
 		style = style .. ' border-left: none; border-right: none;'
 		style = style .. ' box-shadow: inset 0 2px 0 #FEFEFE, inset 0 -2px 0 #FEFEFE;'
 	end
 	style = 'background-color:#' .. background .. ';color:#' .. fontcolor .. ';' .. style
 
 	return style
 end
 
 local function team_boxes(frame, team, order, sep)
 	local function colorbox( h )
 		local r = mw.html.create('')
 		r:tag('span')
 			:css('background-color', '#' .. (h or ''))
 			:css('border', '1px solid #000')
 			:wikitext('&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;')
 		return tostring(r)
 	end
 
 	local colors = get_colors(team, 'unknown')
 
 	if type(colors) ~= 'table' then
 		return ''
 	end
 
 	local colorboxes = {}
 	local colororder = {'1','2','3','4','5'}
 	local namecheck = 0
 	if order == '' then
 		order = colors['order'] or ''
 		namecheck = 1
 	end
 	if order ~= '' then
 		colororder = mw.text.split(order, '')
 	end
 	for k,v in pairs(colororder) do
 		local i = tonumber(v) or 0
 		if(	namecheck == 0 or colors['name' .. i]) then
 			if colors[i] then
 				table.insert(colorboxes,colorbox(colors[i]))
 			end
 		end
 	end
 
 	if (#colorboxes &gt; 0) then
 		return table.concat(colorboxes, sep)
 	end
 
 	return ''
 end
 
 local function team_list(frame, team, num1, num2, num3, num4, num5, sep)
 	local function colorbox( h )
 		local r = mw.html.create('')
 		r:tag('span')
 			:css('background-color', '#' .. (h or ''))
 			:css('border', '1px solid #000')
 			:wikitext('&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;')
 		return tostring(r)
 	end
 
 	local colors = get_colors(team, 'unknown')
 
 	if type(colors) ~= 'table' then
 		return ''
 	end
 
 	local nums = {
 		tonumber(num1:match('[1-5]') or '0') or 0,
 		tonumber(num2:match('[1-5]') or '0') or 0,
 		tonumber(num3:match('[1-5]') or '0') or 0,
 		tonumber(num4:match('[1-5]') or '0') or 0,
 		tonumber(num5:match('[1-5]') or '0') or 0}
 
 	local colorboxes = {}
 	local colornames = {}
 	local colororder = {'1','2','3','4','5'}
 	local order = colors['order'] or ''
 	if(order ~= '') then
 		colororder = mw.text.split(order, '')
 	end
 	for k,v in pairs(colororder) do
 		local i = tonumber(v) or 0
 		if ( nums[i] &gt; 0 ) then
 			if(	colors['name' .. nums[i]]) then
 				table.insert(colornames,colors['name' .. nums[i]])
 				table.insert(colorboxes,colorbox(colors[nums[i]] or ''))
 			end
 		end
 	end
 
 	local res = ''
 	if (#colornames == 1) then
 		res = colornames[1]
 	elseif (#colornames == 2) then
 		res = colornames[1] .. '&amp;nbsp;and&amp;nbsp;' .. colornames[2]
 	elseif (#colornames &gt; 2) then
 		res = colornames[1]
 		for i=2,#colornames do
 			if( i &lt; #colornames ) then
 				res = res .. ',&amp;nbsp;' .. colornames[i]
 			else
 				res = res .. ',&amp;nbsp;and&amp;nbsp;' .. colornames[i]
 			end
 		end
 	end
 
 	if (colors['cite']) then
 		res = res .. frame:preprocess('&lt;ref&gt;' .. colors['cite'] .. '&lt;/ref&gt;')
 	end
 	if (colors['ref']) then
 		res = res .. '[' .. colors['ref'] .. ']'
 	end
 	if (colors['ref2']) then
 		res = res .. '[' .. colors['ref2'] .. ']'
 	end
 
 	if (#colornames &gt; 0) then
 		res = res .. sep
 	end
 
 	if (#colorboxes &gt; 0) then
 		res = res .. table.concat(colorboxes, '&amp;nbsp;')
 	end
 
 	return res
 end
 
 local function team_check(team, unknown)
 	local colors = get_colors(team, unknown)
 	if type(colors) == 'table' then
 		return 'known'
 	else
 		return unknown
 	end
 end
 
 function p.color(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_color(remove_sport(args[1] or ''), args[2] or '', args[3] or '')
 end
 
 function p.color1(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_color(remove_sport(args[1] or ''), '1', '')
 end
 
 function p.color32(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_color(remove_sport(args[1] or ''), '3', '2')
 end
 
 function p.style1(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_style1(remove_sport(args[1] or ''), args['border'], args['color'])
 end
 
 function p.style2(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_style2(remove_sport(args[1] or ''), args['border'], args['color'])
 end
 
 function p.header1(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_header1(remove_sport(args[1] or ''), args['border'])
 end
 
 function p.header2(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_header2(remove_sport(args[1] or ''))
 end
 
 function p.tablehead1(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_table_head(args, remove_sport(args['team'] or ''), 'p')
 end
 
 function p.tablehead2(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_table_head(args, remove_sport(args['team'] or ''), 's')
 end
 
 function p.stripe1(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_stripe1(remove_sport(args[1] or ''), args['border'])
 end
 
 function p.boxes(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_boxes(frame, remove_sport(args[1] or ''),
 		args['order'] or '', args['sep'] or '&amp;nbsp;')
 end
 
 function p.list(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_list(frame, remove_sport(args[1] or ''),
 		args[2] or '1', args[3] or '2', args[4] or '3', args[5] or '4', args[6] or '5', args['sep'] or '')
 end
 
 function p.check(frame)
 	local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
 	return team_check(remove_sport(args[1] or ''), args[2] or '')
 end
 
 function p.check_data()
 	-- In a sandbox, preview {{#invoke:college color|check_data}}
 	local results = {'Problems in [[Module:College color/data]]:'}
 	local function problems(msg)
 		if msg then
 			table.insert(results, msg)
 		elseif results[2] then
 			return table.concat(results, '\n*')
 		else
 			return 'No problems detected.'
 		end
 	end
 	local data = require(data_module)
 	local keys = {}
 	for k, _ in pairs(data) do
 		table.insert(keys, k)
 	end
 	table.sort(keys)
 	for _, key in ipairs(keys) do
 		local val = data[key]
 		if not (type(key) == 'string' and (type(val) == 'table' or type(val) == 'string')) then
 			problems('Invalid type for &quot;' .. tostring(key) .. '&quot;')
 		end
 		if type(val) == 'table' then
 			if not (2 &lt;= #val and #val &lt;= 4) then
 				problems('Invalid number of numbered parameters for &quot;' .. tostring(key) .. '&quot;')
 			end
 			for i, v in ipairs(val) do
 				if not tostring(v):match('^%x%x%x%x%x%x$') then
 					problems('Parameter [' .. i .. '] should be a 6-hex-digit color but is &quot;' .. tostring(v) .. '&quot; for &quot;' .. tostring(key) .. '&quot;')
 				end
 			end
 			for k, v in pairs(val) do
 				if type(k) == 'number' then
 					if not (1 &lt;= k and k &lt;= 4) then
 						problems('Invalid numbered parameter for &quot;' .. tostring(key) .. '&quot;')
 					end
 				elseif type(k) == 'string' then
 					if not (
 							k:match('^name[1-4]$') or
 							k:match('^cite2?$') or
 							k:match('^order$')
 							) then
 						problems('Unexpected key in table for &quot;' .. tostring(key) .. '&quot;')
 					end
 				else
 					problems('Invalid key type in table for &quot;' .. tostring(key) .. '&quot;')
 				end
 			end
 		elseif data[val] == nil then
 			problems('Undefined alias for &quot;' .. tostring(key) .. '&quot;')
 		elseif type(data[val]) ~= 'table' then
 			problems('Alias is not a table for &quot;' .. tostring(key) .. '&quot;')
 		end
 	end
 	return problems()
 end
 
 function p.testtable(frame)
 	local contrasttable_mod = require(&quot;Module:College color/contrast&quot;)
 	return contrasttable_mod._testtable(frame.args)
 end