Module:Sports series

-- 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 parameters

local function expandTemplate(frame, templateName, params)

return frame:expandTemplate{ title = templateName, args = params }

end

-- Function to check the existence of flagTemplate

local function templateExists(templateName)

local title = mw.title.new('Template:' .. templateName)

return title and title.exists

end

-- Function to process country codes and variants, dividing parameters by the "+" sign

local 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

end

end

-- Function to determine the correct ordinal suffix for a given number for the heading

local 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'

end

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

-- Function to clean and process the aggregate score for comparison

local 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

-- Replace wiki links

score = score:gsub("%[%[.-%]%]", replaceLink)

-- Remove MediaWiki's unique placeholder sequences for references

score = score:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')

-- 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, team1, team2, boldWinner, colorWinner, aggregate, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals, skipAutoWinner, aggFormat)

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 aggFormat == 'bold' or aggFormat == 'both' then

aggregate = "" .. aggregate .. ""

end

manualColorDraw = aggFormat == 'italic' or aggFormat == 'both'

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 not skipAutoWinner 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, aggregate

end

-- Function to process score bold/italic formatting

function processScore(s)

if not s or s == "" then

return "", false

end

local scoreFormat = false

-- Check for 5+ apostrophes (both bold and italic)

if s:match("'+") then

scoreFormat = "both"

s = s:gsub("''+", "")

return s, scoreFormat

end

-- Check for 3+ apostrophes (bold)

if s:match("'''+") then

scoreFormat = "bold"

s = s:gsub("''+", "")

return s, scoreFormat

end

-- Check for 2 apostrophes (italic)

if s:match("''") then

scoreFormat = "italic"

s = s:gsub("''+", "")

return s, scoreFormat

end

-- If no matches found, return original string and false

return s, scoreFormat

end

-- Function to check if any parameter in a given row is non-nil and non-empty

local function anyParameterPresent(startIndex, step, args)

-- Check regular parameters

for index = startIndex, startIndex + step - 1 do

if args[index] and args[index]:match("^%s*(.-)%s*$") ~= "" then

return true

end

end

-- Check aggregate note

local rowIndex = math.floor((startIndex - 1) / step) + 1

local aggNote = args['note_agg_' .. rowIndex]

if aggNote and aggNote:match("^%s*(.-)%s*$") ~= "" then

return true

end

-- Check leg notes

local numLegs = step - (noFlagIcons and 3 or 5) -- Calculate number of legs

for leg = 1, numLegs do

local legNote = args['note_leg' .. leg .. '_' .. rowIndex]

if legNote and legNote:match("^%s*(.-)%s*$") ~= "" then

return true

end

end

return false

end

-- Function to check whether to reduce font size for upcoming matches

local function checkSmallText(str)

-- Check for font size or small/big HTML tags

if str:match("font%s?%-?size") or str:match("") or str:match("") then

return false

end

-- Remove MediaWiki's unique placeholder sequences for references

str = str:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')

-- Remove superscript tags and their contents

str = str:gsub('.-', '')

-- Check for walkover-related strings (never shown in small text)

if str:lower():match("walkover") or str:lower():match("w%.o%.") or str:lower():match("w/o") or str:lower():match("bye") then

return false

end

-- Replace wiki links with their display text or link text

str = str:gsub("%[%[.-%]%]", replaceLink)

-- Remove all text inside parentheses

str = str:gsub("%b()", "")

-- Exit if string contains only en/em dash

if str == "—" or str == "–" then

return false

end

-- Convert dashes to a standard format

str = str:gsub('[–—―‒−]+', '-')

-- Remove opening and closing HTML tags

str = str:gsub("]*>", "")

-- Remove apostrophes

str = str:gsub("''+", "")

-- Remove all whitespace

str = str:gsub("%s+", "")

-- Check if the string matches only a scoreline

if str:match("^%d+-%d+$") then

return false

else

return true

end

end

-- Function to rewrite anchor links in a string

local function rewriteAnchorLinks(str, baselink, currentPageTitle)

