Difference between revisions of "Module:Ancient Greek"

From blackwiki
Jump to navigation Jump to search
blackwiki>Erutuon
m (unnecessary now)
m (31 revisions imported)
 
(10 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
local p = {}
 
local p = {}
  
local m_table = require "Module:TableTools"
+
local macron = mw.ustring.char(0x304)
local checkType = require "libraryUtil".checkType
+
local breve = mw.ustring.char(0x306)
 +
local rough = mw.ustring.char(0x314)
 +
local smooth = mw.ustring.char(0x313)
 +
local diaeresis = mw.ustring.char(0x308)
 +
local acute = mw.ustring.char(0x301)
 +
local grave = mw.ustring.char(0x300)
 +
local circumflex = mw.ustring.char(0x342)
 +
local Latin_circumflex = mw.ustring.char(0x302)
 +
local subscript = mw.ustring.char(0x345)
 +
local macron_circumflex = macron .. diaeresis .. '?' .. Latin_circumflex
 +
 
 +
local is_velar = { ['κ'] = true, ['γ'] = true, ['χ'] = true, ['ξ'] = true, }
 +
 
 +
local UTF8_char = "[%z\1-\127\194-\244][\128-\191]*"
 +
local basic_Greek = "[\206-\207][\128-\191]" -- excluding first line of Greek and Coptic block: ͰͱͲͳʹ͵Ͷͷͺͻͼͽ;Ϳ
  
local ustring = mw.ustring
+
local info = {}
local U = ustring.char
 
local len = ustring.len
 
local sub = ustring.sub
 
local find = ustring.find
 
local gsub = ustring.gsub
 
local decompose = ustring.toNFD
 
local lower = ustring.lower
 
local upper = ustring.upper
 
  
local macron = U(0x304)
+
-- The tables are shared among different characters so that they can be checked
local breve = U(0x306)
+
-- for equality if needed, and to use less space.
local rough = U(0x314)
+
local vowel = { vowel = true, diacritic_seat = true }
local smooth = U(0x313)
+
local iota = { vowel = true, diacritic_seat = true, offglide = true }
local diaeresis = U(0x308)
+
local upsilon = { vowel = true, diacritic_seat = true, offglide = true }
local acute = U(0x301)
+
-- Technically rho is only a seat for rough or smooth breathing.
local grave = U(0x300)
+
local rho = { consonant = true, diacritic_seat = true }
local circumflex = U(0x342)
+
local consonant = { consonant = true }
local Latin_circumflex = U(0x302)
+
local diacritic = { diacritic = true }
local subscript = U(0x345)
+
-- Needed for equality comparisons.
local i_diphthong = "[ΑΕΗΟΥΩαεηουω][Ιι]"
+
local breathing = { diacritic = true }
local u_diphthong = "[ΑΕΗΟΩαεηοω][Υυ]"
+
 
local macron_circumflex = macron .. diaeresis .. '?' .. Latin_circumflex
+
local function add_info(characters, t)
 +
if type(characters) == "string" then
 +
for character in string.gmatch(characters, UTF8_char) do
 +
info[character] = t
 +
end
 +
else
 +
for _, character in ipairs(characters) do
 +
info[character] = t
 +
end
 +
end
 +
end
  
local is_velar = { ['κ'] = true, ['γ'] = true, ['χ'] = true, ['ξ'] = true, }
+
add_info({ macron, breve,
 +
diaeresis,
 +
acute, grave, circumflex,
 +
subscript,
 +
}, diacritic)
  
local is_diacritic = {
+
add_info({rough, smooth}, breathing)
[macron] = true, [breve] = true,
+
add_info("ΑΕΗΟΩαεηοω", vowel)
[rough] = true, [smooth] = true, [diaeresis] = true,
+
add_info("Ιι", iota)
[acute] = true, [grave] = true, [circumflex] = true,
+
add_info("Υυ", upsilon)
[subscript] = true,
+
add_info("ΒΓΔΖΘΚΛΜΝΞΠΡΣΤΦΧΨϜϘϺϷͶϠβγδζθκλμνξπρσςτφχψϝϙϻϸͷϡ", consonant)
}
+
add_info("Ρρ", rho)
  
local function _check(funcName)
+
local not_recognized = {}
return function(argIndex, arg, expectType, nilOk)
+
setmetatable(info, { __index =
return checkType(funcName, argIndex, arg, expectType, nilOk)
+
function()
 +
return not_recognized
 
end
 
end
 +
})
 +
 +
local function quote(str)
 +
return "“" ..  str .. "”"
 
end
 
end
  
local tt = {
+
local correspondences = {
 
-- Vowels
 
-- Vowels
 
["α"] = "a",
 
["α"] = "a",
Line 70: Line 95:
 
["τ"] = "t",
 
["τ"] = "t",
 
["φ"] = "ph",
 
["φ"] = "ph",
["χ"] = "kh",
 
 
["ψ"] = "ps",
 
["ψ"] = "ps",
 
 
Line 81: Line 105:
 
 
 
-- Diacritics
 
-- Diacritics
-- unchanged: macron, diaeresis, grave, acute
+
[smooth] = '',
 +
[rough] = '', -- h is added below in the `transliterate` function.
 
[breve] = '',
 
[breve] = '',
[smooth] = '',
+
}
[rough] = '',
+
 
 +
local ALA_LC = {
 +
["χ"] = "ch",
 +
[acute] = '',
 +
[grave] = '',
 +
[circumflex] = '',
 +
[subscript] = '',
 +
[diaeresis] = '',
 +
[macron] = '',
 +
}
 +
 
 +
local Wiktionary_transliteration = {
 +
["χ"] = "kh",
 
[circumflex] = Latin_circumflex,
 
[circumflex] = Latin_circumflex,
 
[subscript] = 'i',
 
[subscript] = 'i',
 
}
 
}
  
local function is_diphthong(chars, next_chars)
+
local function add_index_metamethod(t, index_metamethod)
return (find(chars, '^' .. i_diphthong .. '$')
+
local mt = getmetatable(t)
or find(chars, '^' .. u_diphthong .. '$') )
+
if not mt then
and not find(next_chars, "^[" .. macron .. breve .. "]?" .. diaeresis)
+
mt = {}
end
+
setmetatable(t, mt)
 
 
-- Concatenates a character onto an existing table value, or inserts it,
 
-- then removes it from the text.
 
local function add(list, index, chars, text)
 
if not chars then
 
error("The function add cannot act on a nil character.")
 
end
 
if list[index] then
 
list[index] = list[index] .. chars
 
else
 
list[index] = chars
 
 
end
 
end
return text:sub(#chars + 1)
+
mt.__index = index_metamethod
 
end
 
end
  
Line 115: Line 141:
 
--]=]
 
