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:Sandbox/S.A. Julio

From Wikipedia, the free encyclopedia
local p = {}

local function getRemainingColumns(teamMatchCount, maxMatches)
    local remaining = maxMatches - teamMatchCount
    return remaining > 0 and remaining or 0
end

-- Helper function to get display text from wikilinks
local function getDisplayText(text)
    if not text then return '' end
    
    -- First deflag if needed
    text = mw.ustring.gsub(text, '<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%][^<>]*</span>%s*', '')
    text = mw.ustring.gsub(text, '%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]%s*', '')
    text = mw.ustring.gsub(text, '^%s*&nbsp;%s*', '')
    
    -- Then extract display text from wikilink if present
    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 all wikilinks with their display text
    text = mw.ustring.gsub(text, '%[%[[^%[%]]*%]%]', replaceLink)
    
    return text
end

-- Helper function to get short team names
local function get_short_name(s, t, n, ss, frame)
    -- If name is undefined or empty, use team code
    if not n or n == '' then
        n = t
    end
    
    -- return short name if defined
    if s and s ~= '' then
        return s
    end
    
    -- deflag if necessary
    if ss and n then
        if ss == 'noflag' then
            n = mw.ustring.gsub(n, '%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]', '')
            n = mw.ustring.gsub(n, '^%s*&nbsp;%s*', '')
        elseif ss == 'flag' then
            n = mw.ustring.gsub(n, '(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%][^<>]*</span>)%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
            n = mw.ustring.gsub(n, '(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%])%s*&nbsp;%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
            n = mw.ustring.gsub(n, '(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%])%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
            n = mw.ustring.gsub(n, '.*(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%][^<>]*</span>).*', '%1')
            n = mw.ustring.gsub(n, '.*(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]).*', '%1')
            n = mw.ustring.gsub(n, '&nbsp;(</span>)', '%1')
        elseif ss == 'abbr' then
            -- For abbr style, we want the clean team name without any markup
            local displayName = getDisplayText(n)
            if frame then
                -- Use the Template:Abbr template
                return frame:expandTemplate{
                    title = 'abbr',
                    args = {
                        [1] = t,           -- First parameter: abbreviation
                        [2] = displayName  -- Second parameter: full name
                    }
                }
            end
        end
    end

    -- replace link text in name with team abbr if possible
    if n and t and n:match('(%[%[[^%[%]]*%]%])') and ss ~= 'abbr' then
        n = mw.ustring.gsub(n, '(%[%[[^%|%]]*%|)[^%|%]]*(%]%])', '%1' .. t .. '%2')
        n = mw.ustring.gsub(n, '(%[%[[^%|%]]*)(%]%])', '%1|' .. t .. '%2')
        n = mw.ustring.gsub(n, '(%[%[[^%|%]]*%|)([A-Z][A-Z][A-Z])(%]%])&nbsp;<span[^<>]*>%([A-Z][A-Z][A-Z]%)</span>', '%1%2%3')
        return n
    end
    -- nothing worked, so just return the unlinked team abbr
    return t or ''
end

-- Helper function to determine background colour
local function get_score_background(s, isFBR)
    local s1, s2
    -- Define the colouring based on style
    local wc = isFBR and '#BBF3FF' or '#BBF3BB'  -- blue for FBR, green otherwise
    local lc, tc = '#FBB', '#FFB' -- red and yellow remain the same
    
    -- check for override
    if s:match('^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$') then
        local c = mw.ustring.gsub(s,'^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$', '%1')
        return 'background: ' .. c ..';'
    end

    -- delink if necessary
    if s:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]') then
        s = s:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]')
    end
    if s:match('^%s*%[[^%[%]%s]*%s([^%[%]]*)%]') then
        s = s:match('^%s*%[[^%[%]%s]*%s([^%[%]]*)%]')
    end
    if s:match('<span[^<>]*>(.-)</span>') then
        s = s:match('<span[^<>]*>(.-)</span>')
    end

    -- get the scores
    s1 = tonumber(mw.ustring.gsub(s or '', '^%s*([%d%.]+)%s*[–%-]%s*([%d%.]+).*', '%1') or '') or ''
    s2 = tonumber(mw.ustring.gsub(s or '', '^%s*([%d%.]+)%s*[–%-]%s*([%d%.]+).*', '%2') or '') or ''

    -- return colouring if possible
    if s1 ~= '' and s2 ~= '' then
        if s1 > s2 then
            return 'background: ' .. wc .. ';'
        elseif s2 > s1 then
            return 'background: ' .. lc .. ';'
        else
            return 'background: ' .. tc .. ';'
        end
    end
    return ''
end

-- Helper function to process team order
local function getTeamList(args)
    local teams = {}
    
    -- Always check team_order first and use it if present
    if args.team_order and args.team_order:match("%S") then
        for team in args.team_order:gmatch('[^,%s]+') do
            table.insert(teams, team)
        end
        return teams
    end
    
    -- Only if team_order is nil or empty, try numbered parameters
    local i = 1
    while args['team' .. i] do
        table.insert(teams, args['team' .. i])
        i = i + 1
    end
    
    return teams
end

-- Helper function to split match list into array
local function getMatchList(matchList)
    if not matchList then return {} end
    local matches = {}
    for match in matchList:gmatch('[^,%s]+') do
        -- Special case for 'null'
        if match == 'null' then
            table.insert(matches, match)
        else
            -- Validate match format: must be CODE_[han]
            if not match:match('^[A-Z]+_[han]$') then
                error('Invalid match format: ' .. match .. '. Must be in format CODE_h, CODE_a, or CODE_n')
            end
            table.insert(matches, match)
        end
    end
    return matches
end

-- Helper function to generate match parameter key
local function getMatchKey(team1, team2, matchNum)
    return 'match_' .. team1 .. '_' .. team2 .. (matchNum > 1 and '_' .. matchNum or '')
end

-- Function to check if any neutral matches exist (add at top of module)
local function hasNeutralMatches(teams, args)
    for _, team in ipairs(teams) do
        local matchList = getMatchList(args['match_list_' .. team])
        for _, matchCode in ipairs(matchList) do
            if matchCode:match('_n$') then
                return true
            end
        end
    end
    return false
end

-- Helper function to lookup match result for neutral matches
local function getNeutralMatchResult(args, team1, team2, matchNum)
    -- Try all possible parameter combinations for neutral matches
    local baseKey1 = 'matchn_' .. team1 .. '_' .. team2
    local baseKey2 = 'matchn_' .. team2 .. '_' .. team1
    
    -- Try without suffix first
    if matchNum == 1 then
        if args[baseKey1] then
            return args[baseKey1], false  -- false means don't reverse score
        end
        if args[baseKey2] then
            return args[baseKey2], true   -- true means reverse score
        end
    end
    
    -- Try with suffix
    local suffixKey1 = baseKey1 .. '_' .. matchNum
    local suffixKey2 = baseKey2 .. '_' .. matchNum
    if args[suffixKey1] then
        return args[suffixKey1], false
    end
    if args[suffixKey2] then
        return args[suffixKey2], true
    end
    
    -- For first matches, try with _1 suffix as fallback
    if matchNum == 1 then
        local fallbackKey1 = baseKey1 .. '_1'
        local fallbackKey2 = baseKey2 .. '_1'
        if args[fallbackKey1] then
            return args[fallbackKey1], false
        end
        if args[fallbackKey2] then
            return args[fallbackKey2], true
        end
    end
    
    return nil, false
end

-- Helper function to lookup match result for regular matches
local function getRegularMatchResult(args, team1, team2, matchNum)
    -- Try all possible parameter names
    local baseKey = 'match_' .. team1 .. '_' .. team2
    
    -- Try without suffix first
    if matchNum == 1 then
        if args[baseKey] then
            return args[baseKey]
        end
    end
    
    -- Try with suffix
    local suffixKey = baseKey .. '_' .. matchNum
    if args[suffixKey] then
        return args[suffixKey]
    end
    
    -- For first matches, try with _1 suffix as fallback
    if matchNum == 1 then
        local fallbackKey = baseKey .. '_1'
        return args[fallbackKey]
    end
    
    return nil
end

-- Modify the formatScore function to take the home_first parameter
local function formatScore(score, reverseScore, home_first, isNeutral)
    if not score or score == '' or score:find('–') then return score end
    
    local score1, score2 = score:match('(%d+)[%-–](%d+)')
    if not score1 or not score2 then return score end
    
    -- If it's a neutral match or home_first is false, use the previous logic
    if isNeutral or not home_first then
        if reverseScore and score1 ~= score2 then
            return score2 .. '–' .. score1
        end
        return score1 .. '–' .. score2
    end
    
    -- For home_first=yes, just return the score as-is since it's already home-away order
    return score1 .. '–' .. score2
end

-- Modify the processMatch function to handle home_first
local function processMatch(match, currentTeam, args, matchCounts, useHomeFirst)
    -- Special handling for 'null' matches
    if match == 'null' then
        return {
            isNull = true  -- New flag to indicate null match
        }
    end

    local opponent, venue = match:match('^([A-Z]+)_([han])$')
    if not opponent or not venue then return nil end
    
    -- Skip if team is playing themselves
    if opponent == currentTeam then
        return {
            skip = true
        }
    end
    
    -- Create a directional key for counting matches that includes venue information
    local pairKey
    if venue == 'n' then
        -- For neutral venues, sort teams alphabetically for consistent counting
        pairKey = currentTeam < opponent and 
                 currentTeam .. '_' .. opponent .. '_n' or 
                 opponent .. '_' .. currentTeam .. '_n'
    else
        -- For home/away, track direction specifically
        pairKey = (venue == 'h') and 
                 currentTeam .. '_' .. opponent .. '_h' or
                 opponent .. '_' .. currentTeam .. '_a'
    end
    
    -- Initialize match count for this specific pairing+venue
    matchCounts[pairKey] = matchCounts[pairKey] or 0
    matchCounts[pairKey] = matchCounts[pairKey] + 1
    
    local matchNum = matchCounts[pairKey]
    local isHome = venue == 'h'
    local isNeutral = venue == 'n'
    
    local score, needReverseScore
    if isNeutral then
        -- For neutral matches, try both team orders
        score, needReverseScore = getNeutralMatchResult(
            args,
            currentTeam,
            opponent,
            matchNum
        )
        
        if not score then
            score, needReverseScore = getNeutralMatchResult(
                args,
                opponent,
                currentTeam,
                matchNum
            )
            if score then
                needReverseScore = true
            end
        end
    else
        -- For regular matches
        local team1 = isHome and currentTeam or opponent
        local team2 = isHome and opponent or currentTeam
        score = getRegularMatchResult(args, team1, team2, matchNum)
        -- Only reverse score if home_first is not enabled or if we're showing the away team's perspective
        needReverseScore = not isHome and not useHomeFirst
    end
    
    return {
        opponent = opponent,
        venue = venue,
        score = score,
        reverseScore = needReverseScore,
        isNeutral = isNeutral
    }
end

local function getDisplayCode(args, team)
    -- Return the override code if it exists, otherwise return the internal code
    return args['short_' .. team] or team
end

-- Main function to build the table
function p.main(frame)
    local args = require('Module:Arguments').getArgs(frame, {trim = true})
    local yesno = require('Module:Yesno')
    local useHomeFirst = yesno(args.home_first)

    -- Find maximum number of matches for any team before we need it
    local maxMatches = 0
    local teams = getTeamList(args)
    local teamMatchCounts = {}  -- New table to store match counts per team
    for _, team in ipairs(teams) do
        local matchList = getMatchList(args['match_list_' .. team])
        maxMatches = math.max(maxMatches, #matchList)
        teamMatchCounts[team] = #matchList  -- Store the count
    end

    -- Store reference to table library before we shadow it
    local tbl = table
    
    -- Initialize HTML builder
    local root = mw.html.create()
    
    -- Add table with optional font size
    local table = root:tag('table')
        :addClass('wikitable sports-res plainrowheaders')
        :css('text-align', 'center')
    
    -- Add font-size if specified, assuming percentage if no unit given
    if args.font_size then
        local fontSize = args.font_size
        -- If the font_size contains only numbers, add % sign
        if fontSize:match('^%d+$') then
            fontSize = fontSize .. '%'
        end
        table:css('font-size', fontSize)
    end

    -- Add centering styles if parameter is set
    if yesno(args.center_table) then
        table:addClass('center-table')
    end


    -- Add caption if provided
    if args.caption then
        table:tag('caption')
            :wikitext(args.caption)
    end

    -- Find maximum number of matches for any team before we need it
    local maxMatches = 0
    local teams = getTeamList(args)
    for _, team in ipairs(teams) do
        local matchList = getMatchList(args['match_list_' .. team])
        maxMatches = math.max(maxMatches, #matchList)
    end

    -- Add regular header row
    local header = table:tag('tr')
    header:tag('th'):wikitext('Team')

    -- Add matchday columns
    for i = 1, maxMatches do
        header:tag('th'):wikitext('MD' .. i)
    end

    -- Helper function to wrap text in bold if it matches showteam
    local function boldIfHighlighted(text, isHighlighted)
        if isHighlighted then
            return "'''" .. text .. "'''"
        end
        return text
    end

    -- Process each team
    for _, currentTeam in ipairs(teams) do
        local isHighlightedTeam = args.showteam and currentTeam == args.showteam
        local teamName = args['name_' .. currentTeam] or currentTeam
        
        if args.display_codes then
            local displayMode = args.display_codes:lower()
            if displayMode == 'inline' or displayMode == 'inline bold' then
                local displayCode = getDisplayCode(args, currentTeam)
                if displayMode == 'inline bold' and not isHighlightedTeam then
                    teamName = teamName .. " <small>'''(" .. displayCode .. "''')</small>"
                else
                    teamName = teamName .. ' <small>(' .. displayCode .. ')</small>'
                end
            end
        end
        
        local matchList = getMatchList(args['match_list_' .. currentTeam])
        local matchCounts = {}
        
        local oppRow = table:tag('tr')
        oppRow:tag('th')
            :attr('scope', 'row')
            :attr('rowspan', '2')
            :wikitext(boldIfHighlighted(teamName, isHighlightedTeam))
        
        local scoreRow = table:tag('tr')
        
        local useColors = args.matches_style == 'FBR' or yesno(args.matches_style)
        
        -- Process existing matches
        for _, matchCode in ipairs(matchList) do
            local matchData = processMatch(matchCode, currentTeam, args, matchCounts, useHomeFirst)
            
            if matchData then
                if matchData.isNull then
                    -- Handle null match with optional solid background
                    local cellStyle = ''
                    local cellContent = '—'
                    
                    local solidCell = args.solid_cell
                    if solidCell then
                        local solidLower = solidCell:lower()
                        if solidLower == 'grey' or solidLower == 'gray' or yesno(solidCell) then
                            cellStyle = 'background: #BBB;'
                            cellContent = ''
                        end
                    end
                    
                    oppRow:tag('td')
                        :attr('rowspan', '2')
                        :attr('style', cellStyle)
                        :wikitext(cellContent)
                else
                    -- Add opponent cell with modified get_short_name call
                    local oppShortName = get_short_name(
                        args['short_' .. matchData.opponent],
                        matchData.opponent,
                        args['name_' .. matchData.opponent],
                        args.display_codes == 'abbr' and 'abbr' or args.short_style,
                        frame
                    )
                    
                    local cellStyle = ''
                    local venueDisplay = ''
    
                    if yesno(args.color_venue) then
                        if matchData.venue == 'h' then
                            cellStyle = 'background: #BBB;'
                        elseif matchData.venue == 'n' then
                            cellStyle = 'background: #FEDCBA;'
                        end
                    else
                        venueDisplay = ' <small>(' .. 
                            (matchData.venue == 'n' and 'N' or 
                             (matchData.venue == 'h' and 'H' or 'A')) .. 
                            ')</small>'
                    end
    
                    oppRow:tag('td')
                        :attr('style', cellStyle)
                        :wikitext(boldIfHighlighted(oppShortName .. venueDisplay, isHighlightedTeam))
    
                    if matchData.score then
                        local formattedScore = formatScore(
                            matchData.score, 
                            matchData.reverseScore,
                            yesno(args.home_first),
                            matchData.isNeutral
                        )

                        local bgStyle = ''
                        if useColors then
                            local scoreForColor
                            if yesno(args.home_first) and not matchData.isNeutral then
                                if matchData.venue == 'h' then
                                    scoreForColor = matchData.score
                                else
                                    local score1, score2 = matchData.score:match('(%d+)[%-–](%d+)')
                                    scoreForColor = score2 .. '–' .. score1
                                end
                            else
                                if matchData.reverseScore then
                                    local score1, score2 = matchData.score:match('(%d+)[%-–](%d+)')
                                    scoreForColor = score2 .. '–' .. score1
                                else
                                    scoreForColor = matchData.score
                                end
                            end
                            bgStyle = get_score_background(scoreForColor, args.matches_style == 'FBR')
                        end

                        scoreRow:tag('td')
                            :attr('style', bgStyle)
                            :wikitext(boldIfHighlighted(formattedScore, isHighlightedTeam))
                    else
                        scoreRow:tag('td'):wikitext('')
                    end
                end
            end
        end
    
        local remainingCols = getRemainingColumns(#matchList, maxMatches)
        if remainingCols > 0 then
            oppRow:tag('td')
                :attr('rowspan', '2')
                :attr('colspan', tostring(remainingCols))
                :wikitext('')  -- Empty cell
        end
    
    end
    
    -- Get info for footer
    local update = args.update or 'unknown'
    local start_date = args.start_date or 'unknown'
    local source = args.source
    if not source then
        source = frame:expandTemplate{
            title = 'citation needed',
            args = {
                reason = 'No source parameter defined',
                date = os.date('%B %Y')
            }
        }
    end
    
    -- Add footer
    local footer = {}
    
    -- Date updating with game/match distinction
    local eventWord = yesno(args.use_tie) and 'game' or 'match'
    
    if string.lower(update) == 'complete' then
        -- Do nothing
    elseif update == '' then
        -- Empty parameter
        tbl.insert(footer, string.format('Updated to %s(s) played on unknown.', eventWord))
    elseif string.lower(update) == 'future' then
        -- Future start date
        tbl.insert(footer, string.format('First %s(s) will be played: %s.', eventWord, start_date))
    else
        tbl.insert(footer, string.format('Updated to %s(s) played on %s.', eventWord, update))
    end
    
    -- Always add source since it will either be the provided source or citation needed template
    tbl.insert(footer, 'Source: ' .. source)
            
    -- Add score note
    local showColorText = (args.matches_style == 'FBR' or yesno(args.matches_style) or yesno(args.color_venue))
    local colorWord = yesno(args.use_tie) and 'colors' or 'colours'
    local scoreText
    if useHomeFirst then
        scoreText = 'Scores are listed with the home team\'s score first'
        if hasNeutralMatches(teams, args) then
            scoreText = scoreText .. '. Neutral matches are listed from the perspective of the team shown in the "Team" column'
        end
    else
        scoreText = string.format('Scores%s are listed from the perspective of the team shown in the "Team" column',
            showColorText and ' and legend ' .. colorWord or ''
        )
    end

    tbl.insert(footer, '<br />' .. scoreText .. '.')
    
    -- Handle venue key and legend together
    local needsVenueKey = not yesno(args.color_venue)
    local needsColorLegend = args.matches_style == 'FBR' or yesno(args.matches_style)
    local hasColorVenue = yesno(args.color_venue)
    
    if needsVenueKey or needsColorLegend or hasColorVenue then
        local combinedText = '<br />'
        local parts = {}

        -- First add venue information (either as key or colors)
        if needsVenueKey then
            -- Traditional venue key with separate label
            if needsColorLegend then
                -- If we also have color legend, use "Venue key:"
                tbl.insert(parts, 'Venue key: H = home, A = away' .. 
                    (hasNeutralMatches(teams, args) and ', N = neutral' or ''))
            else
                -- Otherwise use "Legend:" for consistency with other cases
                tbl.insert(parts, 'Legend: H = home, A = away' .. 
                    (hasNeutralMatches(teams, args) and ', N = neutral' or ''))
            end
        elseif hasColorVenue then
            -- Colored venue legend
            local matchWord = yesno(args.use_tie) and 'game' or 'match'
            local greyWord = yesno(args.use_tie) and 'Gray' or 'Grey'
            tbl.insert(parts, string.format('Legend: %s = home %s; Standard background = away %s%s', 
                greyWord,
                matchWord, 
                matchWord,
                hasNeutralMatches(teams, args) and string.format('; Orange = neutral %s', matchWord) or ''
            ))
        end
        
        -- Then add match result colors if enabled
        if needsColorLegend then
            local winColor = (args.matches_style == 'FBR') and 'Blue' or 'Green'
            if needsVenueKey then
                -- Use "Color legend:" when we have separate venue key
                if yesno(args.use_tie) then
                    tbl.insert(parts, 'Legend: ' .. winColor .. ' = win; Red = loss; Yellow = tie')
                else
                    tbl.insert(parts, 'Legend: ' .. winColor .. ' = win; Yellow = draw; Red = loss')
                end
            else
                -- If we're using color_venue, continue the legend with semicolons
                if yesno(args.use_tie) then
                    tbl.insert(parts, winColor .. ' = win; Red = loss; Yellow = tie')
                else
                    tbl.insert(parts, winColor .. ' = win; Yellow = draw; Red = loss')
                end
            end
        end
        
        -- Join parts with period if we have venue key and color legend,
        -- otherwise just concatenate the single part
        combinedText = combinedText .. tbl.concat(parts, '. ') .. '.'
        tbl.insert(footer, combinedText)
    end
    
    if args.display_codes and args.display_codes:lower():match('^footer') then
        local keyParts = {}
        local isUnlinked = args.display_codes:lower() == 'footer unlinked'
        
        -- Handle teams
        for i, team in ipairs(teams) do
            local teamName = args['name_' .. team]
            local displayCode = getDisplayCode(args, team)
            
            -- If using unlinked mode, get the display text without links
            if isUnlinked then
                teamName = getDisplayText(teamName)  -- Using the existing getDisplayText function
            end
            
            keyParts[i] = displayCode .. ' = ' .. teamName
        end
        
        local keyText = '<br />Team key: ' .. tbl.concat(keyParts, '; ') .. '.'
        tbl.insert(footer, keyText)
    end

    -- Add rivalry note if needed
    if args.a_note then
        tbl.insert(footer, '<br />For upcoming matches, an "a" indicates there is an article about the rivalry between the two participants.')
    end
    
    -- Create templatestyles tag
    local templatestyles = frame:extensionTag{
        name = 'templatestyles',
        args = { src = 'Module:Sports results/styles.css' }
    }
    
    -- Create footer content
    local footerHtml = ''
    if #footer > 0 then
        local footerDiv = mw.html.create('div')
            :addClass('sports-results-notes')
            :wikitext(tbl.concat(footer, ' '))
            
        footerHtml = templatestyles .. tostring(footerDiv)
    end
    
    -- Create outer overflow divs
    local outerDiv = mw.html.create('div')
        :css('overflow', 'hidden')
    
    local innerDiv = outerDiv:tag('div')
        :addClass('noresize')
        :addClass('overflowbugx')
        :css('overflow', 'auto')
    
    -- Add the table to the inner div
    innerDiv:node(root)
    
    -- Return everything wrapped together
    return tostring(outerDiv) .. footerHtml
end

return p