if not str or str == "" then

return str

end

-- Add the base link to anchor links when the module is transcluded on another page

if baselink ~= '' then

str = mw.ustring.gsub(str, '(%[%[)(#[^%[%]]*%|)', '%1' .. baselink .. '%2')

end

-- Remove redundant page references when viewing anchors on the current page

if currentPageTitle and currentPageTitle ~= "" then

local escapedTitle = currentPageTitle:gsub("([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")

local titlePattern = '%[%[' .. escapedTitle .. '(#[^%[%]]*%|)'

str = mw.ustring.gsub(str, titlePattern, '[[%1')

end

return str

end

-- Function to format the dashes and winning notes for aggregate/leg score parameters, and divide the score from references/notes/superscripts

local function format_and_extract_score(s, addSpan)

if not s then return , end -- Return empty strings if input is nil

-- Handle walkovers

if s:match("^%s*[Ww]%s*[/.]%s*[Oo]%s*%.?%s*$") then

return "w/o", ""

end

local function format_dash(pattern)

s = mw.ustring.gsub(s, '^' .. pattern, '%1–%2')

s = mw.ustring.gsub(s, '%(' .. pattern, '(%1–%2')

end

-- Format dashes

format_dash('%s*([%d%.]+)%s*[–—―‒−%-]%s*([%d%.]+)')

format_dash('%s*([%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)')

format_dash('%s*(%[%^%[%*%|[%d%.]+)%s*[–—―‒−%-]%s*([%d%.]+)')

format_dash('%s*(%[[^%[%]%s]*%s+[%d%.]+)%s*[–—―‒−%-]%s*([%d%.]+)')

format_dash('%s*(%[%^%[%*%|[%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)')

format_dash('%s*(%[[^%[%]%s]*%s+[%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)')

-- Extract end text

