Jump to content

Module:ja-pron

From Wiktionary, the free dictionary

Japanese pronunciation template.

See Template:ja-pron for usage and examples.


local m_str_utils = require("Module:string utilities")

local concat = table.concat
local gsplit = m_str_utils.gsplit
local gsub = m_str_utils.gsub
local gmatch = m_str_utils.gmatch
local insert = table.insert
local len = m_str_utils.len
local match = m_str_utils.match
local min = math.min
local split = m_str_utils.split
local sub = m_str_utils.sub
local toNFC = mw.ustring.toNFC

local lang = require("Module:languages").getByCode("ja")
local kana_to_romaji = require("Module:Hrkt-translit").tr
local m_accent = require("Module:accent qualifier")
-- also [[Module:qualifier]]
local PAGENAME = mw.loadData("Module:headword/data").pagename

local range = mw.loadData("Module:ja/data/range")
local range_kana = range.kana
local a_kana = range.vowels.a
local i_kana = range.vowels.i
local u_kana = range.vowels.u
local e_kana = range.vowels.e
local o_kana = range.vowels.o
local n_kana = range.vowels.n
local submoraic_kana = range.submoraic_kana

local _accent

local export = {}

local ref_template_name_data = {
	["DJR"] = "R:Daijirin",
	["DJR4"] = "R:Daijirin4",
	["DJS"] = "R:Daijisen",
	["DJS2"] = "R:Daijisen2",
	["KDJ"] = "R:Kokugo Dai Jiten",
	["NHK"] = "R:NHK Hatsuon",
	["NHK16"] = "R:NHK2016",
	["NKD2"] = "R:ja:Nihon Kokugo Daijiten 2 Online",
	["NKD2P"] = "R:ja:Nihon Kokugo Daijiten 2 Print",
	["SMK2"] = "R:Shinmeikai2",
	["SMK5"] = "R:Shinmeikai5",
	["SMK7"] = "R:Shinmeikai7",
	["SMK8"] = "R:Shinmeikai8",
	["SKK8"] = "R:Sankoku8",
	["ZAJ"] = "R:Zenkoku Akusento Jiten",
	["JEL"] = "R:Kenkyusha JEL Pocket",
	["JAC"] = "R:ja:JAccent",
}

local function add_acc_refs(frame, text)
	local output = {}
	for ref_name in gsplit(text, ",") do
		mw.log(ref_name)
		local ref_template_name = ref_template_name_data[ref_name]
		if ref_template_name then
			insert(output, frame:extensionTag("ref", "{{" .. ref_template_name .. "}}", {name = ref_name}))
		elseif match(ref_name, "ref") then
			insert(output, frame:preprocess(ref_name))
		else
			-- [[Special:WhatLinksHere/Wiktionary:Tracking/ja-pron/unrecognized ref]]
			require("Module:debug").track("ja-pron/unrecognized ref")
		end
	end
	return concat(output)
end

