본문으로 이동

모듈:quote

위키낱말사전, 말과 글의 누리

이 모듈에 대한 설명문서는 모듈:quote/설명문서에서 만들 수 있습니다

--[=[
	이 모듈은 quote-* 계열 틀을 구현하는 함수를 포함합니다.

	저자: Benwing2; Sgconlaw가 작성하고 Erutuon과 Benwing2가 일부 수정한
	{{quote-meta/source}} 틀을 Lua로 변환함.

	주요 인터페이스는 quote_t()입니다. 출처 표시는 source() 함수가 처리하는데,
	이 함수는 자신에게 전달된 인수와 부모 틀에 전달된 인수를 모두 읽으며,
	자신에게 전달된 인수가 우선합니다.
]=]

local export = {}

-- 사용하는 모든 모듈에 대한 이름 지정 상수. 샌드박스 버전을 쉽게 교체할 수 있도록 함.
local check_isxn_module = "Module:check isxn"
local debug_track_module = "Module:debug/track"
local italics_module = "Module:italics"
local labels_module = "Module:labels"
local languages_module = "Module:languages"
local languages_error_module = "Module:languages/error"
local links_module = "Module:also/link"
local number_utilities_module = "Module:number utilities"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local qualifier_module = "Module:qualifier"
local roman_numerals_module = "Module:roman numerals"
local scribunto_module = "Module:Scribunto"
local script_utilities_module = "Module:script utilities"
local scripts_module = "Module:scripts"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local usex_module = "Module:usex"
local usex_templates_module = "Module:usex/templates"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"

local concat = table.concat
local insert = table.insert
local remove = table.remove
local require = require
local sort = table.sort
local u = mw.ustring.char
local ugsub = mw.ustring.gsub
local umatch = mw.ustring.match
local unpack = unpack or table.unpack -- Lua 5.2 호환성

-- 파싱 문제를 피하기 위해 HTML 엔티티를 사용 (특히 대괄호 관련)
local SEMICOLON_SPACE = "&#59; "
local SPACE_LBRAC = " ["
local RBRAC = "]"

-- 한국어에서는 세미콜론(;)보다는 마침표(.)를 주로 사용하므로
-- 일부 상수는 추후 변경되거나 사용되지 않을 수 있습니다.

local TEMP_LT = u(0xFFF1)
local TEMP_GT = u(0xFFF2)
local TEMP_LBRAC = u(0xFFF3)
local TEMP_RBRAC = u(0xFFF4)
local TEMP_SEMICOLON = u(0xFFF5)

local function apply_subst(...)
	apply_subst = require(usex_module).apply_subst
	return apply_subst(...)
end

local function check_isbn(...)
	check_isbn = require(check_isxn_module).check_isbn
	return check_isbn(...)
end

local function check_issn(...)
	check_issn = require(check_isxn_module).check_issn
	return check_issn(...)
end

local function debug_track(...)
	debug_track = require(debug_track_module)
	return debug_track(...)
end

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

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

local function escape_wikicode(...)
	escape_wikicode = require(parse_utilities_module).escape_wikicode
	return escape_wikicode(...)
end

local function find_best_script_without_lang(...)
	find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
	return find_best_script_without_lang(...)
end

local function format_categories(...)
	format_categories = require(utilities_module).format_categories
	return format_categories(...)
end

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

local function format_qualifier(...)
	format_qualifier = require(qualifier_module).format_qualifier
	return format_qualifier(...)
end

local function format_usex(...)
	format_usex = require(usex_module).format_usex
	return format_usex(...)
end

local function get_lang(...)
	get_lang = require(languages_module).getByCode
	return get_lang(...)
end

local function get_number(...)
	get_number = require(number_utilities_module).get_number
	return get_number(...)
end

local function get_script(...)
	get_script = require(scripts_module).getByCode
	return get_script(...)
end

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

local function page_should_be_ignored(...)
	page_should_be_ignored = require(usex_templates_module).page_should_be_ignored
	return page_should_be_ignored(...)
end

local function parse_inline_modifiers(...)
	parse_inline_modifiers = require(parse_utilities_module).parse_inline_modifiers
	return parse_inline_modifiers(...)
end

local function parse_inline_modifiers_from_segments(...)
	parse_inline_modifiers_from_segments = require(parse_utilities_module).parse_inline_modifiers_from_segments
	return parse_inline_modifiers_from_segments(...)
end

local function parse_multi_delimiter_balanced_segment_run(...)
	parse_multi_delimiter_balanced_segment_run = require(parse_utilities_module).parse_multi_delimiter_balanced_segment_run
	return parse_multi_delimiter_balanced_segment_run(...)
end

local function parse_term_with_lang(...)
	parse_term_with_lang = require(parse_utilities_module).parse_term_with_lang
	return parse_term_with_lang(...)
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_links(...)
	remove_links = require(links_module).remove_links
	return remove_links(...)
end

local function roman_to_arabic(...)
	roman_to_arabic = require(roman_numerals_module).roman_to_arabic
	return roman_to_arabic(...)
end

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

local function scribunto_parameter_key(...)
	scribunto_parameter_key = require(scribunto_module).scribunto_parameter_key
	return scribunto_parameter_key(...)
end

local function serial_comma_join(...)
	serial_comma_join = require(table_module).serialCommaJoin
	return serial_comma_join(...)
end

local function shallow_copy(...)
	shallow_copy = require(table_module).shallowCopy
	return shallow_copy(...)
end

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

local function split_alternating_runs(...)
	split_alternating_runs = require(parse_utilities_module).split_alternating_runs
	return split_alternating_runs(...)
end

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

local function split_on_comma(...)
	split_on_comma = require(parse_utilities_module).split_on_comma
	return split_on_comma(...)
end

local function tag_text(...)
	tag_text = require(script_utilities_module).tag_text
	return tag_text(...)
end

local function tag_transcription(...)
	tag_transcription = require(script_utilities_module).tag_transcription
	return tag_transcription(...)
end

local function tag_translit(...)
	tag_translit = require(script_utilities_module).tag_translit
	return tag_translit(...)
end

local function ulen(...)
	ulen = require(string_utilities_module).len
	return ulen(...)
end

local function unitalicize_brackets(...)
	unitalicize_brackets = require(italics_module).unitalicize_brackets
	return unitalicize_brackets(...)
end

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

local function usub(...)
	usub = require(string_utilities_module).sub
	return usub(...)
end

local function yesno(...)
	yesno = require(yesno_module)
	return yesno(...)
end

local function track(page)
	debug_track("quote/" .. page)
end

local function maintenance_line(text)
	return '<span class="maintenance-line">(' .. text .. ")</span>"
end

local function isbn(text)
	return "[[특수:책찾기/"
		.. text
		.. "|→ISBN]]"
		.. check_isbn(
			text,
			'&nbsp;<span class="error" style="font-size:88%">잘못된 ISBN</span>[[분류:ISBN 오류가 있는 문서]]'
		)
end

local function issn(text)
	return "[https://www.worldcat.org/issn/"
		.. text
		.. " →ISSN]"
		.. check_issn(
			text,
			'&nbsp;<span class="error" style="font-size:88%">잘못된 ISSN</span>[[분류:ISSN 오류가 있는 문서]]'
		)
end

local function lccn(text)
	text = text:gsub(" ", "")
	if text:find("-") then
		local prefix, part1, part2 = text:match("^(.-)(%d+)%-(%d+)$")
		if prefix then
			if ulen(part2) < 6 then
				part2 = ("0"):rep(6 - ulen(part2)) .. part2
			end
			text = prefix .. part1 .. part2
		end
	end
	return "[https://lccn.loc.gov/" .. mw.uri.encode(text) .. " →LCCN]"
end

local function format_date(text)
	return mw.getCurrentFrame():callParserFunction("#formatdate", text)
end

-- 날짜 파싱을 위해 위키미디어의 파서 함수를 사용
local function parse_to_ymd(datestr, param)
	if not datestr then return nil end
	local has_month = datestr:find('월') or datestr:find('%a') or datestr:match('^%d%d%d%d%-%d+')
	local has_day = datestr:find('일') or datestr:match('^%d%d%d%d%-%d+%-%d+') or (datestr:match('%a') and datestr:match('%d'))
	local parsable_date = datestr:gsub('년', '-'):gsub('월', '-'):gsub('일', ''):gsub('%s', '')
	local lang = mw.getContentLanguage()
	local ok_y, y = pcall(lang.formatDate, lang, 'Y', parsable_date)
	local m, d
	if ok_y then
		if has_month then
			local ok_m, m_val = pcall(lang.formatDate, lang, 'm', parsable_date)
			if ok_m then m = m_val end
		end
		if has_day then
			local ok_d, d_val = pcall(lang.formatDate, lang, 'd', parsable_date)
			if ok_d then d = d_val end
		end
		return {y = y, m = m, d = d, original = datestr}
	else
		return {original = datestr}
	end
end

-- YMD 테이블을 "YYYY년 M월 D일" 형식의 문자열로 변환
local function format_kor_date(ymd_table, show_day)
	if not ymd_table then return nil end
	if not ymd_table.y then return ymd_table.original end

	if not ymd_table.m and not ymd_table.d then
		return ymd_table.y
	end

	local date_parts = {ymd_table.y .. "년"}
	if ymd_table.m then
		table.insert(date_parts, " " .. tonumber(ymd_table.m) .. "월")
		if show_day ~= false and ymd_table.d then
			table.insert(date_parts, " " .. tonumber(ymd_table.d) .. "일")
		end
	end
	return table.concat(date_parts)
end

-- raw_lb= 매개변수 (또는 nil)를 개별 레이블 정보 객체로 분석한 다음,
-- 각 레이블 명세에 있는 `omit_preComma`, `omit_postSpace` 같은 플래그를 존중하여
-- 한정어(qualifier) 입력값으로 적절하게 연결합니다.
local function parse_and_format_labels(raw_lb, lang)
	if not raw_lb then
		return nil
	end
	local labels = split_and_process_raw_labels{labels = raw_lb, lang = lang, nocat = true}
	labels = format_processed_labels{labels = labels, lang = lang, no_ib_content = true}
	if labels ~= "" then -- labels가 빈 문자열일 수도 있는 특정 상황을 대비
		return {labels}
	end
end

-- 쉼표로 구분된 언어 코드 목록을 쉼표로 구분된 언어 이름 목록으로 변환합니다.
-- `langs`는 언어 코드 목록을 가져온 매개변수의 이름입니다.
local function format_langs(langs)
	local names = {}
	for i, lang in ipairs(langs) do
		names[i] = lang:getCanonicalName()
	end
	if #names == 1 then
		return names[1]
	end
	return serial_comma_join(names)
