Module:eu-pron/new

--Based on Module:es-pronunc by Benwing2.

local export = {}

local m_IPA = require("Module:IPA") local m_table = require("Module:table") local put_module = "Module:parse utilities"

local force_cat = false -- for testing

local lang = require("Module:languages").getByCode("eu")

local u = mw.ustring.char local rfind = mw.ustring.find local rsubn = mw.ustring.gsub local rmatch = mw.ustring.match local rsplit = mw.text.split local ulower = mw.ustring.lower local uupper = mw.ustring.upper local usub = mw.ustring.sub local ulen = mw.ustring.len local unfd = mw.ustring.toNFD local unfc = mw.ustring.toNFC

local TILDE = u(0x0303) -- tilde = ̃ local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it local vowel = "aeiouAEIOU" -- vowel; include y so we get single-word y correct and for syllabifying from spelling local V = "[" .. vowel .. "]" -- vowel class local W = "[wʝ]" -- glides, used in a few loanwords local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling local sylsep_c = "[" .. sylsep .. "]" local wordsep = "# " local separator = sylsep .. wordsep local separator_c = "[" .. separator .. "]" local C = "[^" .. vowel .. separator .. "]" -- consonant class including h

-- version of rsubn that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end

-- version of rsubn that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end

-- apply rsub repeatedly until no change local function rsub_repeatedly(term, foo, bar) while true do		local new_term = rsub(term, foo, bar) if new_term == term then return term end term = new_term end end

local function decompose(text) -- decompose everything but ñ and ü text = unfd(text) text = rsub(text, ".[" .. TILDE .. "]", {		["n" .. TILDE] = "ñ",		["N" .. TILDE] = "Ñ",	}) return text end

local function split_on_comma(term) if term:find(",%s") then return require(put_module).split_on_comma(term) else return rsplit(term, ",") end end

-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the -- displayed length. local function convert_to_raw_text(text) text = rsub(text, "<.->", "") if text:find("%[%[") then text = require("Module:links").remove_links(text) end return text end

-- Return the approximate displayed length in characters. local function textual_len(text) return ulen(convert_to_raw_text(text)) end

local function construct_default_differences(dialect) if dialect == "gipuzkoan" then return { northern_different = false, j_different = false, need_bisc = false, }	end return nil end

-- Main syllable-division algorithm local function syllabify_from_spelling_or_pronun(text)

text = rsub_repeatedly(text, "(" .. V .. ")(" .. C .. W .. "?" .. V .. ")", "%1.%2")	text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. V .. ")", "%1.%2")	text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. C .. V .. ")", "%1.%2")	text = rsub_repeatedly(text, "(" .. V .. C .. C .. ")(" .. C .. C .. V .. ")", "%1.%2")	text = rsub(text, "([pfbktdɡg])%.([lrɾ])", ".%1%2") --the first "ɡ" is the IPA one; the second "g" is the regular one text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2") -- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	text = rsub_repeatedly(text, "([ae]" .. ")ui", "%1u.i") text = rsub_repeatedly(text, "([aeiou]" .. ")([aeo])", "%1.%2") text = rsub_repeatedly(text, "ii", "i.i") text = rsub_repeatedly(text, "([iou]" .. ")u", "%1.u")

return text end

local function syllabify_from_spelling(text) text = decompose(text) -- start at FFF1 because FFF0 is used for SYLDIV -- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes -- treat all unknown characters as consonants. local TEMP_tz = u(0xFFF1) local TEMP_ts = u(0xFFF2) local TEMP_tx = u(0xFFF3) local TEMP_tx_CAPS = u(0xFFF4) local TEMP_ll = u(0xFFF5) local TEMP_ll_CAPS = u(0xFFF6) local TEMP_dd = u(0xFFF7) local TEMP_dd_CAPS = u(0xFFF8) local TEMP_tt = u(0xFFF9) local TEMP_tt_CAPS = u(0xFFFA) local TEMP_rr = u(0xFFB) -- Change user-specified. into SYLDIV so we don't shuffle it around when dividing into syllables. text = text:gsub("%.", SYLDIV)

-- handle digraphs text = rsub(text, "tz", TEMP_tz) text = rsub(text, "ts", TEMP_ts) text = rsub(text, "tx", TEMP_tx) text = rsub(text, "Tx", TEMP_tx_CAPS) text = rsub(text, "ll", TEMP_ll) text = rsub(text, "Ll", TEMP_ll_CAPS) text = rsub(text, "tt", TEMP_tt) text = rsub(text, "Tt", TEMP_tt_CAPS) text = rsub(text, "dd", TEMP_dd) text = rsub(text, "Dd", TEMP_dd_CAPS) text = rsub(text, "rr", TEMP_rr) text = syllabify_from_spelling_or_pronun(text)

text = text:gsub(SYLDIV, ".") text = text:gsub(TEMP_tz, "tz") text = text:gsub(TEMP_ts, "ts") text = text:gsub(TEMP_tx, "tx") text = text:gsub(TEMP_tx_CAPS, "Tx") text = text:gsub(TEMP_ll, "ll") text = text:gsub(TEMP_ll_CAPS, "Ll") text = text:gsub(TEMP_tt, "tt") text = text:gsub(TEMP_tt_CAPS, "Tt") text = text:gsub(TEMP_dd, "dd") text = text:gsub(TEMP_dd_CAPS, "Dd") text = text:gsub(TEMP_rr, "rr") text = unfc(text) return text end

-- Generate the IPA of a given respelling, where a respelling is the representation of the pronunciation of a given function export.IPA(text, dialect, phonetic) local northern = dialect == "northern" local southern = dialect == "gipuzkoan" or dialect == "navarrese" or dialect == "biscayan" local nav = dialect == "navarrese" local bisc = dialect == "biscayan" local northern_different = false local j_different = false local need_bisc = false local J = "" local N = "" -- start at FFF1 because FFF0 is used for SYLDIV local TEMP_Y = u(0xFFF1) local TEMP_W = u(0xFFF2)

