Module:Sandbox/S.A. Julio
Appearance
local p = {}
-- Helper function to get display text from wikilinks
local function getDisplayText(text)
if not text then return '' end
-- First deflag if needed
text = mw.ustring.gsub(text, '<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%][^<>]*</span>%s*', '')
text = mw.ustring.gsub(text, '%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]%s*', '')
text = mw.ustring.gsub(text, '^%s* %s*', '')
-- Then extract display text from wikilink if present
local function replaceLink(match)
local pipePos = match:find("|")
if pipePos then
return match:sub(pipePos + 1, -3) -- Return text after the '|'
else
return match:sub(3, -3) -- Return text without the brackets
end
end
-- Replace all wikilinks with their display text
text = mw.ustring.gsub(text, '%[%[[^%[%]]*%]%]', replaceLink)
return text
end
-- Helper function to get short team names
local function get_short_name(s, t, n, ss, frame)
-- If name is undefined or empty, use team code
if not n or n == '' then
n = t
end
-- return short name if defined
if s and s ~= '' then
return s
end
-- deflag if necessary
if ss and n then
if ss == 'noflag' then
n = mw.ustring.gsub(n, '%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]', '')
n = mw.ustring.gsub(n, '^%s* %s*', '')
elseif ss == 'flag' then
n = mw.ustring.gsub(n, '(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%][^<>]*</span>)%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
n = mw.ustring.gsub(n, '(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%])%s* %s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
n = mw.ustring.gsub(n, '(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%])%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
n = mw.ustring.gsub(n, '.*(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%][^<>]*</span>).*', '%1')
n = mw.ustring.gsub(n, '.*(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]).*', '%1')
n = mw.ustring.gsub(n, ' (</span>)', '%1')
elseif ss == 'abbr' then
-- For abbr style, we want the clean team name without any markup
local displayName = getDisplayText(n)
if frame then
-- Use the Template:Abbr template
return frame:expandTemplate{
title = 'abbr',
args = {
[1] = t, -- First parameter: abbreviation
[2] = displayName -- Second parameter: full name
}
}
end
end
end
-- replace link text in name with team abbr if possible
if n and t and n:match('(%[%[[^%[%]]*%]%])') and ss ~= 'abbr' then
n = mw.ustring.gsub(n, '(%[%[[^%|%]]*%|)[^%|%]]*(%]%])', '%1' .. t .. '%2')
n = mw.ustring.gsub(n, '(%[%[[^%|%]]*)(%]%])', '%1|' .. t .. '%2')
n = mw.ustring.gsub(n, '(%[%[[^%|%]]*%|)([A-Z][A-Z][A-Z])(%]%]) <span[^<>]*>%([A-Z][A-Z][A-Z]%)</span>', '%1%2%3')
return n
end
-- nothing worked, so just return the unlinked team abbr
return t or ''
end
-- Helper function to determine background colour
local function get_score_background(s, isFBR)
local s1, s2
-- Define the colouring based on style
local wc = isFBR and '#BBF3FF' or '#BBF3BB' -- blue for FBR, green otherwise
local lc, tc = '#FBB', '#FFB' -- red and yellow remain the same
-- check for override
if s:match('^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$') then
local c = mw.ustring.gsub(s,'^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$', '%1')
return 'background: ' .. c ..';'
end
-- delink if necessary
if s:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]') then
s = s:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]')
end
if s:match('^%s*%[[^%[%]%s]*%s([^%[%]]*)%]') then
s = s:match('^%s*%[[^%[%]%s]*%s([^%[%]]*)%]')
end
if s:match('<span[^<>]*>(.-)</span>') then
s = s:match('<span[^<>]*>(.-)</span>')
end
-- get the scores
s1 = tonumber(mw.ustring.gsub(s or '', '^%s*([%d%.]+)%s*[–%-]%s*([%d%.]+).*', '%1') or '') or ''
s2 = tonumber(mw.ustring.gsub(s or '', '^%s*([%d%.]+)%s*[–%-]%s*([%d%.]+).*', '%2') or '') or ''
-- return colouring if possible
if s1 ~= '' and s2 ~= '' then
if s1 > s2 then
return 'background: ' .. wc .. ';'
elseif s2 > s1 then
return 'background: ' .. lc .. ';'
else
return 'background: ' .. tc .. ';'
end
end
return ''
end
-- Helper function to process team order
local function getTeamList(args)
local teams = {}
-- Always check team_order first and use it if present
if args.team_order and args.team_order:match("%S") then
for team in args.team_order:gmatch('[^,%s]+') do
table.insert(teams, team)
end
return teams
end
-- Only if team_order is nil or empty, try numbered parameters
local i = 1
while args['team' .. i] do
table.insert(teams, args['team' .. i])
i = i + 1
end
return teams
end
-- Helper function to split match list into array
local function getMatchList(matchList)
if not matchList then return {} end
local matches = {}
for match in matchList:gmatch('[^,%s]+') do
-- Special case for 'null'
if match == 'null' then
table.insert(matches, match)
else
-- Validate match format: must be CODE_[han]
if not match:match('^[A-Z]+_[han]$') then
error('Invalid match format: ' .. match .. '. Must be in format CODE_h, CODE_a, or CODE_n')
end
table.insert(matches, match)
end
end
return matches
end
-- Helper function to generate match parameter key
local function getMatchKey(team1, team2, matchNum)
return 'match_' .. team1 .. '_' .. team2 .. (matchNum > 1 and '_' .. matchNum or '')
end
-- Function to check if any neutral matches exist (add at top of module)
local function hasNeutralMatches(teams, args)
for _, team in ipairs(teams) do
local matchList = getMatchList(args['match_list_' .. team])
for _, matchCode in ipairs(matchList) do
if matchCode:match('_n$') then
return true
end
end
end
return false
end
-- Helper function to lookup match result for neutral matches
local function getNeutralMatchResult(args, team1, team2, matchNum)
-- Try all possible parameter combinations for neutral matches
local baseKey1 = 'matchn_' .. team1 .. '_' .. team2
local baseKey2 = 'matchn_' .. team2 .. '_' .. team1
-- Try without suffix first
if matchNum == 1 then
if args[baseKey1] then
return args[baseKey1], false -- false means don't reverse score
end
if args[baseKey2] then
return args[baseKey2], true -- true means reverse score
end
end
-- Try with suffix
local suffixKey1 = baseKey1 .. '_' .. matchNum
local suffixKey2 = baseKey2 .. '_' .. matchNum
if args[suffixKey1] then
return args[suffixKey1], false
end
if args[suffixKey2] then
return args[suffixKey2], true
end
-- For first matches, try with _1 suffix as fallback
if matchNum == 1 then
local fallbackKey1 = baseKey1 .. '_1'
local fallbackKey2 = baseKey2 .. '_1'
if args[fallbackKey1] then
return args[fallbackKey1], false
end
if args[fallbackKey2] then
return args[fallbackKey2], true
end
end
return nil, false
end
-- Helper function to lookup match result for regular matches
local function getRegularMatchResult(args, team1, team2, matchNum)
-- Try all possible parameter names
local baseKey = 'match_' .. team1 .. '_' .. team2
-- Try without suffix first
if matchNum == 1 then
if args[baseKey] then
return args[baseKey]
end
end
-- Try with suffix
local suffixKey = baseKey .. '_' .. matchNum
if args[suffixKey] then
return args[suffixKey]
end
-- For first matches, try with _1 suffix as fallback
if matchNum == 1 then
local fallbackKey = baseKey .. '_1'
return args[fallbackKey]
end
return nil
end
-- Modify the formatScore function to take the home_first parameter
local function formatScore(score, reverseScore, home_first, isNeutral)
if not score or score == '' or score:find('–') then return score end
local score1, score2 = score:match('(%d+)[%-–](%d+)')
if not score1 or not score2 then return score end
-- If it's a neutral match or home_first is false, use the previous logic
if isNeutral or not home_first then
if reverseScore and score1 ~= score2 then
return score2 .. '–' .. score1
end
return score1 .. '–' .. score2
end
-- For home_first=yes, just return the score as-is since it's already home-away order
return score1 .. '–' .. score2
end
-- Modify the processMatch function to handle home_first
local function processMatch(match, currentTeam, args, matchCounts, useHomeFirst)
-- Special handling for 'null' matches
if match == 'null' then
return {
isNull = true -- New flag to indicate null match
}
end
local opponent, venue = match:match('^([A-Z]+)_([han])$')
if not opponent or not venue then return nil end
-- Skip if team is playing themselves
if opponent == currentTeam then
return {
skip = true
}
end
-- Create a directional key for counting matches that includes venue information
local pairKey
if venue == 'n' then
-- For neutral venues, sort teams alphabetically for consistent counting
pairKey = currentTeam < opponent and
currentTeam .. '_' .. opponent .. '_n' or
opponent .. '_' .. currentTeam .. '_n'
else
-- For home/away, track direction specifically
pairKey = (venue == 'h') and
currentTeam .. '_' .. opponent .. '_h' or
opponent .. '_' .. currentTeam .. '_a'
end
-- Initialize match count for this specific pairing+venue
matchCounts[pairKey] = matchCounts[pairKey] or 0
matchCounts[pairKey] = matchCounts[pairKey] + 1
local matchNum = matchCounts[pairKey]
local isHome = venue == 'h'
local isNeutral = venue == 'n'
local score, needReverseScore
if isNeutral then
-- For neutral matches, try both team orders
score, needReverseScore = getNeutralMatchResult(
args,
currentTeam,
opponent,
matchNum
)
if not score then
score, needReverseScore = getNeutralMatchResult(
args,
opponent,
currentTeam,
matchNum
)
if score then
needReverseScore = true
end
end
else
-- For regular matches
local team1 = isHome and currentTeam or opponent
local team2 = isHome and opponent or currentTeam
score = getRegularMatchResult(args, team1, team2, matchNum)
-- Only reverse score if home_first is not enabled or if we're showing the away team's perspective
needReverseScore = not isHome and not useHomeFirst
end
return {
opponent = opponent,
venue = venue,
score = score,
reverseScore = needReverseScore,
isNeutral = isNeutral
}
end
local function getDisplayCode(args, team)
-- Return the override code if it exists, otherwise return the internal code
return args['short_' .. team] or team
end
-- Main function to build the table
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame, {trim = true})
local yesno = require('Module:Yesno')
local useHomeFirst = yesno(args.home_first)
-- Store reference to table library before we shadow it
local tbl = table
-- Initialize HTML builder
local root = mw.html.create()
-- Add table with optional font size
local table = root:tag('table')
:addClass('wikitable sports-res plainrowheaders')
:css('text-align', 'center')
-- Add font-size if specified, assuming percentage if no unit given
if args.font_size then
local fontSize = args.font_size
-- If the font_size contains only numbers, add % sign
if fontSize:match('^%d+$') then
fontSize = fontSize .. '%'
end
table:css('font-size', fontSize)
end
-- Add centering styles if parameter is set
if yesno(args.center_table) then
table:addClass('center-table')
end
-- Add caption if provided
if args.caption then
table:tag('caption')
:wikitext(args.caption)
end
-- Find maximum number of matches for any team before we need it
local maxMatches = 0
local teams = getTeamList(args)
for _, team in ipairs(teams) do
local matchList = getMatchList(args['match_list_' .. team])
maxMatches = math.max(maxMatches, #matchList)
end
-- Add regular header row
local header = table:tag('tr')
header:tag('th'):wikitext('Team')
-- Add matchday columns
for i = 1, maxMatches do
header:tag('th'):wikitext('MD' .. i)
end
-- Helper function to wrap text in bold if it matches showteam
local function boldIfHighlighted(text, isHighlighted)
if isHighlighted then
return "'''" .. text .. "'''"
end
return text
end
-- Process each team
for _, currentTeam in ipairs(teams) do
local isHighlightedTeam = args.showteam and currentTeam == args.showteam
-- Modified to use team code as fallback
local teamName = args['name_' .. currentTeam]
if not teamName or teamName == '' then
teamName = currentTeam
end
-- Modified display_codes handling to support 'inline bold'
if args.display_codes then
local displayMode = args.display_codes:lower()
if displayMode == 'inline' or displayMode == 'inline bold' then
local displayCode = getDisplayCode(args, currentTeam)
local codeText = displayMode == 'inline bold' and
"'''" .. displayCode .. "'''" or -- Bold the code for 'inline bold'
displayCode -- Regular code for 'inline'
teamName = teamName .. ' <small>(' .. codeText .. ')</small>'
end
end
local matchList = getMatchList(args['match_list_' .. currentTeam])
local matchCounts = {}
local oppRow = table:tag('tr')
oppRow:tag('th')
:attr('scope', 'row')
:attr('rowspan', '2')
:wikitext(boldIfHighlighted(teamName, isHighlightedTeam))
local scoreRow = table:tag('tr')
local useColors = args.matches_style == 'FBR' or yesno(args.matches_style)
for _, matchCode in ipairs(matchList) do
local matchData = processMatch(matchCode, currentTeam, args, matchCounts, useHomeFirst)
if matchData then
if matchData.isNull then
-- Handle null match with optional solid background
local cellStyle = ''
local cellContent = '—' -- default em dash
-- Check for solid_cell parameter
local solidCell = args.solid_cell
if solidCell then
-- Convert to lowercase for comparison
local solidLower = solidCell:lower()
if solidLower == 'grey' or solidLower == 'gray' or yesno(solidCell) then
cellStyle = 'background: #BBB;'
cellContent = '' -- Remove em dash for solid cells
end
end
oppRow:tag('td')
:attr('rowspan', '2')
:attr('style', cellStyle)
:wikitext(cellContent)
else
-- Add opponent cell with modified get_short_name call
local oppShortName = get_short_name(
args['short_' .. matchData.opponent],
matchData.opponent,
args['name_' .. matchData.opponent],
args.display_codes == 'abbr' and 'abbr' or args.short_style,
frame
)
local cellStyle = ''
local venueDisplay = ''
if yesno(args.color_venue) then
if matchData.venue == 'h' then
cellStyle = 'background: #BBB;'
elseif matchData.venue == 'n' then
cellStyle = 'background: #FEDCBA;'
end
else
venueDisplay = ' <small>(' ..
(matchData.venue == 'n' and 'N' or
(matchData.venue == 'h' and 'H' or 'A')) ..
')</small>'
end
oppRow:tag('td')
:attr('style', cellStyle)
:wikitext(boldIfHighlighted(oppShortName .. venueDisplay, isHighlightedTeam))
if matchData.score then
local formattedScore = formatScore(
matchData.score,
matchData.reverseScore,
yesno(args.home_first),
matchData.isNeutral
)
local bgStyle = ''
if useColors then
local scoreForColor
if yesno(args.home_first) and not matchData.isNeutral then
if matchData.venue == 'h' then
scoreForColor = matchData.score
else
local score1, score2 = matchData.score:match('(%d+)[%-–](%d+)')
scoreForColor = score2 .. '–' .. score1
end
else
if matchData.reverseScore then
local score1, score2 = matchData.score:match('(%d+)[%-–](%d+)')
scoreForColor = score2 .. '–' .. score1
else
scoreForColor = matchData.score
end
end
bgStyle = get_score_background(scoreForColor, args.matches_style == 'FBR')
end
scoreRow:tag('td')
:attr('style', bgStyle)
:wikitext(boldIfHighlighted(formattedScore, isHighlightedTeam))
else
scoreRow:tag('td'):wikitext('')
end
end
end
end
end
-- Get info for footer
local update = args.update or 'unknown'
local start_date = args.start_date or 'unknown'
local source = args.source
if not source then
source = frame:expandTemplate{
title = 'citation needed',
args = {
reason = 'No source parameter defined',
date = os.date('%B %Y')
}
}
end
-- Add footer
local footer = {}
-- Date updating with game/match distinction
local eventWord = yesno(args.use_tie) and 'game' or 'match'
if string.lower(update) == 'complete' then
-- Do nothing
elseif update == '' then
-- Empty parameter
tbl.insert(footer, string.format('Updated to %s(s) played on unknown.', eventWord))
elseif string.lower(update) == 'future' then
-- Future start date
tbl.insert(footer, string.format('First %s(s) will be played: %s.', eventWord, start_date))
else
tbl.insert(footer, string.format('Updated to %s(s) played on %s.', eventWord, update))
end
-- Always add source since it will either be the provided source or citation needed template
tbl.insert(footer, 'Source: ' .. source)
-- Add score note
local showColorText = (args.matches_style == 'FBR' or yesno(args.matches_style) or yesno(args.color_venue))
local colorWord = yesno(args.use_tie) and 'colors' or 'colours'
local scoreText
if useHomeFirst then
scoreText = 'Scores are listed with the home team\'s score first'
if hasNeutralMatches(teams, args) then
scoreText = scoreText .. '. Neutral matches are listed from the perspective of the team shown in the "Team" column'
end
else
scoreText = string.format('Scores%s are listed from the perspective of the team shown in the "Team" column',
showColorText and ' and legend ' .. colorWord or ''
)
end
tbl.insert(footer, '<br />' .. scoreText .. '.')
-- Handle venue key and legend together
local needsVenueKey = not yesno(args.color_venue)
local needsColorLegend = args.matches_style == 'FBR' or yesno(args.matches_style)
local hasColorVenue = yesno(args.color_venue)
if needsVenueKey or needsColorLegend or hasColorVenue then
local combinedText = '<br />'
local parts = {}
-- First add venue information (either as key or colors)
if needsVenueKey then
-- Traditional venue key with separate label
if needsColorLegend then
-- If we also have color legend, use "Venue key:"
tbl.insert(parts, 'Venue key: H = home, A = away' ..
(hasNeutralMatches(teams, args) and ', N = neutral' or ''))
else
-- Otherwise use "Legend:" for consistency with other cases
tbl.insert(parts, 'Legend: H = home, A = away' ..
(hasNeutralMatches(teams, args) and ', N = neutral' or ''))
end
elseif hasColorVenue then
-- Colored venue legend
local matchWord = yesno(args.use_tie) and 'game' or 'match'
local greyWord = yesno(args.use_tie) and 'Gray' or 'Grey'
tbl.insert(parts, string.format('Legend: %s = home %s; Standard background = away %s%s',
greyWord,
matchWord,
matchWord,
hasNeutralMatches(teams, args) and string.format('; Orange = neutral %s', matchWord) or ''
))
end
-- Then add match result colors if enabled
if needsColorLegend then
local winColor = (args.matches_style == 'FBR') and 'Blue' or 'Green'
if needsVenueKey then
-- Use "Color legend:" when we have separate venue key
if yesno(args.use_tie) then
tbl.insert(parts, 'Legend: ' .. winColor .. ' = win; Red = loss; Yellow = tie')
else
tbl.insert(parts, 'Legend: ' .. winColor .. ' = win; Yellow = draw; Red = loss')
end
else
-- If we're using color_venue, continue the legend with semicolons
if yesno(args.use_tie) then
tbl.insert(parts, winColor .. ' = win; Red = loss; Yellow = tie')
else
tbl.insert(parts, winColor .. ' = win; Yellow = draw; Red = loss')
end
end
end
-- Join parts with period if we have venue key and color legend,
-- otherwise just concatenate the single part
combinedText = combinedText .. tbl.concat(parts, '. ') .. '.'
tbl.insert(footer, combinedText)
end
if args.display_codes and args.display_codes:lower() == 'footer' then
local keyParts = {}
-- Handle teams
for i, team in ipairs(teams) do
local teamName = args['name_' .. team]
local displayCode = getDisplayCode(args, team)
keyParts[i] = displayCode .. ' = ' .. teamName
end
local keyText = '<br />Team key: ' .. tbl.concat(keyParts, '; ') .. '.'
tbl.insert(footer, keyText)
end
-- Add rivalry note if needed
if args.a_note then
tbl.insert(footer, '<br />For upcoming matches, an "a" indicates there is an article about the rivalry between the two participants.')
end
-- Create templatestyles tag
local templatestyles = frame:extensionTag{
name = 'templatestyles',
args = { src = 'Module:Sports results/styles.css' }
}
-- Create footer content
local footerHtml = ''
if #footer > 0 then
local footerDiv = mw.html.create('div')
:addClass('sports-results-notes')
:wikitext(tbl.concat(footer, ' '))
footerHtml = templatestyles .. tostring(footerDiv)
end
-- Create outer overflow divs
local outerDiv = mw.html.create('div')
:css('overflow', 'hidden')
local innerDiv = outerDiv:tag('div')
:addClass('noresize')
:addClass('overflowbugx')
:css('overflow', 'auto')
-- Add the table to the inner div
innerDiv:node(root)
-- Return everything wrapped together
return tostring(outerDiv) .. footerHtml
end
return p