end

local function get_first_lang(langs)
	return langs[1] or get_lang("und")
end

--[=[
일반적으로 다양한 곳에서 인라인 수식어(inline modifier)와 언어 코드 접두사를 분석합니다.
(예: he:מרים<tr:Miryem>). 하지만 {{l|...}}, {{lang|...}} 등으로 인수를 감싸서 생긴
<span ...>, <i ...>, <br/> 같은 HTML 항목은 제외합니다. 기본적으로 여기서 분석하는 모든 태그는
보다 작음(<) 기호, 글자, 콜론으로 구성되어야 합니다(예: <tr:...>).
따라서 최상위 레벨에서 이런 형식이 아닌 태그가 보이면 분석을 시도하지 않습니다.
최상위 레벨로 제한하는 이유는 한정어 수식어 내부에 생성된 HTML을 허용하기 위함입니다.
(예: foo<q:similar to {{m|fr|bar}}>)

또한 URL처럼 보이는 것들이 언어 코드 접두사로 분석되지 않도록 제외합니다.
]=]
local function val_should_not_be_parsed_for_annotations(val)
	return val:find("^[^<]*<%l*[^%l:]") or val:find("^%l+://")
end

local param_mods = {
	t = {
		-- <t:...>와 <gloss:...>는 동의어입니다.
		item_dest = "gloss",
	},
	gloss = {},
	alt = {},
	tr = {},
	ts = {},
	subst = {},
	sc = {type = "script"},
	f = {
		convert = function(arg, parse_err)
			local prefix, val = arg:match("^(.-):([^ ].*)$")
			if not prefix then
				prefix = ""
				val = arg
			end
			local tags, sc_code, sc = prefix:match("^(.*)/(.-)$")
			if sc_code then
				sc = get_script(sc_code) or
					require(languages_error_module)(sc_code, parse_err, "script code", nil, "not real lang")
			else
				tags = prefix
			end
			local quals
			if tags ~= "" then
				quals = split_on_comma(tags)
				for i, qual in ipairs(quals) do
					local obj = get_lang(qual, nil, "allow etym") or get_script(qual)
					quals[i] = obj or qual
				end
			end
			return {
				quals = quals,
				sc = sc,
				val = val,
			}
		end,
		store = "insert",
	},
	q = {},
	qq = {},
}

local function generate_obj_annotated_text(text, parse_err, paramname)
	local obj = {}
	if text:find(":[^ ]") or text:find("%[%[") then
		obj.text, obj.lang, obj.link =
			parse_term_with_lang{
				term = text,
				parse_err = parse_err,
				paramname = paramname
			}
	else
		obj.text = text
		obj.link = text
	end
	return obj
end

--[=[
외국어 또는 다른 문자로 되어 있을 수 있고, 언어 접두사 및/또는 인라인 수식어로 주석이 달릴 수 있는
텍스트 속성을 분석합니다. `val`은 매개변수의 값이고, `fullname`은 값을 가져온 매개변수의 이름입니다.
`explicit_gloss`가 지정되면 <t:...> 또는 <gloss:...> 인라인 수식어로 지정된 설명을 덮어씁니다.

`val`이 nil이면 이 함수의 반환값은 nil입니다. 그렇지 않으면 언어 접두사(예: 'ar:مُؤَلِّف')와
인라인 수식어(예: 'ar:مُؤَلِّف<t:Author>')를 분석하며, 반환값은 다음 필드를 포함하는 객체입니다:
  `lang`: 언어 접두사에 해당하는 언어 객체. 없으면 nil.
  `text`: 언어 접두사와 인라인 수식어를 제거한 후의 텍스트.
  `link`: 텍스트가 두 부분으로 된 링크인 경우 링크 부분. 아니면 `text`와 동일.
  `alt`: <alt:...> 수식어로 지정된 표시 텍스트. 없으면 nil.
  `subst`: 음역 생성을 위해 사용된 치환. subst= 매개변수와 동일한 형식.
  `sc`: <sc:...> 수식어에 해당하는 문자 객체. 없으면 nil.
  `tr`: <tr:...> 수식어에 해당하는 음역. 없으면 nil.
  `ts`: <ts:...> 수식어에 해당하는 전사. 없으면 nil.
  `gloss`: `explicit_gloss` 매개변수로 지정된 설명. 없으면 <t:...> 또는 <gloss:...> 수식어. 둘 다 없으면 nil.
  `f`: 텍스트의 외국어 버전.
  `q`: 왼쪽 한정어.
  `qq`: 오른쪽 한정어.

특별한 경우로, `val`이 최상위 레벨에 HTML 태그를 포함하면 (예: '<span class="Arab">...</span>'),
언어 접두사나 인라인 수식어를 분석하지 않으며, 반환값은 `noscript` 필드가 true로 설정됩니다.
이는 format_annotated_text() 함수에게 텍스트의 문자를 식별하고 CSS 태그를 지정하지 말고,
태그 없이 그대로 두라고 지시합니다.

이 객체는 format_annotated_text() 함수에 전달되어 텍스트와 수식어를 표시하는 문자열로 형식화될 수 있습니다.
]=]
local function parse_annotated_text(val, fullname, explicit_gloss)
	if not val then
		return nil
	end
	-- 인라인 수식어를 확인할 때, {{l|...}}, {{lang|...}} 등으로 인수를 감싸서 생긴
	-- <span ...>, <i ...>, <br/> 같은 HTML 항목은 제외합니다.
	-- 또한 URL이 언어 코드 접두사로 분석되는 것을 방지합니다.
	-- val_should_not_be_parsed_for_annotations()를 참조하세요. 최상위 레벨 HTML이 있는 매개변수 값을 찾으면
	-- 'noscript = true'를 추가하여 문자 추론 및 태깅을 시도하지 않도록 합니다.
	if val_should_not_be_parsed_for_annotations(val) then
		return {text = val, link = val, noscript = true, gloss = explicit_gloss}
	end

	local obj
	if val:find("<") then
		-- 인라인 수식어 확인.
		obj = parse_inline_modifiers(val, {
			paramname = fullname,
			param_mods = param_mods,
			generate_obj = generate_obj_annotated_text,
		})
	else
		obj = generate_obj_annotated_text(val, nil, fullname)
	end

	if explicit_gloss then
		obj.gloss = explicit_gloss
	end

	return obj
end

local html_entity_char_to_replacement = {
	["<"] = TEMP_LT,
	[">"] = TEMP_GT,
	["["] = TEMP_LBRAC,
	["]"] = TEMP_RBRAC,
}

