Module:Sports series explained

-- Module to build tables for aggregated match results in sports-- See documentation for details

local p =

-- Function to parse and expand a template with given parameterslocal function expandTemplate(frame, templateName, params) return frame:expandTemplateend

-- Function to check the existence of template passed via |flag=local function templateExists(templateName) local title = mw.title.new('Template:' .. templateName) return title and title.existsend

-- Function to process country codes and variants OR youth team flag templates and age level, dividing parameters by the "+" signlocal function processIcon(iconString) if not iconString or iconString:match("^%s*$") then return nil, nil -- Return nil for both iconCode and variant if the input is empty or only whitespace elseif iconString:find('+') then local parts = mw.text.split(iconString, '+', true) local iconCode = parts[1] local variant = parts[2] return iconCode, variant else return iconString, nil -- Return the input string as iconCode if no "+" is present endend

-- Function to determine the correct ordinal suffix for a given number for the headinglocal function ordinal(n) local last_digit = n % 10 local last_two_digits = n % 100 if last_digit

1 and last_two_digits ~= 11 then return n .. 'st' elseif last_digit

2 and last_two_digits ~= 12 then return n .. 'nd' elseif last_digit

3 and last_two_digits ~= 13 then return n .. 'rd' else return n .. 'th' endend

-- Function to clean and process the aggregate score for comparisonlocal function cleanScore(score) -- Return an empty string if score is nil or empty to avoid errors if not score or score:match("^%s*$") then return end

-- Function to replace wiki links with their display text or link text 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 wiki links score = score:gsub("%[%[.-%]%]", replaceLink)

-- Remove MediaWiki's unique placeholder sequences for references score = score:gsub('"`UNIQ.-QINU`"', )

-- Remove superscript tags and their contents score = score:gsub('.-', )

-- Convert dashes to a standard format score = score:gsub('[–—―‒−]', '-')

-- Strip all characters except numbers, dashes and parentheses return score:gsub('[^0-9%-]+', )end

-- Function to determine the winner based on scores within parentheses (first) or regular format (second)local function determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner, aggregate, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals) local team1Winner, team2Winner = false, false local score1, score2 local manualBold = false local manualColor = false local isDraw = false

-- Handling for manual bolding if team1 and type(team1)

'string' then manualBold1 = team1:find("") and not (team1:gsub("", ""):match("^%s*$")) team1 = team1:gsub("", "") end if team2 and type(team2)

'string' then manualBold2 = team2:find("") and not (team2:gsub("", ""):match("^%s*$")) team2 = team2:gsub("", "") end

if manualBold1 then team1Winner = true manualBold = true end if manualBold2 then team2Winner = true manualBold = true end

-- Handling for manual coloring of team or aggregate cells if team1 and type(team1)

'string' then manualColor1 = team1:find("") and not (team1:gsub("", ""):match("^%s*$")) team1 = team1:gsub("", "") end if team2 and type(team2)

'string' then manualColor2 = team2:find("") and not (team2:gsub("", ""):match("^%s*$")) team2 = team2:gsub("", "") end if aggregate then if aggregate:find("") then aggregate = aggregate:gsub("", "") aggregate = "" .. aggregate .. "" end manualColorDraw = aggregate:find("") and not (aggregate:gsub("", ""):match("^%s*$")) aggregate = aggregate:gsub("", "") end

if manualColor1 then if not team1Winner then team1Winner = true end manualColor = true end if manualColor2 then if not team2Winner then team2Winner = true end manualColor = true end if manualColorDraw then isDraw = true manualColor = true end

-- Regular winner determination logic if manual bolding or coloring is not conclusive if not team1Winner and not team2Winner and not isDraw and (boldWinner or colorWinner or isFBRStyle) then local parenthetical = cleanAggregate:match('%((%d+%-+%d+)%)') local outsideParenthetical = cleanAggregate:match('^(%d+%-+%d+)') if parenthetical then -- Prioritize checking score inside parenthetical score1, score2 = parenthetical:match('(%d+)%-+(%d+)') elseif outsideParenthetical then score1, score2 = outsideParenthetical:match('(%d+)%-+(%d+)') end

