Modul:Sports series

Revisi sejak 13 Desember 2024 19.34 oleh Py21 (bicara | kontrib) (Penambahan beberapa fitur; Penghapusan fitur type=NT, diganti dengan templat-templat terkait {{fb}})

-- Modul untuk menampilkan tabel agregat hasil pertandingan olahraga
-- Lihat dokumentasi untuk parameter yang digunakan

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('Templat:' .. 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 or last_two_digits ~= 11 then
        return n
    else
        return n
    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('<sup.->.-</sup>', '')

    -- 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)
    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 = "<strong>" .. aggregate .. "</strong>"
    	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) 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 check if any parameter in a given row is non-nil and non-empty
local 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 false
end

-- Function to add a legend to below the table when isFBRStyle is true
local function createFBRLegend()
	return mw.html.create('div')
	:css('font-size', '90%')
	:css('margin-bottom', '0.5em')
	:wikitext("Legenda: Biru = tim kandang menang; Kuning = imbang; Merah = tim tandang menang.")
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("<small>") or str:match("<big>") 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('<sup.->.-</sup>', '')
	
	-- 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") 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("</?%w+[^>]*>", "")
	
	-- 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 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, doWrap)
	if not s then return '', '' end -- Return empty strings if input is nil
	
	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%.]+)')

	-- Format winning notes in brackets
    if doWrap and not (s:match("%(.*%(")) then
        s = mw.ustring.gsub(s, '(%(%d+%s*–%s*%d+)%s+[Pp]%.?[EeSs]?%.?[NnOo]?%.?%)', '%1 [[Adu penalti|a.p]])')
        s = mw.ustring.gsub(s, '%([Aa]%.?[Ee]%.?[Tt]%.?%)', '([[Perpanjangan waktu (sepak bola)|p.w.]])')
    else
        s = mw.ustring.gsub(s, '(%(%d+%s*–%s*%d+)%s+[Pp]%.?[EeSs]?%.?[NnOo]?%.?%)', '<span class="nowrap">%1 [[Adu penalti|a.p]])</span>')
        s = mw.ustring.gsub(s, '%([Aa]%.?[Ee]%.?[Tt]%.?%)', '<span class="nowrap">([[Perpanjangan waktu (sepak bola)|p.w.]])</span>')
    end
    s = mw.ustring.gsub(s, '%([Aa]%.?[Gg]?%.?[Rr]?%.?%)', '([[Peraturan gol tandang|p.g.t]])')

	-- Extract end text
    -- Pattern to match superscript
    local supStart = s:find('<sup')
    -- Pattern to match the unique placeholder
    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

            -- Check if there are unclosed [[ before this position
            local beforeParen = s:sub(1, pos - 1)
            local openLinks = 0
            for linkStart in beforeParen:gmatch('%[%[') do
                openLinks = openLinks + 1
            end
            for linkEnd in beforeParen:gmatch('%]%]') do
                openLinks = openLinks - 1
            end

            -- If there are no unclosed links, this is a valid parenthesis
            if openLinks == 0 then
                return pos
            end

            pos = pos + 1
        end
        return nil
    end

    -- Find the first parenthesis outside of wikilinks
    local parenStart = find_paren_outside_wikilinks(s)

    -- Store all start positions in a table
    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 startPos
    if #startPositions > 0 then
        startPos = math.min(unpack(startPositions))  -- Find the minimum start position
    end

    if startPos then
        -- Find the preceding whitespace
        local wsStart = s:find("%s*$", 1, startPos)

        -- Extract the score and endText
        local scoreMatch = s:sub(1, wsStart and wsStart - 1 or startPos - 1)
        local endText = s:sub(wsStart or startPos)

        return scoreMatch, endText
    else
        -- If no match found, return the entire score
        return s, ""
    end
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("</?%w+[^>]*>", "")
            str = str:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')
            str = str:gsub("%[%[[Ff]ile:[^%]]+%]%]", "")
            str = str:gsub("%[%[[Bb]erkas:[^%]]+%]%]", "")
            str = str:gsub("%[%[[Ii]mage:[^%]]+%]%]", "")
            str = str:gsub("%[%[.-%]%]", replaceLink)
            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 = "[[#" .. team2 .. " v " .. team1 .. "|" .. linkScore .. "]]"
            else
                link = "[[#" .. team1 .. " v " .. team2 .. "|" .. linkScore .. "]]"
            end
            return link .. score:sub(#linkScore + 1)
        end
    end

    return score
end

-- Main function that processes input and returns the wikitable
function p.main(frame)
    local args = require'Modul:Arguments'.getArgs(frame, {trim = true})
    
    -- 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
    
    -- Helper function for boolean checks
    local function isTrue(value)
        if not value then return false end
        local lowerValue = value:lower()
        return lowerValue == 'y' or lowerValue == 'yes' or lowerValue == '1' or lowerValue == 'true'
    end

    -- Helper function for negative boolean checks
    local function isFalse(value)
        if not value then return false end
        local lowerValue = value:lower()
        return lowerValue == 'n' or lowerValue == 'no' or lowerValue == '0' or lowerValue == 'false' or lowerValue == 'null' or lowerValue == 'none'
    end

    local root = mw.html.create()
    local noFlagIcons = false
    local fillBlanks = isTrue(args.fill_blanks)
    local generateLinks = isTrue(args.generate_links)
    local solidCell = isTrue(args.solid_cell) or args.solid_cell == 'grey' or args.solid_cell == 'gray' or args.solid_cell == 'abu-abu'
    local baselink = frame:getParent():getTitle()
    if mw.title.getCurrentTitle().text == baselink then	baselink = '' end
    
    -- 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(size, 85)  -- Ensure font is at least 85
    	end
    end
	
    -- Process flag parameter to determine flag template and variant
    local flagTemplate = args.flag or 'fbaicon'  -- Default to {{fbaicon}}
    local noFlagIcons = isFalse(args.flag)
    local flagSize = args.flag_size
    if flagSize and not flagSize:match('px$') then
        flagSize = flagSize .. 'px'
    end
    
     -- Check if flagTemplate exists and adjust if necessary
	if not noFlagIcons and flagTemplate ~= 'fbaicon' then
        if not templateExists(flagTemplate) then
	        flagTemplate = 'flagicon'
        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 = (isFalse(args.legs) or args.legs == '1') 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 isFalse(args.bold_winner)
    local colorWinner = isTrue(args.color_winner)
    local matchesStyle = args.matches_style
    local isFBRStyle = matchesStyle and matchesStyle:upper() == "FBR"
    local isHA = isTrue(args.h_a)
    local disableAwayGoals = isFalse(args.away_goals)
	local disableSmallText = isFalse(args.small_text)
	local noWrap = isTrue(args.nowrap)
	local disableNoWrap = isFalse(args.nowrap)
	local doWrap = not noWrap and not disableNoWrap
	
    local tableClass = 'wikitable'
    local tableStyle = 'text-align: center;'
    if isTrue(args.collapsed) then
        tableClass = 'wikitable mw-collapsible mw-collapsed'
        tableStyle = tableStyle .. ' width: 100%;'
    end
    if isTrue(args.center_table) then
    	tableStyle = tableStyle .. ' margin-left: auto; margin-right: auto; border: none;'
    end
    if noWrap then
        tableStyle = tableStyle .. ' white-space: nowrap;'
    end
    if fontSize then
    	tableStyle = tableStyle .. ' 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
    
    -- 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')
            :wikitext(args.title)
    end

    -- Create the header row with team and score columns
    local header = table:tag('tr')
    if doWrap then
    	header:css('white-space', 'nowrap')
    end
    local defaultTeam1 = isHA and 'Tim kandang' or 'Tim 1'
    local defaultTeam2 = isHA and 'Tim tandang' or 'Tim 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 'Hasil' or 'Agregat')
    header:tag('th')
    	:attr('scope', 'col')
    	:css('text-align', 'left')
    	:css('width', teamWidth)
    	:wikitext(args['team1'] 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 = isTrue(args.leg_prefix) and ('Leg ' .. leg) or (args.leg_prefix .. ' ' .. leg)
                elseif args.leg_suffix and not isTrue(args.leg_suffix) then
                    legHeading = args.leg_suffix .. ' ' .. ordinal(leg)
                else
                    legHeading = 'Laga ' .. ordinal(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 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')
                headingRow:tag('td')
                    :attr('colspan', colCount)
                    :css('background', 'whitesmoke')
                    :wikitext('<strong>' .. headingParam .. '</strong>')
            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)
            -- Format and rewrite anchor links for aggregate score
            aggregateScore, aggregateEndText = format_and_extract_score(aggregateScore, doWrap)
            if generateLinks and legs == 0 then
                aggregateScore = cleanAndGenerateLinks(team1, team2, aggregateScore, false)
            end

            -- Determine the winning team on aggregate
            local skipDetermineWinner = legs == 0 and aggregateScore ~= '' and checkSmallText(aggregateScore)

            if not skipDetermineWinner then
                team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregateScore = determineWinner(cleanAggregate, team1, team2, boldWinner, colorWinner, aggregateScore, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals)
            end

            -- Add background-color for winning team if set by user
            local team1Style = 'text-align: right;'
            local team2Style = 'text-align: left;'
            if team1Winner and (colorWinner or manualColor) then
                team1Style = team1Style .. ' background-color: #CCFFCC;'
            end
            if team2Winner and (colorWinner or manualColor) then
                team2Style = team2Style .. ' background-color: #CCFFCC;'
            end
        
            -- 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 '') .. '&nbsp;' .. 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)) .. '&nbsp;' .. (team2 or '')) or (team2 or ''))

            -- When set by user, adds blank flag placeholder next to team names
            if fillBlanks and not noFlagIcons then
                if not team1Icon or team1Icon == "" then
                    team1Text = team1Text .. '&nbsp;<span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span>'
                end
                if not team2Icon or team2Icon == "" then
                    team2Text = '<span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span>&nbsp;' .. team2Text
                end
            end

            local aggregateContent
            if not disableSmallText and skipDetermineWinner then
                aggregateContent = '<span style="font-size:85%;">' .. aggregateScore .. '</span>' .. aggregateEndText
            else
                aggregateContent = aggregateScore .. aggregateEndText
            end

            -- Create aggregate score cell with conditional styling
            local aggregateStyle = ''
            if doWrap and not (cleanAggregate:match("%(.*%(")) then
                aggregateStyle = 'white-space: nowrap;'
            end
            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 ('<strong>' .. team1Text .. '</strong>') or team1Text)
            row:tag('td'):cssText(aggregateStyle ~= '' and aggregateStyle or nil):wikitext(aggregateContent)
            row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold) and team2Text ~= '') and ('<strong>' .. team2Text .. '</strong>') 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]
                    if legScore ~= "nil" then
                        if legScore == "null" then
                            if solidCell then
                                row:tag('td'):css('background', '#BBBBBB')
                            else
                                legScore = '—'
                            end
                        end

                        if legScore ~= "null" or not solidCell then
                            -- Format and rewrite anchor links for leg scores
                            local cleanLeg = cleanScore(legScore)
                            legScore, legEndText = format_and_extract_score(legScore, doWrap)
                            if generateLinks then
                                if leg == 1 then
                                    legScore = cleanAndGenerateLinks(team1, team2, legScore, false)
                                elseif leg == 2 then
                                    legScore = cleanAndGenerateLinks(team1, team2, legScore, true)
                                end
                            end
                            local legContent
                            if not disableSmallText and legScore ~= '' and checkSmallText(legScore) then
                                legContent = '<span style="font-size:85%;">' .. legScore .. '</span>' .. legEndText
                            else
                                legContent = legScore .. legEndText
                            end
                            local legStyle = ''
                            if doWrap and not (cleanLeg:match("%(.*%(")) then
                                legStyle = 'white-space: nowrap;'
                            end
                            -- Write cells for legs
                            row:tag('td'):cssText(legStyle ~= '' and legStyle or nil):wikitext(legContent)
                        end
                    end
                end
            end
        end

        i = i + step
    end

    local tableCode = tostring(root)

    -- Rewrite anchor links for the entire table
    if baselink ~= '' then
        tableCode = mw.ustring.gsub(tableCode, '(%[%[)(#[^%[%]]*%|)', '%1' .. baselink .. '%2')
    end

    return tableCode
end

return p