--]=]
 
local function tokenize(text)
 
local function tokenize(text)
local tokens = {}
+
local tokens, vowel_info, prev_info = {}, {}, {}
local i = 0
+
local token_i = 1
while #text > 0 do
+
local prev
local char = sub(text, 1, 1) or ""
+
for character in string.gmatch(mw.ustring.toNFD(text), UTF8_char) do
local chars = sub(text, 1, 2) or ""
+
local curr_info = info[character]
local next_chars = sub(text, 3, 4) or ""
+
-- Split vowels between tokens if not a diphthong.
-- Look for a diacritic and add it to the current token. Remove it from the text.
+
if curr_info.vowel then
if is_diacritic[char] then
+
if prev and (not (curr_info.offglide and prev_info.vowel)
text = add(tokens, i, char, text)
+
-- υυ → υ, υ
--[[ See if the next two characters form a diphthong and if so,
+
-- ιυ → ι, υ
add them to the current token. Remove them from the text.
+
or prev_info.offglide and curr_info == upsilon) then
If there's a diaeresis, it will be immediately after
+
token_i = token_i + 1
the second of the two characters, or after a macron or breve. ]]
+
end
elseif is_diphthong(chars, next_chars) then
+
tokens[token_i] = (tokens[token_i] or "") .. character
i = i + 1
+
table.insert(vowel_info, { index = token_i })
text = add(tokens, i, chars, text)
+
elseif curr_info.diacritic then
 +
