Module:transclude

Hello, you have come here looking for the meaning of the word Module:transclude. In DICTIOUS you will not only get to know all the dictionary meanings for the word Module:transclude, but we will also tell you about its etymology, its characteristics and you will know how to say Module:transclude in singular and plural. Everything you need to know about the word Module:transclude you have here. The definition of the word Module:transclude will help you to be more precise and correct when speaking or writing your texts. Knowing the definition ofModule:transclude, as well as those of other words, enriches your vocabulary and provides you with more and better linguistic resources.

This module powers {{transclude}}.


local export = {}

local anchors_module = "Module:anchors"
local debug_track_module = "Module:debug/track"
local headword_data_module = "Module:headword/data"
local labels_module = "Module:labels"
local languages_module = "Module:languages"
local links_module = "Module:links"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local parse_interface_module = "Module:parse interface"
local place_module = "Module:place"
local string_char_module = "Module:string/char"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_remove_comments_module = "Module:string/removeComments"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local template_parser_module = "Module:template parser"

local m_place = require(place_module)

local enlang = require(languages_module).getByCode("en")

local concat = table.concat
local find = string.find
local insert = table.insert
local ipairs = ipairs
local lower = string.lower
local match = string.match
local pairs = pairs
local unpack = unpack or table.unpack -- Lua 5.2 compatibility

local function codepoint(...)
	codepoint = require(string_utilities_module).codepoint
	return codepoint(...)
end

local function contains(...)
	contains = require(table_module).contains
	return contains(...)
end

local function deep_copy(...)
	deep_copy = require(table_module).deepCopy
	return deep_copy(...)
end

local function find_templates(...)
	find_templates = require(template_parser_module).find_templates
	return find_templates(...)
end

local function full_link(...)
	full_link = require(links_module).full_link
	return full_link(...)
end

local function is_preview(...)
	is_preview = require(pages_module).is_preview
	return is_preview(...)
end

local function pattern_escape(...)
	pattern_escape = require(string_pattern_escape_module)
	return pattern_escape(...)
end

local function process_params(...)
	process_params = require(parameters_module).process
	return process_params(...)
end

local function remove_comments(...)
	remove_comments = require(string_remove_comments_module)
	return remove_comments(...)
end

local function replacement_escape(...)
	replacement_escape = require(string_replacement_escape_module)
	return replacement_escape(...)
end

local function senseid(...)
	senseid = require(anchors_module).senseid
	return senseid(...)
end

local function show_labels(...)
	show_labels = require(labels_module).show_labels
	return show_labels(...)
end

local function split(...)
	split = require(string_utilities_module).split
	return split(...)
end

local function u(...)
	u = require(string_char_module)
	return u(...)
end

-- Add the page to a tracking "category". To see the pages in the "category",
-- go to ] and click on "What links here".
local function track(page)
	require(debug_track_module)("transclude/" .. page)
	return true
end

-- Split an argument on comma, but not comma followed by whitespace.
local function split_on_comma(val)
	if val:find(",") then
		return require(parse_interface_module).split_on_comma(val)
	else
		return {val}
	end
end

-- Split list of labels. For compatibility, we allow splitting on semicolon, but will convert to splitting on
-- comma not followed by space, for compatibility with other modules/params.
local function split_labels(val)
	if val:find(";") then
		track("label-with-semicolon")
		return split(val, ";")
	else
		return split_on_comma(val)
	end
end

-- From ]
local gloss_left = '<span class="mention-gloss-paren">(</span><span class="mention-gloss">'
local gloss_right = '</span><span class="mention-gloss-paren">)</span>'

