Jump to content

Module:Sandbox/isaacl/NHL standings

From Wikipedia, the free encyclopedia
-- This module copies content from Template:2012–13_NHL_Eastern_Conference_standings;
-- see the history of that page for attribution.

-- TODO:
-- add support for annotations:
-- - y = won division
-- - z = best record in conference
-- - p = best record in league
-- add support for sorting by playoff ranking
-- add support for wild card-based tables

local me = { }

local nhlInfo

-- if mw.loadData() not supported, use require() instead
if mw.loadData then
    nhlInfo = mw.loadData('Module:Sandbox/isaacl/NHL standings/2013 2014 NHL info')
else
    nhlInfo = require('Module:Sandbox/isaacl/NHL standings/2013 2014 NHL info')
end

local Navbar = require('Module:Navbar')

local config = { }

config.inputFormat  = '2005-2006-rules'
config.outputFormat = '2013-2014-rules'
config.standingsType = 'league'
config.separatorFormat = 'default'
config.seedInfo = { }
config.tableHeaderText = { }
config.entityForTeam = nil

config.tableTitleForStandingsType = {
    conference = 'Conference standings',
    division = 'Division',
    league = 'League standings',
}

config.scopeForStandingsType = {
    conference = 'conference',
    division = 'division',
    league = 'league',
}

local function initEntityForTeamTable()
    config.entityForTeam = { }

    for conferenceName, conferenceInfo in pairs(nhlInfo.structure.conference) do
        for divisionName, teamList in pairs(conferenceInfo.division) do
            for idx, teamName in ipairs(teamList) do
                if (config.entityForTeam[teamName] == nil) then
                    config.entityForTeam[teamName] = { }
                end
                config.entityForTeam[teamName].conference = conferenceName
                config.entityForTeam[teamName].division   = divisionName
            end  -- loop over team names
        end  -- loop over divisions
    end  -- loop over conferences
end  -- function initTeamsForEntity()

local function filterTeamsByEntity(listOfTeams, entityType, entity)
	if (config.entityForTeam == nil) then
	    initEntityForTeamTable()
	end
    local filteredTeams = { }
    for idx, teamInfo in ipairs(listOfTeams) do
    	if (config.entityForTeam[teamInfo.name]) then
            if (entity == config.entityForTeam[teamInfo.name][entityType]) then
                table.insert(filteredTeams, teamInfo)
            end
        end
    end
    return filteredTeams
end  -- function filterTeamsByEntity

local function compareTeamsByRank(teamA, teamB)
    local result;
    if (teamA.points ~= teamB.points) then
        return teamA.points > teamB.points
    elseif (teamA.games ~= teamB.games) then
        return teamA.games < teamB.games
    elseif (teamA.nonShootoutWins ~= teamB.nonShootoutWins) then
        return teamA.nonShootoutWins > teamB.nonShootoutWins
    else
    	-- The next tie-breaker is the head-to-head records of the
    	-- two teams. As this information is not given to this
    	-- template, just preserve the original order.
        return teamA.originalPosition < teamB.originalPosition
    end
end  -- function compareTeams()

