Module:ExcerptHeaderLevel

local p = {}

local parser = require('Module:WikitextParser')

local function getArg(args, key, fallback)
	local v = args[key]
	if v == nil or v == '' then
		return fallback
	end
	return v
end

local function tonumberOrNil(v)
	local n = tonumber(v)
	if n then
		return n
	end
	return nil
end

local function tonumberOrZero(v)
	local n = tonumber(v)
	if n then
		return n
	end
	return 0
end

local function clamp(n, low, high)
	if n < low then
		return low
	elseif n > high then
		return high
	end
	return n
end

local function trim(s)
	return mw.text.trim(tostring(s or ''))
end

local function isYes(v)
	v = trim(v):lower()
	return v == '1' or v == 'yes' or v == 'y' or v == 'true'
end

local function parseHeadingLine(line)
	local eq1, title, eq2 = tostring(line):match('^(=+)%s*(.-)%s*(=+)%s*$')
	if eq1 and eq2 and eq1 == eq2 then
		return {
			level = #eq1,
			title = title
		}
	end
	return nil
end

local function isMarkerOnlyLine(line)
	return tostring(line):match('^%s*=+%s*$') ~= nil
end

local function rebuildHeading(title, level)
	title = trim(title):gsub('[\r\n]+', ' ')
	level = clamp(level, 2, 6)
	local eq = string.rep('=', level)
	return eq .. ' ' .. title .. ' ' .. eq
end

local function adjustHeading(line, shift)
	local h = parseHeadingLine(line)
	if not h then
		return line
	end
	return rebuildHeading(h.title, h.level + shift)
end

local function adjustHeadingRelativeToTop(line, originalTopLevel, desiredTopLevel)
	local h = parseHeadingLine(line)
	if not h then
		return line
	end

	local relativeDepth = h.level - originalTopLevel
	return rebuildHeading(h.title, desiredTopLevel + relativeDepth)
end

local function makeTopHeading(title, level)
	return rebuildHeading(title, level)
end

local function splitLines(text)
	local lines = {}
	text = tostring(text or '')
	for line in (text .. '\n'):gmatch('(.-)\n') do
		table.insert(lines, line)
	end
	return lines
end

local function stripLeadingGarbage(lines, sectionTitle)
	local normalizedTitle = trim(sectionTitle)
	local removedSomething = true

	while removedSomething and #lines > 0 do
		removedSomething = false

		while #lines > 0 and trim(lines[1]) == '' do
			table.remove(lines, 1)
			removedSomething = true
		end

		if #lines == 0 then
			return
		end

		local first = trim(lines[1])
		local second = trim(lines[2] or '')

		local h = parseHeadingLine(first)
		if h and trim(h.title) == normalizedTitle then
			table.remove(lines, 1)
			removedSomething = true

		elseif isMarkerOnlyLine(first) then
			table.remove(lines, 1)
			removedSomething = true

			if #lines > 0 and trim(lines[1]) == normalizedTitle then
				table.remove(lines, 1)
			end

		elseif first == normalizedTitle then
			table.remove(lines, 1)
			removedSomething = true

		elseif second == normalizedTitle and isMarkerOnlyLine(first) then
			table.remove(lines, 1)
			table.remove(lines, 1)
			removedSomething = true
		end
	end
end