tokens[token_i] = (tokens[token_i] or "") .. character
 +
if prev_info.vowel or prev_info.diacritic then
 +
if character == diaeresis then
 +
-- Current token is vowel, vowel, possibly other diacritics,
 +
-- and a diaeresis.
 +
-- Split the current token into two:
 +
-- the first letter, then the second letter plus any diacritics.
 +
local previous_vowel, vowel_with_diaeresis = string.match(tokens[token_i], "^(" .. basic_Greek .. ")(" .. basic_Greek .. ".+)")
 +
if previous_vowel then
 +
tokens[token_i], tokens[token_i + 1] = previous_vowel, vowel_with_diaeresis
 +
token_i = token_i + 1
 +
end
 +
end
 +
elseif prev_info == rho then
 +
if curr_info ~= breathing then
 +
return string.format("The character %s cannot have the accent %s on it.", prev, "◌" .. character)
 +
end
 +
else
 +
error("The character " .. quote(prev) .. " cannot have a diacritic on it.")
 +
end
 +
elseif curr_info == rho then
 +
if prev and not (prev_info == breathing and info[string.match(tokens[token_i], "^" .. basic_Greek)] == rho) then
 +
token_i = token_i + 1
 +
end
 +
tokens[token_i] = (tokens[token_i] or "") .. character
 
else
 
else
-- Add the current character to the next token. Remove it from the text.
+
if prev then
i = i + 1
+
token_i = token_i + 1
text = add(tokens, i, char, text)
+
end
 +
tokens[token_i] = (tokens[token_i] or "") .. character
 
end
 
end
 +
prev = character
 +
prev_info = curr_info
 
end
 
end
+
return tokens
tokens.maxindex = i
 
 
return m_table.compressSparseArray(tokens)
 
 
end
 
end
  
function p.transliterate(text, lang, sc)
+
function p.transliterate(text, system)
text = decompose(text)
+
add_index_metamethod(correspondences, system == "ALA-LC" and ALA_LC or Wiktionary_transliteration)
--[[
+
 
if text == '῾' then
 
if text == '῾' then
 
return 'h'
 
return 'h'
 
end
 
end
--]]
+
 +
text = mw.ustring.toNFD(text)
 
 
 
--[[
 
--[[
Line 156: Line 208:
 
semicolons in HTML entities).
 
semicolons in HTML entities).
 
--]]
 
--]]
text = gsub(text, "([^A-Za-z0-9])[;" .. U(0x37E) .. "]", "%1?")
+
text = mw.ustring.gsub(text, "([^A-Za-z0-9])[;" .. mw.ustring.char(0x37E) .. "]", "%1?")
 
 
 
-- Handle the middle dot. It is equivalent to semicolon or colon, but semicolon is probably more common.
 
-- Handle the middle dot. It is equivalent to semicolon or colon, but semicolon is probably more common.
Line 167: Line 219:
 
for i, token in pairs(tokens) do
 
for i, token in pairs(tokens) do
 
-- substitute each character in the token for its transliteration
 
-- substitute each character in the token for its transliteration
local translit = gsub(mw.ustring.lower(token), '.', tt)
+
local translit = string.gsub(mw.ustring.lower(token), UTF8_char, correspondences)
 
 
 
if token == 'γ' and is_velar[tokens[i + 1]] then
 
if token == 'γ' and is_velar[tokens[i + 1]] then
Line 175: Line 227:
 
-- ρ after ρ should be <rh>
 
-- ρ after ρ should be <rh>
 
translit = 'rh'
 
translit = 'rh'
elseif find(token, '^[αΑ].*' .. subscript .. '$') then
+
elseif system == "Wiktionary" and mw.ustring.find(token, '^[αΑ].*' .. subscript .. '$') then
 
-- add macron to ᾳ
 
-- add macron to ᾳ
translit = gsub(translit, '([aA])', '%1' .. macron)
+
translit = mw.ustring.gsub(translit, '([aA])', '%1' .. macron)
 
end
 
end
 
 
 
if token:find(rough) then
 
if token:find(rough) then
if find(token, '[Ρρ]') then
+
if mw.ustring.find(token, '[Ρρ]') then
 