--function that gets the last two syllables (used for generating the correct rhymes) local function extract_last_syllables(text) local words = rsplit(text, " ") if #words > 1 then --no rhymes are generated when there are multiple words return "" end text = rsub(text, "([aeou])i", "%1") text = rsub(text, "([ae])u", "%1") local length = string.len(text) local rhyme  = "" local count  = -1 local syl    = 0 while count > -length do			if string.find("aeiou", string.sub(text,count,count)) then rhyme = string.sub(text,count,-1) syl = syl + 1 if syl == 2 then return rhyme end end count = count - 1 end return rhyme end

text = ulower(text or mw.title.getCurrentTitle.text) -- decompose everything but ñ and ü text = decompose(text) -- convert commas and en/en dashes to IPA foot boundaries text = rsub(text, "%s*[,–—]%s*", " | ") -- question mark or exclamation point in the middle of a sentence -> IPA foot boundary text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")

-- canonicalize multiple spaces and remove leading and trailing spaces local function canon_spaces(text) text = rsub(text, "%s+", " ") text = rsub(text, "^ ", "") text = rsub(text, " $", "") return text end

text = canon_spaces(text)

-- Convert hyphens to spaces, to handle Austria-Hungría, franco-italiano, etc.	text = rsub(text, "%-", " ") -- canonicalize multiple spaces again, which may have been introduced by hyphens text = canon_spaces(text) -- now eliminate punctuation text = rsub(text, "[!?']", "") -- put # at word beginning and end and double ## at text/foot boundary beginning/end text = rsub(text, " | ", "# | #") text = "##" .. rsub(text, " ", "# #") .. "##"	--determining whether "h" denotes a phoneme, or the lack of palatalization, palatalization when needed text = rsub(text, "nh", "N") text = rsub(text, "lh", "L") text = rsub(text, "([aeo])il([# .h]*[aeiou])", (northern and "%1ił%2" or "%1ĺ%2")) text = rsub(text, "([aeo])in([# .h]*[aeiou])", (northern and "%1iň%2" or "%1ń%2")) text = rsub(text, "uin([# .h]*[aeiou])", (northern and "uiň%1" or "uiń%1")) text = rsub(text, "uil([# .h]*[aeiou])", (northern and "uił%1" or "uiĺ%1")) text = rsub(text, "il([# .h]*[aeiou])", (northern and "ił%1" or "iĺ%1")) text = rsub(text, "in([# .h]*[aeiou])", (northern and "iň%1" or "iń%1")) if text:find("[łňńĺh]") then northern_different = true end if extract_last_syllables(text):find("[łňńĺh]") then N = "N" end text = rsub(text, "ĺ", "ʎ") text = rsub(text, "ń", "ɲ") text = rsub(text, "N", "n") text = rsub(text, "L", "l") text = rsub(text, "ň", "n") text = rsub(text, "ł", "l")

--c, g, q	text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- Bangkok, angstrom, etc. text = rsub(text, "q", "k") -- quark, Qatar, burqa, Iraq, etc. text = rsub(text, "c", "k") -- campus, etc.

--alphabet-to-phoneme text = rsub(text, "aje#", "aʒe#") -- final -aje is pronounced /aʒe/ in the North and /axe/ in the South, regardless of how native j is pronounced text = rsub(text, "ihoa", "iχoa") -- the  in conjugated forms of joan is pronounced in the South text = rsub(text, "zh", "ź") text = rsub(text, "tx", "C") --not the real sound text = rsub(text, "tz", "Ś") --not the real sound text = rsub(text, "ts", "S") --not the real sound text = rsubb(text, "ll", "ʎ") text = rsubb(text, "dd", "ɟ") text = rsubb(text, "tt", "c") text = rsub(text, "x", "ʃ") text = rsub(text, "#p([st])", "#%1") -- psicologia, pterodaktilo text = rsub(text, "[gñrvz]",			{ ["g"] = "ɡ", ["ñ"] = "ɲ", ["v"] = "b", ["z"] = "ś" }) if text:find("ś") or text:find("S") then need_bisc = true end --/x/~/j/~/ʒ/ sounds if text:find("[yj]") then northern_different = true j_different = true end if extract_last_syllables(text):find("[yj]") then J = "J" end if text:find("ʒ") or text:find("χ") then northern_different = true end if extract_last_syllables(text):find("ʒ") then N = "N" end if text:find("kh") then northern_different = true end if extract_last_syllables(text):find("kh") then N = "N" end if text:find("ź") then northern_different = true end if extract_last_syllables(text):find("ź") then N = "N" end text = rsubb(text, "j", (northern and "ɟ" or "x")) text = rsubb(text, "y", (northern and "ɟ" or "j")) --  is used for words which always have a palatal text = rsubb(text, "kh", (northern and "k" or "χ")) text = rsubb(text, "ź", (northern and "ʒ" or "ʃ"))

-- trilled r (in all cases except between vowels) text = rsub(text, "([aeiou])r([aeiou])", "%1ɾ%2") text = rsub(text, "([aeiou])r([aeiou])", "%1ɾ%2") -- it has to be applied twice in order to handle VrVrV sequences text = rsub(text, "rr", "r")

text = rsub(text, "n([# .]*[bpm])", "m%1") -- lack of /h/ in Southern dialects text = rsub(text, "aha", (northern and "aha" or "a")) text = rsub(text, "ehe", (northern and "ehe" or "e")) text = rsub(text, "ihi", (northern and "ihi" or "i")) text = rsub(text, "oho", (northern and "oho" or "o")) text = rsub(text, "uhu", (northern and "uhu" or "u")) text = rsub(text, "h", (northern and "h" or ""))

text = rsub(text, "n([# .]*[bpm])", "m%1")

-- handle i/u between vowels text = rsub_repeatedly(text, "ui", "U") --this dipthong requires special treatment local vowel_to_glide = { ["i"] = "i.", ["u"] = "u." }	text = rsub_repeatedly(text, "(" .. V .. "*)([iu])(" .. V .. ")",			function(v1, iu, v2)				return v1 .. vowel_to_glide[iu] .. v2			end	) text = rsub_repeatedly(text, "U", "ui")

--syllable division text = syllabify_from_spelling_or_pronun(text)

--dialectal differences if nav then text = rsub(text, "x", "j") elseif bisc then --fake symbols, to be replaced later text = rsub(text, "x", "đ") text = rsub(text, "j", "đ") text = rsub(text, "ś", "s") text = rsub(text, "S", "Ś") end

