<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://stockhub.co/index.php?action=history&amp;feed=atom&amp;title=Module%3ATennis_performance_timeline</id>
	<title>Module:Tennis performance timeline - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://stockhub.co/index.php?action=history&amp;feed=atom&amp;title=Module%3ATennis_performance_timeline"/>
	<link rel="alternate" type="text/html" href="https://stockhub.co/index.php?title=Module:Tennis_performance_timeline&amp;action=history"/>
	<updated>2026-05-28T18:35:22Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://stockhub.co/index.php?title=Module:Tennis_performance_timeline&amp;diff=147245&amp;oldid=prev</id>
		<title>imported&gt;0xDeadbeef: repairing invalid color style tags (via WP:JWB)</title>
		<link rel="alternate" type="text/html" href="https://stockhub.co/index.php?title=Module:Tennis_performance_timeline&amp;diff=147245&amp;oldid=prev"/>
		<updated>2022-07-31T18:17:59Z</updated>

		<summary type="html">&lt;p&gt;repairing invalid color style tags (via &lt;a href=&quot;/index.php?title=WP:JWB&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;WP:JWB (page does not exist)&quot;&gt;WP:JWB&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {}&lt;br /&gt;
&lt;br /&gt;
local concat = table.concat&lt;br /&gt;
local insert = table.insert&lt;br /&gt;
local format = mw.ustring.format&lt;br /&gt;
&lt;br /&gt;
local tConfig = mw.loadData(&amp;quot;Module:Tennis performance timeline/data&amp;quot;)&lt;br /&gt;
local rounds = tConfig.rounds&lt;br /&gt;
local tournaments = tConfig.tournaments&lt;br /&gt;
local environments = tConfig.environments&lt;br /&gt;
local surfaces = tConfig.surfaces&lt;br /&gt;
local tOrders = tConfig.orders&lt;br /&gt;
&lt;br /&gt;
local curYear = os.date(&amp;quot;!*t&amp;quot;).year&lt;br /&gt;
local calendar = mw.loadData(&amp;quot;Module:Tennis performance timeline/calendar&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local genders = {&lt;br /&gt;
	men = &amp;quot;Men&amp;#039;s&amp;quot;,&lt;br /&gt;
	women = &amp;quot;Women&amp;#039;s&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local matchTypes = {&lt;br /&gt;
	singles = &amp;quot;Singles&amp;quot;,&lt;br /&gt;
	doubles = &amp;quot;Doubles&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
--[[Utility functions]]&lt;br /&gt;
&lt;br /&gt;
local function checkNonNil(value, type)&lt;br /&gt;
	if value == nil then&lt;br /&gt;
		error(&amp;quot;Expected &amp;quot; .. type .. &amp;quot;, but is nil&amp;quot;, 2)&lt;br /&gt;
	end&lt;br /&gt;
	return value&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function checkFormat(str, pattern, type)&lt;br /&gt;
	if str == mw.ustring.match(str, pattern) then&lt;br /&gt;
		return str&lt;br /&gt;
	else&lt;br /&gt;
		error(&amp;quot;Invalid &amp;quot; .. type .. &amp;quot;: &amp;quot; .. str, 2)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function checkYear(year)&lt;br /&gt;
	checkNonNil(year, &amp;quot;year&amp;quot;)&lt;br /&gt;
	return checkFormat(year, &amp;quot;%d%d%d%d&amp;quot;, &amp;quot;year&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function checkNum(num, diagMsg)&lt;br /&gt;
	diagMsg = &amp;quot;number&amp;quot; .. (diagMsg and &amp;quot; for &amp;quot; .. diagMsg or &amp;quot;&amp;quot;)&lt;br /&gt;
	checkNonNil(num, diagMsg);&lt;br /&gt;
	return checkFormat(num, &amp;quot;%d+&amp;quot;, diagMsg)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function checkMember(elem, arr, type, diagMsg)&lt;br /&gt;
	if arr[elem] then&lt;br /&gt;
		return elem&lt;br /&gt;
	else&lt;br /&gt;
		diagMsg = type .. (diagMsg and &amp;quot; for &amp;quot; .. diagMsg or &amp;quot;&amp;quot;)&lt;br /&gt;
		checkNonNil(elem, diagMsg)&lt;br /&gt;
		local message = {}&lt;br /&gt;
		insert(message, &amp;quot;Invalid &amp;quot;)&lt;br /&gt;
		insert(message, diagMsg)&lt;br /&gt;
		insert(message, &amp;quot;: &amp;quot;)&lt;br /&gt;
		insert(message, elem)&lt;br /&gt;
		error(concat(message))&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Format an HTML element with link, tooltip, and colors.&lt;br /&gt;
local function tooltip(tag, link, tooltip, text, spec)&lt;br /&gt;
	spec = spec or {}&lt;br /&gt;
	if spec.color then&lt;br /&gt;
		tag:css(&amp;#039;color&amp;#039;, spec.color)&lt;br /&gt;
	end&lt;br /&gt;
	if spec.italic then&lt;br /&gt;
		tag:wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;quot;);&lt;br /&gt;
	end&lt;br /&gt;
	if spec.bold then&lt;br /&gt;
		tag:wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot;);&lt;br /&gt;
	end&lt;br /&gt;
	if link then&lt;br /&gt;
		tag:wikitext(&amp;quot;[[&amp;quot; .. link .. &amp;quot;|&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	if tooltip then&lt;br /&gt;
		if spec.abbr then&lt;br /&gt;
			tag:tag(&amp;#039;abbr&amp;#039;):attr(&amp;#039;title&amp;#039;, tooltip):wikitext(text)&lt;br /&gt;
		else&lt;br /&gt;
			tag:attr(&amp;#039;title&amp;#039;, tooltip):wikitext(text)&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		tag:wikitext(text)&lt;br /&gt;
	end&lt;br /&gt;
	if link then tag:wikitext(&amp;quot;]]&amp;quot;) end&lt;br /&gt;
	if spec.bold then&lt;br /&gt;
		tag:wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot;);&lt;br /&gt;
	end&lt;br /&gt;
	if spec.italic then&lt;br /&gt;
		tag:wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;quot;);&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Substitute &amp;quot;$[`param`]$&amp;quot; appearing in `str` with `value`.&lt;br /&gt;
-- For example, subst(&amp;quot;$year$ ATP Tour&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;1990&amp;quot;) -&amp;gt; &amp;quot;1990 ATP Tour&amp;quot;&lt;br /&gt;
local function subst(str, param, value)&lt;br /&gt;
	if str == nil then return str end&lt;br /&gt;
	return str:gsub(&amp;quot;%$&amp;quot; .. param .. &amp;quot;%$&amp;quot;, value)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function tr()&lt;br /&gt;
	return mw.html.create(&amp;#039;tr&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function th(row)&lt;br /&gt;
	return row:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function td(row)&lt;br /&gt;
	return row:tag(&amp;#039;td&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local years&lt;br /&gt;
local data&lt;br /&gt;
local usedRounds&lt;br /&gt;
&lt;br /&gt;
-- parser for data tables&lt;br /&gt;
local function parse(entry, year, tStats)&lt;br /&gt;
	local entryType = type(entry)&lt;br /&gt;
	if entryType == &amp;quot;string&amp;quot; then&lt;br /&gt;
		return entry&lt;br /&gt;
	elseif entryType == &amp;quot;table&amp;quot; then&lt;br /&gt;
		if entry.type == &amp;quot;chrono&amp;quot; then&lt;br /&gt;
			local numericYear = tonumber(year)&lt;br /&gt;
			for _,elem in ipairs(entry) do&lt;br /&gt;
				if type(elem) == &amp;quot;table&amp;quot; and numericYear &amp;gt;= elem[1] then&lt;br /&gt;
					return parse(elem[2], year, tStats)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			return parse(entry.default, year, tStats)&lt;br /&gt;
		elseif entry.type == &amp;quot;switch&amp;quot; then&lt;br /&gt;
			local param = entry.param&lt;br /&gt;
			local arg = param == &amp;quot;year&amp;quot; and year or param == &amp;quot;gender&amp;quot; and data.gender or tStats[param]&lt;br /&gt;
			if entry[arg] then return parse(entry[arg], year, tStats) end&lt;br /&gt;
			return parse(entry.default, year, tStats)&lt;br /&gt;
		else&lt;br /&gt;
			return entry&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Transform `param` entry in `array` with supplied data.&lt;br /&gt;
local function transform(array, param, data, tournament, year, country)&lt;br /&gt;
	local entry = array[param]&lt;br /&gt;
	entry = subst(entry, &amp;quot;gender&amp;quot;, genders[data.gender])&lt;br /&gt;
	entry = subst(entry, &amp;quot;matchType&amp;quot;, matchTypes[data.matchType])&lt;br /&gt;
	entry = subst(entry, &amp;quot;year&amp;quot;, year)&lt;br /&gt;
	if data[tournament] and data[tournament][year] then&lt;br /&gt;
		local tStats = data[tournament][year]&lt;br /&gt;
		if tournaments[tournament] and tournaments[tournament].region then&lt;br /&gt;
			entry = subst(entry, &amp;quot;region&amp;quot;,&lt;br /&gt;
				parse(tournaments[tournament].region[country]&lt;br /&gt;
					or tournaments[tournament].region.default, year, tStats))&lt;br /&gt;
		end&lt;br /&gt;
		if tournaments[tournament].group then&lt;br /&gt;
			entry = subst(entry, &amp;quot;group&amp;quot;, tournaments[tournament].group[tStats.group] or &amp;quot;&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	array[param] = entry&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Return wiki page title for the tournament in a given year.&lt;br /&gt;
local function annualTournamentLink(year, tStats, tInfo)&lt;br /&gt;
	local annualLink = tInfo.annualLink&lt;br /&gt;
	return parse(annualLink, year, tStats)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[-&lt;br /&gt;
Prepare the header row of the performance timeline table.&lt;br /&gt;
@return three elements:&lt;br /&gt;
- table header wikitext&lt;br /&gt;
- header row&lt;br /&gt;
- the first cell in the header row&lt;br /&gt;
]]&lt;br /&gt;
local function header()&lt;br /&gt;
	local rows = {}&lt;br /&gt;
	local row = tr()&lt;br /&gt;
	local headerCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):addClass(&amp;#039;unsortable&amp;#039;)&lt;br /&gt;
	local tooltipSpec = {abbr = true}&lt;br /&gt;
	if years.amateur or years.professional then&lt;br /&gt;
		headerCell:attr(&amp;#039;rowspan&amp;#039;, 2)&lt;br /&gt;
		local eras = {}&lt;br /&gt;
		if years.amateur then&lt;br /&gt;
			eras[#eras+1] = {years.amateur, &amp;quot;Amateur&amp;quot;}&lt;br /&gt;
		end&lt;br /&gt;
		if years.professional then&lt;br /&gt;
			eras[#eras+1] = {years.professional, &amp;quot;Professional&amp;quot;}&lt;br /&gt;
		end&lt;br /&gt;
		eras[#eras+1] = {&amp;quot;1968&amp;quot;, &amp;quot;Open Era&amp;quot;}&lt;br /&gt;
		eras[#eras+1] = {tostring(years.last + 1)}&lt;br /&gt;
		local lastEra = &amp;quot;?&amp;quot;&lt;br /&gt;
		local idx = 1&lt;br /&gt;
		for _,era in ipairs(eras) do&lt;br /&gt;
			local count = 0&lt;br /&gt;
			while years[idx] and years[idx] ~= era[1] do&lt;br /&gt;
				count = count + 1&lt;br /&gt;
				idx = idx + 1&lt;br /&gt;
			end&lt;br /&gt;
			if count &amp;gt; 0 then&lt;br /&gt;
				th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):attr(&amp;#039;colspan&amp;#039;, count):wikitext(lastEra)&lt;br /&gt;
			end&lt;br /&gt;
			lastEra = era[2]&lt;br /&gt;
		end&lt;br /&gt;
		tooltip(th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):attr(&amp;#039;rowspan&amp;#039;, 2), nil, &amp;quot;Strike rate&amp;quot;, &amp;quot;SR&amp;quot;, tooltipSpec)&lt;br /&gt;
		tooltip(th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):attr(&amp;#039;rowspan&amp;#039;, 2), nil, &amp;quot;Win–Loss&amp;quot;, &amp;quot;W–L&amp;quot;, tooltipSpec)&lt;br /&gt;
		th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):attr(&amp;#039;rowspan&amp;#039;, 2):wikitext(&amp;quot;Win %&amp;quot;)&lt;br /&gt;
		insert(rows, row)&lt;br /&gt;
		row = tr()&lt;br /&gt;
	end&lt;br /&gt;
	if data.categories.count &amp;gt; 1 then&lt;br /&gt;
		headerCell:attr(&amp;#039;colspan&amp;#039;, 2):wikitext(&amp;quot;Tournament&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	for _,year in ipairs(years) do&lt;br /&gt;
		local link = subst(parse(tConfig.tours.link, year), &amp;quot;year&amp;quot;, year)&lt;br /&gt;
		th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):addClass(&amp;#039;unsortable&amp;#039;)&lt;br /&gt;
			:wikitext(link and format(&amp;quot;[[%s|%s]]&amp;quot;, link, year) or year)&lt;br /&gt;
	end&lt;br /&gt;
	if #rows == 0 then&lt;br /&gt;
		tooltip(th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;), nil, &amp;quot;Strike rate&amp;quot;, &amp;quot;SR&amp;quot;, tooltipSpec)&lt;br /&gt;
		tooltip(th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;), nil, &amp;quot;Win–Loss&amp;quot;, &amp;quot;W–L&amp;quot;, tooltipSpec)&lt;br /&gt;
		th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;col&amp;#039;):wikitext(&amp;quot;Win %&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	insert(rows, row)&lt;br /&gt;
&lt;br /&gt;
	-- Add hatnote if needed.&lt;br /&gt;
	local headerText = {}&lt;br /&gt;
	if years.last and data.last and tournaments[data.last] then&lt;br /&gt;
		local tInfo = tournaments[data.last]&lt;br /&gt;
		local annualLink = {link = annualTournamentLink(years.last, nil, tInfo)}&lt;br /&gt;
		transform(annualLink, &amp;quot;link&amp;quot;, data, data.last, years.last)&lt;br /&gt;
		local hatnote = {}&lt;br /&gt;
		insert(hatnote, &amp;quot;&amp;#039;&amp;#039;This table includes results through the conclusion of the [[&amp;quot;)&lt;br /&gt;
		annualLink = annualLink.link:gsub(&amp;quot; – .*$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
		local annualName = annualLink&lt;br /&gt;
		local annualSubst = tInfo.annualSubst or {}&lt;br /&gt;
		for _,subst in ipairs(annualSubst) do&lt;br /&gt;
			annualName = annualName:gsub(subst[1], subst[2])&lt;br /&gt;
		end&lt;br /&gt;
		if annualLink ~= annualName then&lt;br /&gt;
			insert(hatnote, annualLink)&lt;br /&gt;
			insert(hatnote, &amp;quot;|&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		insert(hatnote, annualName)&lt;br /&gt;
		insert(hatnote, &amp;quot;]].&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
		insert(headerText, concat(hatnote))&lt;br /&gt;
	end&lt;br /&gt;
	insert(headerText, &amp;quot;{|class=\&amp;quot;plainrowheaders wikitable sortable\&amp;quot; style=text-align:center&amp;quot;)&lt;br /&gt;
	return concat(headerText, &amp;quot;\n&amp;quot;), rows, headerCell&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function outputSpan(row, span, seenRounds)&lt;br /&gt;
	local cell = td(row):attr(&amp;#039;colspan&amp;#039;, span.span)&lt;br /&gt;
	tooltip(cell, nil,&lt;br /&gt;
		span.span &amp;lt; span.info.minSpellCols and span.info.tooltip,&lt;br /&gt;
		span.span &amp;gt;= span.info.minSpellCols and span.info.tooltip or span.round,&lt;br /&gt;
		span.info)&lt;br /&gt;
	if seenRounds and span.span &amp;lt; span.info.minSpellCols then&lt;br /&gt;
		seenRounds[span.info.round] = span.info&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local footnoteCount&lt;br /&gt;
&lt;br /&gt;
-- Return the year a given tournament was first held.&lt;br /&gt;
local function eventFirstYear(entries, yearInfos, year, yearEntry)&lt;br /&gt;
	year = tonumber(year)&lt;br /&gt;
	local startYear&lt;br /&gt;
	if #entries &amp;gt; 0 then&lt;br /&gt;
		local endYear = entries[#entries][4]&lt;br /&gt;
		for testYear = endYear + 1, year do&lt;br /&gt;
			local testYearTournament = parse(yearInfos, testYear)&lt;br /&gt;
			if testYearTournament ~= entries[#entries][1] then&lt;br /&gt;
				entries[#entries][4] = testYear - 1&lt;br /&gt;
				break;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		for testYear = year, endYear, -1 do&lt;br /&gt;
			local testYearTournament = parse(yearInfos, testYear)&lt;br /&gt;
			if testYearTournament ~= yearEntry then&lt;br /&gt;
				startYear = testYear + 1&lt;br /&gt;
				break;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return startYear&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local frame_&lt;br /&gt;
&lt;br /&gt;
-- Format a footnote.&lt;br /&gt;
local function footnoteText(footnoteChunks, wikilinkFn)&lt;br /&gt;
	if #footnoteChunks &amp;gt; 1 then&lt;br /&gt;
		local footnote = {}&lt;br /&gt;
		local footnoteChunk = {&amp;quot;Held as&amp;quot;}&lt;br /&gt;
		for pos,entry in ipairs(footnoteChunks) do&lt;br /&gt;
			local link, name, abbr = wikilinkFn(entry)&lt;br /&gt;
			insert(footnoteChunk, format(&amp;quot;[[%s%s]]&amp;quot;,&lt;br /&gt;
				link and link .. &amp;quot;|&amp;quot; or &amp;quot;&amp;quot;, name))&lt;br /&gt;
			-- Add abbr if doesn&amp;#039;t appear in name&lt;br /&gt;
			if abbr and not name:match(abbr) then&lt;br /&gt;
				insert(footnoteChunk, &amp;quot;(&amp;quot; .. abbr .. &amp;quot;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			if entry[3] == entry[4] then&lt;br /&gt;
				-- One-year event&lt;br /&gt;
				insert(footnoteChunk, &amp;quot;in&amp;quot;)&lt;br /&gt;
				insert(footnoteChunk, entry[3])&lt;br /&gt;
			else&lt;br /&gt;
				if pos &amp;gt; 1 then&lt;br /&gt;
				insert(footnoteChunk, &amp;quot;from&amp;quot;)&lt;br /&gt;
				insert(footnoteChunk, entry[3])&lt;br /&gt;
				end&lt;br /&gt;
				if pos &amp;lt; #footnoteChunks then&lt;br /&gt;
					insert(footnoteChunk, pos == 1 and &amp;quot;until&amp;quot; or &amp;quot;to&amp;quot;)&lt;br /&gt;
					insert(footnoteChunk, entry[4])&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			insert(footnote, concat(footnoteChunk, (&amp;quot; &amp;quot;)))&lt;br /&gt;
			footnoteChunk = {}&lt;br /&gt;
		end&lt;br /&gt;
		footnote[#footnote] = &amp;quot;and &amp;quot; .. footnote[#footnote]&lt;br /&gt;
		return frame_:callParserFunction{name = &amp;#039;#tag:ref&amp;#039;, args = {&lt;br /&gt;
			concat(footnote, #footnote &amp;gt; 2 and &amp;quot;, &amp;quot; or &amp;quot; &amp;quot;),&lt;br /&gt;
			group = &amp;quot;lower-alpha&amp;quot;&lt;br /&gt;
		}}&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function winLossStatsSummary(row, stats, boldmarkup, summaryRowspan)&lt;br /&gt;
	boldmarkup = boldmarkup or &amp;quot;&amp;quot;&lt;br /&gt;
	-- &amp;amp;#8722; is (nonbreaking) minus sign.&lt;br /&gt;
	local srCell = td(row):attr(&amp;#039;data-sort-value&amp;#039;, stats.count &amp;gt; 0 and stats.champs / stats.count or -1)&lt;br /&gt;
		:addClass(&amp;#039;nowrap&amp;#039;)&lt;br /&gt;
		:wikitext(format(&amp;quot;%s%d / %d%s&amp;quot;, boldmarkup, stats.champs, stats.count, boldmarkup))&lt;br /&gt;
	local matches = stats.wins + stats.losses&lt;br /&gt;
	local wlCell = td(row):attr(&amp;#039;data-sort-value&amp;#039;, matches &amp;gt; 0 and stats.wins / matches or -1)&lt;br /&gt;
	local wrCell = td(row)&lt;br /&gt;
	if summaryRowspan then&lt;br /&gt;
		wlCell:attr(&amp;#039;rowspan&amp;#039;, summaryRowspan)&lt;br /&gt;
		wrCell:attr(&amp;#039;rowspan&amp;#039;, summaryRowspan)&lt;br /&gt;
		srCell:attr(&amp;#039;rowspan&amp;#039;, summaryRowspan)&lt;br /&gt;
	end&lt;br /&gt;
	if matches &amp;gt; 0 then&lt;br /&gt;
		wlCell:wikitext(format(&amp;quot;%s%d–%d%s&amp;quot;, boldmarkup, stats.wins, stats.losses, boldmarkup))&lt;br /&gt;
		wlCell:addClass(&amp;quot;nowrap&amp;quot;)&lt;br /&gt;
		wrCell:wikitext(format(&amp;quot;%s%.2f%%%s&amp;quot;, boldmarkup, stats.wins * 100 / matches, boldmarkup))&lt;br /&gt;
	else&lt;br /&gt;
		wlCell:wikitext(format(&amp;quot;%s–%s&amp;quot;, boldmarkup, boldmarkup))&lt;br /&gt;
		wrCell:attr(&amp;#039;data-sort-value&amp;#039;, &amp;#039;-1%&amp;#039;)&lt;br /&gt;
			:wikitext(format(&amp;quot;%s–%s&amp;quot;, boldmarkup, boldmarkup))&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Prepare a Win-Loss row in the performance timeline table.&lt;br /&gt;
local function winLossStatsRow(row, info, stats, bold, summaryRowspan)&lt;br /&gt;
	local boldmarkup = bold and &amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot; or &amp;quot;&amp;quot;&lt;br /&gt;
	local headerName = info.name and info.name .. &amp;quot; &amp;quot; or &amp;quot;&amp;quot;&lt;br /&gt;
	th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;)&lt;br /&gt;
		:wikitext(format(&amp;quot;%s%sWin–Loss%s&amp;quot;, boldmarkup, headerName, boldmarkup))&lt;br /&gt;
	local span&lt;br /&gt;
	for _,year in ipairs(years) do&lt;br /&gt;
		local yStats = stats[year]&lt;br /&gt;
		local display = {}&lt;br /&gt;
		if yStats and yStats.wins + yStats.losses &amp;gt; 0 then&lt;br /&gt;
			display.text = format(&amp;quot;%s%d–%d%s&amp;quot;, boldmarkup, yStats.wins, yStats.losses, boldmarkup)&lt;br /&gt;
		    bg_col = &amp;#039;background-color:#EAECF0&amp;#039;&lt;br /&gt;
		else&lt;br /&gt;
			display.text = format(&amp;quot;%s–%s&amp;quot;, boldmarkup, boldmarkup)&lt;br /&gt;
			bg_col = &amp;#039;background-color:#EAECF0&amp;#039;&lt;br /&gt;
			if info.absence then&lt;br /&gt;
				local aConfig = parse(info.absence, year)&lt;br /&gt;
				if aConfig then&lt;br /&gt;
					display.text = aConfig.round&lt;br /&gt;
					display.config = aConfig&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if span then&lt;br /&gt;
			if span.info.round == display.text then&lt;br /&gt;
				span.span = span.span + 1&lt;br /&gt;
			else&lt;br /&gt;
				outputSpan(row, span)&lt;br /&gt;
				span = nil&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if not span then&lt;br /&gt;
			if display.config and display.config.span then&lt;br /&gt;
				span = {round = display.text, span = 1, info = display.config}&lt;br /&gt;
			else&lt;br /&gt;
				td(row):wikitext(display.text)&lt;br /&gt;
					:addClass(&amp;quot;nowrap&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if span then&lt;br /&gt;
		outputSpan(row, span)&lt;br /&gt;
		span = nil&lt;br /&gt;
	end&lt;br /&gt;
	winLossStatsSummary(row, stats, boldmarkup, summaryRowspan)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Return true if the player appears in a given tournament.&lt;br /&gt;
local function hasTournamentAppearance(tournament)&lt;br /&gt;
	local tournamentType = type(tournament)&lt;br /&gt;
	if tournamentType == &amp;quot;string&amp;quot; then&lt;br /&gt;
		if data[tournament] then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	elseif tournamentType == &amp;quot;table&amp;quot; then&lt;br /&gt;
		if tournament.type == &amp;quot;chrono&amp;quot; then&lt;br /&gt;
			for _,entry in ipairs(tournament) do&lt;br /&gt;
				if data[entry[2]] then&lt;br /&gt;
					return true&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if data[tournament.default] then&lt;br /&gt;
				return true&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			-- TODO other table type&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Create a fresh statistics table.&lt;br /&gt;
local function statsFactory()&lt;br /&gt;
	return {count = 0, champs = 0, finals = 0, wins = 0, losses = 0}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Generate performance timeline rows for a given tournament level.&lt;br /&gt;
local function body(level, levelHeaderCell)&lt;br /&gt;
	local entries = {}&lt;br /&gt;
	local stats = statsFactory()&lt;br /&gt;
	local levelInfo = parse(tOrders[level])&lt;br /&gt;
	local levelInfos = {}&lt;br /&gt;
	local levelLastAppearance&lt;br /&gt;
	for pos,tournament in ipairs(levelInfo) do&lt;br /&gt;
		if hasTournamentAppearance(tournament) then&lt;br /&gt;
			local tStats = statsFactory()&lt;br /&gt;
			local row = tr()&lt;br /&gt;
			if #entries == 0 and data.categories.count &amp;gt; 1 then&lt;br /&gt;
				levelHeaderCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;)&lt;br /&gt;
									:css(&amp;#039;width&amp;#039;, &amp;#039;6.5em&amp;#039;)&lt;br /&gt;
									:css(&amp;#039;max-width&amp;#039;, &amp;#039;10em&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
			local headerCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;)&lt;br /&gt;
			local tInfos = {}&lt;br /&gt;
			local lastAppearance&lt;br /&gt;
			local seenRounds = {}&lt;br /&gt;
			local span&lt;br /&gt;
			local country = data.country.default&lt;br /&gt;
			for _,year in ipairs(years) do&lt;br /&gt;
				country = data.country[year] or country&lt;br /&gt;
				local yearLevelName = parse(levelInfo.name or levelInfo.tooltip, year)&lt;br /&gt;
				if #entries == 0 and (#levelInfos == 0 or levelInfos[#levelInfos][1] ~= yearLevelName) then&lt;br /&gt;
					-- Add footnote noting series transition.&lt;br /&gt;
					local startYear = eventFirstYear(levelInfos, levelInfo.name or levelInfo.tooltip, year, yearLevelName)&lt;br /&gt;
					insert(levelInfos, {&lt;br /&gt;
						yearLevelName,&lt;br /&gt;
						{link = parse(levelInfo.link, year), abbr = parse(levelInfo.abbr, year)},&lt;br /&gt;
						startYear, tonumber(year)&lt;br /&gt;
					})&lt;br /&gt;
				else&lt;br /&gt;
					levelInfos[#levelInfos][4] = tonumber(year)&lt;br /&gt;
				end&lt;br /&gt;
				local yearTournament = parse(tournament, year)&lt;br /&gt;
				local tInfo = checkNonNil(tournaments[yearTournament], &amp;quot;entry for &amp;quot; .. yearTournament)&lt;br /&gt;
				if #tInfos == 0 or tInfos[#tInfos][2] ~= tInfo then&lt;br /&gt;
					-- Add footnote noting tournament transition.&lt;br /&gt;
					local startYear = eventFirstYear(tInfos, tournament, year, yearTournament)&lt;br /&gt;
					insert(tInfos, {yearTournament, tInfo, startYear, tonumber(year)})&lt;br /&gt;
				else&lt;br /&gt;
					tInfos[#tInfos][4] = tonumber(year)&lt;br /&gt;
				end&lt;br /&gt;
				local display = {}&lt;br /&gt;
				if data[yearTournament] and data[yearTournament][year] then&lt;br /&gt;
					if not levelLastAppearance or levelLastAppearance &amp;lt; year then&lt;br /&gt;
						levelLastAppearance = year&lt;br /&gt;
					end&lt;br /&gt;
					lastAppearance = tInfo&lt;br /&gt;
					local tyStats = data[yearTournament][year]&lt;br /&gt;
					if not rounds[tyStats.round].nocount then&lt;br /&gt;
						tStats.count = tStats.count + 1&lt;br /&gt;
						stats.count = stats.count + 1&lt;br /&gt;
					end&lt;br /&gt;
					if rounds[tyStats.round].strike then&lt;br /&gt;
						tStats.champs = tStats.champs + 1&lt;br /&gt;
						stats.champs = stats.champs + 1&lt;br /&gt;
					end&lt;br /&gt;
					if not stats[year] then&lt;br /&gt;
						stats[year] = statsFactory()&lt;br /&gt;
					end&lt;br /&gt;
					tStats.wins = tStats.wins + tyStats.wins&lt;br /&gt;
					stats[year].wins = stats[year].wins + tyStats.wins&lt;br /&gt;
					stats.wins = stats.wins + tyStats.wins&lt;br /&gt;
					tStats.losses = tStats.losses + tyStats.losses&lt;br /&gt;
					stats[year].losses = stats[year].losses + tyStats.losses&lt;br /&gt;
					stats.losses = stats.losses + tyStats.losses&lt;br /&gt;
					local annualLink = annualTournamentLink(year, tyStats, tInfo)&lt;br /&gt;
					display.round = tyStats.round&lt;br /&gt;
					display.group = tyStats.group&lt;br /&gt;
					display.link = annualLink&lt;br /&gt;
					transform(display, &amp;quot;link&amp;quot;, data, yearTournament, year, country)&lt;br /&gt;
				else&lt;br /&gt;
					display.round = parse(tInfo.absence, year) or &amp;quot;A&amp;quot;&lt;br /&gt;
				end&lt;br /&gt;
				local round = rounds[display.round]&lt;br /&gt;
				if round.absence then&lt;br /&gt;
					local absence = false&lt;br /&gt;
					if year &amp;lt; years.last or not data.last and tonumber(year) &amp;lt; curYear then&lt;br /&gt;
						absence = true&lt;br /&gt;
					elseif year == years.last and data.last and calendar[year] and calendar[year][data.gender] then&lt;br /&gt;
						local tWeek = calendar[year][data.gender].week[yearTournament]&lt;br /&gt;
						local lWeek = calendar[year][data.gender].week[data.last]&lt;br /&gt;
						if tWeek and lWeek and tWeek &amp;lt;= lWeek then&lt;br /&gt;
							absence = true&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					if not absence then display.round = nil end&lt;br /&gt;
				end&lt;br /&gt;
				local roundInfo = {}&lt;br /&gt;
				setmetatable(roundInfo, {__index = rounds[display.round]})&lt;br /&gt;
				transform(roundInfo, &amp;quot;tooltip&amp;quot;, data, yearTournament, year)&lt;br /&gt;
				if roundInfo.group then&lt;br /&gt;
					roundInfo.round = display.round .. display.group&lt;br /&gt;
				else&lt;br /&gt;
					roundInfo.round = display.round&lt;br /&gt;
				end&lt;br /&gt;
				display.round = roundInfo.name or roundInfo.round&lt;br /&gt;
				if span then&lt;br /&gt;
					if span.round == display.round then&lt;br /&gt;
						span.span = span.span + 1&lt;br /&gt;
					else&lt;br /&gt;
						outputSpan(row, span, seenRounds)&lt;br /&gt;
						span = nil&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				if not span then&lt;br /&gt;
					if roundInfo.span then&lt;br /&gt;
						span = {round = display.round, span = 1, info = roundInfo}&lt;br /&gt;
					else&lt;br /&gt;
						local cell = td(row)&lt;br /&gt;
						if roundInfo.round then&lt;br /&gt;
							if roundInfo.bgcolor then&lt;br /&gt;
								cell:css(&amp;#039;background&amp;#039;, roundInfo.bgcolor)&lt;br /&gt;
							end&lt;br /&gt;
							tooltip(cell, display.link, roundInfo.tooltip, display.round, roundInfo)&lt;br /&gt;
							seenRounds[roundInfo.round] = roundInfo&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if span then&lt;br /&gt;
				outputSpan(row, span, seenRounds)&lt;br /&gt;
				span = nil&lt;br /&gt;
			end&lt;br /&gt;
			if lastAppearance then&lt;br /&gt;
				headerCell:wikitext(&lt;br /&gt;
					format(&amp;quot;[[%s%s]]&amp;quot;,&lt;br /&gt;
						lastAppearance.link and lastAppearance.link .. &amp;quot;|&amp;quot; or&lt;br /&gt;
							lastAppearance.abbr and lastAppearance.name .. &amp;quot;|&amp;quot; or &amp;quot;&amp;quot;,&lt;br /&gt;
						lastAppearance.abbr or lastAppearance.name))&lt;br /&gt;
				if #tInfos &amp;gt; 1 then&lt;br /&gt;
					local footnote = footnoteText(tInfos,&lt;br /&gt;
						function(entry)&lt;br /&gt;
							local tInfo = entry[2]&lt;br /&gt;
							return tInfo.link, tInfo.name, tInfo.abbr&lt;br /&gt;
						end)&lt;br /&gt;
					headerCell:wikitext(footnote)&lt;br /&gt;
					footnoteCount = footnoteCount + 1&lt;br /&gt;
				end&lt;br /&gt;
				winLossStatsSummary(row, tStats)&lt;br /&gt;
				insert(entries, row)&lt;br /&gt;
				for _,roundInfo in pairs(seenRounds) do&lt;br /&gt;
					local round = roundInfo.name and not usedRounds[roundInfo.name] and roundInfo.name or roundInfo.round&lt;br /&gt;
					usedRounds[round] = roundInfo&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #entries == 0 then return nil end&lt;br /&gt;
	if levelHeaderCell then&lt;br /&gt;
		local levelLink = parse(levelInfo.link, levelLastAppearance)&lt;br /&gt;
		local levelName = parse(levelInfo.name, levelLastAppearance)&lt;br /&gt;
		local levelAbbr = parse(levelInfo.abbr, levelLastAppearance)&lt;br /&gt;
		local levelTooltip = parse(levelInfo.tooltip, levelLastAppearance)&lt;br /&gt;
		local levelString = levelAbbr or levelName&lt;br /&gt;
		if data.categories.count &amp;gt; 1 then&lt;br /&gt;
			levelHeaderCell:attr(&amp;#039;rowspan&amp;#039;, #entries + 1)&lt;br /&gt;
		end&lt;br /&gt;
		tooltip(levelHeaderCell, levelLink or levelAbbr and levelName,&lt;br /&gt;
			levelTooltip, levelString, {bold = true, abbr = true})&lt;br /&gt;
		if #levelInfos &amp;gt; 1 then&lt;br /&gt;
			local footnote = footnoteText(levelInfos,&lt;br /&gt;
				function(entry)&lt;br /&gt;
					return entry[2].link, entry[1], entry[2].abbr&lt;br /&gt;
				end)&lt;br /&gt;
			levelHeaderCell:wikitext(footnote)&lt;br /&gt;
			footnoteCount = footnoteCount + 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local row = tr()&lt;br /&gt;
	winLossStatsRow(row, {}, stats, true)&lt;br /&gt;
	insert(entries, row)&lt;br /&gt;
	local result = {}&lt;br /&gt;
	for _,entry in ipairs(entries) do&lt;br /&gt;
		insert(result, tostring(entry))&lt;br /&gt;
	end&lt;br /&gt;
	return concat(result, &amp;quot;\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Generate rows for career performance timeline.&lt;br /&gt;
local function summary(envSummary, headerCell)&lt;br /&gt;
	local entries = {}&lt;br /&gt;
	local stats = statsFactory()&lt;br /&gt;
	local environmentInfo = tOrders.environments&lt;br /&gt;
	local surfaceInfo = tOrders.surfaces&lt;br /&gt;
	local surfaceCount = 0&lt;br /&gt;
	for _,environment in ipairs(environmentInfo) do&lt;br /&gt;
		if data[environment] then&lt;br /&gt;
			for _,surface in ipairs(surfaceInfo) do&lt;br /&gt;
				if data[environment][surface] then&lt;br /&gt;
					surfaceCount = surfaceCount + 1&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if surfaceCount == 0 then return nil end&lt;br /&gt;
	-- Aggregate data.&lt;br /&gt;
	local eStats = {}&lt;br /&gt;
	local sStats = {}&lt;br /&gt;
	for _,env in ipairs(environmentInfo) do&lt;br /&gt;
		if data[env] then&lt;br /&gt;
			for _,surface in ipairs(surfaceInfo) do&lt;br /&gt;
				if data[env][surface] then&lt;br /&gt;
					for _,year in ipairs(years) do&lt;br /&gt;
						local esyStats = data[env][surface][year]&lt;br /&gt;
						if esyStats then&lt;br /&gt;
							if not eStats[env] then&lt;br /&gt;
								eStats[env] = statsFactory()&lt;br /&gt;
							end&lt;br /&gt;
							if not eStats[env][year] then&lt;br /&gt;
								eStats[env][year] = statsFactory()&lt;br /&gt;
							end&lt;br /&gt;
							if not sStats[surface] then&lt;br /&gt;
								sStats[surface] = statsFactory()&lt;br /&gt;
							end&lt;br /&gt;
							if not sStats[surface][year] then&lt;br /&gt;
								sStats[surface][year] = statsFactory()&lt;br /&gt;
							end&lt;br /&gt;
							if not stats[year] then&lt;br /&gt;
								stats[year] = statsFactory()&lt;br /&gt;
							end&lt;br /&gt;
							for key,value in pairs(esyStats) do&lt;br /&gt;
								sStats[surface][year][key] = sStats[surface][year][key] + value&lt;br /&gt;
								sStats[surface][key] = sStats[surface][key] + value&lt;br /&gt;
								eStats[env][year][key] = eStats[env][year][key] + value&lt;br /&gt;
								eStats[env][key] = eStats[env][key] + value&lt;br /&gt;
								stats[year][key] = stats[year][key] + value&lt;br /&gt;
								stats[key] = stats[key] + value&lt;br /&gt;
							end&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local function venueWinLossStatsRow(venues, vInfo, vStats)&lt;br /&gt;
		for _,venue in ipairs(venues) do&lt;br /&gt;
			if vStats[venue] then&lt;br /&gt;
				local row = tr()&lt;br /&gt;
				if #entries == 0 and data.categories.count &amp;gt; 1 then&lt;br /&gt;
					-- Add header cell.&lt;br /&gt;
					headerCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;)&lt;br /&gt;
				end&lt;br /&gt;
				winLossStatsRow(row, vInfo[venue], vStats[venue])&lt;br /&gt;
				insert(entries, row)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	venueWinLossStatsRow(surfaceInfo, surfaces, sStats)&lt;br /&gt;
	if envSummary then&lt;br /&gt;
		venueWinLossStatsRow(environmentInfo, environments, eStats)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local row = tr()&lt;br /&gt;
	winLossStatsRow(row, {name = &amp;quot;Overall&amp;quot;}, stats, true, 2)&lt;br /&gt;
	insert(entries, row)&lt;br /&gt;
&lt;br /&gt;
	local row = tr()&lt;br /&gt;
	local wrHdrCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;):wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;#039;Win %&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
	for _,year in ipairs(years) do&lt;br /&gt;
		local cellContent = &amp;quot;&amp;#039;&amp;#039;&amp;#039;–&amp;#039;&amp;#039;&amp;#039;&amp;quot;&lt;br /&gt;
		if stats[year] then&lt;br /&gt;
			local wins = stats[year].wins&lt;br /&gt;
			local losses = stats[year].losses&lt;br /&gt;
			local matches = wins + losses&lt;br /&gt;
			if matches &amp;gt; 0 then&lt;br /&gt;
				cellContent = format(&amp;quot;&amp;#039;&amp;#039;&amp;#039;%.1f%%&amp;#039;&amp;#039;&amp;#039;&amp;quot;, wins * 100 / matches)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		td(row):wikitext(cellContent)&lt;br /&gt;
	end&lt;br /&gt;
	insert(entries, row)&lt;br /&gt;
	if headerCell then &lt;br /&gt;
		if data.categories.count &amp;gt; 1 then&lt;br /&gt;
			headerCell:attr(&amp;#039;rowspan&amp;#039;, #entries)&lt;br /&gt;
		end&lt;br /&gt;
		headerCell:wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;#039;Career&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local function counterStatsRow(row, name, type, bold)&lt;br /&gt;
		local boldmarkup = bold and &amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot; or &amp;quot;&amp;quot;&lt;br /&gt;
		local rowHdrCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;):css(&amp;#039;text-align&amp;#039;, &amp;#039;right&amp;#039;)&lt;br /&gt;
			:wikitext(format(&amp;quot;%s%s%s&amp;quot;, boldmarkup, name, boldmarkup))&lt;br /&gt;
		if data.categories.count &amp;gt; 1 then rowHdrCell:attr(&amp;#039;colspan&amp;#039;, 2) end&lt;br /&gt;
		for _,year in ipairs(years) do&lt;br /&gt;
			td(row):wikitext(format(&amp;quot;%s%s%s&amp;quot;,&lt;br /&gt;
				boldmarkup,&lt;br /&gt;
				stats[year] and tostring(stats[year][type]) or &amp;quot;–&amp;quot;,&lt;br /&gt;
				boldmarkup))&lt;br /&gt;
		end&lt;br /&gt;
		td(row):attr(&amp;#039;colspan&amp;#039;, 2)&lt;br /&gt;
			:wikitext(format(&amp;quot;%s%d total%s&amp;quot;, boldmarkup, stats[type], boldmarkup))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local row = tr():addClass(&amp;#039;sortbottom&amp;#039;)&lt;br /&gt;
	counterStatsRow(row, &amp;quot;Tournaments played&amp;quot;, &amp;quot;count&amp;quot;)&lt;br /&gt;
	td(row):wikitext(format(&amp;quot;&amp;#039;&amp;#039;&amp;#039;%.1f%%&amp;#039;&amp;#039;&amp;#039;&amp;quot;, stats.champs * 100 / stats.count))&lt;br /&gt;
	insert(entries, row)&lt;br /&gt;
&lt;br /&gt;
	if stats.finals &amp;gt; 0 then&lt;br /&gt;
		local row = tr():addClass(&amp;#039;sortbottom&amp;#039;)&lt;br /&gt;
		counterStatsRow(row, &amp;quot;Finals reached&amp;quot;, &amp;quot;finals&amp;quot;)&lt;br /&gt;
		td(row):attr(&amp;#039;rowspan&amp;#039;, 2)&lt;br /&gt;
			:wikitext(format(&amp;quot;&amp;#039;&amp;#039;&amp;#039;%.1f%%&amp;#039;&amp;#039;&amp;#039;&amp;quot;, stats.champs * 100 / stats.finals))&lt;br /&gt;
		insert(entries, row)&lt;br /&gt;
	&lt;br /&gt;
		local row = tr():addClass(&amp;#039;sortbottom&amp;#039;)&lt;br /&gt;
		counterStatsRow(row, &amp;quot;Titles&amp;quot;, &amp;quot;champs&amp;quot;, true)&lt;br /&gt;
		insert(entries, row)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local row = tr():addClass(&amp;#039;sortbottom&amp;#039;)&lt;br /&gt;
	local yearEndHdrCell = th(row):attr(&amp;#039;scope&amp;#039;, &amp;#039;row&amp;#039;):css(&amp;#039;text-align&amp;#039;, &amp;#039;right&amp;#039;)&lt;br /&gt;
		:wikitext(&amp;quot;&amp;#039;&amp;#039;&amp;#039;Year-end ranking&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
	if data.categories.count &amp;gt; 1 then yearEndHdrCell:attr(&amp;#039;colspan&amp;#039;, 2) end&lt;br /&gt;
	for _,year in ipairs(years) do&lt;br /&gt;
		local cell = td(row)&lt;br /&gt;
		if data.rank and data.rank[year] then&lt;br /&gt;
			local rank = data.rank[year]&lt;br /&gt;
			local rankConfig = tConfig.rankings[rank] or {}&lt;br /&gt;
			if rankConfig.bgcolor then&lt;br /&gt;
				cell:css(&amp;#039;background&amp;#039;, rankConfig.bgcolor)&lt;br /&gt;
			end&lt;br /&gt;
			local boldmarkup = rankConfig.bold and &amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot; or &amp;quot;&amp;quot;&lt;br /&gt;
			cell:wikitext(format(&amp;quot;%s%s%s&amp;quot;, boldmarkup, rank, boldmarkup))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local cell = td(row):attr(&amp;#039;colspan&amp;#039;, 3)&lt;br /&gt;
	if data.prizemoney then&lt;br /&gt;
		tooltip(cell, nil, &amp;quot;Career prize money&amp;quot;, data.prizemoney, {bold = true, abbr = true})&lt;br /&gt;
	end&lt;br /&gt;
	insert(entries, row)&lt;br /&gt;
&lt;br /&gt;
	local result = {}&lt;br /&gt;
	for _,entry in ipairs(entries) do&lt;br /&gt;
		insert(result, tostring(entry))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return concat(result, &amp;quot;\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Generate wikitext to conclude performance timeline table, including footnotes.&lt;br /&gt;
local function footer()&lt;br /&gt;
	local result = {}&lt;br /&gt;
	insert(result, &amp;quot;|-\n|}&amp;quot;)&lt;br /&gt;
	if footnoteCount &amp;gt; 0 then&lt;br /&gt;
		local reflistTag = mw.html.create(&amp;quot;div&amp;quot;)&lt;br /&gt;
		reflistTag:addClass(&amp;quot;reflist&amp;quot;):css(&amp;#039;list-style-type&amp;#039;, &amp;#039;lower-alpha&amp;#039;)&lt;br /&gt;
			:wikitext(frame_:callParserFunction{&lt;br /&gt;
				name = &amp;#039;#tag:references&amp;#039;, args = {&amp;quot;&amp;quot;, group = &amp;quot;lower-alpha&amp;quot;}&lt;br /&gt;
			})&lt;br /&gt;
		insert(result, tostring(reflistTag))&lt;br /&gt;
	end&lt;br /&gt;
	return concat(result, &amp;quot;\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Return true if the player appears in a given tournament level.&lt;br /&gt;
local function hasLevelAppearance(level)&lt;br /&gt;
	local levelInfo = parse(tOrders[level])&lt;br /&gt;
	for _,tournament in ipairs(levelInfo) do&lt;br /&gt;
		if hasTournamentAppearance(tournament) then return true end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p._main(args, frame)&lt;br /&gt;
	frame_ = frame&lt;br /&gt;
	data = {}&lt;br /&gt;
	years = {}&lt;br /&gt;
	usedRounds = {}&lt;br /&gt;
	footnoteCount = 0&lt;br /&gt;
	data.gender = args.gender or &amp;quot;men&amp;quot;&lt;br /&gt;
	data.matchType = args.matchType or &amp;quot;singles&amp;quot;&lt;br /&gt;
	data.country = {}&lt;br /&gt;
	data.country.default = args.country or &amp;quot;UNK&amp;quot;&lt;br /&gt;
	local idx = 1&lt;br /&gt;
	local environmentSummary = true&lt;br /&gt;
	local year&lt;br /&gt;
	while args[idx] do&lt;br /&gt;
		local arg = args[idx]&lt;br /&gt;
		if arg == &amp;quot;year&amp;quot; then&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			year = checkYear(args[idx])&lt;br /&gt;
			if years.last and year &amp;lt;= years.last then&lt;br /&gt;
				error(format(&amp;quot;Nonincreasing year: %s appears after %s&amp;quot;, year, years.last))&lt;br /&gt;
			end&lt;br /&gt;
			years.last = year&lt;br /&gt;
			insert(years, year)&lt;br /&gt;
		elseif arg == &amp;quot;country&amp;quot; then&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			local country = checkNonNil(args[idx], year .. &amp;quot; country&amp;quot;)&lt;br /&gt;
			data.country[year] = args[idx]&lt;br /&gt;
		elseif arg == &amp;quot;amateur&amp;quot; then&lt;br /&gt;
			years.amateur = year&lt;br /&gt;
		elseif arg == &amp;quot;professional&amp;quot; then&lt;br /&gt;
			years.professional = year&lt;br /&gt;
		elseif tournaments[arg] then&lt;br /&gt;
			local tournament = arg&lt;br /&gt;
			local diagMsg = year ..&amp;quot; &amp;quot; .. tournament&lt;br /&gt;
			local tStats = {}&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			local round = args[idx]&lt;br /&gt;
			-- Handle zones for Davis Cup.&lt;br /&gt;
			if round and round:sub(1, 2) == &amp;quot;WG&amp;quot; then&lt;br /&gt;
				tStats.round = &amp;quot;WG&amp;quot;&lt;br /&gt;
				tStats.group = round:sub(3)&lt;br /&gt;
			elseif round and round:sub(1, 2) == &amp;quot;PO&amp;quot; then&lt;br /&gt;
				tStats.round = &amp;quot;PO&amp;quot;&lt;br /&gt;
				tStats.group = round:sub(3)&lt;br /&gt;
			elseif round and round:sub(1, 1) == &amp;quot;Z&amp;quot; then&lt;br /&gt;
				tStats.round = &amp;quot;Z&amp;quot;&lt;br /&gt;
				tStats.group = checkNum(round:sub(2), diagMsg .. &amp;quot; zone&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				tStats.round = round&lt;br /&gt;
			end&lt;br /&gt;
			checkMember(tStats.round, rounds, &amp;quot;round&amp;quot;, diagMsg)&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			tStats.wins = checkNum(args[idx], diagMsg)&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			tStats.losses = checkNum(args[idx], diagMsg)&lt;br /&gt;
			if data[tournament] == nil then data[tournament] = {} end&lt;br /&gt;
			data[tournament][year] = tStats&lt;br /&gt;
		elseif environments[arg] then&lt;br /&gt;
			local environment = arg&lt;br /&gt;
			local diagMsg = year .. &amp;quot; &amp;quot; .. &amp;quot; &amp;quot; .. environment&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			local surface = checkNonNil(args[idx], diagMsg .. &amp;quot; surface&amp;quot;)&lt;br /&gt;
			if surfaces[surface] then&lt;br /&gt;
				diagMsg = diagMsg .. &amp;quot; &amp;quot; .. surface&lt;br /&gt;
				local sStats = {}&lt;br /&gt;
				idx = idx + 1&lt;br /&gt;
				sStats.count = checkNum(args[idx], diagMsg)&lt;br /&gt;
				idx = idx + 1&lt;br /&gt;
				sStats.wins = checkNum(args[idx], diagMsg)&lt;br /&gt;
				idx = idx + 1&lt;br /&gt;
				sStats.losses = checkNum(args[idx], diagMsg)&lt;br /&gt;
				idx = idx + 1&lt;br /&gt;
				sStats.champs = checkNum(args[idx], diagMsg)&lt;br /&gt;
				idx = idx + 1&lt;br /&gt;
				sStats.finals = sStats.champs + checkNum(args[idx], diagMsg)&lt;br /&gt;
				if data[environment] == nil then data[environment] = {} end&lt;br /&gt;
				if data[environment][surface] == nil then&lt;br /&gt;
					data[environment][surface] = {}&lt;br /&gt;
				end&lt;br /&gt;
				data[environment][surface][year] = sStats&lt;br /&gt;
			else&lt;br /&gt;
				error(format(&amp;quot;Unknown surface (%s %s): %s&amp;quot;, year, environment, arg))&lt;br /&gt;
			end&lt;br /&gt;
		elseif surfaces[arg] then&lt;br /&gt;
			local surface = arg&lt;br /&gt;
			local diagMsg = year .. &amp;quot; &amp;quot; .. surface&lt;br /&gt;
			local sStats = {}&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			sStats.count = checkNum(args[idx], diagMsg)&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			sStats.wins = checkNum(args[idx], diagMsg)&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			sStats.losses = checkNum(args[idx], diagMsg)&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			sStats.champs = checkNum(args[idx], diagMsg)&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			sStats.finals = sStats.champs + checkNum(args[idx], diagMsg)&lt;br /&gt;
			if data.outdoor == nil then data.outdoor = {} end&lt;br /&gt;
			if data[&amp;quot;outdoor&amp;quot;][surface] == nil then data[&amp;quot;outdoor&amp;quot;][surface] = {} end&lt;br /&gt;
			data[&amp;quot;outdoor&amp;quot;][surface][year] = sStats&lt;br /&gt;
			-- Disable summary by environment.&lt;br /&gt;
			environmentSummary = false&lt;br /&gt;
		elseif arg == &amp;quot;rank&amp;quot; then&lt;br /&gt;
			idx = idx + 1&lt;br /&gt;
			if data.rank == nil then data.rank = {} end&lt;br /&gt;
			data.rank[year] = checkNum(args[idx], year .. &amp;quot; rank&amp;quot;)&lt;br /&gt;
		else&lt;br /&gt;
			error(format(&amp;quot;Unknown argument at position %d (%s): %s&amp;quot;, idx, year, arg))&lt;br /&gt;
		end&lt;br /&gt;
		idx = idx + 1&lt;br /&gt;
	end&lt;br /&gt;
	data.prizemoney = args.prizemoney&lt;br /&gt;
	data.last = args.last&lt;br /&gt;
	data.categories = {}&lt;br /&gt;
	if args.types then&lt;br /&gt;
		local count = 0&lt;br /&gt;
		for _,type in ipairs(mw.text.split(args.types, &amp;quot;,&amp;quot;)) do&lt;br /&gt;
			if type == &amp;quot;Career&amp;quot; or tConfig.orders[type] and hasLevelAppearance(type) then&lt;br /&gt;
				data.categories[type] = true&lt;br /&gt;
				count = count + 1&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		data.categories.count = count&lt;br /&gt;
	else&lt;br /&gt;
		local count = 0&lt;br /&gt;
		for _,type in ipairs(parse(tConfig.orders.order)) do&lt;br /&gt;
			if hasLevelAppearance(type) then&lt;br /&gt;
				data.categories[type] = true&lt;br /&gt;
				count = count + 1&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		data.categories.Career = true&lt;br /&gt;
		data.categories.count = count + 1&lt;br /&gt;
	end&lt;br /&gt;
	local result = {}&lt;br /&gt;
	local tableHeader, headerRows, headerCell = header()&lt;br /&gt;
	insert(result, tableHeader)&lt;br /&gt;
	local function insertHeaderRowsIfNeeded()&lt;br /&gt;
		if #result == 1 then&lt;br /&gt;
			for _,headerRow in ipairs(headerRows) do&lt;br /&gt;
				insert(result, tostring(headerRow))&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	for _,level in ipairs(parse(tConfig.orders.order)) do&lt;br /&gt;
		if data.categories[level] then&lt;br /&gt;
			local levelRows = body(level, data.categories.count == 1 and headerCell)&lt;br /&gt;
			insertHeaderRowsIfNeeded()&lt;br /&gt;
			insert(result, levelRows)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if data.categories.Career then&lt;br /&gt;
		local careerRows = summary(environmentSummary, data.categories.count == 1 and headerCell)&lt;br /&gt;
		insertHeaderRowsIfNeeded()&lt;br /&gt;
		if careerRows then insert(result, careerRows) end&lt;br /&gt;
	end&lt;br /&gt;
	insert(result, footer())&lt;br /&gt;
	return concat(result, &amp;quot;\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	-- Import module function to work with passed arguments&lt;br /&gt;
	local getArgs = require(&amp;#039;Module:Arguments&amp;#039;).getArgs&lt;br /&gt;
	local args = getArgs(frame)&lt;br /&gt;
	return p._main(args, frame)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>imported&gt;0xDeadbeef</name></author>
	</entry>
</feed>