if score1 and score2 then score1 = tonumber(score1) score2 = tonumber(score2) if score1 > score2 then team1Winner = true elseif score1 < score2 then team2Winner = true elseif score1

score2 and legs

2 and not disableAwayGoals then -- Apply away goals rule local cleanLeg1 = cleanScore(leg1Score):gsub('[]', ) local cleanLeg2 = cleanScore(leg2Score):gsub('[]', ) local _, team2AwayGoals = cleanLeg1:match('(%d+)%-+(%d+)') local team1AwayGoals = cleanLeg2:match('(%d+)%-+(%d+)')

if team1AwayGoals and team2AwayGoals then team1AwayGoals, team2AwayGoals = tonumber(team1AwayGoals), tonumber(team2AwayGoals) if team1AwayGoals > team2AwayGoals then team1Winner = true elseif team2AwayGoals > team1AwayGoals then team2Winner = true end end end if (colorWinner or isFBRStyle) and legs

0 then isDraw = not team1Winner and not team2Winner end end end

return team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregateend

-- Function to check if any parameter in a given row is non-nil and non-emptylocal function anyParameterPresent(startIndex, step, args) for index = startIndex, startIndex + step - 1 do if args[index] and args[index]:match("^%s*(.-)%s*$") ~= "" then return true end end return falseend

-- Function to add a legend to below the table when |matches_style=FBRlocal function createFBRLegend return mw.html.create('div') :css('font-size', '90%') :css('margin-bottom', '0.5em') :wikitext("Legend: Blue = home team win; Yellow = draw; Red = away team win.")end

-- Main function that processes input and returns the wikitablefunction p.main(frame) local args = require'Module:Arguments'.getArgs(frame,)

-- Check for section transclusion local tsection = frame:getParent.args['transcludesection'] or frame:getParent.args['section'] or local bsection = args['section'] or if tsection ~= and bsection ~= then if tsection ~= bsection then return -- Return an empty string if sections don't match end end

local root = mw.html.create local matchType = (args.type

'WNT' or args.type

'MNT') and 'NT' or (args.type or 'club') -- Set default match type to 'club' local isWNT = args.type

'WNT' -- Track if WNT was set local flagTemplate, flagParam1 local noFlagIcons = false local fillBlanks = args.fill_blanks and (args.fill_blanks

'y' or args.fill_blanks

'yes' or args.fill_blanks

'1' or args.fill_blanks

'true')

-- Process the font size parameter local size if args.font_size then -- Remove trailing '%' if present and convert to number size = tonumber((args.font_size:gsub('%s*%%$', ))) if size then size = math.max(size, 85) -- Ensure size is at least 85 end end

-- Process flag parameter to determine flag template and variant if args.flag and args.flag:find('+') then flagTemplate, flagParam1 = processIcon(args.flag) -- Process flag icons with variants else if args.flag then flagTemplate = args.flag elseif isWNT then flagTemplate = 'fbw' -- Default to for WNT matches elseif matchType

'NT' then flagTemplate = 'fb' -- Default to for NT/MNT matches else flagTemplate = 'fbaicon' -- Default to for club matches end end

if args.flag and (flagTemplate

'n' or flagTemplate

'no' or flagTemplate

'0' or flagTemplate

'false' or flagTemplate

'null' or flagTemplate

'none' or flagTemplate

'noflag') then noFlagIcons = true -- Hide flag icons for club matches if matchType

'NT' then flagTemplate = isWNT and 'fbw' or 'fb' -- Set flagTemplate to "fbw"/"fb", as disabling flags is not allowed for NT flagParam1 = false end end

-- Check if flagTemplate exists and adjust if necessary if matchType