local supStart = s:find('

local placeholderStart = s:find('\127%\'"`UNIQ')

-- Function to find the first parenthesis outside of wikilinks

local function find_paren_outside_wikilinks(s)

local pos = 1

while true do

pos = s:find('%(', pos)

if not pos then break end

local beforeParen = s:sub(1, pos - 1)

local openLinks = select(2, beforeParen:gsub('%[%[', )) - select(2, beforeParen:gsub('%]%]', ))

if openLinks == 0 then

return pos

end

pos = pos + 1

end

return nil

end

local parenStart = find_paren_outside_wikilinks(s)

local startPositions = {}

if supStart then table.insert(startPositions, supStart) end

if placeholderStart then table.insert(startPositions, placeholderStart) end

if parenStart then table.insert(startPositions, parenStart) end

local scoreMatch, endText

if #startPositions > 0 then

local startPos = math.min(unpack(startPositions))

-- Find the last non-whitespace character before startPos

local scoreEnd = s:sub(1, startPos - 1):match(".*%S") or ""

scoreEnd = #scoreEnd

-- Extract the score and endText

scoreMatch = s:sub(1, scoreEnd)

endText = s:sub(scoreEnd + 1)

else

-- If no match found, return the entire score

scoreMatch = s

endText = ""

end

-- Format winning notes in brackets (only if endText is not empty)

if endText ~= "" then

if addSpan then

endText = mw.ustring.gsub(endText, '(%(%d+%s*–%s*%d+)%s*[Pp]%.?[EeSs]?%.?[NnOo]?%.?%)', '%1 p)')

endText = mw.ustring.gsub(endText, '%([Aa]%.?[Ee]%.?[Tt]%.?%)', '(a.e.t.)')

else

endText = mw.ustring.gsub(endText, '(%(%d+%s*–%s*%d+)%s*[Pp]%.?[EeSs]?%.?[NnOo]?%.?%)', '%1 p)')

endText = mw.ustring.gsub(endText, '%([Aa]%.?[Ee]%.?[Tt]%.?%)', '(a.e.t.)')

end

endText = mw.ustring.gsub(endText, '%([Aa]%.?[Gg]?%.?[Rr]?%.?%)', '(a)')

end

return scoreMatch, endText

end

-- Function to clean team names and generate links

local function cleanAndGenerateLinks(team1, team2, score, isSecondLeg)

local function cleanTeam(str, defaultName)

if str and str ~= "" then

str = str:gsub('.-', '')

str = str:gsub("]*>", "")

str = str:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')

str = str:gsub("%[%Ff]ile:[^%+%]%]", "")

str = str:gsub("%[%Ii]mage:[^%+%]%]", "")

str = str:gsub("%[%[.-%]%]", replaceLink)

str = str:gsub("%s* %s*", "")

str = str:match("^%s*(.-)%s*$") -- Remove leading and trailing whitespace

return str ~= "" and str or defaultName

end

return defaultName

end

team1 = cleanTeam(team1, "Team 1")

team2 = cleanTeam(team2, "Team 2")

if score and score:match("%S") then

local linkScore = score

if score:find('%[') then

linkScore = score:match('^([%d%.]+–[%d%.]+)')

if not linkScore then

return score

end

end

if linkScore then

local link

if isSecondLeg then

link = "" .. linkScore .. ""

else

link = "" .. linkScore .. ""

end

return link .. score:sub(#linkScore + 1)

end

end

return score

end

-- Function to process notes for aggregate and leg scores

local function processNote(frame, notes, noteKey, noteText, endText, rowIndex, rand_val, noteGroup, baselink, currentPageTitle)

if not noteText then return endText, notes end

if noteText:match("^%s*

return noteText .. endText, notes

end

local function createInlineNote(name)

return frame:extensionTag{

name = 'ref',

args = {

name = name,

group = noteGroup

}

}

end

-- Check if noteText is a reference to another note

local referenced_note = noteText:match("^(agg_%d+)$") or noteText:match("^(leg%d+_%d+)$")

if referenced_note then

local referenced_note_id = '"table_note_' .. referenced_note .. '_' .. rand_val .. '"'

return endText .. createInlineNote(referenced_note_id), notes

end

-- Process anchor links in noteText before storing

if noteText:find("%[%[") then

noteText = rewriteAnchorLinks(noteText, baselink, currentPageTitle)

end

local note_id = '"table_note_' .. noteKey .. '_' .. rowIndex .. '_' .. rand_val .. '"'

if not notes[note_id] then

notes[note_id] = noteText

end

return endText .. createInlineNote(note_id), notes

end

-- Function to generate the footer if necessary

local function createFooter(frame, notes, noteGroup, isFBRStyle, displayNotes, externalNotes, legs)

local needFooter = (isFBRStyle and legs == 0) or displayNotes or (next(notes) ~= nil)

if not needFooter then

return '' -- Return an empty string if no footer is needed

end

local divContent = mw.html.create('div')

:addClass('sports-series-notes')

if isFBRStyle and legs == 0 then

divContent:wikitext("Legend: Blue = home team win; Yellow = draw; Red = away team win.")

end

if (next(notes) ~= nil and not externalNotes) or displayNotes then

divContent:wikitext((isFBRStyle and legs == 0) and "
Notes:" or "Notes:")

end

local footer = tostring(divContent)

if next(notes) ~= nil or displayNotes then

local noteDefinitions = {}

for noteId, noteText in pairs(notes) do

if type(noteId) == 'string' and noteId:match('^"table_note') then

table.insert(noteDefinitions, frame:extensionTag{

name = 'ref',

args = {

name = noteId,

group = noteGroup

},

content = noteText

})

end

end

if externalNotes then

local hiddenRefs = mw.html.create('span')

:addClass('sports-series-hidden')

:wikitext(table.concat(noteDefinitions))

if isFBRStyle and legs == 0 then

footer = footer .. tostring(hiddenRefs)

else

footer = tostring(hiddenRefs)

end

else

local reflistArgs = {

refs = table.concat(noteDefinitions),

group = noteGroup

}

footer = footer .. frame:expandTemplate{

title = 'reflist',

args = reflistArgs

}

end

end

return footer

end

-- Main function that processes input and returns the wikitable

function p.main(frame)

local args = require('Module:Arguments').getArgs(frame, {trim = true})

local yesno = require('Module:Yesno')

-- 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 templatestyles = frame:extensionTag{

name = 'templatestyles',

args = { src = 'Screen reader-only/styles.css' }

} .. frame:extensionTag{

name = 'templatestyles',

args = { src = 'Module:Sports series/styles.css' }

}

root:wikitext(templatestyles)

local flagYesno = yesno(args.flag)

local showFlags = flagYesno ~= false

local noFlagIcons = not showFlags

local fillBlanks = yesno(args.fill_blanks)

local generateLinks = yesno(args.generate_links)

local solidCell = yesno(args.solid_cell) or args.solid_cell == 'grey' or args.solid_cell == 'gray'

local baselink = frame:getParent():getTitle()

local currentPageTitle = mw.title.getCurrentTitle().fullText

if currentPageTitle == baselink then baselink = '' end

local notes = {}

local noteGroup = args.note_group or 'lower-alpha'

local noteListValue = yesno(args.note_list)

local displayNotes = noteListValue == true

local externalNotes = noteListValue == false

math.randomseed(os.clock() * 10^8) -- Initialize random number generator

local rand_val = math.random()

-- Process the font size parameter

local fontSize

if args.font_size then

-- Remove trailing '%' if present and convert to number

fontSize = tonumber((args.font_size:gsub('%s*%%$', '')))

if fontSize then

fontSize = math.max(fontSize, 85) -- Ensure font size is at least 85

end

end

-- Process flag parameter to determine flag template and variant

local flagTemplate = 'fbaicon'

local flagSize = args.flag_size

if showFlags then

if args.flag and args.flag ~= '' and not flagYesno then

flagTemplate = args.flag:gsub('^Template:', '')

if not templateExists(flagTemplate) then

flagTemplate = 'flag icon'

end

end

if flagSize and not flagSize:match('px$') then

flagSize = flagSize .. 'px'

end

end

-- Determine whether line should be displayed

local showCountry = args.show_country

local function shouldShowRow(team1Icon, team2Icon)

if not showCountry or noFlagIcons then

return true

end

return team1Icon == showCountry or team2Icon == showCountry

end

local legs = 2

if args.legs then

if yesno(args.legs) == false or args.legs == '1' then

legs = 0

else

legs = tonumber(args.legs) and math.max(tonumber(args.legs), 2) or 2

end

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 = args.bold_winner == nil or yesno(args.bold_winner, true)

local colorWinner = yesno(args.color_winner)

local matchesStyle = args.matches_style

local isFBRStyle = matchesStyle and matchesStyle:upper() == "FBR"

local isHA = yesno(args.h_a) or (isFBRStyle and legs == 0)

local disableAwayGoals = yesno(args.away_goals) == false

local disableSmallText = yesno(args.small_text) == false

local noWrapValue = yesno(args.nowrap)

local noWrap = noWrapValue == true

local disableNoWrap = noWrapValue == false

local aggFormat

local tableClass = 'wikitable sports-series'

local doCollapsed = yesno(args.collapsed)

if doCollapsed then

tableClass = tableClass .. ' mw-collapsible mw-collapsed'

end

if yesno(args.center_table) and not doCollapsed then

tableClass = tableClass .. ' center-table'

end

if fontSize then

table:css('font-size', fontSize .. '%')

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

if noWrap then

table:attr('data-nowrap', 'y')

elseif not disableNoWrap then

table:attr('data-nowrap', 'n')

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'):addClass('title-row')

titleRow:tag('th')

:attr('colspan', colCount)

:attr('scope', 'colgroup')

:wikitext(args.title)

end

-- Create the header row with team and score columns

local header = table:tag('tr')

local defaultTeam1 = isHA and 'Home team' or 'Team 1'

local defaultTeam2 = isHA and 'Away team' or 'Team 2'

header:tag('th'):attr('scope', 'col'):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 'Agg. Tooltip Aggregate score')

header:tag('th'):attr('scope', 'col'):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 = args['leg' .. leg]

-- Check if "legN" parameter is present

if not legHeading then

if args.leg_prefix then

legHeading = yesno(args.leg_prefix) and ('Leg ' .. leg) or (args.leg_prefix .. ' ' .. leg)

elseif args.leg_suffix and not yesno(args.leg_suffix) 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 = (noFlagIcons and 3 or 5) + legs -- Determine the step size based on the presence of flag icons

local i = 1

while anyParameterPresent(i, step, args) do

local rowIndex = math.floor((i - 1) / step) + 1

local aggNote = args['note_agg_' .. rowIndex]

local headingParam = args['heading' .. rowIndex]

local team1, team2, aggregateScore, aggregateEndText, legEndText, team1Icon, team2Icon, team1Variant, team2Variant

local team1Winner, team2Winner, manualBold, manualColor, isDraw = false, false, false, false, false

local leg1Score, leg2Score = false, false

-- Process rows from input

team1 = args[i]

if noFlagIcons then

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

-- Check if the line should be shown based on both teams

if shouldShowRow(team1Icon, team2Icon) then

-- Add a heading above a given row in the table

if headingParam and not showCountry then

local headingRow = table:tag('tr'):addClass('heading-row')

headingRow:tag('td')

:attr('colspan', colCount)

:wikitext('' .. headingParam .. '')

end

local row = table:tag('tr')

-- Name the 1st/2nd leg scores for two-legged ties

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

-- Clean the aggregate score

local cleanAggregate = cleanScore(aggregateScore)

aggregateScore, aggFormat = processScore(aggregateScore)

-- Format anchor links for aggregate score

local aggParen = cleanAggregate:match("%(.*%(")

local aggSpan = (disableNoWrap or (not noWrap and not disableNoWrap and aggParen))

aggregateScore, aggregateEndText = format_and_extract_score(aggregateScore, aggSpan)

-- Apply link rewriting to note text before creating the note

aggregateEndText, notes = processNote(frame, notes, 'agg', aggNote, aggregateEndText, rowIndex, rand_val, noteGroup, baselink, currentPageTitle)

if generateLinks and legs == 0 then

-- Skip link generation for "Bye" entries

local isBye = aggregateScore:match("^%s*[Bb][Yy][Ee]%s*$") or aggregateScore:match("|[Bb][Yy][Ee]%]%]")

if not isBye then

aggregateScore = cleanAndGenerateLinks(team1, team2, aggregateScore, false)

end

end

local skipAutoWinner = legs == 0 and aggregateScore ~= '' and checkSmallText(aggregateScore)

-- Determine the winning team on aggregate

team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregateScore = determineWinner(cleanAggregate, team1, team2, boldWinner, colorWinner, aggregateScore, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals, skipAutoWinner, aggFormat)

-- Function to create flag template parameters

local function getFlagParams(icon, variant)

local params = {icon, variant = variant}

if flagSize then

params.size = flagSize

end

return params

end

-- Generate text to display for each team

local team1Text = noFlagIcons and (team1 or ) or ((team1Icon ~= "" and team1Icon ~= nil) and ((team1 or ) .. ' ' .. expandTemplate(frame, flagTemplate, getFlagParams(team1Icon, team1Variant))) or (team1 or ''))

local team2Text = noFlagIcons and (team2 or ) or ((team2Icon ~= "" and team2Icon ~= nil) and (expandTemplate(frame, flagTemplate, getFlagParams(team2Icon, team2Variant)) .. ' ' .. (team2 or )) or (team2 or ''))

-- When set by user, adds blank flag placeholder next to team names

if fillBlanks and showFlags then

local flagDimensions = flagSize or "25x17px"

local placeholderFlag = string.format('File:Flag placeholder.svg', flagDimensions)

if not team1Icon or team1Icon == "" then

team1Text = team1Text .. ' ' .. placeholderFlag

end

if not team2Icon or team2Icon == "" then

team2Text = placeholderFlag .. ' ' .. team2Text

end

end

local aggregateContent

if not disableSmallText and skipAutoWinner then

aggregateContent = '' .. aggregateScore .. '' .. aggregateEndText

else

aggregateContent = aggregateScore .. aggregateEndText

end

-- Create aggregate score cell with conditional styling

local aggregateClass = ''

if isFBRStyle and legs == 0 then

if team1Winner then

aggregateClass = 'fbr-home-win'

elseif team2Winner then

aggregateClass = 'fbr-away-win'

elseif isDraw then

aggregateClass = 'draw'

end

elseif isDraw then

aggregateClass = 'draw'

end

if not disableNoWrap and (not noWrap and aggParen) then

aggregateClass = (aggregateClass ~= and aggregateClass .. ' ' or ) .. 'allow-wrap'

end

-- Create rows for aggregate score and team names, bolded if set by user

row:tag('td'):addClass(team1Winner and (colorWinner or manualColor) and 'winner' or nil):wikitext((team1Winner and (boldWinner or manualBold) and team1Text ~= '') and ('' .. team1Text .. '') or team1Text)

row:tag('td'):addClass(aggregateClass ~= '' and aggregateClass or nil):wikitext(aggregateContent)

row:tag('td'):addClass(team2Winner and (colorWinner or manualColor) and 'winner' or nil):wikitext((team2Winner and (boldWinner or manualBold) and team2Text ~= '') and ('' .. team2Text .. '') or team2Text)

-- Add columns for each leg score if applicable

if legs > 0 then

for leg = 1, legs do

local legIndex = i + 4 + leg + (noFlagIcons and -2 or 0)

local legScore = args[legIndex]

local legNote = args['note_leg' .. leg .. '_' .. rowIndex]

if legScore ~= "nil" then

if legScore == "null" then

if solidCell then

row:tag('td'):addClass('solid-cell')

else

legScore = '—'

end

end

if legScore ~= "null" then

-- Format anchor links for leg scores

local cleanLeg = cleanScore(legScore)

local legFormat

legScore, legFormat = processScore(legScore)

local legParen = cleanLeg:match("%(.*%(")

local legSpan = (disableNoWrap or (not noWrap and not disableNoWrap and legParen))

legScore, legEndText = format_and_extract_score(legScore, legSpan)

-- Apply link rewriting to note text before creating the note

legEndText, notes = processNote(frame, notes, 'leg' .. leg, legNote, legEndText, rowIndex, rand_val, noteGroup, baselink, currentPageTitle)

if generateLinks and not aggregateContent:lower():find("bye") then

if leg == 1 then

legScore = cleanAndGenerateLinks(team1, team2, legScore, false)

elseif leg == 2 then

legScore = cleanAndGenerateLinks(team1, team2, legScore, true)

end

end

if legFormat == 'bold' or legFormat == 'both' then legScore = '' .. legScore .. '' end

if legFormat == 'italic' or legFormat == 'both' then legScore = '' .. legScore .. '' end

local legContent

if not disableSmallText and legScore ~= '' and checkSmallText(legScore) then

legContent = '' .. legScore .. '' .. legEndText

else

legContent = legScore .. legEndText

end

local legClass = ''

if not disableNoWrap and (not noWrap and legParen) then

legClass = 'allow-wrap'

end

-- Write cells for legs

row:tag('td'):addClass(legClass ~= '' and legClass or nil):wikitext(legContent)

end

end

end

end

end

i = i + step

end

-- Generate footer text

local footerText = createFooter(frame, notes, noteGroup, isFBRStyle, displayNotes, externalNotes, legs)

root:wikitext(footerText)

local tableCode = tostring(root)

-- Rewrite anchor links for the entire table (except for notes which were handled separately)

tableCode = rewriteAnchorLinks(tableCode, baselink, currentPageTitle)

-- Return the completed table with rewritten links

return tableCode

end

return p