# Module:IPA

require('strict')  
local p = {}  

local function multiFind(s, patterns, init)  
local i, j = mw.ustring.find(s, patterns[1], init)  
for n = 2, #patterns do  
local i2, j2 = mw.ustring.find(s, patterns[n], init)  
if i2 and (not i or i2 < i) then  
i, j = i2, j2  
end  
end  
return i, j  
end  

local function wrapAtSpaces(s)  
return mw.ustring.gsub(s, '(%s+)', '<span>%1</span>')  
end  

local function wrapAtSpacesSafely(s)  
local patterns = {  
'%[%[[^%]|]-%s[^%]|]-|', -- Piped links  
'</?[A-Za-z][^>]-%s[^>]->' -- HTML tags  
}  
s = mw.ustring.gsub(s, '%[%[([^%]|]-%s[^%]|]-)%]%]', '%1') -- Pipe all links  
local t = {}  
local init  
while true do  
local i, j = multiFind(s, patterns, init)  
if not i then  
break  
end  
local pre = wrapAtSpaces(mw.ustring.sub(s, init, i - 1)) -- What precedes the match  
table.insert(t, pre)  
table.insert(t, mw.ustring.sub(s, i, j)) -- The match  
init = j + 1  
end  
local post = wrapAtSpaces(mw.ustring.sub(s, init)) -- What follows the last match  
table.insert(t, post)  
return table.concat(t)  
end  

local function checkNamespace(isDebug)  
return isDebug or require('Module:Category handler').main({ true })  
end  

local function renderCats(cats, isDebug)  
if not cats[1] or not checkNamespace(isDebug) then  
return ''  
end  
local t = {}  
for _, v in ipairs(cats) do  
table.insert(t, string.format(  
'%sCategory:%s',  
isDebug and ':' or '',  
v  
))  
end  
return table.concat(t)  
end  

local function resolveSynonym(s)  
return mw.loadData('Module:Lang/ISO 639 synonyms')[s] or s  
end  

local function splitTag(s)  
local langCode = s:gsub('%-.*', ''):lower  
langCode = resolveSynonym(langCode)  
local regionCode = s:match('%-(.+)')  
local isPrivate = regionCode and regionCode:sub(1, 2) == 'x-'  
return langCode, regionCode, isPrivate  
end  

local function getLangName(code, link, raw)  
return require('Module:Lang')._name_from_tag({  
code,  
link = link,  
raw = raw,  
-- Without linking, "" gets expanded in some contexts  
template = 'IPA'  
})  
end  

local function linkLang(name, target, link)  
return link == 'yes' and string.format(  
'%s',  
target or name .. ' language',  
name  
) or name  
end  

function p._main(args)  
local ret, cats = {}, {}  
local isDebug = args.debug == 'yes'  
local s, langCode, regionCode, isPrivate  

-- Guide-linking mode  
if args[2] and args[2] ~= '' then  
local data = mw.loadData('Module:IPA/data')  
local isGeneric = args.generic == 'yes'  
s = args[2]  
langCode, regionCode, isPrivate = splitTag(args[1])  
local langData = data.langs[langCode] or {}  
if regionCode then  
if not isPrivate then  
regionCode = regionCode:upper  
end  
if langData.dialects and langData.dialects[regionCode] then  
-- Overwrite language data with the dialect's  
local newLangData = {}  
for k, v in pairs(langData) do  
if k ~= 'dialects' then  
newLangData[k] = v  
end  
end  
local dialectData = langData.dialects[regionCode]  
if dialectData.aliasOf then  
-- Use the canonical region code  
regionCode = dialectData.aliasOf  
isPrivate = regionCode:sub(1, 2) == 'x-'  
dialectData = langData.dialects[regionCode]  
end  
-- Lowercase IANA variant  
if dialectData.isVariant then  
regionCode = regionCode:lower  
end  
for k, v in pairs(dialectData) do  
newLangData[k] = v  
end  
langData = newLangData  
else  
isGeneric = true  
end  
end  