text = rsubb(text, "ʒ", (southern and "x" or "ʒ")) text = rsubb(text, "χ", (southern and "x" or "h")) --phonetic transcription if phonetic then -- θ, s, f before voiced consonants local voiced = "mnɲbdɟɡʎljđ" local r = "ɾr" local tovoiced = { ["s"] = "z̺", ["ś"] = "z̻", }		local function voice(sound, following) return tovoiced[sound] .. following end text = rsub(text, "([sś])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)

-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops -- after nasals and sometimes after l		local stop_to_fricative = {["b"] = "β", ["d"] = "ð", ["ɡ"] = "ɣ"} local fricative_to_stop = {["β"] = "b", ["ð"] = "d", ["ɣ"] = "ɡ"} text = rsub(text, "[bdɡ]", stop_to_fricative) text = rsub(text, "([mnɲ]" .. separator_c .. "*)([βɣ])",			function(nasal, fricative) return nasal .. fricative_to_stop[fricative] end		) text = rsub(text, "([lʎmnɲ]" .. separator_c .. "*)([ð])",			function(nasal_l, fricative) return nasal_l .. fricative_to_stop[fricative] end		) text = rsub(text, "##β", "##b") text = rsub(text, "##ð", "##d") text = rsub(text, "##ɣ", "##ɡ")

text = rsub(text, "[td]", {["t"] = "t̪", ["d"] = "d̪"})

-- nasal assimilation before consonants local labiodental, dentialveolar, alveolopalatal, palatal, velar = "ɱ", "n̪", "nʲ", "ɲ", "ŋ" local nasal_assimilation = { ["f"] = labiodental, ["t"] = dentialveolar, ["d"] = dentialveolar, ["C"] = alveolopalatal, ["ʃ"] = alveolopalatal, ["đ"] = alveolopalatal, ["ɟ"] = palatal, ["c"] = palatal, ["ʎ"] = palatal, ["j"] = palatal, ["k"] = velar, ["x"] = velar, ["ɡ"] = velar, }		text = rsub(text, "n(" .. separator_c .. "*)(.)",				function(stress, following)					return (nasal_assimilation[following] or "n") .. stress .. following				end		)

-- lateral assimilation before consonants text = rsub(text, "l(" .. separator_c .. "*)(.)",				function(stress, following)					local l = "l"					if following == "t" or following == "d" then						-- dentialveolar						l = "l̪"					elseif following == "C" or following == "ʃ" or following == "đ" then						-- alveolopalatal						l = "lʲ"					elseif following == "ɟ" or following == "c" or following == "j" then						-- alveolopalatal						l = "ʎ"					end					return l .. stress .. following				end)

-- voiced fricatives are actually approximants text = rsub(text, "([βðɣ])", "%1̞") --nasalization of vowels text = rsub(text, "([aeou])i([mnɲ])", "%1" .. TILDE .. "i" .. TILDE .. "%2") text = rsub(text, "([ae])u([mnɲ])", "%1" .. TILDE .. "u" .. TILDE .. "%2") text = rsub(text, "([aeiou])([mnɲ])", "%1" .. TILDE .. "%2") end --semivowels text = rsub(text, "([aeou])" .. TILDE .. "([i])", "%1" .. TILDE .. "%2̯") text = rsub(text, "([ae])" .. TILDE .. "([u])", "%1" .. TILDE .. "%2̯") text = rsub(text, "([aeou][i])", "%1̯") text = rsub(text, "([ae][u])", "%1̯") text = rsub(text, "u̯i̯", "u̯i") --correct the mistake appeareing in words with aui, eui

-- convert fake symbols to real ones local final_conversions = { ["S"] = "t͡s̺", ["Ś"] = "t͡s̻", ["s"] = "s̺", ["ś"] = "s̻", ["C"] = "t͡ʃ", ["X"] = "x", ["đ"] = "d͡ʒ", ["ʝ"] = "j" }	text = rsub(text, "[SŚsśCXđʝ]", final_conversions)

-- remove # symbols at word and text boundaries text = rsub(text, "#", "") text = mw.ustring.toNFC(text)

--add technical characters to the end of the rhyme in order to parse the rhymes correctly text = text .. J .. N

local differences = nil if dialect == "gipuzkoan" then differences = { northern_different = northern_different, need_bisc = need_bisc, j_different = j_different, }	end local ret = { text = text, differences = differences, }	return ret end

function export.IPA_string(frame) local iparams = { [1] = {},		["style"] = {required = true}, ["phonetic"] = {type = "boolean"}, }	local iargs = require("Module:parameters").process(frame.args, iparams) local retval = export.IPA(iargs[1], iargs.style, iargs.phonetic) return retval.text end

-- Generate all relevant dialect pronunciations and group into styles. local function express_all_styles(style_spec, dodialect) local ret = { pronun = {}, expressed_styles = {}, }

local need_bisc

local function express_style(hidden_tag, tag, representative_dialect, matching_styles) matching_styles = matching_styles or representative_dialect

-- If style specified, make sure it matches the requested style. local style_matches if not style_spec then style_matches = true else local style_parts = rsplit(matching_styles, "%-") local or_styles = rsplit(style_spec, "%s*,%s*") for _, or_style in ipairs(or_styles) do				local and_styles = rsplit(or_style, "%s*%+%s*") local and_matches = true for _, and_style in ipairs(and_styles) do					local negate if and_style:find("^%-") then and_style = and_style:gsub("^%-", "") negate = true end local this_style_matches = false for _, part in ipairs(style_parts) do						if part == and_style then this_style_matches = true break end end if negate then this_style_matches = not this_style_matches end if not this_style_matches then and_matches = false end end if and_matches then style_matches = true break end end end if not style_matches then return end

-- Fetch the representative dialect's pronunciation if not already present. if not ret.pronun[representative_dialect] then dodialect(ret, representative_dialect) end -- Insert the new style into the style group, creating the group if necessary. local new_style = { tag = tag, pronun = ret.pronun[representative_dialect], }		for _, hidden_tag_style in ipairs(ret.expressed_styles) do			if hidden_tag_style.tag == hidden_tag then table.insert(hidden_tag_style.styles, new_style) return end end table.insert(ret.expressed_styles, {			tag = hidden_tag,			styles = {new_style},		}) end

-- For each type of difference, figure out if the difference exists in any of the given respellings. We do this by -- generating the pronunciation for the dialect "gipuzkoan", for each respelling. In the process of	-- generating the pronunciation for a given respelling, it computes how the other dialects for that respelling -- differ. Then we take the union of these differences across the respellings. dodialect(ret, "gipuzkoan") local differences = {} for _, difftype in ipairs { "northern_different", "j_different", "need_bisc"} do		for _, pronun in ipairs(ret.pronun["gipuzkoan"]) do			if pronun.differences[difftype] then differences[difftype] = true end end end local northern_different = differences.northern_different local j_different = differences.j_different need_bisc = differences.need_bisc

-- Now, based on the observed differences, figure out how to combine the individual dialects into styles and style groups. if northern_different then express_style("Navarro-Lapurdian","Navarro-Lapurdian","northern") if j_different then express_style("Southern","Gipuzkoan","gipuzkoan") express_style("Southern","Biscayan","biscayan") express_style("Southern","Navarrese","navarrese") else if need_bisc then express_style("Southern","Gipuzkoan, Navarrese","gipuzkoan") express_style("Southern","Biscayan","biscayan") else express_style("Southern","Southern","gipuzkoan") end end else if j_different then express_style("Navarro-Lapurdian","Navarro-Lapurdian","northern") express_style("Southern","Gipuzkoan","gipuzkoan") express_style("Southern","Biscayan","biscayan") express_style("Southern","Navarrese","navarrese") elseif need_bisc then express_style(false,"most dialects","gipuzkoan") express_style(false,"Biscayan","biscayan") else express_style(false,false,"gipuzkoan") end end

return ret end

local function format_all_styles(expressed_styles, format_style) for i, style_group in ipairs(expressed_styles) do		if #style_group.styles == 1 then style_group.formatted, style_group.formatted_len = format_style(style_group.styles[1].tag, style_group.styles[1], i == 1) else style_group.formatted, style_group.formatted_len = format_style(style_group.tag, style_group.styles[1], i == 1) for j, style in ipairs(style_group.styles) do				style.formatted, style.formatted_len = format_style(style.tag, style, i == 1 and j == 1) end end end

local maxlen = 0 for i, style_group in ipairs(expressed_styles) do		local this_len = style_group.formatted_len if #style_group.styles > 1 then for _, style in ipairs(style_group.styles) do				this_len = math.max(this_len, style.formatted_len) end end maxlen = math.max(maxlen, this_len) end

local lines = {}

local need_major_hack = false for i, style_group in ipairs(expressed_styles) do		if #style_group.styles == 1 then table.insert(lines, style_group.formatted) need_major_hack = false else local inline = '\n \n' .. style_group.formatted .. " "			local full_prons = {} for _, style in ipairs(style_group.styles) do				table.insert(full_prons, style.formatted) end local full = '\n \n' .. table.concat(full_prons, "\n") .. " "			local em_length = math.floor(maxlen * 0.68) -- from Module:grc-pronunciation table.insert(lines, ' ' .. inline .. full .. " ") need_major_hack = true end end

-- major hack to get bullets working on the next line after a div box return table.concat(lines, "\n") .. (need_major_hack and "\n " or "") end

local function dodialect_pronun(args, ret, dialect) ret.pronun[dialect] = {} for i, term in ipairs(args.terms) do		local phonemic, phonetic, differences if term.raw then phonemic = term.raw_phonemic phonetic = term.raw_phonetic differences = construct_default_differences(dialect) else phonemic = export.IPA(term.term, dialect, false) phonetic = export.IPA(term.term, dialect, true) differences = phonemic.differences phonemic = phonemic.text phonetic = phonetic.text end local refs if not term.ref then refs = nil else refs = {} for _, refspec in ipairs(term.ref) do				local this_refs = require("Module:references").parse_references(refspec) for _, this_ref in ipairs(this_refs) do					table.insert(refs, this_ref) end end end

ret.pronun[dialect][i] = { raw = term.raw, phonemic = phonemic, phonetic = phonetic, refs = refs, q = term.q,			qq = term.qq, a = term.a,			aa = term.aa, differences = differences, }	end end

local function generate_pronun(args) local function remove_technical_chars(text) return rsub(text, "([JN])", "") end

local function this_dodialect_pronun(ret, dialect) dodialect_pronun(args, ret, dialect) end

local ret = express_all_styles(args.style, this_dodialect_pronun)

local function format_style(tag, expressed_style, is_first) local pronunciations = {} local formatted_pronuns = {}

local function ins(formatted_part) table.insert(formatted_pronuns, formatted_part) end

-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`, -- for formatting by Module:IPA, and also create an approximation of the formatted version so that we can -- compute the appropriate width of the HTML switcher div box that holds the different per-dialect variants. -- NOTE: The code below constructs the formatted approximation out-of-order in some cases but that doesn't -- currently matter because we assume all characters have the same width. If we change the width computation -- in a way that requires the correct order, we need changes to the code below. for j, pronun in ipairs(expressed_style.pronun) do			-- Add tag to left qualifiers if first one -- FIXME: Consider using accent qualifier for the tag instead. local qs = pronun.q			if j == 1 and tag then if qs then qs = m_table.deepcopy(qs) table.insert(qs, tag) else qs = {tag} end end

local first_pronun = #pronunciations + 1

if not pronun.phonemic and not pronun.phonetic then error("Internal error: Saw neither phonemic nor phonetic pronunciation") end

if pronun.phonemic then -- missing if 'raw:[...]' given -- don't display syllable division markers in phonemic local slash_pron = "/" .. remove_technical_chars(pronun.phonemic:gsub("%.", "")) .. "/"				table.insert(pronunciations, {					pron = slash_pron,				}) ins(slash_pron) end

if pronun.phonetic then -- missing if 'raw:/.../' given local bracket_pron = "[" .. remove_technical_chars(pronun.phonetic) .. "]"				table.insert(pronunciations, {					pron = bracket_pron,				}) ins(bracket_pron) end

local last_pronun = #pronunciations

if qs then pronunciations[first_pronun].q = qs			end if pronun.a then pronunciations[first_pronun].a = pronun.a			end if j > 1 then pronunciations[first_pronun].separator = ", " ins(", ") end if pronun.qq then pronunciations[last_pronun].qq = pronun.qq			end if pronun.aa then pronunciations[last_pronun].aa = pronun.aa			end if qs or pronun.qq or pronun.a or pronun.aa then -- Note: This inserts the actual formatted qualifier text, including HTML and such, but the later call -- to textual_len removes all HTML and reduces links. ins(require("Module:pron qualifier").format_qualifiers {					lang = lang,					text = "",					q = qs,					qq = pronun.qq,					a = pronun.a,					aa = pronun.aa,				}) end

if pronun.refs then pronunciations[last_pronun].refs = pronun.refs -- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are -- more than nine references but that is rare. ins(string.rep("[1]", #pronun.refs)) end if first_pronun ~= last_pronun then pronunciations[last_pronun].separator = " " ins(" ") end end

local bullet = string.rep("*", args.bullets) .. " "		-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML -- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing -- the toggle box with the "more" button on the right. local pre = is_first and args.pre and args.pre .. " " or "" local post = is_first and args.post and " " .. args.post or "" local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post return formatted, textual_len(formatted_for_len) end

ret.text = format_all_styles(ret.expressed_styles, format_style)

return ret end

local function parse_respelling(respelling, pagename, parse_err) local raw_respelling = respelling:match("^raw:(.*)$") if raw_respelling then local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$") if not raw_phonemic then raw_phonemic = raw_respelling:match("^/(.*)/$") end if not raw_phonemic then raw_phonetic = raw_respelling:match("^%[(.*)%]$") end if not raw_phonemic and not raw_phonetic then parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")				:format(raw_respelling)) end return { raw = true, raw_phonemic = raw_phonemic, raw_phonetic = raw_phonetic, }	end if respelling == "+" then respelling = pagename end return {term = respelling} end

-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no -- hyphens. local function get_num_syl_from_phonemic(phonemic) -- Maybe we should just count vowels instead of the below code. phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries local words = rsplit(phonemic, " +") for i, word in ipairs(words) do -- IPA stress marks are syllable divisions if between characters; otherwise just remove. word = rsub(word, "(.)[ˌˈ](.)", "%1.%2") word = rsub(word, "[ˌˈ]", "") words[i] = word end -- There should be a syllable boundary between words. phonemic = table.concat(words, ".") return ulen(rsub(phonemic, "[^.]", "")) + 1 end

-- Take the last two syllables for the ryhme, unless the word has a single syllable local function convert_phonemic_to_rhyme(phonemic) local syllables = rsplit(phonemic, "%.") local rh = "" if #syllables > 1 then rh = table.concat(syllables, ".", #syllables - 1, #syllables) else rh = phonemic end return rsub(rh, "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("͡", "") end

local function split_syllabified_spelling(spelling) return rsplit(spelling, "%.") end

-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and -- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we -- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating -- the alignment failed. local function align_syllabification_to_spelling(syllab, spelling) local result = {} local syll_chars = rsplit(decompose(syllab), "") local spelling_chars = rsplit(decompose(spelling), "") local i = 1 local j = 1 while i <= #syll_chars or j <= #spelling_chars do		local ci = syll_chars[i] local cj = spelling_chars[j] if ci == cj then table.insert(result, ci) i = i + 1 j = j + 1 elseif ci == "." then table.insert(result, ci) i = i + 1 elseif ci == AC or ci == GR or ci == CFLEX then -- skip character i = i + 1 else -- non-matching character return nil end end if i <= #syll_chars or j <= #spelling_chars then -- left-over characters on one side or the other return nil end return unfc(table.concat(result)) end

local function generate_hyph_obj(term) return {syllabification = term, hyph = split_syllabified_spelling(term)} end

-- Word should already be decomposed. local function word_has_vowels(word) return rfind(word, V) end

local function all_words_have_vowels(term) local words = rsplit(decompose(term), "[ %-]") for i, word in ipairs(words) do -- Allow empty word; this occurs with prefixes and suffixes. if word ~= "" and not word_has_vowels(word) then return false end end return true end

local function should_generate_rhyme_from_respelling(term) local words = rsplit(decompose(term), " +") return #words == 1 and -- no if multiple words not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. Austria-Hungría) not words[1]:find("%-$") and -- no if word is a prefix not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter) end

local function should_generate_rhyme_from_ipa(ipa) return not ipa:find("%s") and word_has_vowels(decompose(ipa)) end

local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret, dialect) rhyme_ret.pronun[dialect] = {} for _, rhyme in ipairs(rhymes) do		local num_syl = rhyme.num_syl local no_num_syl = false

-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from -- the hyphenation. if not num_syl then num_syl = {} for _, hyph in ipairs(hyphs) do				if should_generate_rhyme_from_respelling(hyph.syllabification) then local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", "")) m_table.insertIfNot(num_syl, this_num_syl) else no_num_syl = true break end end if no_num_syl or #num_syl == 0 then num_syl = nil end end

-- If that fails and term is single-word, try to take it from the phonemic. if not no_num_syl and not num_syl then for _, parsed in ipairs(parsed_respellings) do				for dialect, pronun in pairs(parsed.pronun.pronun[dialect]) do -- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given). if pronun.phonemic then if not should_generate_rhyme_from_ipa(pronun.phonemic) then no_num_syl = true break end -- Count number of syllables by looking at syllable boundaries (including stress marks). local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic) m_table.insertIfNot(num_syl, this_num_syl) end end if no_num_syl then break end end if no_num_syl or #num_syl == 0 then num_syl = nil end end

table.insert(rhyme_ret.pronun[dialect], {			rhyme = rhyme.rhyme,			num_syl = num_syl,			qualifiers = rhyme.qualifiers,			differences = construct_default_differences(dialect),		}) end end

local function parse_pron_modifier(arg, put, parse_err, generate_obj, param_mods, no_split_on_comma) local retval = {}

if arg:find("<") then if not put then put = require(put_module) end

local function get_valid_prefixes local valid_prefixes = {} for param_mod, _ in pairs(param_mods) do				table.insert(valid_prefixes, param_mod) end table.insert(valid_prefixes, "q") table.insert(valid_prefixes, "qq") table.insert(valid_prefixes, "a") table.insert(valid_prefixes, "aa") table.sort(valid_prefixes) return valid_prefixes end

local segments = put.parse_balanced_segment_run(arg, "<", ">") local comma_separated_groups = no_split_on_comma and {segments} or put.split_alternating_runs_on_comma(segments) for _, group in ipairs(comma_separated_groups) do			local obj = generate_obj(group[1]) for j = 2, #group - 1, 2 do				if group[j + 1] ~= "" then parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier") end local modtext = group[j]:match("^<(.*)>$") if not modtext then parse_err("Internal error: Modifier '" .. group[j] .. "' isn't surrounded by angle brackets") end local prefix, val = modtext:match("^([a-z]+):(.*)$") if not prefix then local valid_prefixes = get_valid_prefixes for i, valid_prefix in ipairs(valid_prefixes) do valid_prefixes[i] = "'" .. valid_prefix .. ":'"					end parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with one of " ..						m_table.serialCommaJoin(valid_prefixes)) end if prefix == "q" or prefix == "qq" or prefix == "a" or prefix == "aa" then if not obj[prefix] then obj[prefix] = {} end table.insert(obj[prefix], val) elseif param_mods[prefix] then local key = param_mods[prefix].item_dest or prefix if obj[key] then parse_err("Modifier '" .. prefix .. "' specified more than once") end local convert = param_mods[prefix].convert if convert then obj[key] = convert(val) else obj[key] = val end else local valid_prefixes = get_valid_prefixes for i, valid_prefix in ipairs(valid_prefixes) do valid_prefixes[i] = "'" .. valid_prefix .. "'"					end parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j]						.. ", should be " .. m_table.serialCommaJoin(valid_prefixes)) end end table.insert(retval, obj) end elseif no_split_on_comma then table.insert(retval, generate_obj(arg)) else for _, term in ipairs(split_on_comma(arg)) do			table.insert(retval, generate_obj(term)) end end

return retval end

local function parse_rhyme(arg, put, parse_err) local function generate_obj(term) return {rhyme = term} end local param_mods = { s = { item_dest = "num_syl", convert = function(arg) local nsyls = rsplit(arg, ",") for i, nsyl in ipairs(nsyls) do					if not nsyl:find("^[0-9]+$") then parse_err("Number of syllables '" .. nsyl .. "' should be numeric") end nsyls[i] = tonumber(nsyl) end return nsyls end, },	}

return parse_pron_modifier(arg, put, parse_err, generate_obj, param_mods) end

local function parse_hyph(arg, put, parse_err) -- None other than qualifiers local param_mods = {}

return parse_pron_modifier(arg, put, parse_err, generate_hyph_obj, param_mods) end

local function parse_homophone(arg, put, parse_err) local function generate_obj(term) return {term = term} end local param_mods = { t = { -- We need to store the  inline modifier into the "gloss" key of the parsed term, -- because that is what Module:links (called from Module:homophones) expects. item_dest = "gloss", },		gloss = {}, pos = {}, alt = {}, lit = {}, id = {}, g = { -- We need to store the  inline modifier into the "genders" key of the parsed term, -- because that is what Module:links (called from Module:homophones) expects. item_dest = "genders", convert = function(arg) return rsplit(arg, ",") end, },	}

return parse_pron_modifier(arg, put, parse_err, generate_obj, param_mods) end

local function generate_audio_obj(arg) local file, gloss if arg:find("#") then file, gloss = arg:match("^(.-)%s*#%s*(.*)$") else file, gloss = arg:match("^(.-)%s*;%s*(.*)$") end file = file or arg return {file = file, gloss = gloss} end

local function parse_audio(arg, put, parse_err) -- None other than qualifiers local param_mods = {}

-- Don't split on comma because some filenames have embedded commas not followed by a space -- (typically followed by an underscore). return parse_pron_modifier(arg, put, parse_err, generate_audio_obj, param_mods, "no split on comma") end

-- External entry point for. function export.show_pr(frame) local params = { [1] = {list = true}, ["rhyme"] = {}, ["hyph"] = {}, ["hmp"] = {}, ["audio"] = {list = true}, ["pagename"] = {}, }	local parargs = frame:getParent.args local args = require("Module:parameters").process(parargs, params) local pagename = args.pagename or mw.title.getCurrentTitle.subpageText

-- Parse the arguments. local function remove_technical_chars(text) return rsub(text, "([JN])", "") end local respellings = #args[1] > 0 and args[1] or {"+"} local parsed_respellings = {} local function overall_parse_err(msg, arg, val) error(msg .. ": " .. arg .. "= " .. val) end local overall_rhyme = args.rhyme and parse_rhyme(args.rhyme, nil, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil local overall_hyph = args.hyph and parse_hyph(args.hyph, nil, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil local overall_hmp = args.hmp and parse_homophone(args.hmp, nil, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil local overall_audio if args.audio then overall_audio = {} for _, audio in ipairs(args.audio) do			local parsed_audio = parse_audio(audio, nil, function(msg) overall_parse_err(msg, "audio", audio) end) if #parsed_audio > 1 then error("Internal error: Saw more than one object returned from parse_audio") end table.insert(overall_audio, parsed_audio[1]) end end local put

for i, respelling in ipairs(respellings) do		local function parse_err(msg) error(msg .. ": " .. i .. "= " .. respelling) end if respelling:find("<") then if not put then put = require(put_module) end

local param_mods = { pre = {}, post = {}, style = {}, bullets = { convert = function(arg) if not arg:find("^[0-9]+$") then parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'") end return tonumber(arg) end, },				rhyme = { insert = true, flatten = true, convert = function(arg) return parse_rhyme(arg, put, parse_err) end, },				hyph = { insert = true, flatten = true, convert = function(arg) return parse_hyph(arg, put, parse_err) end, },				hmp = { insert = true, flatten = true, convert = function(arg) return parse_homophone(arg, put, parse_err) end, },				audio = { insert = true, flatten = true, convert = function(arg) return parse_audio(arg, put, parse_err) end, },			}

local function get_valid_prefixes local valid_prefixes = {} for param_mod, _ in pairs(param_mods) do					table.insert(valid_prefixes, param_mod) end table.insert(valid_prefixes, "ref") table.insert(valid_prefixes, "q") table.insert(valid_prefixes, "qq") table.insert(valid_prefixes, "a") table.insert(valid_prefixes, "aa") table.sort(valid_prefixes) return valid_prefixes end

local segments = put.parse_balanced_segment_run(respelling, "<", ">") local comma_separated_groups = put.split_alternating_runs_on_comma(segments, ",") local parsed = {terms = {}, audio = {}, rhyme = {}, hyph = {}, hmp = {}} for j, group in ipairs(comma_separated_groups) do				local termobj = parse_respelling(group[1], pagename, parse_err) for k = 2, #group - 1, 2 do					if group[k + 1] ~= "" then parse_err("Extraneous text '" .. group[k + 1] .. "' after modifier") end local modtext = group[k]:match("^<(.*)>$") if not modtext then parse_err("Internal error: Modifier '" .. group[k] .. "' isn't surrounded by angle brackets") end local prefix, arg = modtext:match("^([a-z]+):(.*)$") if not prefix then local valid_prefixes = get_valid_prefixes for i, valid_prefix in ipairs(valid_prefixes) do valid_prefixes[i] = "'" .. valid_prefix .. ":'"						end parse_err("Modifier " .. group[k] .. " lacks a prefix, should begin with one of " ..							m_table.serialCommaJoin(valid_prefixes)) end if prefix == "ref" or prefix == "q" or prefix == "qq" or prefix == "a" or prefix == "aa" then if not termobj[prefix] then termobj[prefix] = {} end table.insert(termobj[prefix], arg) elseif param_mods[prefix] then if j < #comma_separated_groups then parse_err("Modifier '" .. prefix .. "' should occur after the last comma-separated term") end if not param_mods[prefix].insert and parsed[prefix] then parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. group[k]) end local converted if param_mods[prefix].convert then converted = param_mods[prefix].convert(arg) else converted = arg end if param_mods[prefix].insert then if param_mods[prefix].flatten then for _, obj in ipairs(converted) do									table.insert(parsed[prefix], obj) end else table.insert(parsed[prefix], converted) end else parsed[prefix] = converted end else local valid_prefixes = get_valid_prefixes for i, valid_prefix in ipairs(valid_prefixes) do valid_prefixes[i] = "'" .. valid_prefix .. "'"						end parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[k]							.. ", should be " .. m_table.serialCommaJoin(valid_prefixes)) end end table.insert(parsed.terms, termobj) end if not parsed.bullets then parsed.bullets = 1 end table.insert(parsed_respellings, parsed) else local termobjs = {} for _, term in ipairs(split_on_comma(respelling)) do				table.insert(termobjs, parse_respelling(term, pagename, parse_err)) end table.insert(parsed_respellings, {				terms = termobjs,				audio = {},				rhyme = {},				hyph = {},				hmp = {},				bullets = 1,			}) end end

if overall_hyph then local hyphs = {} for _, hyph in ipairs(overall_hyph) do			if hyph.syllabification == "+" then hyph.syllabification = syllabify_from_spelling(pagename) hyph.hyph = split_syllabified_spelling(hyph.syllabification) elseif hyph.syllabification == "-" then overall_hyph = {} break end end end

-- Loop over individual respellings, processing each. for _, parsed in ipairs(parsed_respellings) do		parsed.pronun = generate_pronun(parsed) local no_auto_rhyme = false for _, term in ipairs(parsed.terms) do			if term.raw then if not should_generate_rhyme_from_ipa(term.raw_phonemic or term.raw_phonetic) then no_auto_rhyme = true break end elseif not should_generate_rhyme_from_respelling(term.term) then no_auto_rhyme = true break end end

if #parsed.hyph == 0 then if not overall_hyph and all_words_have_vowels(pagename) then for _, term in ipairs(parsed.terms) do					if not term.raw then local syllabification = syllabify_from_spelling(term.term) local aligned_syll = align_syllabification_to_spelling(syllabification, pagename) if aligned_syll then m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll)) end end end end else for _, hyph in ipairs(parsed.hyph) do				if hyph.syllabification == "+" then hyph.syllabification = syllabify_from_spelling(pagename) hyph.hyph = split_syllabified_spelling(hyph.syllabification) elseif hyph.syllabification == "-" then parsed.hyph = {} break end end end

-- Generate the rhymes. local function dodialect_rhymes_from_pronun(rhyme_ret, dialect) rhyme_ret.pronun[dialect] = {} -- It's possible the pronunciation for a passed-in dialect was never generated. This happens e.g. with -- . The initial call to generate_pronun fails to generate a pronunciation -- for the dialect 'distinction-yeismo' because the pronunciation of 'cebolla' differs between distincion -- and seseo and so the seseo style restriction rules out generation of pronunciation for distincion -- dialects (other than 'gipuzkoan', which always gets generated so as to determine on which axes			-- the dialects differ). However, when generating the rhyme, it is based only on -olla, whose pronunciation -- does not differ between distincion and seseo, but does differ between lleismo and yeismo, so it needs to			-- generate a yeismo-specific rhyme, and 'distincion-yeismo' is the representative dialect for yeismo in the -- situation where distincion and seseo do not have distinct results (based on the following line in			-- express_all_styles): --  express_style(false, "most of Spain and Latin America", "distincion-yeismo", "distincion-seseo-yeismo") -- In this case we need to generate the missing overall pronunciation ourselves since we need it to generate -- the dialect-specific rhyme pronunciation. if not parsed.pronun.pronun[dialect] then dodialect_pronun(parsed, parsed.pronun, dialect) end for _, pronun in ipairs(parsed.pronun.pronun[dialect]) do				-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see				-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw				-- phonetic-only pronun is given). if pronun.phonemic then -- Count number of syllables by looking at syllable boundaries (including stress marks). local num_syl = get_num_syl_from_phonemic(pronun.phonemic) -- Get the rhyme by truncating everything up through the last stress mark + any following -- consonants, and remove syllable boundary markers. local rhyme = convert_phonemic_to_rhyme(pronun.phonemic) local saw_already = false for _, existing in ipairs(rhyme_ret.pronun[dialect]) do						if existing.rhyme == rhyme then saw_already = true -- We already saw this rhyme but possibly with a different number of syllables, -- e.g. if the user specified two pronunciations 'biología' (4 syllables) and -- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/. m_table.insertIfNot(existing.num_syl, num_syl) break end end if not saw_already then local rh2 = rsub(rhyme, "ts̻", "") local rhyme_diffs = nil if dialect == "gipuzkoan" then rhyme_diffs = {} if rfind(rh2,"s̻") then rhyme_diffs.need_bisc = true end if rfind(rhyme,"ts̺") then rhyme_diffs.need_bisc = true end if rfind(rhyme,"J") then rhyme_diffs.j_different = true end if rfind(rhyme,"N") then rhyme_diffs.northern_different = true end end table.insert(rhyme_ret.pronun[dialect], {							rhyme = remove_technical_chars(rhyme),							num_syl = {num_syl},							differences = rhyme_diffs,						}) end end end end

if #parsed.rhyme == 0 then if overall_rhyme or no_auto_rhyme then parsed.rhyme = nil else parsed.rhyme = express_all_styles(parsed.style, dodialect_rhymes_from_pronun) end else local no_rhyme = false for _, rhyme in ipairs(parsed.rhyme) do				if rhyme.rhyme == "-" then no_rhyme = true break end end if no_rhyme then parsed.rhyme = nil else local function this_dodialect(rhyme_ret, dialect) return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret, dialect) end parsed.rhyme = express_all_styles(parsed.style, this_dodialect) end end end

if overall_rhyme then local no_overall_rhyme = false for _, orhyme in ipairs(overall_rhyme) do			if orhyme.rhyme == "-" then no_overall_rhyme = true break end end if no_overall_rhyme then overall_rhyme = nil else local all_hyphs if overall_hyph then all_hyphs = overall_hyph else all_hyphs = {} for _, parsed in ipairs(parsed_respellings) do					for _, hyph in ipairs(parsed.hyph) do						m_table.insertIfNot(all_hyphs, hyph) end end end local function dodialect_overall_rhyme(rhyme_ret, dialect) return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret, dialect) end overall_rhyme = express_all_styles(parsed.style, dodialect_overall_rhyme) end end

-- If all sets of pronunciations have the same rhymes, display them only once at the bottom. -- Otherwise, display rhymes beneath each set, indented. local first_rhyme_ret local all_rhyme_sets_eq = true for j, parsed in ipairs(parsed_respellings) do		if j == 1 then first_rhyme_ret = parsed.rhyme elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then all_rhyme_sets_eq = false break end end

local function format_rhyme(rhyme_ret, num_bullets) local function format_rhyme_style(tag, expressed_style, is_first) local pronunciations = {} local rhymes = {} for _, pronun in ipairs(expressed_style.pronun) do				table.insert(rhymes, pronun) end local data = { lang = lang, rhymes = rhymes, qualifiers = tag and {tag} or nil, force_cat = force_cat, }			local bullet = string.rep("*", num_bullets) .. " "			local formatted = bullet .. require("Module:rhymes").format_rhymes(data) local formatted_for_len_parts = {} table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or "")) for j, pronun in ipairs(expressed_style.pronun) do				if j > 1 then table.insert(formatted_for_len_parts, ", ") end if pronun.qualifiers then table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.qualifiers, ", ") .. ") ")				end table.insert(formatted_for_len_parts, "-" .. pronun.rhyme) end return formatted, textual_len(table.concat(formatted_for_len_parts)) end

return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style) end

-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom. -- Otherwise, display hyphenations beneath each set, indented. local first_hyphs local all_hyph_sets_eq = true for j, parsed in ipairs(parsed_respellings) do		if j == 1 then first_hyphs = parsed.hyph elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then all_hyph_sets_eq = false break end end

local function format_hyphenations(hyphs, num_bullets) local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs} return string.rep("*", num_bullets) .. " " .. hyphtext end

-- If all sets of pronunciations have the same homophones, display them only once at the bottom. -- Otherwise, display homophones beneath each set, indented. local first_hmps local all_hmp_sets_eq = true for j, parsed in ipairs(parsed_respellings) do		if j == 1 then first_hmps = parsed.hmp elseif not m_table.deepEquals(first_hmps, parsed.hmp) then all_hmp_sets_eq = false break end end

local function format_homophones(hmps, num_bullets) local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps } return string.rep("*", num_bullets) .. " " .. hmptext end

local function format_audio(audios, num_bullets) local ret = {} for i, audio in ipairs(audios) do			local text = require("Module:audio").format_audio { lang = lang, file = audio.file, caption = audio.gloss, q = audio.q,				qq = audio.qq, a = audio.a,				aa = audio.aa, }			table.insert(ret, string.rep("*", num_bullets) .. " " .. text) end return table.concat(ret, "\n") end

local textparts = {} local min_num_bullets = 9999 for j, parsed in ipairs(parsed_respellings) do		if parsed.bullets < min_num_bullets then min_num_bullets = parsed.bullets end if j > 1 then table.insert(textparts, "\n") end table.insert(textparts, parsed.pronun.text) if #parsed.audio > 0 then table.insert(textparts, "\n") -- If only one pronunciation set, add the audio with the same number of bullets, otherwise -- indent audio by one more bullet. table.insert(textparts, format_audio(parsed.audio, #parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1)) end if not all_rhyme_sets_eq and parsed.rhyme then table.insert(textparts, "\n") table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1)) end if not all_hyph_sets_eq and #parsed.hyph > 0 then table.insert(textparts, "\n") table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1)) end if not all_hmp_sets_eq and #parsed.hmp > 0 then table.insert(textparts, "\n") table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1)) end end if overall_audio and #overall_audio > 0 then table.insert(textparts, "\n") table.insert(textparts, format_audio(overall_audio, min_num_bullets)) end if all_rhyme_sets_eq and first_rhyme_ret then table.insert(textparts, "\n") table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets)) end if overall_rhyme then table.insert(textparts, "\n") table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets)) end if all_hyph_sets_eq and #first_hyphs > 0 then table.insert(textparts, "\n") table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets)) end if overall_hyph and #overall_hyph > 0 then table.insert(textparts, "\n") table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets)) end if all_hmp_sets_eq and #first_hmps > 0 then table.insert(textparts, "\n") table.insert(textparts, format_homophones(first_hmps, min_num_bullets)) end if overall_hmp and #overall_hmp > 0 then table.insert(textparts, "\n") table.insert(textparts, format_homophones(overall_hmp, min_num_bullets)) end

return table.concat(textparts) end

return export