Module:Football group summary

local p = {}

local MONTHS = {
	january = 1, february = 2, march = 3, april = 4,
	may = 5, june = 6, july = 7, august = 8,
	september = 9, october = 10, november = 11, december = 12,
}

local MONTH_NAMES = {
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December",
}

local function trim(s)
	if not s then return "" end
	return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end

local function normalizeSpaces(s)
	if not s then return "" end
	s = s:gsub(" ", " ")
	s = s:gsub("\194\160", " ")
	s = s:gsub("&", "&")
	return s
end

local function findTemplateEnd(s, openStart)
	local i = openStart + 2
	local depth = 1
	local len = #s
	while i <= len do
		local c2 = s:sub(i, i + 1)
		if c2 == "{{" then
			depth = depth + 1
			i = i + 2
		elseif c2 == "}}" then
			depth = depth - 1
			if depth == 0 then
				return i + 1
			end
			i = i + 2
		else
			i = i + 1
		end
	end
	return nil
end

local function splitTopLevelPipes(body)
	local parts = {}
	local buf = {}
	local i = 1
	local len = #body
	local braceDepth = 0
	local bracketDepth = 0
	local tagDepth = 0
	while i <= len do
		local c = body:sub(i, i)
		local c2 = body:sub(i, i + 1)
		if c2 == "{{" then
			braceDepth = braceDepth + 1
			buf[#buf + 1] = c2
			i = i + 2
		elseif c2 == "}}" then
			braceDepth = math.max(0, braceDepth - 1)
			buf[#buf + 1] = c2
			i = i + 2
		elseif c2 == "[[" then
			bracketDepth = bracketDepth + 1
			buf[#buf + 1] = c2
			i = i + 2
		elseif c2 == "]]" then
			bracketDepth = math.max(0, bracketDepth - 1)
			buf[#buf + 1] = c2
			i = i + 2
		elseif c == "<" then
			tagDepth = tagDepth + 1
			buf[#buf + 1] = c
			i = i + 1
		elseif c == ">" then
			tagDepth = math.max(0, tagDepth - 1)
			buf[#buf + 1] = c
			i = i + 1
		elseif c == "|" and braceDepth == 0 and bracketDepth == 0 and tagDepth == 0 then
			parts[#parts + 1] = table.concat(buf)
			buf = {}
			i = i + 1
		else
			buf[#buf + 1] = c
			i = i + 1
		end
	end
	parts[#parts + 1] = table.concat(buf)
	return parts
end

local function parseTemplateBody(body)
	local segments = splitTopLevelPipes(body)
	local result = { _name = trim(segments[1] or "") }
	local positional = 0
	for i = 2, #segments do
		local seg = segments[i]

		local eqPos
		local depth, brack = 0, 0
		for j = 1, #seg do
			local c = seg:sub(j, j)
			local c2 = seg:sub(j, j + 1)
			if c2 == "{{" then depth = depth + 1
			elseif c2 == "}}" then depth = depth - 1
			elseif c2 == "[[" then brack = brack + 1
			elseif c2 == "]]" then brack = brack - 1
			elseif c == "=" and depth == 0 and brack == 0 then
				eqPos = j
				break
			end
		end
		if eqPos then
			local name = trim(seg:sub(1, eqPos - 1))
			local value = trim(seg:sub(eqPos + 1))
			result[name] = value
		else
			positional = positional + 1
			result[positional] = trim(seg)
		end
	end
	return result
end

local function stripQuotes(s)
	if not s then return s end
	s = trim(s)
	local q = s:sub(1, 1)
	if (q == '"' or q == "'") and s:sub(-1) == q then
		return s:sub(2, -2)
	end
	return s
end

local function extractLabeledSection(wikitext, sectionName)
	local target = trim(sectionName)
	local out = {}
	local i = 1
	local len = #wikitext
	while i <= len do

		local tagStart, tagEnd, attrs =
			wikitext:find("<%s*[Ss][Ee][Cc][Tt][Ii][Oo][Nn]%s+([^<>]-)/?%s*>", i)
		if not tagStart then break end

		local kind = attrs:match("^%s*([Bb][Ee][Gg][Ii][Nn])%s*=") and "begin"
		           or attrs:match("^%s*([Ee][Nn][Dd])%s*=") and "end"
		local nameMatch
		if kind then
			nameMatch = attrs:match("=%s*(.+)%s*$")
			if nameMatch then nameMatch = stripQuotes(nameMatch) end
		end
		if kind == "begin" and nameMatch == target then

			local searchFrom = tagEnd + 1
			while true do
				local eStart, eEnd, eAttrs =
					wikitext:find("<%s*[Ss][Ee][Cc][Tt][Ii][Oo][Nn]%s+([^<>]-)/?%s*>", searchFrom)
				if not eStart then break end
				local eKind = eAttrs:match("^%s*([Ee][Nn][Dd])%s*=") and "end"
				              or eAttrs:match("^%s*([Bb][Ee][Gg][Ii][Nn])%s*=") and "begin"
				local eName = eAttrs:match("=%s*(.+)%s*$")
				if eName then eName = stripQuotes(eName) end
				if eKind == "end" and eName == target then
					out[#out + 1] = wikitext:sub(tagEnd + 1, eStart - 1)
					i = eEnd + 1
					break
				end
				searchFrom = eEnd + 1
			end
			if i <= tagEnd then i = tagEnd + 1 end
		else
			i = tagEnd + 1
		end
	end
	return table.concat(out, "\n")
end

local function extractHeaderSection(wikitext, headerName)
	local target = trim(headerName)

	local lines = {}
	local lineStart = 1
	for s in wikitext:gmatch("[^\n]*\n?") do
		if #s == 0 then break end
		lines[#lines + 1] = { text = s, start = lineStart }
		lineStart = lineStart + #s
	end

	local found
	local foundLevel
	for idx, line in ipairs(lines) do

		local eqL, name, eqR = line.text:match("^%s*(=+)%s*(.-)%s*(=+)%s*$")
		if eqL and name and eqR then
			local level = math.min(#eqL, #eqR)
			if trim(name) == target then
				found = idx
				foundLevel = level
				break
			end
		end
	end
	if not found then return "" end

	local startIdx = found + 1
	local endIdx = #lines
	for idx = startIdx, #lines do
		local line = lines[idx]
		local eqL, _, eqR = line.text:match("^%s*(=+)%s*(.-)%s*(=+)%s*$")
		if eqL and eqR then
			local level = math.min(#eqL, #eqR)
			if level <= foundLevel then
				endIdx = idx - 1
				break
			end
		end
	end
	local out = {}
	for idx = startIdx, endIdx do
		out[#out + 1] = lines[idx].text
	end
	return table.concat(out)
end

local function fetchArticle(titleStr)
	if not titleStr or trim(titleStr) == "" then return "" end
	local title = mw.title.new(trim(titleStr))
	if not title or not title.exists then return "" end
	return title:getContent() or ""
end

local function identifyTransclusion(wikitext, openStart)
	local closeEnd = findTemplateEnd(wikitext, openStart)
	if not closeEnd then return nil end
	local body = wikitext:sub(openStart + 2, closeEnd - 2)
	local segments = splitTopLevelPipes(body)
	local head = trim(segments[1] or "")

	local fullTitle = head:match("^:%s*(.+)$")
	if fullTitle then
		return "full", trim(fullTitle), nil, closeEnd
	end

	local fn, target = head:match("^#%s*([%w%-]+)%s*:%s*(.+)$")
	if fn and target then
		local fnLower = fn:lower()
		local arg = trim(segments[2] or "")
		if fnLower == "lst" or fnLower == "section" then
			return "lst", trim(target), arg, closeEnd
		elseif fnLower == "lsth" or fnLower == "section-h" then
			return "lsth", trim(target), arg, closeEnd
		end
	end

	return nil
end

local function findFootballBoxInvocations(wikitext, fetchFn)
	fetchFn = fetchFn or fetchArticle
	local results = {}
	local i = 1
	local len = #wikitext
	while i <= len do
		local openStart = wikitext:find("{{", i, true)
		if not openStart then break end

		local after = wikitext:sub(openStart + 2, openStart + 80):lower()
		local stripped = after:gsub("^%s+", "")
		local isFootballBox = false
		if stripped:match("^football%s*box[%s|}]") then
			isFootballBox = true
		elseif stripped:match("^#invoke:%s*football%s*box%s*|%s*main[%s|}]") then
			isFootballBox = true
		end
		if isFootballBox then
			local closeEnd = findTemplateEnd(wikitext, openStart)
			if closeEnd then
				local body = wikitext:sub(openStart + 2, closeEnd - 2)
				results[#results + 1] = body
				i = closeEnd + 1
			else
				i = openStart + 2
			end
		else

			local kind, target, arg, closeEnd =
				identifyTransclusion(wikitext, openStart)
			if kind and target then
				local fetched = fetchFn(target)
				local slice = ""
				if fetched and fetched ~= "" then
					if kind == "full" then
						slice = fetched
					elseif kind == "lst" then
						slice = extractLabeledSection(fetched, arg or "")
					elseif kind == "lsth" then
						slice = extractHeaderSection(fetched, arg or "")
					end
				end
				if slice and slice ~= "" then

					local nestedBoxes = findFootballBoxInvocations(slice,
						function() return "" end)
					for _, b in ipairs(nestedBoxes) do
						results[#results + 1] = b
					end
				end
				i = (closeEnd or (openStart + 1)) + 1
			else

				i = openStart + 2
			end
		end
	end
	return results
end

local function findNestedTemplate(s, targetName)
	local normalizedTarget = targetName:lower():gsub("%s+", " ")
	local i = 1
	local len = #s
	while i <= len do
		local openStart = s:find("{{", i, true)
		if not openStart then return nil end
		local closeEnd = findTemplateEnd(s, openStart)
		if not closeEnd then return nil end
		local body = s:sub(openStart + 2, closeEnd - 2)
		local firstPipe = body:find("|", 1, true)
		local name
		if firstPipe then
			name = trim(body:sub(1, firstPipe - 1))
		else
			name = trim(body)
		end
		local normalizedName = name:lower():gsub("%s+", " ")
		if normalizedName == normalizedTarget then
			return openStart, closeEnd, parseTemplateBody(body)
		end
		i = openStart + 2
	end
	return nil
end

local function formatDate(d, style)
	if not d then return "" end
	if style == "mdy" then
		return string.format("%s %d, %d", MONTH_NAMES[d.month], d.day, d.year)
	else
		return string.format("%d %s %d", d.day, MONTH_NAMES[d.month], d.year)
	end
end

local function parseDateParam(value)
	if not value or value == "" then return nil, "" end
	value = trim(value)

	local sdOpen = value:match("^{{%s*[Ss][Tt][Aa][Rr][Tt]%s+[Dd][Aa][Tt][Ee]%s*[|}]")
	if sdOpen then
		local closeEnd = findTemplateEnd(value, 1)
		if closeEnd then
			local body = value:sub(3, closeEnd - 2)
			local params = parseTemplateBody(body)
			local y = tonumber(params[1])
			local m = tonumber(params[2])
			local d = tonumber(params[3])
			if y and m and d and m >= 1 and m <= 12 then
				local style
				if params["df"] and trim(params["df"]):lower() == "y" then
					style = "dmy"
				elseif params["mf"] and trim(params["mf"]):lower() == "y" then
					style = "mdy"
				else

					style = "mdy"
				end
				local dt = { year = y, month = m, day = d }
				return dt, formatDate(dt, style)
			end
		end
	end

	local plain = normalizeSpaces(value)

	plain = plain:gsub("%[%[[^%[%]|]-|([^%[%]]-)%]%]", "%1")
	plain = plain:gsub("%[%[([^%[%]]-)%]%]", "%1")
	plain = plain:gsub("<[^<>]->", "")
	plain = trim(plain:gsub("%s+", " "))

	local y, m, d = plain:match("(%d%d%d%d)%-(%d%d)%-(%d%d)")
	if y then
		local dt = { year = tonumber(y), month = tonumber(m), day = tonumber(d) }
		return dt, formatDate(dt, "mdy")
	end

	local day, mname, year = plain:match("(%d%d?)%s+([A-Za-z]+)%s+(%d%d%d%d)")
	if day and mname and year then
		local mi = MONTHS[mname:lower()]
		if mi then
			local dt = { year = tonumber(year), month = mi, day = tonumber(day) }
			return dt, plain
		end
	end

	local mname2, day2, year2 = plain:match("([A-Za-z]+)%s+(%d%d?),?%s+(%d%d%d%d)")
	if mname2 and day2 and year2 then
		local mi = MONTHS[mname2:lower()]
		if mi then
			local dt = { year = tonumber(year2), month = mi, day = tonumber(day2) }
			return dt, plain
		end
	end

	return nil, plain
end

local function buildScoreOutput(scoreValue, aetValue)
	scoreValue = scoreValue or ""
	local output

	local startPos, endPos, params = findNestedTemplate(scoreValue, "score link")
	if startPos and params then
		local p1 = params[1] or ""
		local p2 = params[2] or ""
		local linked = "[[" .. p1 .. "|" .. p2 .. "]]"

		local before = scoreValue:sub(1, startPos - 1)
		local after = scoreValue:sub(endPos + 1)
		output = before .. linked .. after
	else
		output = scoreValue
	end

	output = trim(output)

	if aetValue and trim(aetValue) ~= "" then
		output = output .. " ([[Overtime (sports)#Association football|a.e.t.]])"
	end

	return output
end

local function buildMatchRecord(params, sourceOrder)
	local record = { sourceOrder = sourceOrder }

	local dt, dateStr = parseDateParam(params["date"])
	record.date = dt
	record.dateStr = dateStr

	record.home = trim(params["team1"] or "")
	record.away = trim(params["team2"] or "")

	record.score = buildScoreOutput(params["score"] or "", params["aet"])

	local stadium = trim(params["stadium"] or "")
	local location = trim(params["location"] or "")
	if stadium ~= "" and location ~= "" then
		record.venue = stadium .. ", " .. location
	elseif stadium ~= "" then
		record.venue = stadium
	else
		record.venue = location
	end

	return record
end

local function sortMatches(matches)
	table.sort(matches, function(a, b)
		local ad, bd = a.date, b.date
		if ad and bd then
			if ad.year ~= bd.year then return ad.year < bd.year end
			if ad.month ~= bd.month then return ad.month < bd.month end
			if ad.day ~= bd.day then return ad.day < bd.day end
			return a.sourceOrder < b.sourceOrder
		elseif ad and not bd then
			return true
		elseif not ad and bd then
			return false
		else
			return a.sourceOrder < b.sourceOrder
		end
	end)
end

local function buildTable(matches)
	local out = {}
	out[#out + 1] = "{{table alignment}}"
	out[#out + 1] = '{| class="col1right col2center" style="width:100%" cellspacing="1"'
	out[#out + 1] = "|-"
	out[#out + 1] = '!style="width:25%"|'
	out[#out + 1] = '!style="width:10%"|'
	out[#out + 1] = '!style="width:25%"|'
	out[#out + 1] = '!style="width:40%"|'

	local lastKey
	for _, m in ipairs(matches) do
		local key
		if m.date then
			key = string.format("%04d-%02d-%02d", m.date.year, m.date.month, m.date.day)
		else
			key = "__" .. tostring(m.dateStr)
		end
		if key ~= lastKey then
			out[#out + 1] = "|-"
			out[#out + 1] = '|style="text-align:left"|' .. (m.dateStr or "")
			lastKey = key
		end
		out[#out + 1] = '|- style="font-size:90%"'
		out[#out + 1] = string.format("|%s||%s||%s||%s",
			m.home or "", m.score or "", m.away or "", m.venue or "")
	end
	out[#out + 1] = "|}"
	return table.concat(out, "\n")
end

function p.main(frame)
	local args = frame.args or {}
	if (not args.article or args.article == "") and frame.getParent then
		local parent = frame:getParent()
		if parent and parent.args then
			args.article = (args.article and args.article ~= "") and args.article or parent.args.article
			args[1] = args[1] or parent.args[1]
		end
	end
	local articleTitle = args.article or args[1]
	if not articleTitle or trim(articleTitle) == "" then
		articleTitle = mw.title.getCurrentTitle().prefixedText
	end

	local title = mw.title.new(articleTitle)
	if not title or not title.exists then
		return '<strong class="error">Article not found: ' .. tostring(articleTitle) .. "</strong>"
	end
	local wikitext = title:getContent()
	if not wikitext then
		return '<strong class="error">Could not read content of: ' .. tostring(articleTitle) .. "</strong>"
	end

	local bodies = findFootballBoxInvocations(wikitext)
	local matches = {}
	for i, body in ipairs(bodies) do
		local params = parseTemplateBody(body)
		matches[#matches + 1] = buildMatchRecord(params, i)
	end

	if #matches == 0 then
		return "<em>No football box matches found in " .. articleTitle .. ".</em>"
	end

	sortMatches(matches)
	local wikitable = buildTable(matches)

	return frame:preprocess(wikitable)
end

p._internal = {
	trim = trim,
	stripQuotes = stripQuotes,
	findTemplateEnd = findTemplateEnd,
	splitTopLevelPipes = splitTopLevelPipes,
	parseTemplateBody = parseTemplateBody,
	extractLabeledSection = extractLabeledSection,
	extractHeaderSection = extractHeaderSection,
	identifyTransclusion = identifyTransclusion,
	findFootballBoxInvocations = findFootballBoxInvocations,
	findNestedTemplate = findNestedTemplate,
	formatDate = formatDate,
	parseDateParam = parseDateParam,
	buildScoreOutput = buildScoreOutput,
	buildMatchRecord = buildMatchRecord,
	sortMatches = sortMatches,
	buildTable = buildTable,
}

return p

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.