translit = translit .. 'h'
 
translit = translit .. 'h'
 
else -- vowel
 
else -- vowel
 
translit = 'h' .. translit
 
translit = 'h' .. translit
 
end
 
end
 +
end
 +
 +
if system == "ALA-LC" and mw.ustring.find(token, '^[υΥ][^ιΙ]*$') then
 +
translit = translit:gsub('u', 'y'):gsub('U', 'Y')
 
end
 
end
 
 
 
-- Remove macron from a vowel that has a circumflex.
 
-- Remove macron from a vowel that has a circumflex.
if find(translit, macron_circumflex) then
+
if mw.ustring.find(translit, macron_circumflex) then
 
translit = translit:gsub(macron, '')
 
translit = translit:gsub(macron, '')
 
end
 
end
 
 
 
-- Capitalize first character of transliteration.
 
-- Capitalize first character of transliteration.
if token ~= lower(token) then
+
if token ~= mw.ustring.lower(token) then
translit = gsub(translit, "^.", upper)
+
translit = mw.ustring.gsub(translit, "^.", mw.ustring.upper)
 
end
 
end
 
 
Line 205: Line 261:
  
 
function p.translit(frame)
 
function p.translit(frame)
local args = mw.getCurrentFrame():getParent().args
+
local text = frame.args[1] or frame:getParent().args[1]
local text = frame.args[1] or args[1]
+
local transliteration = p.transliterate(text)
+
local system = frame.args.system
return '<span title="Ancient Greek transliteration" class="Unicode" style="white-space:normal; text-decoration: none"><i>' .. transliteration .. '</i></span>'
+
if system == nil or system == "" then
 +
system = "Wiktionary"
 +
elseif not (system == "ALA-LC" or system == "Wiktionary") then
 +
error('Transliteration system in |system= not recognized; choose between "ALA-LC" and "Wiktionary"')
 +
end
 +
 +
local transliteration = p.transliterate(text, system)
 +
return '<span title="Ancient Greek transliteration" lang="grc-Latn"><i>' .. transliteration .. '</i></span>'
 
end
 
end
  
 +
function p.bare_translit(frame)
 +
return p.transliterate(frame.args[1] or frame:getParent().args[1])
 +
end
  
 
return p
 
return p

Latest revision as of 13:09, 26 September 2020

Usage

This module transliterates Ancient Greek text. It is based on an old version of the Ancient Greek transliteration module on Wiktionary, with minor modifications to make it callable through a template.

