Module:Sports series

Source: Wikipedia, the free encyclopedia.

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

local function templateExists(templateName)
    local title = mw.title.new('Template:' .. templateName)
    return title and title.exists
end

-- Function to process country codes and variants OR youth team flag templates and age level, 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 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

    -- 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('<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(cleanScore, matchType, team1, team2, boldWinner, colorWinner)
    local team1Winner, team2Winner = false, false
    local score1, score2
    local manualBold = false
    local manualColor = 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
    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 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

    -- Additional check for empty team names in NT matches
    if matchType == 'NT' and ((not team1 or team1:match("^%s*$")) or (not team2 or team2:match("^%s*$"))) then
        -- Skip further processing if either team name is effectively empty
        return team1, team2, team1Winner, team2Winner, manualBold, manualColor
    end

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

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

    return team1, team2, team1Winner, team2Winner, manualBold, manualColor
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

-- Main function that processes input and returns the wikitable
function p.main(frame)
    local args = require'Module:Arguments'.getArgs(frame, {trim = true})
    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 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 {{fbw}} for WNT matches
        elseif matchType == 'NT' then
            flagTemplate = 'fb'  -- Default to {{fb}} for NT/MNT matches
        else
            flagTemplate = 'fbaicon'  -- Default to {{fbaicon}} 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
    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 and (args.bold_winner == 'y' or args.bold_winner == 'yes' or args.bold_winner == '1' or args.bold_winner == 'true')
    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 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

    -- 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 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)
            :css('text-align', 'center')
            :wikitext(args.title)
    end

    -- Create the header row with team and score columns
    local header = table:tag('tr')
    local defaultTeam1 = (args.h_a == 'y' or args.h_a == 'yes' or args.h_a == '1' or args.h_a == 'true') and 'Home' or 'Team 1'
    local defaultTeam2 = (args.h_a == 'y' or args.h_a == 'yes' or args.h_a == '1' or args.h_a == 'true') and 'Away' or 'Team 2'
    header:tag('th'):css('text-align', 'right'):css('width', teamWidth):wikitext(args['team1'] or defaultTeam1)
    header:tag('th'):css('width', scoreWidth):wikitext(args['aggregate'] or legs == 0 and 'Score' or expandTemplate(frame, 'Abbrlink', {'Agg.', 'Aggregate score'}))
    header:tag('th'):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'):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('<strong>' .. headingParam .. '</strong>')
        end

        local row = table:tag('tr')
        local team1, aggregateScore, team2
        local team1Winner, team2Winner, manualBold, manualColor = false, false, 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)
            -- Determine the winning team on aggregate
            team1, team2, team1Winner, team2Winner, manualBold, manualColor = determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner)
            -- 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', {flagParam1, team1, variant = team1Variant})) or team1
                team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate, {flagParam1, team2, variant = team2Variant})) or team2
            else  -- Use standard national team flag template without age level
                team1Text = (not team1Asterick and team1 ~= "" and team1 ~= nil) and (expandTemplate(frame, flagTemplate .. '-rt', {team1, variant = team1Variant})) or team1
                team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate, {team2, variant = team2Variant})) or team2
            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 .. ' <span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span>'
                end
                if team2Asterick then
                    team2Text = '<span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span> ' .. team2Text
                end
            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 '<strong>' .. team1Text .. '</strong>' or team1Text)
            row:tag('td'):css('text-align', 'center'):css('width', scoreWidth):wikitext(aggregateScore)
            row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold)) and '<strong>' .. team2Text .. '</strong>' 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)
            -- Determine the winning team on aggregate
            team1, team2, team1Winner, team2Winner, manualBold, manualColor = determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner)
            -- 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 (team1Icon ~= "" and team1Icon ~= nil) and (team1 .. ' ' .. expandTemplate(frame, flagTemplate, {team1Icon, variant = team1Variant})) or team1
            local team2Text = noFlagIcons and team2 or (team2Icon ~= "" and team2Icon ~= nil) and (expandTemplate(frame, flagTemplate, {team2Icon, variant = team2Variant}) .. ' ' .. team2) or team2
            -- 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 .. ' <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> ' .. team2Text
                    end
                end
            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 '<strong>' .. team1Text .. '</strong>' or team1Text)
            row:tag('td'):css('text-align', 'center'):css('width', scoreWidth):wikitext(aggregateScore)
            row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold)) and '<strong>' .. team2Text .. '</strong>' 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'):css('width', scoreWidth):wikitext(legScore)
                end
            end
        end

        i = i + step
    end

    return tostring(root)
end

return p