'NT' and (flagTemplate ~= 'fb' and flagTemplate ~= 'fbw') then if not templateExists(flagTemplate) or not templateExists(flagTemplate .. '-rt') then flagTemplate = isWNT and 'fbw' or 'fb' end elseif not noFlagIcons and flagTemplate ~= 'fbaicon' then if not templateExists(flagTemplate) then flagTemplate = 'fbaicon' end end

local legs = (args.legs

'1' or args.legs

'n' or args.legs

'no' or args.legs

'false' or args.legs

'null' or args.legs

'none' or args.legs

'single' or args.legs

'one') and 0 or tonumber(args.legs) or 2 if legs and legs < 0 then legs = 2 end local teamWidth = (tonumber(args['team_width']) and args['team_width'] .. 'px') or '250px' local scoreWidth = (tonumber(args['score_width']) and args['score_width'] .. 'px') or '80px' local boldWinner = not (args.bold_winner

'n' or args.bold_winner

'no' or args.bold_winner

'0' or args.bold_winner

'false' or args.bold_winner

'null') local colorWinner = args.color_winner and (args.color_winner

'y' or args.color_winner

'yes' or args.color_winner

'1' or args.color_winner

'true') local matchesStyle = args.matches_style local isFBRStyle = matchesStyle and matchesStyle:upper

"FBR" local isHA = args.h_a

'y' or args.h_a

'yes' or args.h_a

'1' or args.h_a

'true' local disableAwayGoals = args.away_goals

'n' or args.away_goals

'no' or args.away_goals

'0' or args.away_goals

'false' or args.away_goals

'null'

local tableClass = 'wikitable' local tableStyle = 'text-align: center;' if args.collapsed and (args.collapsed

'y' or args.collapsed

'yes' or args.collapsed

'1' or args.collapsed

'true') then tableClass = 'wikitable mw-collapsible mw-collapsed' tableStyle = 'width: 100%; text-align: center;' end if args.nowrap and (args.nowrap

'y' or args.nowrap

'yes' or args.nowrap

'1' or args.nowrap

'true') then tableStyle = tableStyle .. ' white-space: nowrap;' end if size then tableStyle = tableStyle .. ' font-size: ' .. size .. '%;' end

-- Create the table element local table = root:tag('table') :addClass(tableClass) :cssText(tableStyle) if args.id then table:attr('id', args.id) -- Optional id parameter to allow anchor to table end

-- Add FBR legend if isFBRStyle is true if isFBRStyle and legs

0 then root:node(createFBRLegend) isHA = true end

-- Add a caption to table if the "caption" parameter is passed if args.caption then table:tag('caption'):wikitext(args.caption) end

-- Count number of columns local colCount = 3 + legs

-- Add a title row above column headings if the "title" parameter is passed if args.title then local titleRow = table:tag('tr') titleRow:tag('th') :attr('colspan', colCount) :attr('scope', 'colgroup') :css('text-align', 'center') :wikitext(args.title) end

-- Create the header row with team and score columns local header = table:tag('tr') local defaultTeam1 = isHA and 'Home' or 'Team 1' local defaultTeam2 = isHA and 'Away' or 'Team 2' header:tag('th') :attr('scope', 'col') :css('text-align', 'right') :css('width', teamWidth) :wikitext(args['team1'] or defaultTeam1) header:tag('th') :attr('scope', 'col') :css('width', scoreWidth) :wikitext(args['aggregate'] or legs

0 and 'Score' or expandTemplate(frame, 'Abbrlink',)) header:tag('th') :attr('scope', 'col') :css('text-align', 'left') :css('width', teamWidth) :wikitext(args['team2'] or defaultTeam2)

-- Add columns for each leg if applicable if legs > 0 then for leg = 1, legs do local legHeading

-- Check if "legN" parameter is present if args['leg' .. leg] then legHeading = args['leg' .. leg] else -- Check if "leg_prefix" parameter is present if args.leg_prefix then -- Check if leg_prefix is y, yes, 1, or true if args.leg_prefix