local fullLangCode = regionCode and langCode .. '-' .. regionCode  
or langCode  
local langName = langData.name  
and linkLang(langData.name, langData.link, args.link)  
or getLangName(fullLangCode, args.link)  
if langName:sub(1, 5) == '<span' then  
-- Module:Lang has returned an error  
return langName .. renderCats({ 'IPA template errors' }, isDebug)  
end  
if args.cat ~= 'no' then  
local catLangName = langData.name  
or getLangName(fullLangCode, nil, 'yes')  
if catLangName:sub(1, 5) == '<span' then  
-- Module:Lang has returned an error, but it's not fatal  
table.insert(cats, 'IPA template errors')  
mw.addWarning(catLangName)  
else  
table.insert(cats, string.format('Pages with %s IPA', catLangName))  
end  
end  

-- Label  
local label = args.label  
if not label then  
local labelCode = args[3] and args[3]:lower  
or langData.defaultLabelCode  
if labelCode == '' then  
label = ''  
else  
local langText  
if langData.text then  
langText = linkLang(  
langData.text,  
mw.ustring.match(langName, '^%[%[([^|%]]+)'),  
args.link  
else  
langText = mw.ustring.gsub(  
langName,  
'^%[%[(([^|]+) languages)%]%]$',  
'%2'  
langText = mw.ustring.gsub(  
langText,  
' languages(%]?%]?)$',  
'%1'  
end  
if labelCode and data.labels[labelCode] then  
label = data.labels[labelCode]:format(langText)  
else  
label = data.defaultLabel:format(langText)  
end  
end  
end  
if label and label ~= '' then  
local span = mw.html.create('span')  
:addClass('IPA-label')  
:wikitext(label)  
if args.small ~= 'no' then  
span:addClass('IPA-label-small')  
table.insert(ret, mw.getCurrentFrame:extensionTag({  
name = 'templatestyles',  
args = { src = 'Module:IPA/styles.css' }  
}))  
end  
table.insert(ret, tostring(span) .. ' ')  
end  

-- Brackets  
s = (not isGeneric and langData.format or '&#91;%s&#93;'):format(s)  

-- Link to key  
local key = not isGeneric and langData.key or data.defaultKey  
s = string.format('%s', key, s)  
else  
-- Basic mode  
s = args[1]  
if args.lang and args.lang ~= '' then  
langCode, regionCode, isPrivate = splitTag(args.lang)  
end  
if args.cat ~= 'no' then  
table.insert(cats, 'Pages with plain IPA')  
end  
end  

-- Transcription  
do  
local lang = (langCode or 'und') .. '-Latn'  
if not isPrivate and regionCode then  
lang = lang .. '-' .. regionCode  
end  
lang = lang .. '-fonipa'  
local span = mw.html.create('span')  
:addClass('IPA')  
:addClass(args.class)  
:attr('lang', lang)  
-- wrap=all: Do nothing  
-- wrap=: Never break  
-- Otherwise: Break at spaces only  
if args.wrap ~= 'all' then  
span:addClass('nowrap')  
if args.wrap ~= '' then  
s = wrapAtSpacesSafely(s)  
end  
end  
if (not args[2] or args[2] == *) and args.tooltip ~= * then  
local tooltip = args.tooltip or  
'Representation in the International Phonetic Alphabet (IPA)'  
span:attr('title', tooltip)  
end  
s = tostring(span:wikitext(s))  
table.insert(ret, s)  
end  

-- Audio  
local audio = args.audio ~= * and args.audio or args[4] ~= * and args[4]  
if audio then  
local button = mw.getCurrentFrame:expandTemplate({  
title = 'Audio',  
args = { audio, '' }  
})  
table.insert(ret, ' ' .. button)  
table.insert(cats, 'Pages including recorded pronunciations')  
end  

-- Categories  
table.insert(ret, renderCats(cats, isDebug))  

return table.concat(ret)  
end  

function p.main  
local args = :getParent.args  
if not args[1] then  
return ''  
end  
for i, v in ipairs(args) do  
args[i] = mw.text.trim(v)  
end  
return p._main(args)  
end  

return p