-- Ensure that Wikicode (template calls, bracketed links, HTML, bold/italics, etc.) displays literally in error messages
-- by inserting a Unicode word-joiner symbol after all characters that may trigger Wikicode interpretation. Replacing
-- with equivalent HTML escapes doesn't work because they are displayed literally. I could not get this to work using
-- <nowiki>...</nowiki> (those tags display literally), using using {{#tag:nowiki|...}} (same thing) or using
-- mw.getCurrentFrame():extensionTag("nowiki", ...) (everything gets converted to a strip marker
-- `UNIQ--nowiki-00000000-QINU` or similar). FIXME: This is a massive hack; there must be a better way.
local function escape_wikicode(text)
	text = text:gsub("()", "%1" .. u(0x2060))
	return text
end

local function preprocess(frame, text)
	if text:find("{") or text:find("<math>") then
		return frame:preprocess(text)
	else
		return text
	end
end

local function ine(arg)
	return arg ~= "" and arg or nil
end

local function discard(offset, iter, obj, index)
	return iter, obj, index + offset
end

local function remove_templates_if(haystack, predicate)
	local remaining = {}
	local last_start = 1
	for template in find_templates(haystack) do
		local name = template:get_name()
		if name ~= nil and predicate(name, template:get_arguments(), next(remaining) == nil) then
			local index = template.index
			if last_start < index then
				local chunk = haystack:sub(last_start, index - 1)
				if chunk:find("%S") then
					insert(remaining, chunk)
				end
			end
			last_start = index + template.raw:len()
		end
	end
	if last_start == 1 then
		return haystack
	else
		insert(remaining, haystack:sub(last_start))
		return concat(remaining)
	end
end

local function copy_unnamed_args_maybe_except_code(to, from, deny_list, first_argument)
	first_argument = first_argument or 2
	for _, value in discard(first_argument - 1, ipairs(from)) do
		if not deny_list or not contains(deny_list, value) then
			insert(to, value)
		end
	end
end


local function parse_form_of_directive(value, param)
	local new_directive, new_value = value:match("^@(+):(.*)$")
	if not new_directive then
		if param then
			error(("Misformatted value %s=%s; should be e.g. 'place_acronym_of=" ..
				"@init of:ehemalige jugoslawische Republik Mazedonien' to replace " ..
				"'@acronym of:...' with '@init of:...'"):format(
					param, value))
		else
			error(("Misformatted form-of directive '%s'; should be e.g. '@ellip of:Santiago del Estero'"):format(
				value))
		end
	end
	if not m_place.all_form_of_directives then
		local known_directives = {}
		for k, _ in pairs(m_place.all_form_of_directives) do
			insert(known_directives, '"' .. k .. '"')
		end
		table.sort(known_directives)
		known_directives = concat(known_directives, ", ")
		if param then
			error(("Unrecognized form-of directive '%s' in replacement @-directive %s=%s; " ..
				"recognized directives are %s"):format(new_directive, param, value, known_directives))
		else
			error(("Unrecognized form-of directive '%s' in '%s'; recognized directives are %s"):format(
				new_directive, value, known_directives))
		end
	else
		-- canonicalize replacement directive aliases
		new_directive = m_place.all_form_of_directives.alias_of or new_directive
	end
	return new_directive, new_value
end

local function handle_definition_template(data)
	local name, args, transclude_args = data.name, data.template_args, data.transclude_args
	if name == "place" then
		local place_form_of_directives = data.place_form_of_directives
		return {
			should_remove = true,
			must_be_first = true,
			generate = function(data)
				if data.formatted_to and data.formatted_to ~= "" then
					error("{{place}} cannot be used in conjunction with |to=")
				end
				local place_args = {}
				local langcode = data.lang:getCode()
				local place_translation_follows = transclude_args.place_translation_follows
				local include_place_extra_info = transclude_args.include_place_extra_info
				local drop_extra = not include_place_extra_info -- false or unspecified
				local extra_info_overridden_set = {}
				for _, extra_info_spec in pairs(m_place.extra_info_args) do
					local overriding_arg = transclude_args
					if overriding_arg and overriding_arg then
						extra_info_overridden_set = true
					end
				end
				local form_of_overridden_args = {}
				for form_of_directive, directive_spec in pairs(m_place.all_form_of_directives) do
					if not directive_spec.alias_of then
						local transclude_key = "place_" .. (form_of_directive:gsub(" ", "_"))
						local transclude_value = transclude_args
						if transclude_value then
							local new_directive, new_value
							if transclude_value:find("^@") then
								new_directive, new_value = parse_form_of_directive(transclude_value, transclude_key)
							else
								new_directive = form_of_directive
								new_value = transclude_value
							end
							form_of_overridden_args = {
								new_directive = new_directive,
								new_value = new_value,
							}
							if directive_spec.default_foreign and place_translation_follows == nil then
								place_translation_follows = true
							end
						end
					end
				end

				place_args = langcode
				place_args.pagename = data.source

				-- If form-of directives specified in the numeric args to {{tcl}}, they get inserted before any
				-- numeric args taken from {{place}}.
				local next_numarg = 2
				for _, form_of_directive in ipairs(place_form_of_directives) do
					place_args = ("@%s:%s"):format(form_of_directive.directive, form_of_directive.value)
					next_numarg = next_numarg + 1
				end

				local saw_tcl_t
				local saw_t
				-- Copy the arguments but drop translations, maybe the "extra info", and maybe the numbered args
				-- (if tcl= given)
				local tcl_arg = ine(args.tcl)
				for key, val in pairs(args) do
					local base = tostring(key):match("^(.-)(%d*)$")
					if base == "tcl_t" or base == "tcl_tid" then
						saw_tcl_t = true -- otherwise ignore
					elseif base == "tcl_nolb" then
						data.nolb = val -- otherwise ignore
					elseif base == "t" or base == "tid" then
						if transclude_args.t then
							-- ignore it if the user specified t= in {{tcl}}, otherwise keep it unless tcl_t is given
						else
							saw_t = true
						end
					elseif m_place.extra_info_arg_map and extra_info_overridden_set then
						-- don't copy any extra info arguments that we will be overriding, in case there are more
						-- original values than overrides for this particular argument
					elseif base == "" then
						if not tcl_arg and key > 1 then
							-- We want keys starting at 2 to go into positions starting at `next_numarg`.
							place_args = val
						end
					else
						place_args = val
					end
				end

				local function sub_plus(t)
					if t:find("+") then
						t = t:gsub("+", replacement_escape(data.source))
					end
					return t
				end

				-- If tcl= given, copy its value into the numeric args.
				if tcl_arg then
					place_args = tcl_arg
					next_numarg = next_numarg + 1
					place_args.a = nil
				end

				if transclude_args.t then
					local argno = 1
					for _, t in ipairs(transclude_args.t) do
						if t ~= "-" then
							place_args = sub_plus(t)
							argno = argno + 1
						end
					end
				elseif langcode ~= "en" then
					if saw_tcl_t then
						for key, val in pairs(args) do
							local base, num = tostring(key):match("^(.-)(%d*)$")
							if base == "tcl_t" then
								place_args = sub_plus(val)
							elseif base == "tcl_tid" then
								place_args = val
							end
						end
					elseif saw_t then
						for key, val in pairs(args) do
							local base = tostring(key):match("^(.-)(%d*)$")
							if base == "t" or base == "tid" then
								place_args = val
							end
						end
					else
						place_args = data.source
						place_args = data.id
					end
				end
				place_args = data.sort
				if data.nocat then
					place_args = "1"
				end
				if transclude_args.place_addl then
					place_args.addl = transclude_args.place_addl
				end
				if data.no_gloss then
					place_args = "-"
				else
					local gloss = data.gloss

					-- Copy overriding extra info values. They are in the term language rather than English,
					-- which we signal through `extra_info_overridden_set`.
					for _, extra_info_spec in pairs(m_place.extra_info_args) do
						for i, v in ipairs(transclude_args) do
							place_args = v
						end
					end

					if not args.tcl_noextratext and not tcl_arg and gloss ~= "" then
						-- Copy text after {{place}} into {{place}}, unless tcl= or tcl_noextratext= is given.
						local first_free = 2
						while place_args ~= nil do first_free = first_free + 1 end
						if place_args:find("<<") then
							-- new-style argument; concatenate to end of argument
							if not gloss:find("^") then
								gloss = " " .. gloss
							end
							place_args = place_args .. gloss
						else
							-- old-style argument; add as separate argument
							if gloss:find("^,") then
								place_args = gloss:gsub("^, *", "")
							elseif gloss:find("^;") then
								place_args = ";"
								place_args = gloss:gsub("^; *", "")
							else
								-- the "*" ensures no extra comma
								place_args = "*" .. gloss:gsub("^ *", "")
							end
						end
					end
				end
				return m_place.format {
					template_args = place_args,
					from_tcl = true,
					drop_extra_info = drop_extra,
					extra_info_overridden_set = extra_info_overridden_set,
					form_of_overridden_args = form_of_overridden_args,
					translation_follows = place_translation_follows,
				}
			end,
		}
	elseif name == "abbreviation of" or name == "abbr of" or name == "abbrev of"
		or name == "acronym of" or name == "ellipsis of"
		or name == "contraction of" or name == "contr of"
		or name == "initialism of" or name == "init of"
		or name == "short for" or name == "synonym of" then
		return {
			should_remove = true,
			must_be_first = true,
			generate = function(data)
				local formatted_gloss = ""
				if not data.no_gloss then
					local formatted_link = full_link{
						term = args, alt = args, lang = data.source_lang, id = args
					}
					local after_link = ""
					if data.gloss ~= "" then
						local separator = (args and "") or ((args or ";") .. " ")
						after_link = separator .. data.gloss
					end
					formatted_gloss = " " .. gloss_left .. formatted_link .. after_link .. gloss_right
				end
				return data.formatted_to .. full_link{
					term = data.source, lang = data.source_lang, id = data.id
				} .. formatted_gloss
			end,
		}
	end
	return nil
end

function export.show(frame)
	local boolean = {type = "boolean"}
	local list = {list = true}
	local required = {required = true}
   	local params = {
		 = {required = true, type = "language"}, -- langcode of target language (the current entry's language)
		 = {list = true, required = true}, -- source English term to transclude from and/or form-or directives
		id = true, -- can have multiple comma-separated IDs
		sort = true,
		nogloss = {default = false, type = "boolean"},
		no_truncate_gloss = boolean,
		-- Normally, we ignore most of the extra info (capital, largest city, official name, etc.) when transcluding
		-- {{place}} because the given terms are in English and will likely differ from language to language.
		include_place_extra_info = boolean,
		-- Normally the translation (the transcluded page or overriding value in t=) comes first with the definition
		-- following in parens, but that may not produce sensible results in some cases, such as initialisms; e.g. if
		-- we define ] as
		-- {{place|en|@init of:German Democratic Republic|@official name of:East Germany|former country|r/Central Europe}}
		-- and then we define Polish ] as
		-- {{tcl|pl|GDR|place_init_of=Niemiecka Republika Demokratyczna<eq:German Democratic Republic>}}, we get
		-- "GDR (initialism of Niemiecka Republika Demokratyczna (= German Democratic Republic), official name of East Germany, a former country in Central Europe)"
		-- which makes no sense as GDR is not an initialism of ]. Instead what we
		-- want is a display more like
		-- "initialism of Niemiecka Republika Demokratyczna (= German Democratic Republic), official name of East Germany, a former country in Central Europe: ]".
		-- `place_translation_follows=1` causes that to happen, and it also happens by default whenever an argument like
		-- place_init_of= (or more generally, any of the form-of directives that are marked as `default_foreign`); to
		-- disable the postposed display in that case, use `place_translation_follows=0`.
		place_translation_follows = boolean,
		place_addl = true,
		lb = true, -- can have multiple comma-separated or (for compatibility) semicolon-separated labels
		nolb = true, -- can have multiple comma-separated or (for compatibility) semicolon-separated labels
		nocat = boolean,
		to = boolean,
		t = list,
		indent = true,
		dot = boolean,
		pagename = true,
	}
	for _, extra_arg_spec in ipairs(m_place.extra_info_args) do
		params = list
	end
	for form_of_directive, directive_spec in pairs(m_place.all_form_of_directives) do
		params = directive_spec.alias_of and
			{alias_of = "place_" .. (directive_spec.alias_of:gsub(" ", "_"))} or true
	end

   	local args = process_params(frame:getParent().args, params)
	local pagename = args.pagename or mw.loadData(headword_data_module).pagename

	local language = args
	local language_code = language:getCode()
	local source
	local place_form_of_directives = {}
	if args:find("^@") then
		-- form-of directives in place of source
		for i, arg in ipairs(args) do
			if arg:find("^@") then
				local directive, value = parse_form_of_directive(arg)
				insert(place_form_of_directives, {
					directive = directive,
					value = value,
				})
			elseif i ~= #args then
				error(("When form-of directives are specified, the source must come last, but saw source %s=%s " ..
					"when higher-numbered arguments exist"):format(i + 1, arg))
			else
				source = arg
			end
		end
		if not source then
			source = place_form_of_directives.value:gsub("<.*", "")
		end
	elseif args then
		error(("Extraneous argument 3=%s"):format(args))
	else
		source = args
	end
	local source_lang = enlang
	local source_langcode = source_lang:getCode()
	local source_langname = source_lang:getFullName()
	local ids = args.id and split(args.id, ",") or {""}
	local sort = args.sort
	if source == "+" then
		source = pagename
	end
	local source_is_current_page = source == pagename
	local copy_sortkey = (sort == nil) and source_is_current_page
	local no_gloss = args.nogloss
	local labels = args.lb and split_labels(args.lb) or {}
	local to = args.to

	local function issue_error(msg)
		if source_is_current_page and is_preview() then
			msg = msg .. ". NOTE: You are in preview mode. If you're previewing only part of the page, try previewing the full page, as the error may go away."
		end
		error(msg)
	end

	local content = mw.title.new(source):getContent()
	if content == nil then
		issue_error("Couldn't find the entry ]")
	end

	-- Remove HTML comments.
	content = remove_comments(content)
	-- Remove <ref></ref>.
	content = content:gsub("< ***>.-< */ * *>", "")
	-- Remove <ref/>.
	content = content:gsub("< ***/ *>", "")
	-- TODO: Handle <nowiki> (it's more complex than just cutting it out too).

	local retlines = {}

	for _, id in ipairs(ids) do
		local nolb
		local found_labels = {}
		local line_start, line
		if id == "" then
			id = nil
		end
		if id ~= nil then
			local senseid_start, senseid_end = content:find("{{ *senseid *| *" .. pattern_escape(source_langcode) .. " *| *" .. pattern_escape(id) .. " *}}")
			if senseid_start == nil then
				senseid_start, senseid_end = content:find("{{ *sid *| *" .. pattern_escape(source_langcode) .. " *| *" .. pattern_escape(id) .. " *}}")
			end
			if senseid_start == nil then
				local alternatives = nil
				for id in content:gmatch("{{ *senseid *| *" .. pattern_escape(source_langcode) .. " *| *(*)}}") do
					alternatives = alternatives and alternatives .. ", " .. id or id
				end
				for id in content:gmatch("{{ *sid *| *" .. pattern_escape(source_langcode) .. " *| *(*)}}") do
					alternatives = alternatives and alternatives .. ", " .. id or id
				end
				if alternatives then
					alternatives = ": Alternatives for |id= are: " .. alternatives
				else
					alternatives = ""
				end
				issue_error("Couldn't find the template {{]|" .. source_langcode .. "|" .. id .. "}} within entry ]" .. alternatives)
			end

			-- Do the following manually instead of using regex or iterators in hopes of saving memory.
			local newline, pound = 10, 35
			line_start = senseid_start
			while line_start > 0 and content:byte(line_start - 1) ~= newline do line_start = line_start - 1 end
			local def_start = line_start
			while content:byte(def_start) == pound do def_start = def_start + 1 end
			local line_end = senseid_end
			while line_end < content:len() and content:byte(line_end + 1) ~= newline do line_end = line_end + 1 end
			line = content:sub(def_start, senseid_start - 1) .. content:sub(senseid_end + 1, line_end)
		else -- id == nil
			local _, start_source = find(content, "==*" .. pattern_escape(source_langname) .. "*==")
			if not start_source then
				issue_error(("Couldn't find L2 header for source language '%s' on page ]"):format(source_langname,
					source))
			end
			-- Find index of start of next language; may be nil if no language follows.
			local _, start_next_lang = find(content, "\n==+==", start_source, false)
			content = content:sub(start_source, start_next_lang)
			while true do
				local next_line_start
				_, next_line_start = find(content, "\n#+", line_start, false)
				if not next_line_start then
					break
				end
				if line_start then
					local first_line = match(content, "(.-)%f", line_start)
					local next_line = match(content, "(.-)%f", next_line_start + 1)
					issue_error(("No id specified and saw two definition lines '%s' and '%s' for source language '%s' on page ]"):format(
						escape_wikicode(first_line), escape_wikicode(next_line), source_langname, source))
				end
				line_start = next_line_start + 1
			end
			if not line_start then
				issue_error(("Couldn't find any definition lines for source language '%s' on page ]"):format(
					source_langname, source))
			end
			line = match(content, "(.-)%f", line_start)
		end

		if to == nil then
			local i = line_start
			while i > 1 do
				i = i - 1 -- i is now the index of the newline
				while i > 1 and content:byte(i - 1) ~= 0xA do i = i - 1 end
				local header = content:match("^===+(+)===+ *\n", i)
				if header then
					to = (header:match("Verb") ~= nil)
					break
				end
			end
		end

		-- TODO: Remove this error once <nowiki> is handled correctly (see above TODO).
		if line:find("< *nowiki%W") or line:find("< */ *nowiki%W") then
			error("Cannot handle <nowiki>")
		end

		-- Quick'n'dirty templatization of manual cats so that the below code also works for them.
		for _, v in ipairs({{source_langcode .. ":", "c"}, {source_lang:getCanonicalName() .. " ", "cln"}, {"", "cat"}}) do
			line = line:gsub("% .. "(|]*)%]%]", "{{" .. v .. "|" .. source_langcode .. "|%1}}")
			line = line:gsub("% .. "(|]*)%|(|]*)%]%]", "{{" .. v .. "|" .. source_langcode .. "|%1|sort=%2}}")
		end

		-- Extract template information.
		local cats = {}
		local cats_cln = {}
		local cats_top = {}
		local encountered_label = false
		local generator = nil
		local sortkeys = {}
		local sortkey_most_frequent = nil
		local sortkey_most_frequent_n = 0
		local function process_template(name, tempargs, is_at_the_start)
			-- Expand any nested templates in template arguments.
			for k, v in pairs(tempargs) do
				tempargs = preprocess(frame, v)
			end
			local supports_sortkey = false
			local should_remove = true -- If set, removes the template from the line after processing.
			local must_be_first = false -- If set, ensures that nothing (except for removed templates) preceeds this template.
			local definition_template_handler = handle_definition_template {
				name = name,
				template_args = tempargs,
				transclude_args = args,
				place_form_of_directives = place_form_of_directives,
			}
			if definition_template_handler ~= nil then
				if generator ~= nil then
					error("Encountered {{]}} even though a full definition template has already been processed")
				end
				should_remove = definition_template_handler.should_remove
				must_be_first = definition_template_handler.must_be_first
				generator = definition_template_handler.generate
			elseif name == "categorize" or name == "cat" then
				copy_unnamed_args_maybe_except_code(cats, tempargs)
				supports_sortkey = true
			elseif name == "catlangname" or name == "cln" then
				copy_unnamed_args_maybe_except_code(cats, tempargs)
				supports_sortkey = true
			elseif name == "catlangcode" or name == "topics" or name == "top" or name == "C" or name == "c" then
				copy_unnamed_args_maybe_except_code(cats_top, tempargs)
				supports_sortkey = true
			elseif name == "label" or name == "lbl" or name == "lb" then
				if encountered_label then
					error("Encountered multiple {{]}} templates in the definition line")
				end
				encountered_label = true
				copy_unnamed_args_maybe_except_code(found_labels, tempargs)
				supports_sortkey = true
				must_be_first = true
			elseif name == "defdate" or name == "defdt" or name == "century" or name == "ref" or name == "refn" or name == "rfd-sense" or name == "rfv-sense" or name == "senseid" or name == "sid" then
				-- Remove and do nothing.
			else
				-- We are dealing with a template other than the above hard-coded ones.
				-- If it contains the language code, we cannot handle it.
				if tempargs == source_langcode then
					error("Cannot handle template {{]}}")
				end
				supports_sortkey = tempargs or tempargs -- TODO: This doesn't handle the case where there is only sortn but not sort1/sort.
				should_remove = false -- Leave the template in and just copy it, e.g. ], ], ], ] etc.
			end
			if supports_sortkey then
				if tempargs ~= nil then
					error("Cannot handle multiple sort keys")
				end
				local sortkey = tempargs
				if sortkey ~= nil then
					if sortkeys == nil then
						sortkeys = 1
					else
						sortkeys = sortkeys + 1
					end
					if sortkeys > sortkey_most_frequent_n then
						sortkey_most_frequent = sortkey
						sortkey_most_frequent_n = sortkeys
					end
				end
			end
			if must_be_first and not is_at_the_start then
				error("The template {{]}} should occur to the front of the definition line")
			end
			return should_remove
		end
		line = remove_templates_if(line, process_template)
		line = line:gsub("^%s+", ""):gsub("%s+$", "") -- Prune ends.

		-- Tidy up the remaining definition (to be used as a gloss).
		-- Truncate full sentences after a period, as they won't be formatted well as a gloss. Require a space after
		-- the period as a possible way of reducing false positives with abbreviations.
		local gloss = line
		if not args.no_truncate_gloss then
			-- Substitute a list of known abbreviations that shouldn't mark the end-point of the gloss, which will be reinserted after truncation.
			local abbrevs = {"A.D.", "B.C.", "B.C.E.", "c?.", "C.E.", "e.g.", "fl.", "i..", "r.", "sc.", "scil.", "viz.", "vs?."}
			local substitutes, i = {}, 0

			local function insert_substitute(m)
				i = i + 1
				insert(substitutes, m)
				return u(0x80000 + i)
			end

			for j, abbrev in ipairs(abbrevs) do
				abbrev = abbrev:gsub("%.", "%%.")
					:gsub("%f.", " *%0")
				abbrevs = abbrev
				gloss = gloss:gsub("%f" .. abbrev .. "%f", insert_substitute)
			end

			gloss = gloss:gsub("%s*%. .*$", "")
				:gsub("\242*", function(m)
					return substitutes
				end)
		end
		gloss = gloss:gsub("^%u", lower):gsub("%.$", "")
		gloss = gloss:gsub("^{{1|(*)}}", "%1") -- Remove ]
		local _, link_end, link_dest_head, link_dest_tail, link_face_head, link_face_tail = gloss:find("^%]*)|(.)(]*)%]%]") -- Remove ]
		if link_end ~= nil and link_dest_tail == link_face_tail and link_face_head:lower() == link_dest_head then
			gloss = "[[" .. link_dest_head .. link_dest_tail .. gloss:sub(link_end - 1)
		end
		gloss = preprocess(frame, gloss)

		if copy_sortkey then
			sort = sortkey_most_frequent
		end

		local formatted_senseid = ""
		local formatted_senseid_close = ""
		if id ~= nil then
			formatted_senseid = senseid(language, id, "span")
			if formatted_senseid:find("<span") then
				formatted_senseid_close = "</span>"
			end
		end

		local formatted_categories = args.nocat and "" or (
			((next(cats    ) == nil) and "" or frame:expandTemplate({title = "cat", args = {language_code, unpack(cats    )}})) ..
			((next(cats_cln) == nil) and "" or frame:expandTemplate({title = "cln", args = {language_code, unpack(cats_cln)}})) ..
			((next(cats_top) == nil) and "" or frame:expandTemplate({title = "top", args = {language_code, unpack(cats_top)}}))
		)
		local formatted_to = to and "to " or ""
		local formatted_definition
		if generator ~= nil then
			local data = {
				frame = frame, lang = language, source = source, source_lang = source_lang, id = id,
				sort = sort, nocat = args.nocat, no_gloss = no_gloss, gloss = gloss, formatted_to = formatted_to,
			}
			formatted_definition = generator(data)
			nolb = data.nolb or nolb
		else
			local formatted_link = full_link{term = source, lang = source_lang, id = id}
			local formatted_gloss = no_gloss and "" or (" " .. gloss_left .. gloss .. gloss_right)
			formatted_definition = formatted_to .. formatted_link .. formatted_gloss
		end

		nolb = args.nolb or nolb
		local labels_to_ignore = nil
		local ignore_all_labels = false
		if nolb then
			if nolb == "+" or nolb == "1" or nolb == "*" then
				ignore_all_labels = true
			else
				labels_to_ignore = split_labels(nolb)
			end
		end
		local this_labels = deep_copy(labels)
		if not ignore_all_labels then
			copy_unnamed_args_maybe_except_code(this_labels, found_labels, labels_to_ignore, 1)
		end
		local formatted_labels = (next(this_labels) == nil) and "" or (show_labels{labels = this_labels, lang = language, sort = sort} .. " ")

		insert(retlines, formatted_senseid .. formatted_categories .. formatted_labels .. formatted_definition ..
			formatted_senseid_close .. (args.dot and "." or ""))
	end

	return concat(retlines, "\n" .. (args.indent or "#") .. " ")
end

return export