Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:MLB standings

Permanently protected module
From Wikipedia, the free encyclopedia

-- This module copies content from Template:MLB_standings; see the history of that page
-- for attribution.

local me = { }

local mlbData = mw.loadData('Module:MLB standings/data')
local Navbar = require('Module:Navbar')

--
-- defaultOutputForInput: table mapping from input format to default output format
--   (if the output format is not specified in the template arguments)
--
local defaultOutputForInput = {
    default = 'default',
    overallWinLoss = 'winLossOnly',
}

--
-- readTeamInfo: table of input parsers
--   Keys are the input formats, values are functions that parse the unnamed parameters
--   that were passed to the template and return a table holding the team name
--   and the win-loss records (either overall, or home and away, depending on the
--   input format).
--   The parsers take the following parameters:
--     args: table holding the parameters (indexed by numeric position)
--     currentIdx: the current index from where the next set of data should be parsed
--     returnData: table that the parser will update to pass additional data back to the caller.
--       returnData.cIndicesRead is updated with the number of parameters that were parsed
--
local readTeamInfo = {
    default = function(args, currentIdx, returnData)
        if (args[currentIdx]   == nil or
            args[currentIdx+1] == nil or
            args[currentIdx+2] == nil or
            args[currentIdx+3] == nil or
            args[currentIdx+4] == nil ) then
            return nil
        end
        teamInfo = {
            name       = mw.text.trim(args[currentIdx]),
            homeWins   = tonumber(mw.text.trim(args[currentIdx+1])),
            homeLosses = tonumber(mw.text.trim(args[currentIdx+2])),
            roadWins   = tonumber(mw.text.trim(args[currentIdx+3])),
            roadLosses = tonumber(mw.text.trim(args[currentIdx+4])),
        }
        returnData.cIndicesRead = 5
        teamInfo.wins = teamInfo.homeWins + teamInfo.roadWins
        teamInfo.losses = teamInfo.homeLosses + teamInfo.roadLosses
        return teamInfo
    end,  -- function readTeamInfo.default()

    overallWinLoss = function(args, currentIdx, returnData)
        if (args[currentIdx]   == nil or
            args[currentIdx+1] == nil or
            args[currentIdx+2] == nil ) then
            return nil
        end
        teamInfo = {
            name   = mw.text.trim(args[currentIdx]),
            wins   = tonumber(mw.text.trim(args[currentIdx+1])),
            losses = tonumber(mw.text.trim(args[currentIdx+2])),
        }
        returnData.cIndicesRead = 3
        return teamInfo
    end,  -- function readTeamInfo.default()

}  -- readTeamInfo object

--
-- generateTableHeader: table of functions that generate table header
--   Keys are the output formats, values are functions that return a string with the table header
--   The generator functions take the following parameter:
--     tableHeaderInfo: table that contains the information needed for the header
--
local generateTableHeader = {
    default = function(tableHeaderInfo)
        return
'{| class="wikitable MLBStandingsTable" \
|+  |' .. tableHeaderInfo.navbarText .. '[[' .. tableHeaderInfo.divisionLink
.. '|' .. tableHeaderInfo.division .. ']]\
|- \
! width="51%" | Team \
! width="6%" | [[Win (baseball)|W]]\
! width="6%" | [[Loss (baseball)|L]]\
! width="9%" | [[Winning percentage|Pct.]]\
! width="8%" | [[Games behind|GB]]\
! width="10%" | [[Home (sports)|Home]]\
! width="10%" | [[Road (sports)|Road]]\
'
    end,  -- function generateTableHeader.default()

    winLossOnly = function(tableHeaderInfo)
        return
'{| class="wikitable MLBStandingsTable" \
|+  |' .. tableHeaderInfo.navbarText .. tableHeaderInfo.division .. '\
|- \
! width="66%" | Team\
! width="10%" | [[Win (baseball)|W]]\
! width="10%" | [[Loss (baseball)|L]]\
! width="14%" | [[Winning percentage|Pct.]]\
'
    end,  -- function generateTableHeader.winLossOnlyNoNavBar()

    wildCard2012 = function(tableHeaderInfo)
        return
'{| class="wikitable MLBStandingsTable" \
|+  |' .. tableHeaderInfo.navbarText .. 'Wild Card teams<br><small>(Top 2 teams qualify for postseason)</small>\
|- \
! width="64%" | Team \
! width="8%" | [[Win (baseball)|W]]\
! width="8%" | [[Loss (baseball)|L]]\
! width="10%" | [[Winning percentage|Pct.]]\
! width="10%" | [[Games behind|GB]]\
'
    end,  -- function generateTableHeader.wildCard2012

    wildCard = function(tableHeaderInfo)
    	local teamText = 'team'
    	local numberOfTeamsText = 'team qualifies'
    	if tableHeaderInfo.wildCardsPerLeague > 1 then
    		teamText = 'teams'
    		numberOfTeamsText = tableHeaderInfo.wildCardsPerLeague .. ' teams qualify'
    	end
        return
'{| class="wikitable MLBStandingsTable" \
|+  |' .. tableHeaderInfo.navbarText .. 'Wild Card ' .. teamText .. '<br><small>(Top ' .. numberOfTeamsText ..
	' for postseason)</small>\
|- \
! width="64%" | Team \
! width="8%" | [[Win (baseball)|W]]\
! width="8%" | [[Loss (baseball)|L]]\
! width="10%" | [[Winning percentage|Pct.]]\
! width="10%" | [[Games behind|GB]]\
'
    end,  -- function generateTableHeader.wildCard

}  -- generateTableHeader object

