Jump to content

Module:mn-common

From Wiktionary, the free dictionary


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

local export = {}

local concat = table.concat
local find = m_str_utils.find
local insert = table.insert
local len = m_str_utils.len
local match = m_str_utils.match
local gmatch = m_str_utils.gmatch
local sub = m_str_utils.sub
local gsub = m_str_utils.gsub
local lower = m_str_utils.lower
local reverse = m_str_utils.reverse
local reverse_array = m_table.reverse
local remove_duplicates = m_table.removeDuplicates
local sort = table.sort
local u = m_str_utils.char

export.FVS1 = u( 0x180B )
export.FVS2 = u( 0x180C )
export.FVS3 = u( 0x180D )
export.FVS4 = u( 0x180F )
export.MVS = u( 0x180E )
export.NNBSP = u( 0x202F )
export.stem_barrier = u( 0xF000 )

local function format_Mongolian_text(text) return "<span class=\"Mong\" lang=\"mn\">" .. text .. "</span>" end

function export.see(frame)
	local params = {
		[1] = {},
	}
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	local title = args[1]
	local curr_title = mw.title.getCurrentTitle().subpageText
	local content = mw.title.new(title):getContent()
	local senses = {}
	local sense_id = 0
	local j, pos, s, section
	
	if title == curr_title then
		return error("The soft-directed item is the same as the page title.")
	end
	
	if content then
		if not match(content, "==Mongolian==") then
			categories = categories .. "[[Category:Mongolian redlinks/mn-see]]"
		elseif not match(content, "mn%-IPA") and not match(content, "mn%-see") then
			require("Module:debug").track("mn-see/unidirectional reference to variant")
		elseif not match(content, curr_title) then
			require("Module:debug").track("mn-see/unidirectional reference variant→orthodox")
		end
	end
	
	while true do
		_, j, language_name, s = content:find("%f[=]==%s*([^=]+)%s*==(\n.-)\n==%f[^=]", pos)
		
		if j == nil then
			i, j, language_name, s = content:find("%f[=]==%s*([^=]+)%s*==(\n.+)", pos)
		end
		
		if j == nil then
			break
		else
			pos = j - 1
		end
		
		if language_name == "Mongolian" then
			section = s
		end
	end
		
	if not section then
		return ""
	end
	
	section = section:gsub("\n===+Etymology.-(\n==)", "%1")
	
	local text = {}
	
	for sense in section:gmatch("\n# ([^\n]+)") do
		if not sense:match("rfdef") and not sense:match("defn") then
			sense_id = sense_id + 1
			insert(senses, sense)
		end
	end
	insert(text, concat(senses, "\n# "))
	
	insert(text, "</div>")
	
	return frame:preprocess( concat(text) )
	
end

function export.ipasee(frame)
	local params = {
		[1] = {},
	}
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	local title = args[1]
	local curr_title = mw.title.getCurrentTitle().subpageText
	local content = mw.title.new(title):getContent()
	local senses = {}
	local sense_id = 0
	local j, pos, s, section
	
	if title == curr_title then
		return error("The soft-directed item is the same as the page title.")
	end
	
	if content then
		if not match(content, "==Mongolian==") then
			categories = categories .. "[[Category:Mongolian redlinks/mn-IPA-see]]"
		elseif not match(content, "mn%-IPA") and not match(content, "mn%-see") and not match(content, "mn%-IPA-see") then
			require("Module:debug").track("mn-IPA-see/unidirectional reference to variant")
		elseif not match(content, curr_title) then
			require("Module:debug").track("mn-IPA-see/unidirectional reference variant→orthodox")
		end
	end
	
	while true do
		_, j, language_name, s = content:find("%f[=]==%s*([^=]+)%s*==(\n.-)\n==%f[^=]", pos)
		
		if j == nil then
			i, j, language_name, s = content:find("%f[=]==%s*([^=]+)%s*==(\n.+)", pos)
		end
		
		if j == nil then
			break
		else
			pos = j - 1
		end
		
		if language_name == "Mongolian" then
			section = s
		end
	end
		
	if not section then
		return ""
	end
	
	section = section:gsub("\n===+Etymology.-(\n==)", "%1")
	
	local text = {}
	
	if section:match("{{mn-IPA%|([^\n]+)}}") then
		return frame:expandTemplate{ title = "mn-IPA", args = { section:match("{{mn-IPA%|([^\n]+)}}") } }
	else
		return frame:expandTemplate{ title = "mn-IPA", args = { title } }
	end
	
	