'y' or args.leg_prefix

'yes' or args.leg_prefix

'1' or args.leg_prefix

'true' then legHeading = 'Leg ' .. leg else legHeading = args.leg_prefix .. ' ' .. leg end -- Check if "leg_suffix" parameter is present and does not equal y, yes, 1, or true elseif args.leg_suffix and args.leg_suffix ~= 'y' and args.leg_suffix ~= 'yes' and args.leg_suffix ~= '1' and args.leg_suffix ~= 'true' then legHeading = ordinal(leg) .. ' ' .. args.leg_suffix else legHeading = ordinal(leg) .. ' leg' end end

header:tag('th') :attr('scope', 'col') :css('width', scoreWidth) :wikitext(legHeading) end end

local step = (matchType

'NT' and 3 or (noFlagIcons and 3 or 5)) + legs -- Determine the step size based on the match type and presence of flag icons local i = 1 while anyParameterPresent(i, step, args) do local rowIndex = math.floor((i - 1) / step) + 1 local headingParam = args['heading' .. rowIndex] -- Add a heading above a given row in the table if headingParam then local headingRow = table:tag('tr') headingRow:tag('td') :attr('colspan', colCount) :css('text-align', 'center') :css('background', 'whitesmoke') :wikitext('' .. headingParam .. '') end

local row = table:tag('tr') local team1, aggregateScore, team2 local team1Winner, team2Winner, manualBold, manualColor, isDraw = false, false, false, false, false local leg1Score, leg2Score = false, false local team1Asterick, team2Asterick = false, false

-- Process rows for national team matches if matchType

'NT' then -- Check if team parameter beings with an asterick instead of a country code, indicating a string will be displayed instead of national team flag team1 = args[i] if team1 and team1:match("^%s*%*") then team1 = team1:gsub("^%s*%*", "") team1Asterick = true else team1, team1Variant = processIcon(args[i]) end aggregateScore = args[i+1] team2 = args[i+2] if team2 and team2:match("^%s*%*") then team2 = team2:gsub("^%s*%*", "") team2Asterick = true else team2, team2Variant = processIcon(args[i+2]) end

-- Clean the aggregate score local cleanAggregate = cleanScore(aggregateScore) -- Name the 1st/2nd leg scores for two-legged ties for possibly determining the winner on away goals if legs

2 then leg1Score = args[i+3] leg2Score = args[i+4] end -- Determine the winning team on aggregate team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregateScore = determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner, aggregateScore, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals) -- Add background-color for winning team if set by user local team1Style = team1Winner and ((colorWinner or manualColor) and 'background-color: #CCFFCC;' or ) .. 'text-align: right;' or 'text-align: right;' local team2Style = team2Winner and ((colorWinner or manualColor) and 'background-color: #CCFFCC;' or ) .. 'text-align: left;' or 'text-align: left;' -- Generate text to display for each team local team1Text, team2Text if flagParam1 then -- Check whether youth team flag template with age level is used team1Text = (not team1Asterick and team1 ~= "" and team1 ~= nil) and (expandTemplate(frame, flagTemplate .. '-rt',)) or (team1 ~= nil and team1 or "") team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate,)) or (team2 ~= nil and team2 or "") else -- Use standard national team flag template without age level team1Text = (not team1Asterick and team1 ~= "" and team1 ~= nil) and (expandTemplate(frame, flagTemplate .. '-rt',)) or (team1 ~= nil and team1 or "") team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate,)) or (team2 ~= nil and team2 or "") end -- When set by user, adds blank flags when string is used for a team instead of national team flag template if fillBlanks then if team1Asterick then team1Text = team1Text .. '

' end if team2Asterick then team2Text = ' ' .. team2Text end end -- Create aggregate score cell with conditional styling local aggregateStyle = 'text-align: center;' if isFBRStyle and legs