--
-- generateTeamRow: table of functions that generate a table row
--   Keys are the output formats, values are functions that return a string with the table row
--   The generator functions take the following parameter:
--     tableRowInfo: table that contains additional the information needed for the row
--     teamInfo: table that contains the team name and win-loss info
--
local generateTeamRow = {
    default = function(teamRowInfo, teamInfo)
        return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\
|| ' .. teamRowInfo.gamesBehind .. '\
|| ' .. teamInfo.homeWins .. '&zwj;–&zwj;' .. teamInfo.homeLosses ..'\
|| ' .. teamInfo.roadWins .. '&zwj;–&zwj;' .. teamInfo.roadLosses .. '\n'

    end,  -- function generateTeamRow.default()

    winLossOnly = function(teamRowInfo, teamInfo)
        return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\n'
    end,  -- function generateTeamRow.winLossOnly

    wildCard2012 = function(teamRowInfo, teamInfo)
        return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\
|| ' .. teamRowInfo.gamesBehind .. '\n'
    end,  -- function generateTeamRow.wildCard2012

    wildCard = function(teamRowInfo, teamInfo)
        return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\
|| ' .. teamRowInfo.gamesBehind .. '\n'
    end,  -- function generateTeamRow.wildCard
}   -- generateTeamRow object

--
-- parseSeeds: function to parse the seeds template argument
--
local function parseSeeds(seedsArg, seeds)
    local seedList = mw.text.split(seedsArg, '%s*,%s*')
    if (#seedList == 0) then
        return
    end

    for idx, seed in ipairs(seedList) do
        local seedData = mw.text.split(seed, '%s*:%s*')
        if (#seedData >= 2) then
            local seedNumber = tonumber(mw.text.trim(seedData[1]))
            local team = mw.text.trim(seedData[2])
            seeds[seedNumber] = team
            seeds[team] = seedNumber
        end
    end
end  -- function parseSeeds()

--
-- parseHighlightArg: function to parse the highlight template argument
--
local function parseHighlightArg(highlightArg, teamsToHighlight)
    local teamList = mw.text.split(highlightArg, '%s*,%s*')
    if (#teamList == 0) then
        return
    end

    for idx, team in ipairs(teamList) do
        teamsToHighlight[mw.text.trim(team)] = true
    end

end  -- function parseHighlightArg

--
-- parseTeamLInks: function to parse the team_links template argument
--
local function parseTeamLinks(teamLinksArg, linkForTeam)
    local teamList = mw.text.split(teamLinksArg, '%s*,%s*')
    if (#teamList == 0) then
        return
    end

    for idx, teamLinkInfo in ipairs(teamList) do
        local teamData = mw.text.split(teamLinkInfo, '%s*:%s*')
        if (#teamData >= 2) then
            local team = mw.text.trim(teamData[1])
            local teamLink = mw.text.trim(teamData[2])
            linkForTeam[team] = teamLink
        end
    end
end  -- function parseTeamLinks

local function getWildCardsPerLeagueForYear(year)
	if year == '' then
		return 0
	end
	for idx, wildCardInfo in ipairs(mlbData.wildCardInfo) do
		if wildCardInfo.startYear <= year and year <= wildCardInfo.endYear then
			return wildCardInfo.wildCardsPerLeague;
		end
	end
	-- year not found, thus no wild cards for specified year
	return 0;
end  -- function getWildCardsPerLeagueForYear

--
-- function generateStandingsTable
--
-- Parameters: frame object from template
--   frame.args.input: input format for standings info
--     if not specified, the default is team name followed by home win-loss and road win-loss records
--     - overallWinLoss: team name followed by overall win-loss record
--
--   frame.args.output: output format for standings table
--     if not specified, the output format is based on the input format (see defaultOutputForInput table):
--       - default => games behind and home and road win-loss records displayed
--       - overallWinLoss => overall win-loss records displayed, no games behind column
--     - winLossOnly: overall win-loss records displayed, no games behind column
--     - wildCard: wildcard standings table displayed
--     - wildCard2012: wildcard standings table displayed (effectively the same as wildcard for years from 2012-2021; kept for backwards compatibility)
--
--   frame.args.template_name: name of standings template
--     if not specified, the default is <year> <division name> standings
--
--   frame.args.seeds: list of team seedings
--   frame.args.highlight: list of teams to highlight
--   frame.args.team_links: list of link targets for each team
--     If not specified, the default is just the team name.
--     This is used to generate the season page for each team, in the form
--     <year> <team link target> season
--
function me.generateStandingsTable(frame)
    local inputFormat = 'default'

	-- If the input parameter is specified in the template, use it as the input format.
    if (frame.args.input ~= nil) then
        local inputArg = mw.text.trim(frame.args.input)
        if (inputArg == 'overallWinLoss') then
            inputFormat = 'overallWinLoss'
        end
    end

    local templateName = nil
    if (frame.args.template_name ~= nil) then
        templateName = frame.args.template_name
    end

    local outputFormat = defaultOutputForInput[inputFormat]
    local fDisplayNavbar = true
    local fDisplayGamesBehind = true

    -- If the output parameter is specified in the template, use it as the output format.
    -- Note no cross validation is performed to check if it is valid given the input format.
    if (frame.args.output ~= nil) then
        local outputArg = mw.text.trim(frame.args.output)
        if (outputArg == 'winLossOnly') then
            outputFormat = 'winLossOnly'
            fDisplayGamesBehind = false
        end
        if (outputArg == 'wildCard2012') then
            outputFormat = 'wildCard2012'
        end
        if (outputArg == 'wildCard') then
        	outputFormat = 'wildCard'
    	end
    end

    local year = tonumber(mw.text.trim(frame.args.year or '0'))
    local division = mw.text.trim(frame.args.division or '')
    local divisionLink = mw.text.trim(frame.args.division_link or division)
    local wildCardsPerLeague = getWildCardsPerLeagueForYear(year)

    local seedInfo = {}
    if (frame.args.seeds ~= nil) then
        parseSeeds(frame.args.seeds, seedInfo)
    end

    local teamsToHighlight = {}
    if (frame.args.highlight ~= nil) then
        parseHighlightArg(frame.args.highlight, teamsToHighlight)
    end

    local linkForTeam = {}
    if (frame.args.team_links ~= nil) then
        parseTeamLinks(frame.args.team_links, linkForTeam)
    end

    local listOfTeams = {};
    local currentArgIdx = 1;

    -- Parse the unnamed parameters from the template. This consists of the
    -- team names and their win-loss records.
    while (frame.args[currentArgIdx] ~= nil) do
        local returnData = { }
        local teamInfo = readTeamInfo[inputFormat](frame.args, currentArgIdx, returnData);
        if (teamInfo == nil) then
            break
        end
        if (linkForTeam[teamInfo.name] ~= nil) then
            teamInfo.teamLink = linkForTeam[teamInfo.name]
        else
            teamInfo.teamLink = teamInfo.name
        end
        table.insert(listOfTeams, teamInfo)
        currentArgIdx = currentArgIdx + returnData.cIndicesRead
    end

    if (#listOfTeams == 0) then
        return ''
    end

    -- table to hold list of strings that will be concatenated at the end
    -- to create a string with the standings table
    local outputBuffer = { }

    local tableHeaderInfo = {
        division = division,
        divisionLink = divisionLink,
        wildCardsPerLeague = wildCardsPerLeague,
    }

    if (fDisplayNavbar) then
        local divisionForNavbox = division
        if (mlbData.abbreviationForDivision[division] ~= nil) then
            divisionForNavbox = mlbData.abbreviationForDivision[division]
        end

        local standingsPage
        if (templateName ~= nil) then
            standingsPage = templateName
        else
            standingsPage = year .. ' ' .. divisionForNavbox .. ' standings'
        end
        tableHeaderInfo.navbarText =
            Navbar.navbar({
                standingsPage,
                mini = 1,
                style = 'float:left;width:0;',
            })
    end

    table.insert(outputBuffer,
        generateTableHeader[outputFormat](tableHeaderInfo)
    )

    local leadingHalfGames = nil;
    if (fDisplayGamesBehind) then
        local standingsLeaderIdx = 1
        if (outputFormat == 'wildCard2012' and #listOfTeams > 1) then
            standingsLeaderIdx = 2
        end
        if (outputFormat == 'wildCard' and #listOfTeams >= wildCardsPerLeague) then
			standingsLeaderIdx = wildCardsPerLeague
        end

        local teamInfo = listOfTeams[standingsLeaderIdx]
        leadingHalfGames = (teamInfo.wins - teamInfo.losses)
    end

    for idx, teamInfo in ipairs(listOfTeams) do
    	local winningPercentage = string.format(
                '%.3f', teamInfo.wins / ( teamInfo.wins + teamInfo.losses )
                )
        winningPercentage = string.gsub(winningPercentage, '^0', '')
        local teamRowInfo = {
            teamSeasonPage = year .. ' ' .. teamInfo.teamLink .. ' season',
            winningPercentage = winningPercentage,
            gamesBehind = '',
            seedText = '',
            rowStyle = '',
        }

        if (fDisplayGamesBehind) then
            local halfGamesBehind = leadingHalfGames - (teamInfo.wins - teamInfo.losses)
            local prefix = nil
            -- if games behind is negative, take the absolute value and prefix a +
            -- character
            if (halfGamesBehind < 0) then
                halfGamesBehind = -halfGamesBehind
                prefix = '+'
            end
            if (halfGamesBehind == 0) then
                teamRowInfo.gamesBehind = '—'
            else  -- if halfGamesBehind is not 0
                teamRowInfo.gamesBehind = math.floor(halfGamesBehind / 2)
                if (halfGamesBehind % 2 == 1) then
                    if (halfGamesBehind == 1) then
                        teamRowInfo.gamesBehind = '½'
                    else
                        teamRowInfo.gamesBehind = teamRowInfo.gamesBehind .. '½'
                    end
                end
                if ( prefix ~= nil ) then
                    teamRowInfo.gamesBehind = prefix .. teamRowInfo.gamesBehind
                end
            end  -- if halfGamesBehind is not 0
        end  -- if (fDisplayGamesBehind)

        if (seedInfo[teamInfo.name] ~= nil) then
            teamRowInfo.seedText = '<sup>(' .. seedInfo[teamInfo.name] .. ')</sup>&nbsp;'
            teamRowInfo.rowStyle = ' class="MLBStandingsHighlightedRow"'
        end

        if (teamsToHighlight[teamInfo.name]) then
            teamRowInfo.rowStyle =  ' class="MLBStandingsHighlightedRow"'
        end

        table.insert(outputBuffer,
            generateTeamRow[outputFormat](teamRowInfo, teamInfo)
        )
    end  -- end of looping over listOfTeams

    table.insert(outputBuffer, '|}')

    return table.concat(outputBuffer)

end  -- function me.generateStandingsTable()

function me.generateStandingsTable_fromTemplate(frame)
    return me.generateStandingsTable(frame:getParent())
end  -- function me.generateStandingsTable_fromTemplate()

return me