end

--Breaks down a string into vowel harmonic segments
function export.vowelharmony( text, params )
	
	if not params then params = {} end
	
	local vh = {}
	local breaks = { 1 }
	
	local switchers = { "ау", "оу", "уу", "иу", "яу", "ёу", "еу", "юу", "уй", "эү", "өү", "үү", "иү", "еү", "юү", "үй", " ", "-" }
	local switchers2 = { "аа", "оо", "өө", "ээ", "яа", "ёо", "еө", "еэ", "Аа", "Оо", "Өө", "Ээ", "Яа", "Ёо", "Еө", "Еэ" }
	
	for _, v in pairs( switchers ) do
		v = gsub( v, "(.)(.)", "%1" .. u( 0x301 ) .. "?" .. u( 0x300 ) .. "?%2" )
		local c = 0
		while c ~= nil do
			c = find( lower( text ), v, c + 1 )
			if c ~= nil and c ~= 1 then
				insert( breaks, c )
			end
		end
	end
	
	if params.bor then
		for _,v in pairs( switchers2 ) do
			if match( text, v .. "$" ) then
				insert( breaks, len( text ) - 1 )
			end
		end
	end
	
	sort( breaks )
	
	for i, b in ipairs( breaks ) do
		insert( vh, { Cyrl = {}, Mong = {} } )
		if i == #breaks then
			vh[i].substring = sub( text, b, len( text ) )
		else
			vh[i].substring = sub( text, b, breaks[i + 1] - 1 )
		end
	end
	
	if params.bor then
		for i,s in ipairs( vh ) do
			vh[i] = { Cyrl = {}, Mong = {} }
			local orig_text = s.substring
			s.substring = lower( s.substring )
			if params.bor == "Russian" then s.substring = gsub( s.substring, "у", "ү" ) end
			local substring_nostress = gsub( s.substring, "[" .. u( 0x301 ) .. u( 0x300 ) .. "]", "" )
			if match( s.substring, "кило" .. u( 0x301 ) .. "?$" ) then -- irregular
				vh[i].Cyrl.a = "э"
				vh[i].location = find( s.substring, "[эүею]" )
				vh[i].position = "front"
				vh[i].quality = "unrounded"
			elseif match( substring_nostress, "[аеёиоөуүэюя]у" ) or match( substring_nostress, "уй" ) then
				vh[i].Cyrl.a = "а"
				vh[i].location = 1
				vh[i].position = "back"
				vh[i].quality = "unrounded"
			elseif match( substring_nostress, "[аеёиоөуүэюя]ү" ) or match( substring_nostress, "үй" ) then
				vh[i].Cyrl.a = "э"
				vh[i].location = 1
				vh[i].position = "front"
				vh[i].quality = "unrounded"
			elseif match( s.substring, "[ауяᠠᠣᠤ]" .. u( 0x301 ) ) then
				vh[i].Cyrl.a = "а"
				vh[i].location = find( s.substring, u( 0x301 ) ) - 1
				vh[i].position = "back"
				vh[i].quality = "unrounded"
			elseif match( s.substring, "[оё]" .. u( 0x301 ) ) then
				vh[i].Cyrl.a = "о"
				vh[i].location = find( s.substring, u( 0x301 ) ) - 1
				vh[i].position = "back"
				vh[i].quality = "rounded"
			elseif ( match( substring_nostress, "[ауяоё]" ) and ( find( substring_nostress, "[ауяоё]" ) == find( substring_nostress, "[ауя]" ) ) ) or match( substring_nostress, "[ᠠᠣᠤ]" ) then
				vh[i].Cyrl.a = "а"
				vh[i].location = find( substring_nostress, "[ауя]" )
				vh[i].position = "back"
				vh[i].quality = "unrounded"
			elseif match( substring_nostress, "[ауяоё]" ) and find( substring_nostress, "[ауяоё]" ) == find( substring_nostress, "[оё]" ) then
				vh[i].Cyrl.a = "о"
				vh[i].location = find( substring_nostress, "[оё]" )
				vh[i].position = "back"
				vh[i].quality = "rounded"
			elseif ( match( substring_nostress, "[эүеюө]" ) and find( substring_nostress, "[эүеюө]" ) == find( substring_nostress, "[эүею]" ) ) or match( substring_nostress, "[ᠡᠥᠦᠧ]" ) then
				vh[i].Cyrl.a = "э"
				vh[i].location = find( substring_nostress, "[эүею]" )
				vh[i].position = "front"
				vh[i].quality = "unrounded"
			elseif match( substring_nostress, "[эүеюө]" ) and find( substring_nostress, "[эүеюө]" ) == find( substring_nostress, "ө" ) then
				vh[i].Cyrl.a = "ө"
				vh[i].location = find( substring_nostress, "ө" )
				vh[i].position = "front"
				vh[i].quality = "rounded"
			else
				vh[i].Cyrl.a = "э"
				vh[i].location = find( substring_nostress, "и" ) or 1
				vh[i].position = "front"
				vh[i].quality = "unrounded"
			end
			if match( vh[i].Cyrl.a, "[ао]" ) then
				vh[i].Cyrl.ii = "ы"
				vh[i].Cyrl.u = "у"
				vh[i].Mong.a = "ᠠ"
				vh[i].Mong.u = "ᠤ"
			else
				vh[i].Cyrl.ii = "ий"
				vh[i].Cyrl.u = "ү"
				vh[i].Mong.a = "ᠡ"
				vh[i].Mong.u = "ᠦ"
			end
			if match( vh[i].Cyrl.a, "ө" ) then -- ө takes the diphthong эй not өй
				vh[i].Cyrl.ai = "эй"
			else
				vh[i].Cyrl.ai = vh[i].Cyrl.a .. "й"
			end
			vh[i].Cyrl.aa = vh[i].Cyrl.a .. vh[i].Cyrl.a
			vh[i].Cyrl.uu = vh[i].Cyrl.u .. vh[i].Cyrl.u
			vh[i].substring = orig_text
		end
	else
		local location
		local pattern
		for i,s in ipairs( vh ) do
			local orig = s.substring
			s.substring = reverse( lower( s.substring ) )
			local vowel = match( s.substring, "[аеёоөуүэюя]" )
			location = (find( s.substring, "[аеёоөуүэюя]" ))
			if vh[i].Cyrl.a == nil then
				vh[i].Cyrl.a = "э"
				vh[i].position = "front"
				vh[i].quality = "unrounded"
				pattern = "и"
			end
			vh[i].Cyrl.a = lower( vh[i].Cyrl.a )
			if vowel == "а" or vowel == "у" or vowel == "я" then
				vh[i].Cyrl.a = "а"
				vh[i].position = "back"
				vh[i].quality = "unrounded"
				pattern = "[ауюя]"
				if match( s.substring, "[еёоөүэ]" ) then vh[i].violation = true else vh[i].violation = false end
			elseif vowel == "о" or vowel == "ё" then
				vh[i].Cyrl.a = "о"
				vh[i].position = "back"
				vh[i].quality = "rounded"
				pattern = "[ёо]"
				if match( s.substring, "[аеөуүэюя]" ) then vh[i].violation = true else vh[i].violation = false end
			elseif vowel == "э" then
				vh[i].position = "front"
				if location and sub( s.substring, location-1, location-1 ) == "й" and match( s.substring, "[аеёоөуүэюя]", location+1 ) == "ө" then
						vh[i].Cyrl.a = "ө"
						vh[i].quality = "rounded"
						pattern = "[еө]"
						if match( s.substring, "[аёоуүэюя]" ) then vh[i].violation = true else vh[i].violation = false end
				else
					vh[i].Cyrl.a = "э"
					vh[i].quality = "unrounded"
					pattern = "[еүэю]"
					if match( s.substring, "[аёоөуя]" ) then vh[i].violation = true else vh[i].violation = false end
				end
			elseif vowel == "ү" then
				vh[i].Cyrl.a = "э"
				vh[i].position = "front"
				vh[i].quality = "unrounded"
				pattern = "[еүэю]"
				if match( s.substring, "[аёоөуя]" ) then vh[i].violation = true else vh[i].violation = false end
			elseif vowel == "ө" then
				vh[i].Cyrl.a = "ө"
				vh[i].position = "front"
				vh[i].quality = "rounded"
				pattern = "[еө]"
				if match( s.substring, "[аёоуүэюя]" ) then vh[i].violation = true else vh[i].violation = false end
			elseif vowel == "е" then
				vh[i].position = "front"
				if match( s.substring, "ө", location+1 ) then
					vh[i].Cyrl.a = "ө"
					vh[i].quality = "rounded"
					pattern = "[еө]"
					if match( s.substring, "[аёоуүэюя]" ) then vh[i].violation = true else vh[i].violation = false end
				else
					vh[i].Cyrl.a = "э"
					vh[i].quality = "unrounded"
					pattern = "[еүэю]"
					if match( s.substring, "[аёоөуя]" ) then vh[i].violation = true else vh[i].violation = false end
				end
			elseif vowel == "ю" then
				vh[i].quality = "unrounded"
				if match( s.substring, "[ауя]", location+1 ) then
					vh[i].Cyrl.a = "а"
					vh[i].position = "back"
					pattern = "[ауюя]"
					if match( s.substring, "[еёоөүэ]" ) then vh[i].violation = true else vh[i].violation = false end
				else
					vh[i].Cyrl.a = "э"
					vh[i].position = "front"
					pattern = "[еүэю]"
					if match( s.substring, "[аёоөуя]" ) then vh[i].violation = true else vh[i].violation = false end
				end
			end
			location = 0
			local function prev_vowel( n ) return match( s.substring, "[аеёиоөуүэюя]", n+1 ) end
			local function prev_hvowel( n ) return match( s.substring, pattern, n+1 ) end
			while prev_vowel( location ) and ( prev_vowel( location ) == prev_hvowel( location ) or prev_vowel( location ) == "и" ) do
				if prev_vowel( location ) == prev_hvowel( location ) then
					location = find( s.substring, pattern, location+1 )
				else -- if и
					local icheck = location+1
					while prev_vowel( icheck ) == "и" do
						icheck = find( s.substring, "и", icheck+1 )
					end
					if prev_vowel( icheck ) and prev_vowel( icheck ) == prev_hvowel( icheck ) then
						location = icheck
					elseif not prev_vowel( icheck ) and vowel == "э" then
						location = icheck
					elseif vowel == "э" and sub( s.substring, icheck-1, icheck-1 ) == "й" then
						location = icheck
					else
						break
					end
				end
			end
			if match( vh[i].Cyrl.a, "[ао]" ) then
				vh[i].Cyrl.ii = "ы"
				vh[i].Cyrl.u = "у"
				vh[i].Mong.a = "ᠠ"
				vh[i].Mong.u = "ᠤ"
			else
				vh[i].Cyrl.ii = "ий"
				vh[i].Cyrl.u = "ү"
				vh[i].Mong.a = "ᠡ"
				vh[i].Mong.u = "ᠦ"
			end
			if match( vh[i].Cyrl.a, "ө" ) then -- ө takes the diphthong эй not өй
				vh[i].Cyrl.ai = "эй"
			else
				vh[i].Cyrl.ai = vh[i].Cyrl.a .. "й"
			end
			vh[i].Cyrl.aa = vh[i].Cyrl.a .. vh[i].Cyrl.a
			vh[i].Cyrl.uu = vh[i].Cyrl.u .. vh[i].Cyrl.u
			s.substring = orig
			vh[i].location = len( s.substring ) + breaks[i] - location
		end
	end
	
	return vh