{{#invoke:Ancient Greek|translit|οἷος}}
  • hoîos

The code below uses the basic string functions (for instance, str:gsub(...)) when possible. Ustring functions have to be used when patterns contain sets with multiple-byte characters (for instance, "[αΑ]"), or quantifiers that act on multiple-byte characters ("α+"). And they must be used to correctly get a substring of the ith to the jth Unicode character. In other situations, basic string functions can be used, and are preferred for efficiency's sake, as they don't have to parse the string into codepoints before operating on it.


local p = {}

local macron = mw.ustring.char(0x304)
local breve = mw.ustring.char(0x306)
local rough = mw.ustring.char(0x314)
local smooth = mw.ustring.char(0x313)
local diaeresis = mw.ustring.char(0x308)
local acute = mw.ustring.char(0x301)
local grave = mw.ustring.char(0x300)
local circumflex = mw.ustring.char(0x342)
local Latin_circumflex = mw.ustring.char(0x302)
local subscript = mw.ustring.char(0x345)
local macron_circumflex = macron .. diaeresis .. '?' .. Latin_circumflex

local is_velar = { ['κ'] = true, ['γ'] = true, ['χ'] = true, ['ξ'] = true, }

local UTF8_char = "[%z\1-\127\194-\244][\128-\191]*"
local basic_Greek = "[\206-\207][\128-\191]" -- excluding first line of Greek and Coptic block: ͰͱͲͳʹ͵Ͷͷͺͻͼͽ;Ϳ

local info = {}

-- The tables are shared among different characters so that they can be checked
-- for equality if needed, and to use less space.
local vowel = { vowel = true, diacritic_seat = true }
local iota = { vowel = true, diacritic_seat = true, offglide = true }
local upsilon = { vowel = true, diacritic_seat = true, offglide = true }
-- Technically rho is only a seat for rough or smooth breathing.
local rho = { consonant = true, diacritic_seat = true }
local consonant = { consonant = true }
local diacritic = { diacritic = true }
-- Needed for equality comparisons.
local breathing = { diacritic = true }

local function add_info(characters, t)
	if type(characters) == "string" then
		for character in string.gmatch(characters, UTF8_char) do
			info[character] = t
		end
	else
		for _, character in ipairs(characters) do
			info[character] = t
		end
	end
end

add_info({ macron, breve,
		diaeresis,
		acute, grave, circumflex,
		subscript,
	}, diacritic)

add_info({rough, smooth}, breathing)
add_info("ΑΕΗΟΩαεηοω", vowel)
add_info("Ιι", iota)
add_info("Υυ", upsilon)
add_info("ΒΓΔΖΘΚΛΜΝΞΠΡΣΤΦΧΨϜϘϺϷͶϠβγδζθκλμνξπρσςτφχψϝϙϻϸͷϡ", consonant)
add_info("Ρρ", rho)

local not_recognized = {}
setmetatable(info, { __index =
	function()
		return not_recognized
	end
})

local function quote(str)
	return "“" ..  str .. "”"
end

local correspondences = {
	-- Vowels
	["α"] = "a",
	["ε"] = "e",
	["η"] = "e" .. macron,
	["ι"] = "i",
	["ο"] = "o",
	["υ"] = "u",
	["ω"] = "o" .. macron,

	-- Consonants
	["β"] = "b",
	["γ"] = "g",
	["δ"] = "d",
	["ζ"] = "z",
	["θ"] = "th",
	["κ"] = "k",
	["λ"] = "l",
	["μ"] = "m",
	["ν"] = "n",
	["ξ"] = "x",
	["π"] = "p",
	["ρ"] = "r",
	["σ"] = "s",
	["ς"] = "s",
	["τ"] = "t",
	["φ"] = "ph",
	["ψ"] = "ps",
	
	-- Archaic letters
	["ϝ"] = "w",
	["ϻ"] = "ś",
	["ϙ"] = "q",
	["ϡ"] = "š",
	["ͷ"] = "v",
	
	-- Diacritics
	[smooth] = '',
	[rough] = '', -- h is added below in the `transliterate` function.
	[breve] = '',
}

local ALA_LC = {
	["χ"] = "ch",
	[acute] = '',
	[grave] = '',
	[circumflex] = '',
	[subscript] = '',
	[diaeresis] = '',
	[macron] = '',
}

local Wiktionary_transliteration = {
	["χ"] = "kh",
	[circumflex] = Latin_circumflex,
	[subscript] = 'i',
}

local function add_index_metamethod(t, index_metamethod)
	local mt = getmetatable(t)
	if not mt then
		mt = {}
		setmetatable(t, mt)
	end
	mt.__index = index_metamethod
end

--[=[
		This breaks a word into meaningful "tokens", which are
		individual letters or diphthongs with their diacritics.
		Used by [[Module:grc-accent]] and [[Module:grc-pronunciation]].
--]=]
local function tokenize(text)
	local tokens, vowel_info, prev_info = {}, {}, {}
	local token_i = 1
	local prev
	for character in string.gmatch(mw.ustring.toNFD(text), UTF8_char) do
		local curr_info = info[character]
		-- Split vowels between tokens if not a diphthong.
		if curr_info.vowel then
			if prev and (not (curr_info.offglide and prev_info.vowel)
					-- υυ → υ, υ
					-- ιυ → ι, υ
					or prev_info.offglide and curr_info == upsilon) then
				token_i = token_i + 1
			end
			tokens[token_i] = (tokens[token_i] or "") .. character
			table.insert(vowel_info, { index = token_i })
		elseif curr_info.diacritic then
			tokens[token_i] = (tokens[token_i] or "") .. character
			if prev_info.vowel or prev_info.diacritic then
				if character == diaeresis then
					-- Current token is vowel, vowel, possibly other diacritics,
					-- and a diaeresis.
					-- Split the current token into two:
					-- the first letter, then the second letter plus any diacritics.
					local previous_vowel, vowel_with_diaeresis = string.match(tokens[token_i], "^(" .. basic_Greek .. ")(" .. basic_Greek .. ".+)")
					if previous_vowel then
						tokens[token_i], tokens[token_i + 1] = previous_vowel, vowel_with_diaeresis
						token_i = token_i + 1
					end
				end
			elseif prev_info == rho then
				if curr_info ~= breathing then
					return string.format("The character %s cannot have the accent %s on it.", prev, "◌" .. character)
				end
			else
				error("The character " .. quote(prev) .. " cannot have a diacritic on it.")
			end
		elseif curr_info == rho then
			if prev and not (prev_info == breathing and info[string.match(tokens[token_i], "^" .. basic_Greek)] == rho) then
				token_i = token_i + 1
			end
			tokens[token_i] = (tokens[token_i] or "") .. character
		else
			if prev then
				token_i = token_i + 1
			end
			tokens[token_i] = (tokens[token_i] or "") .. character
		end
		prev = character
		prev_info = curr_info
	end
	return tokens
end

function p.transliterate(text, system)
	add_index_metamethod(correspondences, system == "ALA-LC" and ALA_LC or Wiktionary_transliteration)
	
	if text == '῾' then
		return 'h'
	end
	
	text = mw.ustring.toNFD(text)
	
	--[[
		Replace semicolon or Greek question mark with regular question mark,
		except after an ASCII alphanumeric character (to avoid converting
		semicolons in HTML entities).
	--]]
	text = mw.ustring.gsub(text, "([^A-Za-z0-9])[;" .. mw.ustring.char(0x37E) .. "]", "%1?")
	
	-- Handle the middle dot. It is equivalent to semicolon or colon, but semicolon is probably more common.
	text = text:gsub("·", ";")
	
	local tokens = tokenize(text)

	--now read the tokens
	local output = {}
	for i, token in pairs(tokens) do
		-- substitute each character in the token for its transliteration
		local translit = string.gsub(mw.ustring.lower(token), UTF8_char, correspondences)
		
		if token == 'γ' and is_velar[tokens[i + 1]] then
			-- γ before a velar should be <n>
			translit = 'n'
		elseif token == 'ρ' and tokens[i - 1] == 'ρ' then
			-- ρ after ρ should be <rh>
			translit = 'rh'
		elseif system == "Wiktionary" and mw.ustring.find(token, '^[αΑ].*' .. subscript .. '$') then
			-- add macron to ᾳ
			translit = mw.ustring.gsub(translit, '([aA])', '%1' .. macron)
		end
		
		if token:find(rough) then
			if mw.ustring.find(token, '[Ρρ]') then
				translit = translit .. 'h'
			else -- vowel
				translit = 'h' .. translit
			end
		end
		
		if system == "ALA-LC" and mw.ustring.find(token, '^[υΥ][^ιΙ]*$') then
			translit = translit:gsub('u', 'y'):gsub('U', 'Y')
		end
		
		-- Remove macron from a vowel that has a circumflex.
		if mw.ustring.find(translit, macron_circumflex) then
			translit = translit:gsub(macron, '')
		end
		
		-- Capitalize first character of transliteration.
		if token ~= mw.ustring.lower(token) then
			translit = mw.ustring.gsub(translit, "^.", mw.ustring.upper)
		end
		
		table.insert(output, translit)
	end
	
	return table.concat(output)
end

function p.translit(frame)
	local text = frame.args[1] or frame:getParent().args[1]
	
	local system = frame.args.system
	if system == nil or system == "" then
		system = "Wiktionary"
	elseif not (system == "ALA-LC" or system == "Wiktionary") then
		error('Transliteration system in |system= not recognized; choose between "ALA-LC" and "Wiktionary"')
	end
	
	local transliteration = p.transliterate(text, system)
	return '<span title="Ancient Greek transliteration" lang="grc-Latn"><i>' .. transliteration .. '</i></span>'
end

function p.bare_translit(frame)
	return p.transliterate(frame.args[1] or frame:getParent().args[1])
end

return p