local readTeamInfo = {
    ['2005-2006-rules'] = function(args, currentIdx, returnData)
        if (config.separatorFormat == 'whitespace') then
            if (args[currentIdx] == nil or
               	args[currentIdx+1] == nil) then
                returnData.errorMessage = 'Insufficient number of arguments for format: ' ..
                  config.separatorFormat
                return nil
            end
        elseif (args[currentIdx]   == nil or
            args[currentIdx+1] == nil or
            args[currentIdx+2] == nil or
            args[currentIdx+3] == nil or
            args[currentIdx+4] == nil or
            args[currentIdx+5] == nil or
            args[currentIdx+6] == nil   ) then
            returnData.errorMessage = 'Insufficient number of arguments for format: ' ..
              config.separatorFormat
            return nil
        end

        local teamInfo = {
            name = mw.text.trim(args[currentIdx])
		}
        if (config.separatorFormat == 'whitespace') then
            local argList = mw.text.split(args[currentIdx+1], '%s+')
            teamInfo.wins = tonumber(mw.text.trim(argList[1]))
            teamInfo.losses = tonumber(mw.text.trim(argList[2]))
            teamInfo.nonRegulationLosses = tonumber(mw.text.trim(argList[3]))
            teamInfo.nonShootoutWins     = tonumber(mw.text.trim(argList[4]))
            teamInfo.goalsFor     = tonumber(mw.text.trim(argList[5]))
            teamInfo.goalsAgainst = tonumber(mw.text.trim(argList[6]))

            returnData.cIndicesRead = 2
        else
            teamInfo.wins = tonumber(mw.text.trim(args[currentIdx+1]))
            teamInfo.losses = tonumber(mw.text.trim(args[currentIdx+2]))
            teamInfo.nonRegulationLosses = tonumber(mw.text.trim(args[currentIdx+3]))
            teamInfo.nonShootoutWins     = tonumber(mw.text.trim(args[currentIdx+4]))
            teamInfo.goalsFor     = tonumber(mw.text.trim(args[currentIdx+5]))
            teamInfo.goalsAgainst = tonumber(mw.text.trim(args[currentIdx+6]))

            returnData.cIndicesRead = 7
        end
        teamInfo.games = teamInfo.wins + teamInfo.losses + teamInfo.nonRegulationLosses
        teamInfo.points = 2 * teamInfo.wins + teamInfo.nonRegulationLosses
        return teamInfo
    end,  -- function readTeamInfo.default()
}  -- readTeamInfo object

local function initTableHeaderText(frame)
    config.tableHeaderText = {
		rank     = frame:expandTemplate{ title='abbr', args={'R', 'Rank'} },
		division = frame:expandTemplate{ title='abbr', args={'Div', 'Division'} },
		games    = frame:expandTemplate{ title='abbr', args={'GP', 'Games played'} },
		wins     = frame:expandTemplate{ title='abbr', args={'W', 'Wins'} },
		losses   = frame:expandTemplate{ title='abbr', args={'L', 'Regulation losses'} },
		nonRegulationLosses = frame:expandTemplate{ title='abbr', args={'OTL', 'Overtime + shootout losses'} },
		nonShootoutWins  = frame:expandTemplate{ title='abbr', args={'ROW', 'Regulation + overtime wins'} },
		goalsFor = frame:expandTemplate{ title='abbr', args={'GF', 'Goals for'} },
		goalsAgainst = frame:expandTemplate{ title='abbr', args={'GA', 'Goals against'} },
		points   = frame:expandTemplate{ title='abbr', args={'Pts', 'Points'} },
	}
end

local generateTableHeader = {
    ['2013-2014-rules'] = function(config)
        return
'{| class="wikitable sortable" style="text-align:center;"\
|+ ' .. config.tableTitleForStandingsType[config.standingsType] .. '\
! style="width:5%" | ' .. config.tableHeaderText.rank .. '\
! style="width:25%;" class="unsortable" |\
! style="width:8%;" class="unsortable" | ' .. config.tableHeaderText.games .. '\
! style="width:8%;" | ' .. config.tableHeaderText.wins .. '\
! style="width:8%;" | ' .. config.tableHeaderText.losses .. '\
! style="width:8%;" | ' .. config.tableHeaderText.nonRegulationLosses .. '\
! style="width:8%;" | ' .. config.tableHeaderText.nonShootoutWins .. '\
! style="width:10%;" | ' .. config.tableHeaderText.goalsFor .. '\
! style="width:10%;" | ' .. config.tableHeaderText.goalsAgainst .. '\
! style="width:10%;" | ' .. config.tableHeaderText.points .. '\n'

	end,  -- function generateTableHeader.'2013-2014-rules'
}  -- generateTableHeader object

local generateTeamRow = {
    ['2013-2014-rules'] = function(config, teamRowInfo, teamInfo)
        return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.rank .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.games ..'\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamInfo.nonRegulationLosses .. '\
|| ' .. teamInfo.nonShootoutWins .. '\
|| ' .. teamInfo.goalsFor ..'\
|| ' .. teamInfo.goalsAgainst .. '\
|| ' .. teamInfo.points .. '\n'

    end,  -- function generateTeamRow.'2013-2014-rules'
}   -- generateTeamRow object