end

--Breaks down a string into syllables and returns a table
function export.syllables( text, params )
	
	local consonant = "[БВГДЖЗКЛМНПРСТФХЦЧШЩбвгджзклмнпрстфхцчшщ]"
	local vowel = "[АОУЭӨҮИЙЫЯЕЁЮаоуэөүийыяеёю]"
	local sign = "[ЪЬъь]"
	local iotated = "[ЯЕЁЮяеёю]"
	local punctuation = "[%s%p]"
	local final_clusters = require( "Module:mn/data" ).syll_final_cons
	local stress = u( 0x301 ) .. u( 0x300 )
	
	-- Strip diacritics.
	local chars = {}
	for v in gmatch( text, "[%w%s%p" .. stress .. export.stem_barrier .. "]" ) do
		insert( chars, v )
	end
	
	local breaks = {}
	for i, v in pairs( chars ) do
		-- First letter.
		if i == 1 or match( chars[i-1], punctuation ) then
			insert( breaks, i )
		-- Stem barrier is used by the inflection templates.
		elseif match( chars[i-1], export.stem_barrier ) then
			insert( breaks, i )
		-- If a vowel preceded by a hard sign or the temporary break character, then must be the break.
		elseif match( v, vowel ) and match( chars[i-1], "[Ъъ]" ) then
			insert( breaks, i )
		-- If Е/е preceded by a soft sign, count backwards until vowel, punctuation/space or start of string is found; if a vowel is found first, then preceding sign must be medial, so is the break; if punctuation/start of string found first, letter is part of word-initial cluster, so is not the break (occurs in loanwords, e.g. Вьет|нам ("Vietnam")).
		elseif match( v, "[Ее]" ) and match( chars[i-1], "[Ьь]" ) then
			local j = i - 1
			while j > 1 and ( match( chars[j], consonant ) or match( chars[j], sign ) or match( chars[j], "[" .. stress .. "]" ) ) do
				j = j - 1
				if match( chars[j], vowel ) then
					-- If break, replaces the consonant preceding the soft sign as the break.
					if breaks[#breaks] == i - 2 then breaks[#breaks] = nil end
					insert( breaks, i )
				end
			end
		-- If Ю/ю preceded by a soft sign, calculate vowel harmony and iterate through vowel harmonic segments until reaching the one the letter is in; once found, if front harmonic then letter is the break.
		elseif match( v, "[Юю]" ) and match( chars[i-1], "[Ьь]" ) then
			vh = export.vowelharmony( text, params )
			local k = 0
			for j, substring in ipairs(vh) do
				local k_increase = mw.ustring.len(gsub(vh[j].substring, "[^%w%s%p" .. stress .. export.stem_barrier .. "]", ""))
				if k + k_increase > i then
					if vh[j].position == "front" then
						-- If break, replaces the consonant preceding the soft sign as the break.
						if breaks[#breaks] == i - 2 then breaks[#breaks] = nil end
						insert( breaks, i )
					end
					break
				end
				k = k + k_increase
			end
		-- If a consonant followed by vowel (i.e. if lone/cluster-final), count backwards until vowel, punctuation/space or start of string is found; if a vowel is found first, then letter must be medial and lone/cluster-final, so is the break; if punctuation/start of string found first, letter is part of word-initial cluster, so is not the break (occurs in loanwords, e.g. трол|лей|бус ("trolleybus")).
		elseif match( v, consonant ) and chars[i+1] and ( match( chars[i+1], vowel ) or ( match( chars[i+1], "[Ьь]" ) and chars[i+2] and match( chars[i+2], vowel ) ) ) then
			local j = i
			while j > 1 and ( match( chars[j], consonant ) or match( chars[j], sign ) or match( chars[j], "[" .. stress .. "]" ) ) do
				j = j - 1
				if match( chars[j], vowel ) then
					insert( breaks, i )
				end
			end
		-- If word-final consonant, count backwards until vowel, checking if each cluster is allowed as a word-final cluster; if it is, increase "stable" (the number of stable consonants (and signs) at the end) by one; if a vowel is found before an unstable cluster, the loop ends with no change; if an unstable cluster is found, "stable" will not iterate which will trigger an additional unvoweled syllable break at that consonant (occurs in loanwords, e.g. буд|ди|зм ("Buddhism"), ал|ге|бр ("algebra")).
		elseif ( match( v, consonant ) or match( v, sign ) ) and ( i == #chars or match( chars[i+1], punctuation ) ) then
			local j = i
			local check = { chars[j] }
			local stable = 1
			while j > 1 and j > i - #final_clusters and stable > i - j and ( match( chars[j-1], consonant ) or match( chars[j-1], sign ) ) do
				j = j - 1
				insert( check, chars[j] )
				for k,cluster in ipairs( final_clusters[#check] ) do
					if match( concat( reverse_array( check ) ), cluster ) then
						stable = stable + 1
						break
					end
				end
				if stable == i - j then
					insert( breaks, j )
				end
			end
		-- Iotated ("ya"-type) vowel after a vowel.
		elseif match( v, iotated ) and ( match( chars[i-1], vowel ) or ( match( chars[i-1], "[".. stress .. "]" ) and match( chars[i-2], vowel ) ) ) then
			insert( breaks, i )
		end
	end
	
	-- Reform text without diacritics.
	text = concat( chars )
	
	breaks = remove_duplicates( breaks )		
		
	local syll = {}
	for i,v in ipairs( breaks ) do
		if i == #breaks then
			insert( syll, sub( text, v ) )
		else
			insert( syll, sub( text, v, breaks[i+1] - 1 ) )
		end
	end
	
	return syll
end

function export.remove_final_short_vowel( text, params )
	
	if not params then params = {} end
	local vh = export.vowelharmony( text, params )[#export.vowelharmony( text, params )]
	local syllables = #export.syllables( text )
	local reduced = text
	local no_fv = false
	if ( syllables > 1 and match( text, "[бвгджзклмнпрстфхцчшщ][аоөэиь]$" ) ) or match( text, "[ьъ]$" ) then
		
		local matches = {
			not params.bor,
			match( text, "[ьъ]$" ),
			match( text, "[ауя][влмн]ба$" ),
			match( text, "[оё][влмн]бо$" ),
			match( text, "ө[влмн]бө$" ),
			match( text, "[эүе][влмн]бэ$" ),
			match( text, "[бвглмнр]" .. vh.Cyrl.a .. u( 0x301 ) .. "?" .. u( 0x300 ) .. "?н" .. vh.Cyrl.a .. "$" ),
			match( text, "[ауя]нга$" ),
			match( text, "[оё]нго$" ),
			match( text, "өнгө$" ),
			match( text, "[эүе]нгэ$" )
		}
		
		for _,v in pairs( matches ) do
			if v then
				reduced = sub( text, 1, len( text ) - 1 )
				no_fv = true
			end
		end
	end
	return reduced, no_fv
end

function export.remove_penultimate_short_vowel( text, params )
	
	if not params then params = {} end
	local vh = export.vowelharmony( text, params )[#export.vowelharmony( text, params )]
	local syllables = reverse_array( export.syllables( text ) )
	
	if not params.proper and ( vh.location ~= len( text ) - 1 or vh.violation == false ) and not params.bor then -- exclude proper nouns, loanwords and terms where the deleted vowel determines the vowel harmony
	
		local check
		local syllable_orig
		for i,syllable in ipairs( syllables ) do
			if i ~=1 and i ~= #syllables then
				syllable_orig = syllable
				if match( syllable, export.stem_barrier ) then
					break
				end
				local prev_syllable = gsub( syllables[i+1], export.stem_barrier, "" )
				check = ( match( prev_syllable, "[бвгджзклмнпрстфхцчшщь]*$" ) or "" ) .. syllable .. ( match( syllables[i-1], "^[бвгджзклмнпрстфхцчшщь]*" ) or "" )
				local matches = {
					-- CVC
					match( check, "^[влмр]ь?[аиоөэ][вгклмнпр]$" ), -- not [бн]VC
					match( check, "^гь?[аиоөэ][влмнр]$" ) and vh.position == "front", -- гV[влмнр] if front vowel only
					-- CCVC
					match( check, "^[вглмнр]?ь?[джзкпстхцчшщ]ь?[аиоөэ][вгджзклмнпрстцчшщ]$" ),
					match( check, "^[вглмнр]?ь?[сх]ь?[тч]ь?[аиоөэ][вглмнр]$" ),
				}
				local exclusions = {
					match( check, "[лм]ь?[аиоөэ]в$" ), -- not [лм]Vв
					match( check, "[вдзклмпрстхц]ь?и[вгджзлмнпрстцчшщ]$" ), -- only [кжчш]иC
				}
				for _,v in pairs( matches ) do
					if v then
						syllables[i] = sub( syllable, 1, len( syllable ) - 1 )
						for _,e in pairs( exclusions ) do
							if e then syllables[i] = syllable_orig end
						end
					end
				end
				if syllables[i] ~= syllable_orig then
					break
				end
			end
		end
	end
	return concat( reverse_array( syllables ) )
end

function export.concat_forms_in_slot( forms )
	if forms then
		local new_vals = {}
		for _, v in ipairs( forms ) do
			local val = gsub( v.form, "|", "<!>" )
			insert( new_vals, val )
		end
		return concat( new_vals, "," )
	else
		return nil
	end
end


function export.combine_stem_ending( stem, ending )
	if stem == "?" then
		return "?"
	else
		return stem .. ending
	end
end


function export.generate_form( form, footnotes )
	if type( footnotes ) == "string" then
		footnotes = { footnotes }
	end
	if footnotes then
		return { form = form, footnotes = footnotes }
	else
		return form
	end
end
	
return export