0 then if team1Winner then aggregateStyle = aggregateStyle .. '; background-color: #BBF3FF;' elseif team2Winner then aggregateStyle = aggregateStyle .. '; background-color: #FFBBBB;' elseif isDraw then aggregateStyle = aggregateStyle .. '; background-color: #FFFFBB;' end elseif isDraw then aggregateStyle = aggregateStyle .. '; background-color: #FFFFBB;' end -- Create rows for aggregate score and team names, bolded if set by user row:tag('td'):cssText(team1Style):wikitext((team1Winner and (boldWinner or manualBold) and team1Text ~= ) and ('' .. team1Text .. '') or team1Text) row:tag('td'):cssText(aggregateStyle):wikitext(aggregateScore) row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold) and team2Text ~= ) and ('' .. team2Text .. '') or team2Text) else -- Process rows for club matches team1 = args[i] if noFlagIcons then -- Remove use of flag icons if set by user aggregateScore = args[i+1] team2 = args[i+2] else team1Icon, team1Variant = processIcon(args[i+1]) aggregateScore = args[i+2] team2 = args[i+3] team2Icon, team2Variant = processIcon(args[i+4]) end -- Clean the aggregate score local cleanAggregate = cleanScore(aggregateScore) -- Name the 1st/2nd leg scores for two-legged ties for possibly determining the winner on away goals if legs

2 then if noFlagIcons then leg1Score = args[i+3] leg2Score = args[i+4] else leg1Score = args[i+5] leg2Score = args[i+6] end end -- Determine the winning team on aggregate team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregateScore = determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner, aggregateScore, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals) -- Add background-color for winning team if set by user local team1Style = team1Winner and ((colorWinner or manualColor) and 'background-color: #CCFFCC;' or ) .. 'text-align: right;' or 'text-align: right;' local team2Style = team2Winner and ((colorWinner or manualColor) and 'background-color: #CCFFCC;' or ) .. 'text-align: left;' or 'text-align: left;' -- Generate text, and flags (if not disabled), to display for each team local team1Text = noFlagIcons and (team1 or ) or ((team1Icon ~= "" and team1Icon ~= nil) and ((team1 or ) .. ' ' .. expandTemplate(frame, flagTemplate,)) or (team1 or )) local team2Text = noFlagIcons and (team2 or ) or ((team2Icon ~= "" and team2Icon ~= nil) and (expandTemplate(frame, flagTemplate,) .. ' ' .. (team2 or )) or (team2 or )) -- When set by user, adds blank flags when country code parameter is left blank if fillBlanks then if not noFlagIcons then if not team1Icon or team1Icon

"" then team1Text = team1Text .. '

' end if not team2Icon or team2Icon

"" then team2Text = '

' .. team2Text end end end -- Create aggregate score cell with conditional styling local aggregateStyle = 'text-align: center;' if isFBRStyle and legs

0 then if team1Winner then aggregateStyle = aggregateStyle .. '; background-color: #BBF3FF;' elseif team2Winner then aggregateStyle = aggregateStyle .. '; background-color: #FFBBBB;' elseif isDraw then aggregateStyle = aggregateStyle .. '; background-color: #FFFFBB;' end elseif isDraw then aggregateStyle = aggregateStyle .. '; background-color: #FFFFBB;' end -- Create rows for aggregate score and team names, bolded if set by user row:tag('td'):cssText(team1Style):wikitext((team1Winner and (boldWinner or manualBold)) and '' .. team1Text .. '' or team1Text) row:tag('td'):cssText(aggregateStyle):wikitext(aggregateScore) row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold)) and '' .. team2Text .. '' or team2Text) end

-- Add columns for each leg score if applicable if legs > 0 then for leg = 1, legs do local legIndex = i + 4 + leg + (matchType

'NT' and -2 or (noFlagIcons and -2 or 0)) local legScore = args[legIndex] if legScore ~= "null" then row:tag('td'):css('text-align', 'center'):wikitext(legScore) end end end

i = i + step end

return tostring(root)end

return p