local function html_entity_replacement(entity, code_without_semicolon, hash, xcode, x, code)
	-- 엔티티 디코딩을 시도합니다. 성공하면, 특정 특수 HTML 엔티티(괄호류)를 단일 유니코드 문자로 바꿉니다.
	-- 그렇지 않으면, 세미콜론이 구분자로 해석되지 않도록 특수 문자로 바꿉니다.
	local ch = decode_entities(entity)
	if ch ~= entity then
		return html_entity_char_to_replacement[ch] or code_without_semicolon .. TEMP_SEMICOLON
	end
	-- 엔티티가 디코딩되지 않으면, 유효한 형식일 경우에만 이스케이프합니다.
	if hash == "" then
		-- 비표준 미디어위키 전용 엔티티는 이제 걸러졌으므로, ASCII 문자가 아닌 문자는 유효하지 않은 것으로 처리합니다.
		return xcode:match("^[^\128-\255]+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
	elseif x == "" then
		return xcode:match("^%d+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
	end
	return code:match("^%x+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
end

local html_entity_replacement_to_char = {
	[TEMP_LT] = "&lt;",
	[TEMP_GT] = "&gt;",
	[TEMP_LBRAC] = "&#91;",
	[TEMP_RBRAC] = "&#93;",
	[TEMP_SEMICOLON] = ";",
}

local function undo_html_entity_replacement(txt)
	-- html_entity_replacement_to_char에 있는 모든 것을 커버하는 패턴.
	return (txt:gsub("\239\191[\177-\181]", html_entity_replacement_to_char))
end

-- 참고: 일반적인 경우에 대해 이 함수를 최적화하여 Module:parse utilities를 로드하지 않도록 노력합니다.
-- 해당 모듈 없이 처리할 수 있는 경우는 인라인 수식어나 언어 접두사가 없는 단일 값,
-- 그리고 괄호, 앰퍼샌드, 콜론이 없는 다중 값입니다.
local function generate_obj_multivalued_annotated_text(text, parse_err, paramname, no_undo_html_entity_replacement)
	local obj = {}
	if text:find(":[^ ]") or text:find("%[%[") then
		obj.text, obj.lang, obj.link =
			parse_term_with_lang{
				term = text,
				parse_err = parse_err,
				paramname = paramname,
			}
	else
		obj.text = text
		obj.link = text
	end
	if not no_undo_html_entity_replacement then
		obj.text = undo_html_entity_replacement(obj.text)
		obj.link = undo_html_entity_replacement(obj.link)
	end
	return obj
end

--[=[
parse_annotated_text()와 유사하지만, 매개변수 값이 세미콜론으로 구분된 여러 개체를 포함할 수 있으며,
각각 고유한 인라인 수식어를 가질 수 있습니다.

대괄호, 중괄호, 소괄호 안에 있는 세미콜론은 구분자로 처리되지 않아야 합니다.
또한 세미콜론을 포함하는 HTML 엔티티도 있을 수 있습니다.

반환값은 parse_annotated_text()가 반환하는 것과 같은 종류의 객체 목록입니다.
]=]
local function parse_multivalued_annotated_text(val, fullname, explicit_gloss, explicit_gloss_fullname)
	if not val then
		return nil
	end
	-- 아래 코드에서 'entity'는 주로 세미콜론으로 구분된 값(주로 사람 이름)을 의미합니다.
	local splitchar, korean_delim
	if val:find("^,") then
		splitchar = ","
		korean_delim = "쉼표"
		val = val:gsub("^,", "")
	else
		splitchar = ";"
		korean_delim = "세미콜론"
	end

	-- 최적화 #1: 세미콜론/쉼표나 꺾쇠괄호(인라인 수식어)가 없는 경우.
	if not val:find("[<" .. splitchar .. "]") then
		if val_should_not_be_parsed_for_annotations(val) then
			return {{text = val, link = val, noscript = true}}
		else
			return {generate_obj_multivalued_annotated_text(val, nil, fullname, "no undo html entity replacement")}
		end
	end

	-- 최적화 #2: 세미콜론/쉼표는 있지만, 꺾쇠괄호, 중괄호, 대괄호, 소괄호나 앰퍼샌드가 없는 경우.
	if not val:find("[<>%[%](){}&]") then
		local entity_objs = {}
		for entity in gsplit(val, "%s*" .. splitchar .. "%s*") do
			if val_should_not_be_parsed_for_annotations(entity) then
				insert(entity_objs, {
					text = entity,
					link = entity,
					noscript = true
				})
			else
				insert(entity_objs, generate_obj_multivalued_annotated_text(entity, nil, fullname, "no undo html entity replacement"))
			end
		end
		return entity_objs
	end

	-- HTML 엔티티를 이스케이프하고 방향성 마커를 제거합니다.
	local amp = val:find("&", nil, true)
	if amp then
		-- The pattern is more permissive than the usual entity pattern, as MediaWiki has some nonstandard entities
		-- that have non-ASCII characters in their codes.
		val = val:gsub("((&(#?)(([xX]?)([%w\128-\255]+)));)", html_entity_replacement)
	end
	-- left-to-right (U+200E) and right-to-left (U+200F) 마커 제거.
	val = val:gsub("\226\128[\142\143]", "")

	-- 짝이 맞는 구분자(balanced delimiter)를 분석합니다.
	local entity_runs = parse_multi_delimiter_balanced_segment_run(
		val,
		{{"[" .. TEMP_LBRAC, "]" .. TEMP_RBRAC}, {"(", ")"}, {"{", "}"}, {"<" .. TEMP_LT, ">" .. TEMP_GT}},
		true
	)
	if type(entity_runs) == "string" then
		local undo_val = undo_html_entity_replacement(val)
		-- 짝이 맞지 않는 구분자로 인한 파싱 오류. 오류를 발생시키지 않고 값을 그대로 반환합니다.
		return {{text = undo_val, link = undo_val, noscript = not not val_should_not_be_parsed_for_annotations(val)}}
	end

	-- 세미콜론(또는 쉼표)으로 분리합니다.
	local separated_groups = split_alternating_runs(entity_runs, "%s*" .. splitchar .. "%s*")

	-- 각 값을 처리합니다.
	local entity_objs = {}
	for _, entity_group in ipairs(separated_groups) do
		-- <...>가 아닌 부분을 다시 합칩니다.
		local j = 2
		while j <= #entity_group do
			if not entity_group[j]:find("^<.*>$") then
				entity_group[j - 1] = entity_group[j - 1] .. entity_group[j] .. entity_group[j + 1]
				remove(entity_group, j)
				remove(entity_group, j)
			else
				j = j + 2
			end
		end

		local oneval = undo_html_entity_replacement(concat(entity_group))
		-- When checking for inline modifiers, exclude HTML entry with <span ...>, <i ...>, <br/> or similar in it,
		-- caused by wrapping an argument in {{l|...}}, {{lang|...}} or similar. Also exclude URL's from being parsed
		-- as having language code prefixes. This works analogously to parse_annotated_text(); see there for more.
		if val_should_not_be_parsed_for_annotations(oneval) then
			insert(entity_objs, {
				text = oneval,
				link = oneval,
				noscript = true
			})
		else
			local obj
			if #entity_group > 1 then
				-- 인라인 수식어 확인.
				obj = parse_inline_modifiers_from_segments({
					group = entity_group,
					arg = oneval,
					props = {
						paramname = fullname,
						param_mods = param_mods,
						generate_obj = generate_obj_multivalued_annotated_text,
					},
				})
			else
				obj = generate_obj_multivalued_annotated_text(entity_group[1], nil, fullname)
			end
			insert(entity_objs, obj)
		end
	end

	if explicit_gloss then
		if #entity_objs > 1 then
			error(
				(
					"|%s=와 |%s=의 여러 %s 구분 개체를 함께 지정할 수 없습니다; "
					.. "개별 개체에 <t:...> 인라인 수식어를 사용하세요."
				):format(explicit_gloss_fullname, english_delim, fullname)
			)
		end
		entity_objs[1].gloss = explicit_gloss
	end

	return entity_objs
end

--[=[
외국어 또는 다른 문자로 된 텍스트 속성을 주석과 함께 형식화합니다.
Module:links의 full_link() 함수와 개념적으로 유사하지만, 서지 정보에 더 적합한 다른 형식으로 주석을 표시합니다.
출력 형식은 다음과 같습니다:

TEXT [TRANSLIT /TRANSCRIPTION/, GLOSS]

`textobj`는 parse_annotated_text()가 반환한 객체입니다.
`tag_text_func`는 텍스트 처리 및 CSS 태깅 후 텍스트를 추가로 감싸는 함수입니다.
`tag_gloss_func`는 설명(gloss)에 대한 유사한 함수입니다.
]=]
local function format_annotated_text(textobj, tag_text_func, tag_gloss_func)
	if not textobj then
		return nil
	end
	local text, link = textobj.text, textobj.link
	local subst, tr, ts, f, gloss, alt = textobj.subst, textobj.tr, textobj.ts, textobj.f, textobj.gloss, textobj.alt

	if alt then
		if link:find("%[%[") or link:find("%]%]") then
			local errmsg = ("현재 '%s'에 포함된 링크와 <alt:...> 텍스트 '%s'를 함께 처리할 수 없습니다."):format(link, alt)
			error(escape_wikicode(errmsg))
		end
		text = ("[[%s|%s]]"):format(link, alt)
	end

	-- `noscript`는 텍스트 값에서 HTML이 발견되었음을 의미하며, 아마도 {{lang|...}}을 사용하여 생성된 것입니다.
	-- {{lang}}은 이미 텍스트에 문자 태그를 지정하고 내장 언어 링크를 처리하므로 다시 수행하지 않습니다.
	if not textobj.noscript then
		local lang = textobj.lang
		-- 최적화를 위해 ASCII로만 구성된 인수에 대해서는 문자 감지를 수행하지 않습니다.
		local sc = textobj.sc
			or lang and lang:findBestScript(text)
			or not text:find("^[ -~]$") and find_best_script_without_lang(text)
			or nil
		-- 최적화를 위해 언어, 문자, 음역 또는 전사가 없는 경우 다음을 수행하지 않습니다.
		if lang or sc or tr or ts then
			if not lang then
				lang = get_lang("und")
			end
			
			local auto_tr_exclusions = { ["ko"] = true, ["jje"] = true }

			if tr == "-" then
				tr = nil
			elseif not tr and sc and not sc:getCode():find("Lat") and not auto_tr_exclusions[lang:getCode()] then
				-- might return nil
				local text_for_tr = text
				if subst then
					text_for_tr = apply_subst(text_for_tr, subst)
				else
					text_for_tr = remove_links(text)
				end

				tr = (lang:transliterate(text_for_tr, sc))
			end

			if text:find("%[%[") then
				text = embedded_language_links({
					term = text,
					lang = lang,
					sc = sc,
				})
			end
			if lang:getCode() ~= "und" or sc:getCode() ~= "Latn" then
				text = tag_text(text, lang, sc)
			end

			if tr then
				tr = tag_translit(tr, lang, "usex")
			end
			if ts then
				ts = tag_transcription(ts, lang, "usex")
			end
		end
	end

	text = unitalicize_brackets(text)
	if tag_text_func then
		text = tag_text_func(text)
	end

	local parts = {}

	if textobj.q then
		insert(parts, format_qualifier(textobj.q) .. " ")
	end

	insert(parts, text)

	if tr or ts or f or gloss then
		insert(parts, " (") -- 여는 괄호
		local subparts = {}
		if tr then
			insert(subparts, "음역: " .. tr)
		end
		if ts then
			insert(subparts, "발음: /" .. ts .. "/")
		end
		if f then
			for _, ff in ipairs(f) do
				local sc = ff.sc
				local lang
				if not sc and ff.quals then
					local qual = ff.quals[1]
					if type(qual) == "string" then
						-- 아무것도 하지 않음. 아래에서 문자 감지를 수행함.
					elseif qual:hasType("script") then
						sc = qual
					else -- language
						sc = qual:findBestScript(ff.val)
						lang = qual
					end
				end
				if not lang then
					lang = get_lang("und")
				end
				sc = sc or find_best_script_without_lang(ff.val)
				local val = embedded_language_links({
					term = ff.val,
					lang = lang,
					sc = sc,
				})
				if lang:getCode() ~= "und" or sc:getCode() ~= "Latn" then
					val = tag_text(val, lang, sc)
				end
				local qual_prefix
				if ff.quals then
					for i, qual in ipairs(ff.quals) do
						if type(qual) ~= "string" and (qual:hasType("script") or qual:hasType("language")) then
							ff.quals[i] = qual:getCanonicalName()
						end
					end
					qual_prefix = concat(ff.quals, "/") .. ": "
				else
					qual_prefix = ""
				end
				insert(subparts, qual_prefix .. val)
			end
		end
		if gloss then
			gloss = '<span class="e-translation">' .. gloss .. "</span>"
			gloss = unitalicize_brackets(gloss)
			if tag_gloss_func then
				gloss = tag_gloss_func(gloss)
			end
			insert(subparts, "번역: " .. gloss)
		end
		insert(parts, concat(subparts, ", "))
		insert(parts, ")") -- 닫는 괄호
	end

	if textobj.qq then
		insert(parts, " " .. format_qualifier(textobj.qq))
	end

	return concat(parts)
end

--[=[
외국어 또는 다른 문자로 된 다중 값 텍스트 속성을 주석과 함께 형식화합니다.
이것은 format_annotated_text()의 다중 값 버전이며, 각 개별 개체를
format_annotated_text()를 사용하여 형식화하고 결과를 `delimiter`로 결합합니다.
기본값은 ", "입니다. `delimiter`가 "and" 또는 "or"이면 지정된 접속사로 결합합니다.

`textobjs`는 parse_multivalued_annotated_text()가 반환한 객체입니다.
`tag_text_func`와 `tag_gloss_func`는 format_annotated_text()에서와 동일합니다.
]=]
local function format_multivalued_annotated_text(textobjs, delimiter, tag_text_func, tag_gloss_func)
	if not textobjs then
		return nil
	end
	if #textobjs == 1 then
		return format_annotated_text(textobjs[1], tag_text_func, tag_gloss_func)
	end
	local parts = {}
	for _, textobj in ipairs(textobjs) do
		insert(parts, format_annotated_text(textobj, tag_text_func, tag_gloss_func))
	end
	local n = #parts
	-- 한국어화 주석: 'et al.'을 '외'로 처리합니다.
	if n > 0 and parts[n]:match("^'*외[%.']*$") then
		parts[n] = "''외''"
		if n == 2 then
			return concat(parts, " ")
		end
		if delimiter == "and" or delimiter == "or" then
			delimiter = ", "
		end
		return concat(parts, delimiter)
	end
	if delimiter == "and" or delimiter == "or" then
		return serial_comma_join(parts, {conj = delimiter})
	end
	return concat(parts, delimiter or ", ")
end

-- ine() (if-not-empty)의 향상된 버전. 빈 문자열을 nil로 변환하고, 앞뒤 공백도 제거합니다.
local function ine(arg)
	if not arg then
		return nil
	elseif type(arg) ~= "string" then
		return arg
	end
	arg = mw.text.trim(arg)
	if arg == "" then
		return nil
	end
	return arg
end

local abbrs = {
	["a."] = {anchor = "a.", full = "이전(ante)"},
	["c."] = {anchor = "c.", full = "대략(circa)"},
	["p."] = {anchor = "p.", full = "이후(post)"},
}

-- 임의의 날짜 또는 연도 사양 시작 부분의 접두사 'a.' (ante), 'c.' (circa), 'p.' (post)를 처리합니다.
-- 두 개의 값을 반환합니다: 형식화된 접두사와 접두사가 제거된 날짜 사양.
-- 접두사가 없으면 빈 문자열과 전체 날짜를 반환합니다.
local function process_ante_circa_post(date)
	local prefix = usub(date, 1, 2)
	local abbr = abbrs[prefix]
	local abbr_prefix = ""

	if abbr then
		abbr_prefix = "''[[부록:용어집#"
			.. abbr.anchor
			.. '|<abbr title="'
			.. abbr.full
			.. '">'
			.. abbr.anchor
			.. "</abbr>]]'' "
		-- 날짜 매개변수 시작 부분의 소문자, 마침표, 공백을 제거합니다.
		date = ugsub(date, "^%l%.%s*", "")
	end

	return abbr_prefix, date
end

-- 인용 날짜를 지정하는 인수들의 형식을 지정합니다.
local function format_date_args(
	a,
	get_full_paramname,
	alias_map,
	parampref,
	paramsuf,
	bold_year, -- 호환성을 위해 유지
	maintenance_line_no_date,
	year_last -- 호환성을 위해 유지
)
	
	local output = {}

	parampref = parampref or ""
	paramsuf = paramsuf or ""
	local function getp(param)
		return a(parampref .. param .. paramsuf)
	end
	local function pname(param)
		local fullname = get_full_paramname(parampref .. param .. paramsuf)
		return alias_map[fullname] or fullname
	end
	
	local start_date_str = getp("start_date")
	local date_str = getp("date")
	local year = getp("year")
	local month = getp("month")
	local start_year = getp("start_year")
	local start_month = getp("start_month")
	
	if not start_date_str and start_year then
		local start_year_val = (tostring(start_year):gsub('년', ''))
		if start_month then
			local start_month_val = (tostring(start_month):gsub('월', ''))
			start_date_str = start_year_val .. "년 " .. start_month_val .. "월"
		else
			start_date_str = start_year_val .. "년"
		end
	end
	
	-- 오류 검사
	if date and year then
		error(
			("|%s= 또는 |%s= 중 하나만 지정해야 합니다."):format(
				pname("date"),
				pname("year")
			)
		)
	end
	if start_date_str and start_year then
		error(
			("|%s=|와 |%s=| 중 하나만 지정해야 합니다."):format(
				pname("start_date"), 
				pname("start_year")
			)
		)
	end
	if (start_date_str or start_year) and not (date_str or year) then
		error(
			("|%s=| 또는 |%s=/%s=|가 없으면 |%s=| 또는 |%s=/%s=|를 지정할 수 없습니다."):format(
				pname("date"),
				pname("year"),
				pname("month"),
				pname("start_date"),
				pname("start_year"),
				pname("start_month")
			)
		)
	end

	-- 최종 날짜 및 시작 날짜 조합
	local final_date_str = date_str or (year and (month and year .. " " .. month or year))
	local final_start_date_str = start_date_str or (start_year and (start_month and start_year .. " " .. start_month or start_year))

	local final_date_parts = parse_to_ymd(final_date_str)
	local final_start_date_parts = parse_to_ymd(final_start_date_str)

	local date_has_day = date_str and (date_str:find("일") or tonumber(final_date_parts.d) ~= 1)
	local start_date_has_day = start_date_str and (start_date_str:find("일") or tonumber(final_start_date_parts.d) ~= 1)
	
	-- circa 등 접두사 처리
	if final_start_date_parts then
		local abbr_prefix, original = process_ante_circa_post(final_start_date_parts.original)
		final_start_date_parts.original = original
		insert(output, abbr_prefix)
	elseif final_date_parts then
		local abbr_prefix, original = process_ante_circa_post(final_date_parts.original)
		final_date_parts.original = original
		insert(output, abbr_prefix)
	end
	
	if final_start_date_parts then -- 날짜 범위가 있는 경우
		insert(output, format_kor_date(final_start_date_parts, start_date_has_day))
		
		if final_date_parts.y and final_date_parts.y ~= final_start_date_parts.y then
			-- 연도가 다르면 전체 종료 날짜를 표시
			insert(output, "&nbsp;– ")
			insert(output, format_kor_date(final_date_parts, date_has_day))
		elseif final_date_parts.m and final_date_parts.m ~= final_start_date_parts.m then
			-- 연도는 같고 월이 다르면, 월과 일을 표시
			insert(output, "&nbsp;– ")
			local end_date_parts = {}
			insert(end_date_parts, tonumber(final_date_parts.m) .. "월")
			if date_has_day then
				insert(end_date_parts, " " .. tonumber(final_date_parts.d) .. "일")
			end
			insert(output, table.concat(end_date_parts))
		elseif final_date_parts.d and final_date_parts.d ~= final_start_date_parts.d then
			-- 연도와 월이 같고 일이 다르면, 일만 표시
			insert(output, "–")
			insert(output, tonumber(final_date_parts.d) .. "일")
		end
	elseif final_date_parts then -- 단일 날짜
		insert(output, format_kor_date(final_date_parts, date_has_day))
	elseif not maintenance_line_no_date then
		-- 주 인용 날짜가 아님. nil을 반환하면 호출자가 처리함.
		return nil, nil
	elseif not getp("nodate") then
		local accessdate = getp("accessdate")
		if accessdate then
			insert(output, format_kor_date(parse_to_ymd(accessdate), true) .. " (마지막 확인)")
		else
			if mw.title.getCurrentTitle().namespace ~= 10 then -- 틀 이름공간이 아니면
				return maintenance_line(maintenance_line_no_date), true
			end
			return nil, nil
		end
	end

	return ine(concat(output)), nil
end

local function tag_with_cite(txt)
	-- <cite> 태그 대신 겹낫표 『』를 사용합니다.
	-- 책 제목, 저널 이름 등에 적용됩니다.
	return "『" .. txt .. "』"
end

-- 인용의 출처 라인을 표시합니다. 이 부분에 모듈 로직의 대부분이 포함되어 있습니다.
function export.source(args, alias_map, format_as_cite, other_controls)

	local tracking_categories = {}

	local argslang = args[1] or args.lang
	if not argslang then
		local current_title = mw.title.getCurrentTitle()
		if not (current_title.namespace == 10 or page_should_be_ignored(current_title.fullText)) then
			require(languages_error_module)(nil, 1)
		end
	end

	local function alias(param)
		return alias_map[param] or param
	end

	local output, sep = {}
	local overrides = other_controls and other_controls.overrides or {}

	local function add(text)
		if sep then
			insert(output, sep)
		end
		insert(output, text)
		sep = nil
	end
	local function add_with_sep(text, next_sep)
		add(text)
		sep = next_sep or ". "
	end

	local function make_get_full_paramname(ind)
		return function(param)
			return param .. ind
		end
	end
	
	local get_full_paramname
	
	local function a_with_name(param)
		if not param then
			return nil
		elseif type(param) ~= "table" then
			local fullname = get_full_paramname(param)
			return args[fullname], alias(fullname)
		end
		for _, par in ipairs(param) do
			local val, fullname = a_with_name(par)
			if val then
				return val, alias(fullname)
			end
		end
		return nil
	end
	local function a(param)
		return (a_with_name(param))
	end

	local function aurl_with_name(param)
		local value, fullname = a_with_name(param)
		if value and value:find(" ") and not value:find("%[") then
			error(("URL에 공백을 포함할 수 없습니다: |%s=%s"):format(fullname, value))
		end
		return value, fullname
	end
	local function aurl(param)
		return (aurl_with_name(param))
	end

	local function parse_and_format_annotated_text_with_name(param, tag_text_func, tag_gloss_func)
		local val, fullname = a_with_name(param)
		local obj = parse_annotated_text(val, fullname)
		return format_annotated_text(obj, tag_text_func, tag_gloss_func), fullname
	end

	local function parse_and_format_annotated_text(param, tag_text_func, tag_gloss_func)
		return (parse_and_format_annotated_text_with_name(param, tag_text_func, tag_gloss_func))
	end

	local function parse_and_format_multivalued_annotated_text_with_name(param, delimiter, tag_text_func, tag_gloss_func)
		local val, fullname = a_with_name(param)
		local objs = parse_multivalued_annotated_text(val, fullname)
		local num_objs = objs and #objs or 0
		return format_multivalued_annotated_text(objs, delimiter, tag_text_func, tag_gloss_func), fullname, num_objs
	end

	local function parse_and_format_multivalued_annotated_text(param, delimiter, tag_text_func, tag_gloss_func)
		return (parse_and_format_multivalued_annotated_text_with_name(param, delimiter, tag_text_func, tag_gloss_func))
	end

	local author_outputted = false

	local date_outputted, formatted_date, formatted_origdate = false
	local function add_date(no_paren)
		if not date_outputted then
			if formatted_date or formatted_origdate then
				if formatted_date then
					add(" (" .. formatted_date .. ")")
				end
				if formatted_origdate then
					add(SPACE_LBRAC .. formatted_origdate .. RBRAC)
				end

				sep = ". "
			end
			date_outputted = true
		end
	end

	local function is_anonymous(val)
		return val:match("^[Aa]nonymous$") or val:match("^[Aa]non%.?$") or val == "작자 미상"
	end
	
	local function has_newversion()
		return a("2ndauthor") or a("2ndlast") or a("chapter2") or a("title2") or
			a("tlr2") or a("mainauthor2") or a("editor2") or a("editors2") or
			a("newversion") or a("location2") or a("publisher2") or a("year2") or
			a("date2") or a("page2")
	end
	
	local function add_newversion()
		sep = nil
		add(SEMICOLON_SPACE)
		add(a("newversion") or "재출간:")
		add(" ")
	end

	local function add_author(
		author, author_fullname, trans_author, trans_author_fullname,
		authorlink, authorlink_fullname, trans_authorlink, trans_authorlink_fullname,
		first, first_fullname, trans_first, trans_first_fullname,
		last, last_fullname, trans_last, trans_last_fullname,
		last_first
	)
		local function make_author_with_url(txt, txtparam, authorlink, authorlink_param)
			if authorlink then
				if authorlink:find("%[%[") then
					error(("|%s=%s에 링크를 지정할 수 없습니다."):format(authorlink_param, authorlink))
				end
				if txt:find("%[%[") then
					error(("%s=%s에 링크를 지정할 수 없습니다."):format(txtparam, txt))
				end
				return "[[w:" .. authorlink .. "|" .. txt .. "]]"
			else
				return txt
			end
		end

		local num_authorobjs
		if author then
			local authorobjs = parse_multivalued_annotated_text(author, author_fullname, trans_author, trans_author_fullname)
			num_authorobjs = #authorobjs
			if num_authorobjs == 1 then
				if is_anonymous(authorobjs[1].text) then
					authorobjs[1].text = "작자 미상"
					authorobjs[1].link = "작자 미상"
				end
				if authorlink then
					authorobjs[1].text = make_author_with_url(authorobjs[1].text, "|" .. author_fullname, authorlink, "|" .. authorlink_fullname)
					authorobjs[1].link = make_author_with_url(authorobjs[1].link, "|" .. author_fullname, authorlink, "|" .. authorlink_fullname)
				end
				if authorobjs[1].gloss and trans_authorlink then
					authorobjs[1].gloss = make_author_with_url(authorobjs[1].gloss, ("<t:...> in |%s"):format(author_fullname), trans_authorlink, "|" .. trans_author_fullname)
				end
				add(format_multivalued_annotated_text(authorobjs, "; "))
			else
				local formatted_text = format_multivalued_annotated_text(authorobjs, "; ")
				if authorlink then
					formatted_text = make_author_with_url(formatted_text, "|" .. author_fullname, authorlink, "|" .. authorlink_fullname)
				end
				add(formatted_text)
			end
		else
			num_authorobjs = 1
			if first then
				if last_first then
					author = last .. ", " .. first
				else
					author = first .. " " .. last
				end
			else
				author = last
			end
			if authorlink then
				local authorparam = first and ("|%s |%s"):format(first_fullname, last_fullname) or "|" .. last_fullname
				author = make_author_with_url(author, authorparam, authorlink, authorlink_fullname)
			end
			local trans_author
			if trans_last then
				if trans_first then
					trans_author = trans_first .. " " .. trans_last
				else
					trans_author = trans_last
				end
				if trans_authorlink then
					local trans_authorparam = trans_first and ("|%s |%s"):format(trans_first_fullname, trans_last_fullname) or "|" .. trans_last_fullname
					trans_author = make_author_with_url(trans_author, trans_authorparam, trans_authorlink, trans_authorlink_fullname)
				end
			end
			add(author)
			if trans_author then
				add(" (" .. trans_author .. ")")
			end
		end

		author_outputted = true
		return num_authorobjs
	end

	local function add_authorlike(param, prefix_when_author, suffix_no_author, suffix_multiple)
		local delimiter = "; "
		local entities, _, num_entities = parse_and_format_multivalued_annotated_text_with_name(param, delimiter)
		if not entities then return end
		
		local suffix = suffix_no_author
		if num_entities > 1 and suffix_multiple then
			suffix = suffix_multiple
		end

		if is_anonymous(entities) then
			add_with_sep("작자 미상의" .. suffix)
		else
			if author_outputted then
				add_with_sep(prefix_when_author .. entities)
			else
				add_with_sep(entities .. " (" .. suffix .. ")")
			end
		end
		author_outputted = true
	end

	local function format_chapterlike(param, numeric_prefix, textual_prefix, textual_suffix)
		local chap, chap_fullname = a_with_name(param)
		if not chap then return nil end

		-- 저널 인용 등에서 chapterurl 대신 url을 사용하도록 수정
		local chapterurl = aurl(param .. "url") or aurl("url")
		local function make_chapter_with_url(chap_text)
			return chapterurl and ("[" .. chapterurl .. " " .. chap_text .. "]") or chap_text
		end

		local chapterobj = parse_annotated_text(chap, chap_fullname, a("trans-" .. param))
		chapterobj.text = make_chapter_with_url(chapterobj.text)
		chapterobj.link = make_chapter_with_url(chapterobj.link)
		return "「" .. format_annotated_text(chapterobj) .. "」"
	end
	
	-- 저자 정보 출력 후 나머지 모든 서지 정보를 처리하는 함수
	local function postauthor(ind, num_authors, format_as_cite)
		get_full_paramname = make_get_full_paramname(ind)

		-- URL이 챕터(기사 제목)에 이미 사용되었는지 추적하는 변수
		local url_already_used = (a("chapter") or a("episode")) and (a("chapterurl") or a("url"))

		-- 1. 챕터/항목 (제목 등)
		local formatted_entry = format_chapterlike("entry") or format_chapterlike("chapter") or format_chapterlike("episode")
		if formatted_entry then
			add_with_sep(formatted_entry)
		end
		
		-- 2. 제목 (책, 저널 등)
		local title_param_name = "title"
		if (args.type == "journal" or args.magazine) and not args.title then
			title_param_name = "magazine"
		end
		local title, title_fullname = a_with_name(title_param_name)

		if title then
			-- URL이 챕터에 사용되지 않았을 경우, 주 제목에 URL을 링크합니다.
			local title_url = not url_used_for_chapter and aurl("url")
			local function tag_title_with_link(txt)
				local linked_txt = title_url and ("[" .. title_url .. " " .. txt .. "]") or txt
				return "『" .. linked_txt .. "』"
			end
			local titleobj = parse_annotated_text(title, title_fullname, a("trans-" .. title_param_name))
			add(format_annotated_text(titleobj, tag_title_with_link))
		elseif ind == "" and not a("notitle") then
			add(maintenance_line("책 제목이나 저널명을 입력해주세요."))
		end

		-- 3. 시리즈 정보
		local series = parse_and_format_annotated_text("series")
		if series then
			add(" (" .. series)
			local seriesvolume = parse_and_format_annotated_text("seriesvolume")
			if seriesvolume then
				add(SEMICOLON_SPACE .. seriesvolume)
			end
			add(")")
		end
		
		sep = ". "
		
		local season = a("season")
		if season then
			add_with_sep("시즌 " .. season)
		end
		
		local number = a("number")
		if number then
			add_with_sep(number .. "화")
		end

		local note = a("note")
		if note then
			add(note) 
		end

		-- 4. 편집자, 번역가 등
		add_authorlike("editor", nil, "엮음", "엮음")
		add_authorlike("tlr", nil, "번역", "번역")

		-- 5. 판차
		local edition = parse_and_format_annotated_text("edition")
		if edition then add_with_sep(edition .. "판") end

		-- 6. 출판 정보 (장소, 출판사)
		local location = parse_and_format_multivalued_annotated_text("location")
		local publisher = parse_and_format_multivalued_annotated_text("publisher", "; ")
		if publisher then
			if location then
				add_with_sep(location .. ": " .. publisher)
			else
				add_with_sep(publisher)
			end
		elseif location then
			add_with_sep(location)
		end

		-- 7. 페이지 (URL 링크 기능 추가)
		local page = a("page") or a("pages")
		if page then
			local pageurl = aurl("pageurl")
			if pageurl then
				add_with_sep("[" .. pageurl .. " " .. page .. "쪽]")
			else
				add_with_sep(page .. "쪽")
			end
		end
		
		-- 8. 식별자 (DOI, ISBN 등)
		local function small(txt)
			add("<small>" .. txt .. "</small>")
		end
		
		local function add_identifier(param_or_params, pretext, posttext, process)
			local val = a(param_or_params)
			if val then
				val = (process or mw.uri.encode)(val)
				small(pretext .. val .. posttext)
			end
		end

		add_identifier("issn", "[https://www.worldcat.org/issn/", " →ISSN]")
		add_identifier("oclc", "[https://search.worldcat.org/title/", " →OCLC]")
		add_identifier("isbn", "", "", isbn)
		add_identifier("doi", '<span class="neverexpand">[https://doi.org/', " →DOI]</span>")
		
		local id = a("id")
		if id then small(id) end
		
		-- 9. 확인 날짜 및 보존 정보
		local archiveurl = aurl("archiveurl")
		local accessdate = a("accessdate")
		local original_url = aurl("url")
		
		if archiveurl or accessdate then
			local text_to_add = ""
			if archiveurl then
				local original_url = aurl("url")
				local archivedate_str = a("archivedate")
				local formatted_archivedate = ""
				if archivedate_str then
					formatted_archivedate = format_kor_date(parse_to_ymd(archivedate_str), true)
				end
				
				local archive_text = ""
				if original_url then
					 archive_text = "[" .. original_url .. " 원본 문서]에서 "
				end
				archive_text = archive_text .. "[" .. archiveurl .. " 보존된 사본]"
				if formatted_archivedate and formatted_archivedate ~= "" then
					archive_text = archive_text .. " (" .. formatted_archivedate .. ")"
				end
				add_with_sep(archive_text)
			-- |date=나 |year=가 있을 때만 "확인함" 정보를 맨 뒤에 추가합니다.
			elseif accessdate and (a("date") or a("year")) then
				local formatted_accessdate = format_kor_date(parse_to_ymd(accessdate), true)
				add_with_sep(formatted_accessdate .. "에 확인함")
			end
			
			sep = ". "
			add(text_to_add)
		end
	end

	local function add_authors(args, last_first)
		local maxind = math.max(args.author.maxindex, args.last.maxindex)
		local ancillary_params = {"trans-author", "authorlink", "trans-authorlink", "first", "trans-first", "trans-last"}
		for _, ancillary in ipairs(ancillary_params) do
			maxind = math.max(maxind, args[ancillary].maxindex)
		end

		local num_authors = 0
		for i = 1, maxind do
			local ind = i == 1 and "" or i
			local author, last = args.author[i], args.last[i]
			if author or last then
				local this_num_authors = add_author(
					author, "author" .. ind, args["trans-author"][i], "trans-author" .. ind,
					args.authorlink[i], "authorlink" .. ind, args["trans-authorlink"][i], "trans-authorlink" .. ind,
					args.first[i], "first" .. ind, args["trans-first"][i], "trans-first" .. ind,
					last, "last" .. ind, args["trans-last"][i], "trans-last" .. ind,
					last_first
				)
				num_authors = num_authors + this_num_authors
				sep = ""
			else
				for _, cant_have in ipairs(ancillary_params) do
					if args[cant_have][i] then
						error(("|%s%s= 없이 |author%s= 또는 |last%s=를 지정할 수 없습니다."):format(cant_have, ind, ind, ind))
					end
				end
			end
		end
		return num_authors
	end

	------------------- 여기서부터 텍스트 출력을 시작합니다 ----------------------
	
	get_full_paramname = make_get_full_paramname("")
	
	-- 1. 원본 날짜 정보 처리
	formatted_date, need_date = format_date_args(a, get_full_paramname, alias_map, nil, nil, nil, "인용 날짜를 입력해주세요.")
	formatted_origdate = format_date_args(a, get_full_paramname, alias_map, "orig", nil, nil, nil, nil)
	
	-- 2. 원본 저자 및 서지 정보 출력
	local num_authors = add_authors(args)
	add_date()
	postauthor("", num_authors, format_as_cite)
	
	-- 3. 재출간/번역 등 'newversion' 처리
	if has_newversion() then
		add_newversion()

		get_full_paramname = make_get_full_paramname("2")
		
		-- |2ndauthor= 또는 |2ndlast=가 있을 때만 저자 정보를 추가합니다.
		local num_authors2 = 0
		if a("2ndauthor") or a("2ndlast") then
			num_authors2 = add_authors(args)
		end
		
		local formatted_date2, _ = format_date_args(a, get_full_paramname, alias_map, nil, nil, nil, nil)
		if formatted_date2 then
			if num_authors2 > 0 then
				add(" (" .. formatted_date2 .. ")")
			else
				add(formatted_date2)
			end
			sep = ". "
		end
		
		postauthor("2", num_authors2, format_as_cite)
	end
	
	-- 최종 문자열을 조립합니다.
	local output_text = concat(output)
	if sep then -- 남아있는 구분자가 있으면 추가합니다.
		output_text = output_text .. sep
	end

	-- 4. 최종 구두점 처리
	if not args.nocolon then
		-- 문자열 끝의 공백이나 구분자를 모두 제거하고,
		output_text = output_text:gsub("%s*[%.%s,;]%s*$", "")
		-- 최종 마침표를 하나만 추가합니다.
		output_text = output_text .. "."
	end

	-- 5. 분류 추가 로직
	local categories = {}
	if not args.nocat then
		if need_date then
			local argslangobj = get_first_lang(argslang)
			insert(categories, "인용 날짜가 필요한 " .. argslangobj:getCanonicalName() .. " 항목")
		end
	end

	return output_text .. ((#categories > 0 and format_categories(categories, get_first_lang(argslang), args.sort) or ""))
end

-- Alias specs for type= and type2=. Each spec is `{canon, aliases, with_newversion}` where `canon` is the canonical
-- parameter (with "2" added if type2= is being handled), `aliases` is a comma-separated string of aliases (with "2"
-- added if type2= is being handled, except for numeric params), and `with_newversion` indicates whether we should
-- process this spec if type2= is being handled.
local type_alias_specs = {
	book = {
		{"author", "3"},
		{"title", "4"},
		{"url", "5"},
		{"year", "2"},
		{"page", "6"},
		{"text", "7"},
		{"t", "8"},
	},
	journal = {
		{"year", "2"},
		{"author", "3"},
		{"chapter", "title,article,4", true},
		{"chapterurl", "titleurl,articleurl", true},
		{"trans-chapter", "trans-title,trans-article", true},
		{"chapter_tlr", "article_tlr", true},
		{"chapter_series", "article_series", true},
		{"chapter_seriesvolume", "article_seriesvolume", true},
		{"chapter_number", "article_number", true},
		{"chapter_plain", "title_plain,article_plain", true},
		{"title", "journal,magazine,newspaper,work,5", true},
		{"trans-title", "trans-journal,trans-magazine,trans-newspaper,trans-work", true},
		{"url", "6"},
		{"page", "7"},
		{"source", "newsagency", true},
		{"text", "8"},
		{"t", "9"},
	},
}

-- `type=` 또는 `type2=`를 사용한 내부 지정 별칭 처리
local function process_type_aliases(args, typ, newversion, alias_map)
	local ind = newversion and "2" or ""
	local deprecated = ine(args.lang)
	if not type_alias_specs[typ] then
		local possible_values = {}
		for possible, _ in pairs(type_alias_specs) do
			insert(possible_values, possible)
		end
		sort(possible_values)
		error(
			("type%s=에 인식할 수 없는 값 '%s'이(가) 있습니다; 가능한 값은 %s입니다."):format(
				typ,
				ind,
				concat(possible_values, ",")
			)
		)
	end

	for _, alias_spec in ipairs(type_alias_specs[typ]) do
		local canon, aliases, with_newversion = unpack(alias_spec)
		if with_newversion or not newversion then
			canon = canon .. ind
			aliases = split(aliases, ",", true)
			local saw_alias = nil
			for _, alias in ipairs(aliases) do
				if alias:match("^%d+$") then
					alias = tonumber(alias)
					if deprecated then
						alias = alias - 1
					end
				else
					alias = alias .. ind
				end
				if args[alias] then
					if saw_alias == nil then
						saw_alias = alias
					else
						error(("|%s=|와 |%s=|는 별칭 관계입니다; 둘 다 값을 지정할 수 없습니다."):format(saw_alias, alias))
					end
				end
			end
			if saw_alias and (not newversion or type(saw_alias) == "string") then
				if args[canon] then
					error(("|%s=|는 |%s=|의 별칭입니다; 둘 다 값을 지정할 수 없습니다."):format(saw_alias, canon))
				end
				args[canon] = args[saw_alias]
				-- Wipe out the original after copying. This is important in case of a param that has general significance
				-- but has been redefined (e.g. {{quote-av}} redefines number= for the episode number, and
				-- {{quote-journal}} redefines title= for the chapter= (article). It's also important due to unhandled
				-- parameter checking.
				args[saw_alias] = nil
				alias_map[canon] = saw_alias
			end
		end
	end
end

-- 프레임 인수와 부모 인수를 복제하고 결합하며, 빈 문자열은 nil로 처리합니다.
-- 별칭(alias)과 무시(ignore)할 매개변수도 처리합니다.
local function clone_args(direct_args, parent_args)
	local args = {}

	-- 부모 인수를 먼저 처리하여 직접 인수가 부모 인수를 덮어쓰도록 합니다.
	for pname, param in pairs(parent_args) do
		track("param/" .. pname)
		args[pname] = ine(param)
	end

	-- 무시할 매개변수 처리. `ignore` 값은 무시할 매개변수 이름의 쉼표 구분 목록입니다.
	local ignores = ine(direct_args.ignore)
	if ignores then
		for ignore in gsplit(ignores, "%s*,%s*") do
			args[ignore] = nil
		end
	end

	local alias_map = {}
	local other_controls = {}
	
	-- 로마 숫자 자동 변환을 비활성화할 매개변수 처리
	local noroman = ine(direct_args.noroman)
	if noroman then
		other_controls.overrides = other_controls.overrides or {}
		for param in gsplit(noroman, "%s*,%s*") do
			other_controls.overrides[param] = (other_controls.overrides[param] or {})
			other_controls.overrides[param].noroman = true
		end
	end

	-- Process internally-specified aliases using type= or type2=.
	local typ = args.type or direct_args.type
	if typ then
		process_type_aliases(args, typ, false, alias_map)
	end
	local typ2 = args.type2 or direct_args.type2
	if typ2 then
		process_type_aliases(args, typ2, true, alias_map)
	end

	-- Process externally-specified aliases. The value of `alias` is a list of semicolon-separated specs, each of which
	-- is of the form DEST:SOURCE,SOURCE,... where DEST is the canonical name of a parameter and SOURCE refers to an
	-- alias. Whitespace is allowed between all delimiters. The order of aliases may be important. For example, for
	-- {{quote-journal}}, title= contains the article name and is an alias of underlying chapter=, while journal= or
	-- work= contains the journal name and is an alias of underlying title=. As a result, the title -> chapter alias
	-- must be specified before the journal/work -> title alias.
	--
	-- Whenever we copy a value from argument SOURCE to argument DEST, we record an entry for the pair in alias_map, so
	-- that when we would display an error message about DEST, we display SOURCE instead.
	--
	-- Do alias processing (and ignore and error_if processing) before processing direct_args so that e.g. we can set up
	-- an alias of title -> chapter and then set title= to something else in the direct args ({{quote-hansard}} does
	-- this).
	--
	-- FIXME: Delete this once we've converted all alias processing to internal.
	local aliases = ine(direct_args.alias)
	if aliases then
		-- Allow and discard a trailing semicolon, to make managing multiple aliases easier.
		aliases = ugsub(aliases, "%s*;$", "")
		for alias_spec in gsplit(aliases, "%s*;%s*") do
			local alias_spec_parts = split(alias_spec, "%s*:%s*")
			if #alias_spec_parts ~= 2 then
				error(("별칭 지정 '%s'에는 콜론이 하나만 있어야 합니다."):format(alias_spec))
			end
			local dest, sources = unpack(alias_spec_parts)
			sources = split(sources, "%s*,%s*")
			local saw_source = nil
			for _, source in ipairs(sources) do
				if source:match("^%d+$") then
					source = tonumber(source)
				end
				if args[source] then
					if saw_source == nil then
						saw_source = source
					else
						error(("|%s=|와 |%s=|는 별칭 관계입니다; 둘 다 값을 지정할 수 없습니다."):format(saw_source, source))
					end
				end
			end
			if saw_source then
				if args[dest] then
					error(("|%s=|는 |%s=|의 별칭입니다; 둘 다 값을 지정할 수 없습니다."):format(saw_source, dest))
				end
				args[dest] = args[saw_source]
				-- Wipe out the original after copying. This important in case of a param that has general significance
				-- but has been redefined (e.g. {{quote-av}} redefines number= for the episode number, and
				-- {{quote-journal}} redefines title= for the chapter= (article). It's also important due to unhandled
				-- parameter checking.
				args[saw_source] = nil
				alias_map[dest] = saw_source
			end
		end
	end

	-- Process error_if. The value of `error_if` is a comma-separated list of parameter names to throw an error if seen
	-- in parent_args (they are params we overwrite in the direct args).
	local error_ifs = ine(direct_args.error_if)
	if error_ifs then
		for error_if in gsplit(error_ifs, "%s*,%s*") do
			if ine(parent_args[error_if]) then
				error(
					("Cannot specify a value |%s=%s as it would be overwritten or ignored"):format(
						error_if,
						ine(parent_args[error_if])
					)
				)
			end
		end
	end

	for pname, param in pairs(direct_args) do
		-- 제어 매개변수 무시
		if pname ~= "ignore" and pname ~= "alias" and pname ~= "error_if" and pname ~= "noroman" then
			args[pname] = ine(param)
		end
	end

	return args, alias_map, other_controls
end

local function get_args(frame_args, parent_args, require_lang)
	-- FIXME: We are processing arguments twice, once in clone_args() and then again in [[Module:parameters]]. This is
	-- wasteful of memory.

	local cloned_args, alias_map, other_controls = clone_args(frame_args, parent_args)
	local deprecated = ine(parent_args.lang)

	local alias_of_t = {alias_of = "t"}
	local boolean = {type = "boolean"}
	local language_sublist = {type = "language", sublist = true}
	local list_allow_holes = {list = true, allow_holes = true}
	local script = {type = "script"}

	-- First, the "single" params that don't have FOO2 or FOOn versions.
	local params = {
		[deprecated and "lang" or 1] = {required = require_lang, type = "language", sublist = true, default = "und"},
		["lang"] = {alias_of = (deprecated and "lang" or 1)},
		["언어"] = {alias_of = (deprecated and "lang" or 1)},
		["lang2"] = language_sublist,
		["newversion"] = true,
		["author"] = list_allow_holes,
		["저자"] = {alias_of = "author"},
		["2ndauthor"] = true,
		["trans-author"] = list_allow_holes,
		["authorlink"] = list_allow_holes,
		["저자링크"] = {alias_of = "authorlink"},
		["2ndauthorlink"] = true,
		["trans-authorlink"] = list_allow_holes,
		["first"] = list_allow_holes,
		["이름"] = {alias_of = "first"},
		["2ndfirst"] = true,
		["trans-first"] = list_allow_holes,
		["last"] = list_allow_holes,
		["성"] = {alias_of = "last"},
		["2ndlast"] = true,
		["trans-last"] = list_allow_holes,
		["nocat"] = boolean,
		["분류없음"] = {alias_of = "nocat"},
		["nocolon"] = boolean,

		-- 인용문 관련 매개변수
		["text"] = true,
		["passage"] = {alias_of = "text"},
		["인용"] = {alias_of = "text"},
		["인용문"] = {alias_of = "text"},
		["tr"] = true,
		["transliteration"] = {alias_of = "tr"},
		["ts"] = true,
		["transcription"] = {alias_of = "ts"},
		["norm"] = true,
		["normalization"] = {alias_of = "norm"},
		["sc"] = script,
		["normsc"] = script,
		["sort"] = true,
		["subst"] = true,
		["footer"] = true,
		["lit"] = true,
		["t"] = true,
		["translation"] = alias_of_t,
		["번역"] = alias_of_t,
		["gloss"] = alias_of_t,
		["lb"] = true,
		["brackets"] = boolean,
		-- 원문 인용 관련 매개변수
		["origtext"] = true,
		["origtr"] = true,
		["origts"] = true,
		["orignorm"] = true,
		["origsc"] = script,
		["orignormsc"] = script,
		["origsubst"] = true,
		["origlb"] = true,
		["usenodot"] = boolean,
		["nodot"] = boolean,
		["inline"] = boolean,
	}

	-- 대부분의 매개변수는 `PARAM2` 형태를 가집니다.
	local function add_with_2(param, value)
		params[param] = value
		params[param .. "2"] = value
	end

	local function alias_with_2(alias, canon)
		params[alias] = {alias_of = canon}
		params[alias .. "2"] = {alias_of = canon .. "2"}
	end
		
	for _, param12 in ipairs{
		"worklang",
		"termlang",
		"origlang",
		"origworklang"
	} do
		add_with_2(param12, language_sublist)
	end

	-- Then the newversion params (which have FOO2 versions).
	for _, param12 in ipairs{
		-- author-like params; author params themselves are either list params (author=, last=, etc.) or single params
		-- (2ndauthor=, 2ndlast=, etc.)
		"coauthors",
		"quotee",
		"tlr",
		"editor",
		"editors",
		"mainauthor",
		"compiler",
		"compilers",
		"director",
		"directors",
		"lyricist",
		"lyrics-translator",
		"composer",
		"role",
		"actor",
		"artist",
		"feat",
		"season",

		-- author control params
		"default-authorlabel",
		"authorlabel",

		-- title
		"title",
		"trans-title",
		"series",
		"seriesvolume",
		"notitle",

		-- entry
		"entry",
		"entryurl",
		"trans-entry",

		-- chapter
		"chapter",
		"chapterurl",
		"chapter_number",
		"chapter_plain",
		"chapter_series",
		"chapter_seriesvolume",
		"trans-chapter",
		"chapter_tlr",

		-- section
		"section",
		"sectionurl",
		"section_number",
		"section_plain",
		"section_series",
		"section_seriesvolume",
		"trans-section",

		-- other video-game params
		"system",
		"scene",
		"level",

		-- URL
		"url",
		"urls",
		"archiveurl",

		-- edition
		"edition",
		"edition_plain",

		-- ID params
		"bibcode",
		"doi",
		"isbn",
		"issn",
		"jstor",
		"lccn",
		"oclc",
		"ol",
		"pmid",
		"pmcid",
		"ssrn",
		"urn",
		"id",

		-- misc date params; most date params handled below
		"archivedate",
		"accessdate",
		"nodate",

		-- numeric params handled below

		-- other params
		"type",
		"genre",
		"format",
		"medium",
		"others",
		"quoted_in",
		"location",
		"publisher",
		"original",
		"by",
		"deriv",
		"note",
		"note_plain",
		"other",
		"source",
		"platform",
	} do
		add_with_2(param12, true)
	end

	-- Then the aliases of newversion params (which have FOO2 versions).
	for _, param12_aliased in ipairs{
		{"role", "roles"},
		{"role", "speaker"},
		{"tlr", "translator"},
		{"tlr", "translators"},
		{"doi", "DOI"},
		{"isbn", "ISBN"},
		{"issn", "ISSN"},
		{"jstor", "JSTOR"},
		{"lccn", "LCCN"},
		{"oclc", "OCLC"},
		{"ol", "OL"},
		{"pmid", "PMID"},
		{"pmcid", "PMCID"},
		{"ssrn", "SSRN"},
		{"urn", "URN"},
	} do
		local canon, alias = unpack(param12_aliased)
		alias_with_2(alias, canon)
	end

	-- Then the date params.
	for _, datelike in ipairs{{"", ""}, {"orig", ""}, {"", "_published"}} do
		local pref, suf = unpack(datelike)
		for _, arg in ipairs{"date", "year", "month", "start_date", "start_year", "start_month"} do
			add_with_2(pref .. arg .. suf, true)
		end
	end

	local numeric_param_suffixes = {"", "s", "_plain", "url", "_prefix"}
	-- Then the numeric params.
	for _, numeric in ipairs{"volume", "issue", "number", "line", "page", "column"} do
		for _, suf in ipairs(numeric_param_suffixes) do
			add_with_2(numeric .. suf, true)
		end
	end
	-- And the aliases of numeric params.
	for _, numeric_aliased in ipairs{{"volume", "vol"}} do
		local canon, alias = unpack(numeric_aliased)
		for _, suf in ipairs(numeric_param_suffixes) do
			alias_with_2(alias .. suf, canon .. suf)
		end
	end

	return process_params(cloned_args, params), alias_map, other_controls
end

local function get_origtext_params(args)
	local origtext, origtextlang, origsc, orignormsc
	if args.origtext then
		-- Wiktionary language codes have at least two lowercase letters followed possibly by lowercase letters and/or
		-- hyphens (there are more restrictions but this is close enough). Also check for nonstandard Latin etymology
		-- language codes (e.g. VL. or LL.). (There used to be more nonstandard codes but they have all been
		-- eliminated.)
		origtextlang, origtext = args.origtext:match("^(%l%l[%l-]*):([^ ].*)$")
		if not origtextlang then
			-- Special hack for Latin variants, which can have nonstandard etym codes, e.g. VL., LL.
			origtextlang, origtext = args.origtext:match("^(%uL%.):([^ ].*)$")
		end
		if not origtextlang then
			error("origtext=는 언어 코드 접두사로 시작해야 합니다.")
		end
		origtextlang = get_lang(origtextlang, nil, "allow etym") or
			error("origtext=는 언어 코드 접두사로 시작해야 합니다.")
		origsc = args.origsc
		orignormsc = args.orignormsc
	else
		for _, noparam in ipairs{"origtr", "origts", "origsc", "orignorm", "orignormsc", "origsubst", "origlb"} do
			if args[noparam] then
				error(("%s=는 origtext= 없이 지정할 수 없습니다."):format(noparam))
			end
		end
	end
	return origtext, origtextlang, origsc, orignormsc
end

local function get_quote(args, is_cite)
	local text = args.text
	local gloss = args.t
	local tr = args.tr
	local ts = args.ts
	local norm = args.norm
	local sc = args.sc
	local normsc = args.normsc

	-- Fetch original-text parameters.
	local origtext, origtextlang, origsc, orignormsc = get_origtext_params(args)

	-- 인용 관련 인수가 있으면 인용문을 표시하고, 그렇지 않으면 아무것도 표시하지 않습니다.
	if text or gloss or tr or ts or norm or args.origtext then
		-- Pass "und" here rather than cause an error; there will be an error on mainspace, Citations, etc. pages
		-- in any case in source() if the language is omitted.
		local lang = get_first_lang(args[1] or args.lang)
		local termlang = args.termlang and get_first_lang(args.termlang) or lang

		local usex_data = {
			lang = lang,
			termlang = termlang,
			usex = text,
			sc = sc,
			translation = gloss,
			normalization = norm,
			normsc = normsc,
			transliteration = tr,
			transcription = ts,
			brackets = args.brackets,
			subst = args.subst,
			lit = args.lit,
			footer = args.footer,
			qq = parse_and_format_labels(args.lb, lang),
			quote = "quote-meta",
			orig = origtext,
			origlang = origtextlang,
			origsc = origsc,
			orignorm = args.orignorm,
			orignormsc = orignormsc,
			origtr = args.origtr,
			origts = args.origts,
			origsubst = args.origsubst,
			origqq = parse_and_format_labels(args.origlb, lang),
			noreq = args.noreq,
			nocat = is_cite or args.nocat,
		}

		if args.inline then
			-- don't let usex format the footer, otherwise it gets inlined with the rest of the quoted text
			usex_data.footer = nil
			usex_data.inline = 1

			text = format_usex(usex_data)
			if text then
				text = " “" .. text .. "”"
			else
				text = ""
			end
			if args.footer then
				text = text .. "<dl><dd>" .. args.footer .. "</dd></dl>"
			end
		else
			text = "<dl><dd>" .. format_usex(usex_data) .. "</dd></dl>"
		end
	elseif args.footer then
		text = "<dl><dd>" .. args.footer .. "</dd></dl>"
	end
	return text
end

-- 틀에서 호출하기 위한 외부 인터페이스 ({{quote-*}} 계열 틀의 기본 인터페이스)
function export.quote_t(frame)
	local args, alias_map, other_controls = get_args(frame.args, frame:getParent().args, "require_lang")

	local parts = {}

	insert(parts, '<div class="citation-whole"><span class="cited-source">')
	insert(parts, export.source(args, alias_map, nil, other_controls))
	insert(parts, "</span>")

	insert(parts, get_quote(args))

	insert(parts, "</div>")
	local retval = concat(parts)
	return deprecated and frame:expandTemplate({
			title = "check deprecated lang param usage",
			args = {retval, lang = args.lang},
		}) or retval
end

-- 틀에서 호출하기 위한 외부 인터페이스 ({{cite-*}} 계열 틀의 기본 인터페이스)
function export.cite_t(frame)
	local parent_args = {}
	for k, v in pairs(frame:getParent().args) do
		parent_args[k] = v
	end

	local parts = {}

	-- use "und" as lang if none provided
	if parent_args[1] == nil then
		parent_args[1] = "und"
	end

	local args, alias_map, other_controls = get_args(frame.args, parent_args)

	-- don't nag for translations
	if args.text and not args.t then
		args.noreq = 1
	end

	local len_visible = args.text and ulen((args.text:gsub("<[^<>]+>", ""))) or 0
	if len_visible == 0 then
		if not args.t or args.t == "-" then
			args.nocolon = true
		end
	elseif args.inline == nil then
		local is_block_quote = (
			args.block_text or
			(len_visible > 300) or
			args.norm or
			(args.t and args.t ~= "-" and len_visible > 80) or
			(args.text and string.match(args.text, "<br>"))
		)
		
		args.inline = not is_block_quote
	end

	insert(parts, '<span class="citation-whole"><span class="cited-source">')
	insert(parts, export.source(args, alias_map, "format_as_cite", other_controls))
	insert(parts, "</span>")
	
	local quote_content = get_quote(args, "is_cite")
	
	if quote_content then
		insert(parts, "<dl><dd>" .. quote_content .. "</dd></dl>")
	end
	
	insert(parts, "</span>")

	local retval = concat(parts)
	return deprecated
			and frame:expandTemplate({
				title = "check deprecated lang param usage",
				args = {retval, lang = args.lang},
			})
		or retval
end

-- External interface, meant to be called from a template.
function export.call_quote_template(frame)
	local parameter_sublist = {type = "parameter", sublist = true}
	local iargs, other_direct_args = process_params(frame.args, {
		["template"] = true,
		["textparam"] = parameter_sublist,
		["pageparam"] = parameter_sublist,
		["propagateparams"] = parameter_sublist,
		["allowparams"] = {sublist = true}, -- Doesn't use type = "parameter", because any that end in :list get processed differently.
	}, true)

	local function fetch_param(source, params)
		for _, param in ipairs(params) do
			if source[param] then
				return source[param]
			end
		end
		return nil
	end

	local params = {
		["text"] = true,
		["passage"] = true,
		["footer"] = true,
		["brackets"] = true,
	}

	local textparam = iargs.textparam or {}
	for _, param in ipairs(textparam) do
		params[param] = true
	end

	local pageparam = iargs.pageparam or {}
	local pageparam1 = pageparam[1]
	if pageparam1 ~= nil then
		params["page"], params["pages"] = true, true
		for _, param in ipairs(pageparam) do
			params[param] = true
		end
	end

	local allowparams, allow_all, list = iargs.allowparams, false
	if allowparams ~= nil then
		for _, allow in ipairs(allowparams) do
			local param = allow:match("^(.*):list$")
			if param then
				if list == nil then
					list = {list = true}
				end
				params[scribunto_parameter_key(param)] = list
			elseif allow == "*" then
				track("no parameter checking")
				allow_all = true
			else
				params[scribunto_parameter_key(allow)] = true
			end
		end
	end

	local propagateparams = iargs.propagateparams or {}
	for _, param in ipairs(propagateparams) do
		params[param] = true
	end

	local parent_args = frame:getParent().args
	local args = process_params(parent_args, params, allow_all)
	parent_args = shallow_copy(parent_args)

	if textparam[1] ~= "-" then
		other_direct_args.passage = args.text or args.passage or fetch_param(args, textparam)
	end
	if not (pageparam1 == nil or pageparam1 == "-") then
		other_direct_args.page = fetch_param(args, pageparam) or args.page or nil
		other_direct_args.pages = args.pages
	end
	if args.footer then
		other_direct_args.footer = frame:expandTemplate{title = "small", args = {args.footer}}
	end
	if args.brackets then
		other_direct_args.brackets = args.brackets
	end

	-- authorlink=- can be used to prevent copying of author= to authorlink= but we don't want to propagate this to
	-- the actual {{quote-*}} code.
	if other_direct_args.authorlink == "-" then
		other_direct_args.authorlink = nil
	end
	for _, param in ipairs(propagateparams) do
		if args[param] then
			other_direct_args[param] = args[param]
		end
	end

	return frame:expandTemplate{title = iargs.template or "quote-book", args = other_direct_args}
end

local paramdoc_param_replacements = {
	passage = {
		param_with_synonym = "<<synonym>>, {{para|text}}, 또는 {{para|passage}}",
		param_no_synonym = "{{para|text}} 또는 {{para|passage}}",
		text = [=[
* <<params>> – 인용할 구절.]=],
	},
	page = {
		param_with_synonym = "<<synonym>> 또는 {{para|page}}, 또는 {{para|pages}}",
		param_no_synonym = "{{para|page}} 또는 {{para|pages}}",
		text = "* <<params>> – '''경우에 따라 필수''': 인용된 쪽 번호. 쪽 범위를 인용할 때는 다음을 참고하세요:\n** 첫 쪽과 마지막 쪽을 엔 대시(–)로 구분하세요. 예: {{para|pages|10–11}}.\n** {{para|pageref}}를 사용하여 링크할 쪽(보통 위키낱말사전 표제어가 나오는 쪽)을 지정해야 합니다.",
	},
	page_with_roman_preface = {
		param_with_synonym = {"inherit", "page"},
		param_no_synonym = {"inherit", "page"},
		text = [=[
* <<params>> – '''mandatory in some cases''': the page number(s) quoted from. If quoting from the preface, specify the page number(s) in lowercase Roman numerals. When quoting a range of pages, note the following:
** Separate the first and last page number of the range with an [[en dash]], like this: {{para|pages|10–11}} or {{para|pages|iii–iv}}.
** You must also use {{para|pageref}} to indicate the page to be linked to (usually the page on which the Wiktionary entry appears).
: This parameter must be specified to have the template link to the online version of the work.]=],
	},
	chapter = {
		param_with_synonym = "<<synonym>> 또는 {{para|chapter}}",
		param_no_synonym = "{{para|chapter}}",
		text = "* <<params>> – 인용된 장(chapter)의 이름.",
	},
	roman_chapter = {
		param_with_synonym = {"inherit", "chapter"},
		param_no_synonym = {"inherit", "chapter"},
		text = [=[
* <<params>> – the chapter number quoted from in uppercase Roman numerals.]=],
	},
	arabic_chapter = {
		param_with_synonym = {"inherit", "chapter"},
		param_no_synonym = {"inherit", "chapter"},
		text = [=[
* <<params>> – the chapter number quoted from in Arabic numerals.]=],
	},
	trailing_params = {
		text = "* {{para|footer}} – 인용된 구절에 대한 주석.\n* {{para|brackets}} – 인용문을 대괄호로 묶으려면 {{para|brackets|on}}을 사용하세요. 이는 해당 인용문이 용어의 실제 사용례가 아니라 단순 언급이거나 관련 용어 정보를 제공함을 나타냅니다.",
	},
}

function export.paramdoc(frame)
	local parargs = frame:getParent().args
	local args = process_params(parargs, {
		[1] = true,
	})

	local text = args[1]

	local function do_param_with_optional_synonym(param, text_to_sub, paramtext_synonym, paramtext_no_synonym)
		local function sub_param(synonym)
			local subbed_paramtext
			if synonym then
				subbed_paramtext = paramtext_synonym:gsub("<<synonym>>", "{{para|" .. replacement_escape(synonym) .. "}}")
			else
				subbed_paramtext = paramtext_no_synonym
			end
			return frame:preprocess((text_to_sub:gsub("<<params>>", replacement_escape(subbed_paramtext))))
		end
		text = text:gsub("<<" .. pattern_escape(param) .. ">>", function()
			return sub_param()
		end)
		text = text:gsub("<<" .. pattern_escape(param) .. ":(.-)>>", sub_param)
	end

	local function fetch_text(param_to_replace, key)
		local spec = paramdoc_param_replacements[param_to_replace]
		local val = spec[key]
		if type(val) == "string" then
			return val
		end
		if type(val) == "table" and val[1] == "inherit" then
			return fetch_text(val[2], key)
		end
		error(
			"Internal error: Unrecognized value for param '"
				.. param_to_replace
				.. "', key '"
				.. key
				.. "': "
				.. mw.dumpObject(val)
		)
	end

	for param_to_replace, spec in pairs(paramdoc_param_replacements) do
		if not spec.param_no_synonym then
			-- Text to substitute directly.
			text = text:gsub("<<" .. pattern_escape(param_to_replace) .. ">>", function()
				return frame:preprocess(fetch_text(param_to_replace, "text"))
			end)
		else
			do_param_with_optional_synonym(
				param_to_replace,
				fetch_text(param_to_replace, "text"),
				fetch_text(param_to_replace, "param_with_synonym"),
				fetch_text(param_to_replace, "param_no_synonym")
			)
		end
	end

	-- Remove final newline so template code can add a newline after invocation
	text = text:gsub("\n$", "")
	return text
end

return export