function export.show(frame)
	local params = {
		[1] = {default = PAGENAME, list = true},
		
		["accent"] = {list = true},
		["accent\1_loc"] = {list = true, allow_holes = true},
		["accent\1_ref"] = {list = true, allow_holes = true},
		["accent\1_note"] = {list = true, allow_holes = true},
		
		["acc"] = {alias_of = "accent", list = true},
		["acc\1_loc"] = {alias_of = "accent\1_loc", list = true},
		["acc\1_ref"] = {alias_of = "accent\1_ref", list = true},
		["acc\1_note"] = {alias_of = "accent\1_note", list = true},
		
		["dev"] = {},
		["dev2"] = {},
		["devm"] = {},
		
		["a"] = {alias_of = "audio"},
		["audio"] = {}
	}
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	
	--Validate kana
	for i, text in ipairs(args[1]) do
		if match(text, " ") then
			error("Fullwidth CJK space instead of ASCII space")
		end
		local non_kana_char = match(text, "[^" .. range_kana .. "%. ]")
		if non_kana_char then
			error("Non-kana char: " .. non_kana_char)
		end
	end
	
	local au = args.audio
	local dev = args.dev or args.devm
	local dev2 = args.dev2
	local maxindex = #args[1]	-- replace with args[1].maxindex if 1 gets allow_holes=true
	local html_list_main = mw.html.create("ul")
	local categories = {}
	
	-- Deals with the accents
	local a, al, ar, an = args.accent, args.accent_loc, args.accent_ref, args.accent_note
	for i, position in ipairs(a) do
		local text = args[1][min(maxindex,i)]
		local al_is_tokyo_default = false
		if not al[i] then
			al_is_tokyo_default = true
			al[i] = "[[w:Tokyo dialect|Tokyo]]"
		end
		local result = m_accent.format_qualifiers(lang, {al[i]}) .. " "
		
		local acc_wikitext, acc_type = _accent(text, position, dev, dev2)
		
		result = result .. acc_wikitext
		if ar[i] then
			result = result .. add_acc_refs(frame, ar[i])
		else
			require("Module:debug").track("ja-pron/unsourced accent")
		end
		result = result .. (an[i] and (" " .. an[i]) or "")
		
		if al_is_tokyo_default then
			if acc_type == "h" then
				insert(categories, lang:getCanonicalName() .. " terms with Heiban pitch accent (Tōkyō)")
			elseif acc_type == "a" then
				insert(categories, lang:getCanonicalName() .. " terms with Atamadaka pitch accent (Tōkyō)")
			elseif acc_type == "n" then
				insert(categories, lang:getCanonicalName() .. " terms with Nakadaka pitch accent (Tōkyō)")
			elseif acc_type == "o" then
				insert(categories, lang:getCanonicalName() .. " terms with Odaka pitch accent (Tōkyō)")
			elseif acc_type == "c" then
				insert(categories, lang:getCanonicalName() .. " terms with complex pitch accent (Tōkyō)")
			end
		end

		html_list_main:tag("li"):wikitext(
			result
		)
	end
	
	-- Deals with the IPA
	local m_IPA, no_acc = require("Module:IPA"), false
	for i, text in ipairs(args[1]) do
		html_list_main:tag("li"):wikitext(
			m_IPA.format_IPA_full {
				lang = lang,
				items = {{ pron = "[" .. export.ipa(text, dev, dev2) .. "]" }},
			}
		)
		if not a[i] then
			no_acc = true
		end
	end
	
	if no_acc then
		insert(categories, lang:getCanonicalName() .. " terms with IPA pronunciation missing pitch accent")
	else
		insert(categories, lang:getCanonicalName() .. " terms with IPA pronunciation with pitch accent")
	end
	
	-- Deals with the audio
	if au then
		html_list_main:tag("li"):wikitext(
			require("Module:audio").format_audio {
				lang = lang,
				file = au,
			}
		)
	end
	
	return "\n" .. tostring(html_list_main) ..
		(#categories > 0 and require("Module:utilities").format_categories(categories, lang) or "")
end

function export.ipa(text, dev, dev2)
	if type(text) == "table" then
		text, dev, dev2 = text.args[1], text.args["dev"], text.args["dev2"] end
	dev = dev or ""
	dev2 = dev2 or ""
	
	if dev2 ~= "" then error("Please remove parameter dev2 and change parameter dev to \"dev=" .. dev .. "," .. dev2 .. "\"") end
	
	-- Convert 〜 and 〰 to a regular ー.
	text = gsub(text, "[〜〰]", "ー")
	
	local position_mora = {}
	
	for i = 1, len(text) do
		if not match(sub(text, i, i), "[ " .. submoraic_kana .. "%.]") then
			local nxt = sub(text, i + 1,i + 1)
			if nxt and match(nxt, "[" .. submoraic_kana .. "]") then
				insert(position_mora, i + 1)
			else
				insert(position_mora, i)
			end
		end
	end
	
	-- insert @ to stand for devoicing
	if dev ~= "" then
		for position in gsplit(dev, ",") do
			position = tonumber(position)
			if #position_mora == position then
				text = text .. "@"
			else
				local position_devspace = position_mora[position]
				text = sub(text, 1, position_devspace) .. "@" .. sub(text, position_devspace+1, -1)
			end
			for i = position + 1, #position_mora do
				position_mora[i] = position_mora[i] + 1
			end
		end
	end
	
	text = toNFC(kana_to_romaji(text, "ja", nil, {keep_dot = true, disambig = true}))
	
	text = gsub(text, "[lv'@]", {
		["l"] = "r", ["v"] = "b", ["'"] = "ʔ", ["@"] = "̥"
	})
	
	-- Hyphens which have been geminated over are removed; otherwise converted to dots.
	text = gsub(text, "([bcdfghjkmnprstvw])%-%1", "%1%1")
		:gsub("-", ".")
	
	text = text:gsub("([kprt])(%s*)%1", "%1̚%2%1")
		:gsub("t(%s*)ch", "t̚%1ch")
		:gsub("([bd])(%s*)%1", "%1̚%2%1̥")
		:gsub("g(%s*)g", "g̚%1g̊")
		:gsub("([jz])(%s*)%1", "d̚%2%1")
		:gsub("s(%s*)sh", "ɕ%1ɕ")
	
	text = gsub(text, "ei", "ē")
	text = gsub(text, "[āēīōūfvjryz]", {
		["ā"] = "aː", ["ē"] = "eː", ["ī"] = "iː", ["ō"] = "oː", ["ū"] = "uː", 
		["f"] = "ɸ", ["j"] = "d͡ʑ", ["r"] = "ɾ", ["y"] = "j", ["z"] = "d͡z" })
	
	text = gsub(text, "[sct][hs]", {
		["sh"] = "ɕ", 
		["ch"] = "t͡ɕ", 
		["ts"] = "t͡s" })
	
	text = gsub(text, "([aeiouː̥])d͡([zʑ])", "%1%2")

	text = gsub(text, "([bdɸgkmnpɾstz][̥̊]*)i", "%1ʲi")
	text = gsub(text, "([bdɸgkmnpɾstwz][̥̊]*)j", "%1ʲ")
	text = gsub(text, "([bɕdɸghjkmpɾstzʑʲ][̥̊]*)w", "%1ᵝ")
	text = gsub(text, "nʲ", "ɲ̟")
	text = gsub(text, "n+$", function(n)
		return ("ɴ"):rep(#n)
	end)
	text = gsub(text, "([^ ː_nɴʔ])(ː*)ɴ", "%1̃%2ɴ")
	text = gsub(text, "([^ ː_nʔ])(ː*)n([^aeou%s])", "%1̃%2n%3")
	text = gsub(text, "n[n ]*[bmp]", function(m)
		return m:gsub("n", "m")
	end)
	text = gsub(text, "n[n ]*.͡[ɕʑ]", function(m)
		return m:gsub("n", "ɲ̟")
	end)
	text = gsub(text, "n[n ]*ɲ̟", function(m)
		return m:gsub("n", "ɲ̟")
	end)
	text = gsub(text, "n[n ]*ɾ", function(m)
		return m:gsub("n", "n̺")
	end)
	text = gsub(text, "_ng", "ŋ")
	text = gsub(text, "(n[n ]*)([kg])([ʲᵝ]*)", function(m1, m2, m3)
		return m1:gsub("n", "ŋ" .. m3) .. m2 .. m3
	end)
	text = gsub(text, "_nw", "nᵝ")
	text = gsub(text, "n[n ]*[ɸszɕhjw]", function(m)
		return m:gsub("n", "ɰ̃")
	end)
	text = gsub(text, "([n ]*n)[ʔ_]", function(m) -- ʔ to be removed once Hrkt-translit `disambig` flag is implemented, as ʔ will always represent a glottal
		return m:gsub("n", "ɰ̃")
	end)
	text = gsub(text, "n[n ]* [aeiou]", function(m)
		return m:gsub("n", "ɰ̃")
	end)
	
	text = gsub(text, "h[iju]", {
		["hi"] = "çi", ["hj"] = "ç",
		["hu"] = "ɸu"
	})
	
	text = gsub(text, "h([çɸ])", "%1%1")
	for _, cons in ipairs{"ç", "ɕ", "ɸ", "h", "j", "m", "n", "ɴ", "ŋ", "ɾ", "s", "w", "z", "ʑ"} do
		text = gsub(text, "(" .. cons .. ")()" .. cons .. "+()", function(cons, i, j)
			return cons .. ("ː"):rep(j - i)
		end)
	end
	for _, cons in ipairs{"n̺", "nᵝ", "ɲ̟", "ŋʲ", "ŋᵝ", "ɰ̃"} do
		local char1, char2 = cons:match("(.[\128-\191]*)(.*)")
		text = gsub(text, "(" .. cons .. ")()" .. char1 .. "[" .. char1 .. char2 .. "]*" .. char2 .. "()", function(cons, i, j)
			return cons .. ("ː"):rep((j - i) / 2)
		end)
	end
	text = gsub(text, "(ː+)([ʲᵝ]+)", "%2%1")
	text = gsub(text, "̚(.[̥̊]*)([ʲᵝ]+)", "̚%2%1%2")
	text = gsub(text, "[aeiouw]", {
		["a"] = "a̠", 
		["e"] = "e̞", 
		["o"] = "o̞", 
		["u"] = "ɯ̟", 
		["w"] = "β̞"
	})
	
	text = gsub(text, "([szɕʑɲçʲ][̟̥̊]*ː*)ɯ̟", "%1ɨ")
	
	text = gsub(text, "̠[̥̃][̥̃]", "̥̃˗")
	text = gsub(text, "̞[̥̃][̥̃]", "̥̃˕")
	text = gsub(text, "̟[̥̃][̥̃]", "̥̃˖")
	text = gsub(text, "([̠̞̟])̥", "%1̊")
		:gsub("[%._]", "")
		:gsub("g", "ɡ")
	
	return text
end

function export.rise_and_fall(word, rftype)
	word = gsub(word, "([" .. o_kana .. "][゙゚]?)([うウ])", "%1.%2")
	word = gsub(word, "([" .. e_kana .. "][゙゚]?)([いイ])", "%1.%2")
	word = kana_to_romaji(word, "ja")

	if rftype == "rise" then
		word = gsub(word, ".", {
			["a"] = "á", ["e"] = "é", ["i"] = "í", ["o"] = "ó", ["u"] = "ú", 
			["ā"] = "áá", ["ē"] = "éé", ["ī"] = "íí", ["ō"] = "óó", ["ū"] = "úú" })
	
		word = gsub(gsub(word, "n([bcdfghjkmnprstvw%'z ])", "ń%1"), "n$", "ń")
		
	elseif rftype == "fall" then
		word = gsub(word, ".", {
			["a"] = "à", ["e"] = "è", ["i"] = "ì", ["o"] = "ò", ["u"] = "ù", 
			["ā"] = "àà", ["ē"] = "èè", ["ī"] = "ìì", ["ō"] = "òò", ["ū"] = "ùù" })
		
		word = gsub(gsub(word, "n([bcdfghjkmnprstvw%'z ])", "ǹ%1"), "n$", "ǹ")
		
	else
		return error("Type not recognised.")
	end
	
	return word
end

-- [[Module:ja-acc-table]], [[Module:ja-ojad]], [[Module:ja-infl-demo]] are reliant on the output format of this function
function export.accent(text, class, dev, dev2)
	return select(1, _accent(text, class, dev, dev2))
end

_accent = function(text, class, dev, dev2)
	if(type(text)) == "table" then text, class, dev, dev2 = text.args[1], text.args[2], text.args["dev"], text.args["dev2"] end
	if dev == "" then dev = false end
	if dev2 == "" then dev2 = false end
	
	local multi_acc_pattern = "^([%. %+" .. range_kana .. "]+)(.*)"
	local submoraic_pattern = "[" .. submoraic_kana .. "]"
	local morae_pattern = "[%. ]*[^%. ][%. " .. submoraic_kana .. "]*"
	local space_dot_submoraic_pattern = "[%. " .. submoraic_kana .. "]"
	local leading_submoraic_pattern = "^[" .. submoraic_kana .. "]"
		
	if match(gsub(text, "[%. ]", ""), leading_submoraic_pattern) then
		error("Initial kana character is submoraic")
	end
		
	local dev_table = {}
	if dev then
		for dev_val in gsplit(dev, ",") do
			local trimmed = gsub(dev_val, "^%s*(.-)%s*$", "%1")
			if not match(trimmed, "^[0-9]+$") then
				error(("Non-integer dev value %s"):format(trimmed))
			end
			local num = tonumber(trimmed)
			dev_table[num] = true
		end
	end
	
	local highspan = '<span style="border-top:1px solid;position:relative;padding:1px;">'
	local downstepspan = '<span style="position:absolute;top:0;bottom:67%;right:0%;border-right:1px solid;"></span>'
	local devspan = '<span style="border:1px dotted var(--wikt-palette-grey-9,gray); border-radius:50%;">'
	local spanend = '</span>'
	
	local acc_parts = {}
	
	if match(class, "+") then
		local kana, acc = match(class, multi_acc_pattern)
		if not (kana and acc) then
			error(("Unrecognized accent %s."):format(class))
		end
		
		local plain_kana_1 = gsub(kana, "[%. %+]", "")
		local plain_kana_2 = gsub(text, "[%. ]", "")
		if plain_kana_1 ~= plain_kana_2 then
			error(("Accent kana is not the same %s, %s."):format(plain_kana_1, plain_kana_2))
		end
		
		local kana_splits = {}
		local morae_iterator = gmatch(text, morae_pattern)
		for kana_split in gsplit(kana, "+") do
			local plain_kana = gsub(kana_split, "[%. ]", "")
			if plain_kana == "" then
				error("Empty split")
			end
			if match(plain_kana, leading_submoraic_pattern) then
				error("Initial kana character is submoraic")
			end
			local n_morae = len(gsub(plain_kana, submoraic_pattern, ""))
			local kana_split = ""
			for i = 1, n_morae do
				local mora = morae_iterator()
				kana_split = kana_split .. mora
			end
			table.insert(kana_splits, kana_split)
		end
		
		if match(acc, ">>") then
			local splits = gsplit(acc, ">>")
			local split_1 = splits()
			local split_2 = splits()
			
			local base_acc_splits = {}
			for split in gsplit(split_1, "+") do
				local trimmed = gsub(split, "^%s*(.-)%s*$", "%1")
				table.insert(base_acc_splits, trimmed)
			end
			
			local shifted_acc_splits = {}
			for split in gsplit(split_2, "+") do
				local trimmed = gsub(split, "^%s*(.-)%s*$", "%1")
				table.insert(shifted_acc_splits, trimmed)
			end
		
			if (#kana_splits ~= #base_acc_splits) or (#kana_splits ~= #shifted_acc_splits) then
				error("Different number of splits")
			end
			
			for i, kana_split in ipairs(kana_splits) do
				local base_acc_split = base_acc_splits[i]
				local shifted_acc_split = shifted_acc_splits[i]
				table.insert(acc_parts, {kana = kana_split, acc = base_acc_split, shifted_acc = shifted_acc_split})
			end
		else
			local acc_splits = {}
			for split in gsplit(acc, "+") do
				local trimmed = gsub(split, "^%s*(.-)%s*$", "%1")
				table.insert(acc_splits, trimmed)
			end
		
			if #kana_splits ~= #acc_splits then
				error("Different number of splits")
			end
		
			for i, kana_split in ipairs(kana_splits) do
				local acc_split = acc_splits[i]
				table.insert(acc_parts, {kana = kana_split, acc = acc_split})
			end
		end
	else
		table.insert(acc_parts, {kana = text, acc = class})
	end
	
	local kana_buffer = {}
	local romaji_buffer = {}
	local accname_buffer = {}
	local accnumber_buffer = {}
	local high_mora_state = false
	
	table.insert(kana_buffer, '<span lang="ja" class="Jpan">')
	
	local acc_type
	
	local mora_index = 1
	for i, acc_part in ipairs(acc_parts) do
		local kana = acc_part.kana
		
		kana = gsub(kana, "([" .. o_kana .. "][゙゚]?)[うウ]", "%1ー")
		kana = gsub(kana, "([" .. e_kana .. "][゙゚]?)[いイ]", "%1ー")
		kana = gsub(kana, "%.", "")
		
		local romaji_kana = kana
		romaji_kana = gsub(romaji_kana, "([" .. o_kana .. "][゙゚]?)ー", "%1お")
		romaji_kana = gsub(romaji_kana, "([" .. e_kana .. "][゙゚]?)ー", "%1え")
		romaji_kana = gsub(romaji_kana, "([" .. u_kana .. "][゙゚]?)ー", "%1う")
		romaji_kana = gsub(romaji_kana, "([" .. i_kana .. "][゙゚]?)ー", "%1い")
		romaji_kana = gsub(romaji_kana, "([" .. a_kana .. "][゙゚]?)ー", "%1あ")
		romaji_kana = gsub(romaji_kana, "([" .. n_kana .. "][゙゚]?)ー", "%1%1")
		
		local kana_morae = {}
		local romaji_morae = {}
		for str in gmatch(kana, morae_pattern) do
			table.insert(kana_morae, str)
		end
		for str in gmatch(romaji_kana, morae_pattern) do
			table.insert(romaji_morae, str)
		end
		
		local n_morae = #kana_morae
		
		local acc_val_func = function(str)
			if match(str, "^h$") then
				return 0
			elseif match(str, "^a$") then
				return 1
			elseif match(str, "^o$") then
				return n_morae
			elseif match(str, "^p$") then
				return nil
			elseif match(str, "^[0-9]+$") then
				local num = tonumber(str)
				if num > n_morae then
					error(("Mora count (%d) is smaller than position of downstep mora (%s).")
						:format(n_morae, num))
				end
				return num
			else
				error(("Unrecognized accent value %s"):format(str))
			end
		end
		
		local acc_val, base_acc_val
		if acc_part.shifted_acc then
			base_acc_val = acc_val_func(acc_part.acc)
			acc_val = acc_val_func(acc_part.shifted_acc)
		else
			base_acc_val = acc_val_func(acc_part.acc)
			acc_val = base_acc_val
		end
		
		local mora_part_index = 1
		
		local function do_insert_mora_index()
			local mora = kana_morae[mora_part_index]
			local mora_is_downstep = acc_val ~= 0 and mora_part_index == acc_val
			local mora_is_devoiced = dev_table[mora_index]
			
			local trimmed_mora = gsub(mora, " ", "")
			if mora_is_devoiced then
				table.insert(kana_buffer, devspan)
				table.insert(kana_buffer, trimmed_mora)
				table.insert(kana_buffer, spanend)
			else
				table.insert(kana_buffer, trimmed_mora)
			end
			
			local romaji_mora = romaji_morae[mora_part_index]
			if mora_is_devoiced then
				romaji_mora = gsub(romaji_mora .. "@", "([%. ]+)@", "@%1")
			end
			if mora_is_downstep then
				romaji_mora = gsub(romaji_mora .. "#", "([%. ]+)#", "#%1")
			end
			if (acc_val == nil or mora_part_index > 1) and
					romaji_buffer[#romaji_buffer] and
					(not romaji_buffer[#romaji_buffer].particle) and
					romaji_buffer[#romaji_buffer].high == high_mora_state then
				romaji_buffer[#romaji_buffer].kana = romaji_buffer[#romaji_buffer].kana .. romaji_mora
			else
				table.insert(romaji_buffer,
					{kana = romaji_mora, high = high_mora_state, particle = acc_val == nil})
			end
			
			mora_part_index = mora_part_index + 1
			mora_index = mora_index + 1
		end
		
		if i ~= 1 then
			if not (acc_val == nil) then
				if high_mora_state then
					table.insert(kana_buffer, spanend)
					table.insert(kana_buffer, "・")
					table.insert(kana_buffer, highspan)
				else
					table.insert(kana_buffer, "・")
				end
			end
		end
		
		if acc_val == nil then -- pitch accent: particle
			for j = 1, n_morae do
				do_insert_mora_index()
			end
		elseif acc_val == 0 then -- pitch accent: Heiban
			if high_mora_state then
				table.insert(kana_buffer, spanend)
				high_mora_state = false
			end
			do_insert_mora_index()
			table.insert(kana_buffer, highspan)
			high_mora_state = true
			for j = 2, n_morae do
				do_insert_mora_index()
			end
		else -- pitch accent: Atamadaka, Nakadaka, or Odaka
			if acc_val == 1 then
				if not high_mora_state then
					table.insert(kana_buffer, highspan)
					high_mora_state = true
				end
				do_insert_mora_index()
				high_mora_state = false
			else
				if high_mora_state then
					table.insert(kana_buffer, spanend)
					high_mora_state = false
				end
				do_insert_mora_index()
				table.insert(kana_buffer, highspan)
				high_mora_state = true
			end
			for j = 2, acc_val do
				do_insert_mora_index()
			end
			table.insert(kana_buffer, downstepspan)
			table.insert(kana_buffer, spanend)
			high_mora_state = false
			for j = acc_val + 1, n_morae do
				do_insert_mora_index()
			end
		end
		
		if not (base_acc_val == nil) then
			local link_str
			if base_acc_val == 0 then
				link_str = "[[平板型|Heiban]]"
				acc_type = "h"
			elseif base_acc_val == 1 then
				link_str = "[[頭高型|Atamadaka]]"
				acc_type = "a"
			elseif base_acc_val == n_morae then
				link_str = "[[尾高型|Odaka]]"
				acc_type = "o"
			else
				link_str = "[[中高型|Nakadaka]]"
				acc_type = "n"
			end
			
			if base_acc_val ~= acc_val then
				link_str = link_str .. "*"
			end
			
			local num_str
			if base_acc_val ~= acc_val then
				num_str = "[" .. base_acc_val .. "*]"
			else
				num_str = "[" .. base_acc_val .. "]"
			end
			
			table.insert(accname_buffer, link_str)
			table.insert(accnumber_buffer, num_str)
		end
		
	end
	
	for i = #kana_buffer, 1, -1 do
		if kana_buffer[i] == highspan and kana_buffer[i + 1] == spanend then
			table.remove(kana_buffer, i + 1)
			table.remove(kana_buffer, i)
		end
	end
	
	if kana_buffer[#kana_buffer] == highspan then
		table.remove(kana_buffer, #kana_buffer)
		high_mora_state = false
	end
	
	if high_mora_state then
		table.insert(kana_buffer, spanend)
		high_mora_state = false
	end
	
	table.insert(kana_buffer, spanend)
	
	local output_buffer = {}
	for _, str in ipairs(kana_buffer) do
		table.insert(output_buffer, str)
	end
	if #romaji_buffer > 0 then
		table.insert(output_buffer, ' <span class="Latn"><samp>[')
		for i, _ in ipairs(romaji_buffer) do
			local leading_pattern = "^([%. #@]+)"
			local trailing_pattern = "([%. #@]+)$"
			local romaji_kana = romaji_buffer[i].kana
			local leading = match(romaji_kana, leading_pattern)
			local trailing = match(romaji_kana, trailing_pattern)
			romaji_kana = gsub(romaji_kana, leading_pattern, "")
			romaji_kana = gsub(romaji_kana, trailing_pattern, "")
			if romaji_buffer[i].particle then
				if romaji_kana == "は" then
					romaji_kana = "わ"
				elseif romaji_kana == "へ" then
					romaji_kana = "え"
				end
			end
			romaji = export.rise_and_fall(romaji_kana, romaji_buffer[i].high and "rise" or "fall")
			romaji = (leading or "") .. romaji .. (trailing or "")
			romaji = gsub(romaji, "(.)@", "<del>%1</del>")
			romaji = gsub(romaji, "#", "ꜜ")
			table.insert(output_buffer, romaji)
		end
		table.insert(output_buffer, "]</samp></span>")
	end
	if #accname_buffer > 0 and #accnumber_buffer > 0 then
		table.insert(output_buffer, " (")
		for i, str in ipairs(accname_buffer) do
			table.insert(output_buffer, str)
			if i ~= #accname_buffer then
				table.insert(output_buffer, " + ")
			end
		end
		table.insert(output_buffer, " – ")
		for i, str in ipairs(accnumber_buffer) do
			table.insert(output_buffer, str)
			if i ~= #accnumber_buffer then
				table.insert(output_buffer, "+")
			end
		end
		table.insert(output_buffer, ")")
	end
	
	local wikitext = table.concat(output_buffer)
	
	if #acc_parts > 1 then
		acc_type = "c"
	end
	
	return wikitext, acc_type
end

return export