local function stripTrailingBlankLines(lines)
	while #lines > 0 and trim(lines[#lines]) == '' do
		table.remove(lines, #lines)
	end
end

local function makeExcerptHatnote(page, section)
	local displaySection = section:gsub('%[%[([^]|]+)|?[^]]*%]%]', '%1')
	local link = '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. displaySection .. ']]'
	local title = mw.title.new(page)
	local editUrl = title and title:fullUrl('action=edit') or nil

	local hat = 'These paragraphs are an excerpt from ' .. link .. '.'
	if editUrl then
		hat = hat
			.. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>['
			.. tostring(editUrl) .. ' ' .. mw.message.new('editsection'):plain()
			.. ']<span class="mw-editsection-bracket">]</span></span>'
	end

	return tostring(
		mw.html.create('div')
			:addClass('dablink')
			:addClass('excerpt-hat')
			:wikitext(hat)
	)
end

local function escapeString(str)
	return tostring(str):gsub('[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0')
end

local function removeString(text, str)
	local pattern = escapeString(str)
	if #pattern > 9999 then
		pattern = escapeString(mw.ustring.sub(str, 1, 999)) .. '.-' .. escapeString(mw.ustring.sub(str, -999))
	end
	return text:gsub(pattern, '')
end

local function parseFilter(filter)
	local filters = {}
	local isBlacklist = false

	if string.sub(filter, 1, 1) == '-' then
		isBlacklist = true
		filter = string.sub(filter, 2)
	end

	local values = mw.text.split(filter, ',')
	for _, value in pairs(values) do
		value = mw.text.trim(value)
		local min, max = mw.ustring.match(value, '^(%d+)%s*[-–—]%s*(%d+)$')
		if not max then
			min, max = string.match(value, '^((%d+))$')
		end
		if max then
			for i = min, max do
				filters[i] = true
			end
		else
			filters[value] = true
		end
	end

	return { cache = {}, terms = filters }, isBlacklist
end

local function matchFilter(value, filter)
	if value == nil then
		return false
	elseif type(value) == 'number' then
		return filter.terms[value]
	else
		local cached = filter.cache[value]
		if cached ~= nil then
			return cached
		end

		local lang = mw.language.getContentLanguage()
		local lcvalue = lang:lcfirst(value)
		local ucvalue = lang:ucfirst(value)

		for term in pairs(filter.terms) do
			if value == tostring(term)
				or type(term) == 'string' and (
					lcvalue == term
					or ucvalue == term
					or mw.ustring.match(value, term)
				)
			then
				filter.cache[value] = true
				return true
			end
		end

		filter.cache[value] = false
		return false
	end
end

local function filterTemplates(wikitext, filter)
	if not filter or filter == '' then
		return wikitext
	end

	local filters, isBlacklist = parseFilter(filter)
	local templates = parser.getTemplates(wikitext)

	for index, template in pairs(templates) do
		local name = parser.getTemplateName(template)
		if
			(isBlacklist and (matchFilter(index, filters) or matchFilter(name, filters)))
			or
			(not isBlacklist and (not matchFilter(index, filters) and not matchFilter(name, filters)))
		then
			wikitext = removeString(wikitext, template)
		end
	end

	return wikitext
end

local function getRawSectionWikitext(page, section, includeSubsections)
	local title = mw.title.new(page)
	if not title then
		return nil, 'invalid-title'
	end
	if title.isRedirect then
		title = title.redirectTarget
	end
	if not title or not title.exists then
		return nil, 'page-not-found'
	end

	local wikitext = title:getContent()
	if not wikitext or wikitext == '' then
		return nil, 'page-empty'
	end

	local excerpt = parser.getSectionTag(wikitext, section)
	if not excerpt then
		if includeSubsections then
			excerpt = parser.getSection(wikitext, section)
		else
			local sections = parser.getSections(wikitext)
			excerpt = sections[section]
		end
	end

	if not excerpt or excerpt == '' then
		return nil, 'section-not-found'
	end

	return excerpt
end

function p.main(frame)
	local args = frame:getParent().args

	local page = trim(getArg(args, 1, getArg(args, 'page', nil)))
	local section = trim(getArg(args, 2, getArg(args, 'section', nil)))

	if page == '' or section == '' then
		return '<strong class="error">Template:ExcerptHeaderLevel requires both a page and a section heading.</strong>'
	end

	local includeTopHeader = isYes(getArg(args, 'include-top-header', 'yes'))

	local showExcerptNoticeDefault = includeTopHeader and 'yes' or 'no'
	local showExcerptNotice = isYes(getArg(args, 'show-excerpt-notice', showExcerptNoticeDefault))
	local includeSubsections = isYes(getArg(args, 'subsections', 'yes'))
	local templateFilter = getArg(args, 'templates', nil)
	local topHeaderLevel = tonumberOrNil(getArg(args, 'top-header-level', nil))

	local shift
	if topHeaderLevel then
		topHeaderLevel = clamp(topHeaderLevel, 2, 6)
	else
		shift = tonumberOrZero(getArg(args, 'shift', 0))
		local higher = tonumberOrZero(getArg(args, 'higher', 0))
		local lower = tonumberOrZero(getArg(args, 'lower', 0))
		shift = shift - higher + lower
	end

	local raw, err = getRawSectionWikitext(page, section, includeSubsections)
	if not raw then
		return '<strong class="error">ExcerptHeaderLevel error: ' .. tostring(err) .. '.</strong>'
	end

	if templateFilter and templateFilter ~= '' then
		raw = filterTemplates(raw, templateFilter)
	end

	raw = raw:gsub('<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '')
	raw = raw:gsub('\n\n\n+', '\n\n')
	raw = mw.text.trim(raw)
	raw = '\n' .. raw .. '\n'

	raw = frame:preprocess(raw)

	local lines = splitLines(raw)
	stripLeadingGarbage(lines, section)

	local out = {}

	if showExcerptNotice then
		table.insert(out, makeExcerptHatnote(page, section))
	end

	if topHeaderLevel then
		if includeTopHeader then
			table.insert(out, makeTopHeading(section, topHeaderLevel))
		end

		for _, line in ipairs(lines) do
			table.insert(out, adjustHeadingRelativeToTop(line, 2, topHeaderLevel + 1))
		end
	else
		if includeTopHeader then
			table.insert(out, makeTopHeading(section, clamp(2 + shift, 2, 6)))
		end

		for _, line in ipairs(lines) do
			table.insert(out, adjustHeading(line, shift))
		end
	end

	stripTrailingBlankLines(out)
	return table.concat(out, '\n')
end

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.