Modul:Sports series

Revisi sejak 20 September 2024 04.50 oleh Py21 (bicara | kontrib) (tim pemenang otomatis cetak tebal)

-- 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 template passed via |flag=
local function templateExists(templateName)
    local title = mw.title.new('Templat:' .. 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 or last_two_digits ~= 11 then
        return n
    else
        return n
    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(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 = "<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 |matches_style=FBR
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

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

            -- 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 = 'Laga ' .. 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 = 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 = (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, 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', {flagParam1, team1, variant = team1Variant})) or (team1 ~= nil and team1 or "")
                team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate, {flagParam1, team2, variant = team2Variant})) 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', {team1, variant = team1Variant})) or (team1 ~= nil and team1 or "")
                team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate, {team2, variant = team2Variant})) 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 .. ' <span class="flagicon">[[Berkas:Flag placeholder.svg|25x17px|link=]]</span>'
                end
                if team2Asterick then
                    team2Text = '<span class="flagicon">[[Berkas:Flag placeholder.svg|25x17px|link=]]</span> ' .. 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 ('<strong>' .. team1Text .. '</strong>') or team1Text)
            row:tag('td'):cssText(aggregateStyle):wikitext(aggregateScore)
            row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold) and team2Text ~= '') 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)
            -- 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, {team1Icon, variant = team1Variant})) or (team1 or ''))
            local team2Text = noFlagIcons and (team2 or '') or ((team2Icon ~= "" and team2Icon ~= nil) and (expandTemplate(frame, flagTemplate, {team2Icon, variant = team2Variant}) .. ' ' .. (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 .. ' <span class="flagicon">[[Berkas:Flag placeholder.svg|25x17px|link=]]</span>'
                    end
                    if not team2Icon or team2Icon == "" then
                        team2Text = '<span class="flagicon">[[Berkas:Flag placeholder.svg|25x17px|link=]]</span> ' .. 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 '<strong>' .. team1Text .. '</strong>' or team1Text)
            row:tag('td'):cssText(aggregateStyle):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'):wikitext(legScore)
                end
            end
        end

        i = i + step
    end

    return tostring(root)
end

return p