local calculateSeeds = {
	['2013-2014-rules'] = function()
		return ''   -- TODO
	end,  -- function calculateSeeds.'2013-2014-rules'
}  -- calculateSeeds object

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

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

function me.generateStandingsTable(frame)
    config.templateName = nil
    if (frame.args.template_name ~= nil) then
        config.templateName = frame.args.template_name
    end

    if (frame.args.standingsType ~= nil) then
        local standingsTypeArg = mw.text.trim(frame.args.standingsType)
        if (standingsTypeArg ~= '') then
            config.standingsType = standingsTypeArg
        end
    end

    if (config.standingsType ~= nil) then
    	if (frame.args.entity ~= nil) then
            local entityArg = mw.text.trim(frame.args.entity)
            if (entityArg ~= '') then
                config.entity = entityArg
            end
        end
    end

    if (frame.args.separatorFormat ~= nil) then
    	local separatorFormatArg = mw.text.trim(frame.args.separatorFormat)
    	config.separatorFormat = separatorFormatArg
    end

    config.season = mw.text.trim(frame.args.season or '')
    -- For convenience, replace hyphen with en-dash
    config.season = mw.ustring.gsub(config.season, '-', '–')

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

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

    local listOfTeams = {}
    local currentArgIdx = 1
    local originalPosition = 1

    while (frame.args[currentArgIdx] ~= nil) do
        local returnData = { }
        local teamInfo = readTeamInfo[config.inputFormat](frame.args, currentArgIdx, returnData);
        if (teamInfo == nil) then
        	if (returnData.errorMessage ~= nil) then
        		return returnData.errorMessage
        	end
            break
        end
        if (config.linkForTeam[teamInfo.name] ~= nil) then
            teamInfo.teamLink = config.linkForTeam[teamInfo.name]
        else
            teamInfo.teamLink = teamInfo.name
        end
        teamInfo.originalPosition = originalPosition
        table.insert(listOfTeams, teamInfo)
        currentArgIdx = currentArgIdx + returnData.cIndicesRead
        originalPosition = originalPosition + 1
    end

    if (#listOfTeams == 0) then
        return 'No teams!'
    end

    local outputBuffer = { }

    initTableHeaderText(frame)

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

    local teamsToDisplay = listOfTeams

    -- filter teams to display, based on scope

    if (config.scopeForStandingsType[config.standingsType] == 'division') then
        teamsToDisplay = filterTeamsByEntity(listOfTeams, 'division', config.entity)
    end

    if (config.scopeForStandingsType[config.standingsType] == 'conference') then
        teamsToDisplay = filterTeamsByEntity(listOfTeams, 'conference', config.entity)
    end

    table.sort(teamsToDisplay, compareTeamsByRank)

    -- TODO: further filter teams after sorting (for division leaders
    -- table)

    local rankIdx = 1
    for idx, teamInfo in ipairs(teamsToDisplay) do
    	local teamRowInfo = {
    	    seedText = '',
    	    rowStyle = '',
    	    teamSeasonPage = config.season .. ' ' .. teamInfo.teamLink .. ' season',
    	    rank = rankIdx,
    	}
        if (config.seedInfo[teamInfo.name] ~= nil) then
            teamRowInfo.seedText = '<sup>(' .. seedInfo[teamInfo.name] .. ')</sup> '
            teamRowInfo.rowStyle = ' style="background:#CCFFCC"'
        end

        if (config.teamsToHighlight[teamInfo.name]) then
            teamRowInfo.rowStyle =  ' style="background:#CCFFCC"'
        end

        table.insert(outputBuffer,
            generateTeamRow[config.outputFormat](config, teamRowInfo, teamInfo)
        )
        rankIdx = rankIdx + 1
    end  -- end of looping over listOfTeams

    table.insert(outputBuffer, '|}\n')

    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