Documentation for this module may be created at Module:stringconvert/doc

local p = {}

local GRAVE     = mw.ustring.char(0x0300)
local ACUTE     = mw.ustring.char(0x0301)
local MACRON    = mw.ustring.char(0x0304)
local BREVE     = mw.ustring.char(0x0306)
local DIAER     = mw.ustring.char(0x0308)

local grave_tbl =
{
	["Ѐ"] = "Е", ["ѐ"] = "е",
	["Ѝ"] = "И", ["ѝ"] = "и", [GRAVE] = ''
}
local function strip_grave(str)
	for k, v in pairs( grave_tbl ) do
		str = mw.ustring.gsub(str, "[" .. k .. "]", function(c)
			return v
		end)
	end
	return str;
end	

local macron_tbl = 
{
  ["ĀĂ"] = "A", ["āă"] = "a", 
  ["ĒĔË"] = "E", ["ēĕë"] = "e", 
  ["ĪĬÏ"] = "I", ["īĭï"] = "i",
  ["ŌŎ"] = "O", ["ōŏ"] = "o",
  ["ŪŬÜ"] = "U", ["ūŭü"] = "u",
  ["Ȳ"] = "Y", ["ȳ"] = "y", 
  [GRAVE .. ACUTE .. MACRON .. BREVE .. DIAER] = ''
}

--[[
strip_macrons

Эта функция удаляет макроны, знаки ударения и заменяет слоговые знаки на обычные.

Использование:
{{#invoke:stringconvert|strip_macrons|текст}}
]]

function p.strip_macrons(str)
	if type(str) == 'table' then -- проверка на глобальное использование
		str = str.args[1]
	end
	for k, v in pairs( macron_tbl ) do
		str = mw.ustring.gsub(str, "[" .. k .. "]", function(c)
			return v
		end)
	end
	return str;
end

--[[
correct_accent

Эта функция убирает знак ударения в слове с одной гласной.

Использование:
{{#invoke:stringconvert|correct_accent|текст}}
]]

function p.correct_accent(str)
	if type(str) == 'table' then -- проверка на глобальное использование
		str = str.args[1]
	end
	local vowel = '[аеиоуыэюяАЕИОУЫЭЮЯ]'
	local index = mw.ustring.find(str, vowel, 1, false); -- позиция гласной
	if index ~= nil and mw.ustring.find(str, vowel, index+1, false) ~= nil then
		return str; -- если больше одной гласной то возвращаем без изменения
	else
		str = mw.ustring.gsub(str, ACUTE, '') -- убираем знак ударения
		return str 
	end 
end

--[[
aslink

Эта функция преобразует разделённый запятыми текст в разделённые запятыми викиссылки.
Встречающиеся знаки ударения сохраняются в тексте ссылки, но убираются из цели.

Использование:
{{#invoke:stringconvert|aslinks|текст|разделитель}}
или 
{{#invoke:stringconvert|aslinks|text=текст|delimiter=разделитель|nospace=1|hide=слово|nocomment=1|lang=код языка}}
]]

function p.aslinks ( frame )
    -- разбор параметров
    local function getParameters( frame_args, arg_list )
        local new_args = {};
        local index = 1;
        local value;
        for i,arg in ipairs( arg_list ) do
            value = frame_args[arg]
            if value == nil then
                value = frame_args[index];
                index = index + 1;
            end
            new_args[arg] = value;
        end
        return new_args;
    end
    -- определение булевого значения в параметре
    function getBoolean( boolean_str )
        local boolean_value = false;
        if type( boolean_str ) == 'string' then
            boolean_str = boolean_str:lower();
            if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0' or boolean_str == '' then
                boolean_value = false;
            else
                boolean_value = true;
            end    
        elseif type( boolean_str ) == 'boolean' then
            boolean_value = boolean_str;
        end    
        return boolean_value
    end
    
    local new_args = getParameters( frame.args, {'text', 'delimiter', 'nospace', 'hide', 'nocomment', 'lang' } );
    local hide_word = new_args['hide'] or '';
    local text = new_args['text'] or '';
    local delimiter = new_args['delimiter'];
    if delimiter == nil or delimiter == '' then
        delimiter = ',';
    else
        delimiter = mw.text.decode(delimiter);
    end
    local nospace = getBoolean( new_args['nospace']);
    if not nospace then
        -- добавляем пробел и неразрывный пробел
        delimiter = delimiter .. ' ' .. mw.ustring.char(160);
    end
	local nocomment = getBoolean( new_args['nocomment']);
	if  nocomment then
		delimiter = delimiter .. '%(%)';
	end
	local lang= new_args['lang'] or '';
	local langlink = '';
	if lang ~= '' then
		local trimmed = mw.text.trim(lang);
		if trimmed == 'ru' then
			langlink = "Русский"	
		else
			local languages = mw.loadData("Module:languages/data");
			local l = languages[trimmed];
			if l and l[2] then
				langlink = l[2];
			end
		end
	end
	-- разделение строки 
    local function mygsplit(str, pattern) 
        local l = mw.ustring.len( str );
        return function (state, s)
            if s <= l then
                local e, n = mw.ustring.find( str, pattern, s, false );
                local ret;
                local sep = '';
                if not e then
                    ret = mw.ustring.sub( str, s );
                    s = l + 1
                elseif n < e then
                    ret = mw.ustring.sub( str, s, e );
                    if e < l then
                        s = e + 1;
                    else
                        s = l + 1;
                    end
                else
                    ret = e > s and mw.ustring.sub( str, s, e - 1 ) or '';
                    sep = mw.ustring.sub( str, e, n );
                    s = n + 1
                end
                return s, ret, sep
            else
                s = nil;
                return s, ret, sep
            end
        end, nil, 1 -- iterator, state, initial value
    end    
    -- поиск тэгов с учётом вложенности
    local function findtag( str, s )
        local ct = 1;
        local start_pos = 1;
        local start_tag = '';
        while true do
            local e, n, close_tag, tag_name, single_tag  = mw.ustring.find( str, "<(/?)([A-Za-z]+).-(/?)>", s, false );
            if not e then
                return nil, -1
            else 
                if single_tag ~= '' then 
                    if start_tag == '' then
                        return e, n; -- найден одиночный непарный тэг
                    else 
                        s = n + 1; -- пропускаем вложенный непартный тэг
                    end
                elseif close_tag ~= '' then
                    if start_tag == '' then
                        return e, n; -- найден одиночный закрывающий тэг
                    elseif tag_name == start_tag then
                        ct = ct - 1;
                        if ct == 1 then
                            return  start_pos, n; -- найден закрывающий тэг
                        else
                            s = n + 1; -- найден вложенный закрывающий тэг
                        end
                    else
                        s = n + 1; -- пропускаем вложенный закрывающий тэг
                    end
                else
                    if start_tag == '' then
                        start_tag = tag_name;
                        start_pos = e;
                        ct = ct + 1;
                        s = n + 1; -- найден первый открывающий тэг
                    elseif tag_name == start_tag then
                        ct = ct + 1;
                        s = n + 1; -- найден вложенный открывающий тэг с тем же именем
                    else
                        s = n + 1; -- пропускаем вложенный открывающий тэг
                    end
                end
            end
        end
    end
    -- разделение тэгов
    local function tsplit(str) 
        local l = mw.ustring.len( str );
        return function (state, s)
            if s <= l then
                local e, n = findtag( str, s );
                local ret;
                local sep = '';
                if not e then
                    ret = mw.ustring.sub( str, s );
                    s = l + 1
                elseif n < e then
                    ret = mw.ustring.sub( str, s, e );
                    if e < l then
                        s = e + 1;
                    else
                        s = l + 1;
                    end
                else
                    ret = e > s and mw.ustring.sub( str, s, e - 1 ) or '';
                    sep = mw.ustring.sub( str, e, n );
                    s = n + 1
                end
                return s, ret, sep
            else
                s = nil;
                return s, ret, sep
            end
        end, nil, 1 -- iterator, state, initial value
    end  
    -- разделение оставшегося текста
    local function myparser(text, level)
        if text ~= '' then
            if mw.ustring.find(text, '[|&]', 1, false) ~= nil or -- не обрабатываем текст, содержащий пайп и апмерсант
               mw.ustring.find(text, '^[%p%d]+$', 1, false) ~= nil then  -- и одиночные знаки пунктуации и цифры
               return text; 
            else
                local before, str, after = mw.ustring.match(text, "^(%s*)(.-)(%s*)$", 1);
                if before ~= nil and str ~= nil and str ~= '' then
                	local section = ''
                	local link = str;
                	if lang == "la" then
                    	link = p.strip_macrons(str) -- убираем диакритические знаки
                    else
                    	link = mw.ustring.gsub(str, ACUTE, '') -- убираем только знак ударения
                    	link = strip_grave(link) -- убираем гравис
                    	if link ~= str and (lang == '' or lang == 'ru') then
							section = mw.ustring.match( strip_grave(str), '[^#]+'); -- секция с ударением
                    	end
                	end
					local page, loc = mw.ustring.match( link, '^([^#]-)#([^#]-)$');
					if page ~= nil then
						loc = mw.ustring.gsub(loc, '%%28', '(');
						loc = mw.ustring.gsub(loc, '%%29', ')');
						if section ~= '' then
							section = section .. '_'.. loc
						else
							section = page .. '_'.. loc
						end
						local num, ext  =  mw.ustring.match(loc, '^([XVI]+)_?(.-)$')
						if num ~= nil then
							-- добавляем римские цифры
							str =  mw.ustring.gsub( str, '^([^#]-)#([XVI]+)_?.-$', '%1<sup>%2</sup>')
							if nocomment and ext ~= '' then
								after = ' ' .. ext .. after
							end
						else
							str = mw.ustring.gsub( str, '^([^#]-)#([^#]-)$', '%1');
						end
						link = page
					end                	
					if section == '' then
                		section = langlink;
                	end
                    if str ~= link or section ~= ''  then
                        str = link .. '#' .. section .. '|'.. str;
                    end
                    text = before .. "[[" .. str .. "]]" .. after;
                end
            end
        end
        return text; 
    end
    local handlers = {};
    -- разделение текста по шаблону
    local function parse(text, level)
        local result = "";
        local pattern = handlers[level][1];
        local parser = handlers[level][2];
        for i, str, sep in mygsplit(text, pattern, false ) do
            if str ~= "" then
                result = result .. parser(str, level+1);
            end
            if sep ~= "" then
                result = result .. sep;
            end
        end
        if result == "" then
            return text;
        else
            return result;
        end
    end
    local stripers = {};
    local striped = {};
    local stripid = 0;
    local k = 1;
    -- замена текста с сохранением
    local function striptext(text)
        stripid = stripid +1;
        local key = '<' .. stripid .. '>';
        striped[key] = text;
        return key;
    end
    -- вырезание текста по шаблону
    local function strip(text)
        for j, pattern in ipairs(stripers) do
            local result = "";
            for i, str, sep in mygsplit(text, pattern, false ) do
                if str ~= "" then
                    result = result .. str;
                end
                if sep ~= "" then
                    result = result .. striptext(sep);
                end
            end
            if result ~= '' then
                text = result; -- текст для следующего обработчика
            end;
        end
        return text;
    end
    -- восстановление вырезанного текста по шаблону
    local function unstrip(text)
        local result = '';
        for i, str, sep in mygsplit(text, "<%d->", false ) do
            if str ~= "" then
                result = result .. str;
            end
            if sep ~= "" then
                result = result .. unstrip(striped[sep]);
            end
        end
        return result;
    end
    -- вырезание тэгов
    local function striptags(text)
        local result = "";
        for i, str, sep in tsplit(text) do
            if str ~= "" then
                result = result .. str;
            end
            if sep ~= "" then
                result = result .. striptext(sep);
            end
        end
        if result == '' then
            return text;
        else
            return result;
        end
    end
    -- обратная совестимость с шаблоном lang, куда иногда передается текст сразделенный шаблоном {{!}}
    text = mw.ustring.gsub(text, '^%s*([^%[|,;]+|[^%]|,;]+)%s*$', '[[%1]]'); 
  	-- маскируем круглые скобки внутри ссылок
    text = mw.ustring.gsub(text, '%s*[^#<>%[%]%p]-#[XVI]-%s*%([^|<>%[%]%)]-%)%s*[,;]*', function (c) 
    	return mw.ustring.gsub(c, ' ?[%(%)]', function (ch) 
    		if ch == ' (' then
    			return '_%28'
    		elseif ch == '(' then
    			return '%28'
    		elseif ch == ' )' or ch == ')' then
    			return '%29'
    		end
    	end)
    end)
    text = striptags(text); -- вырезаем HTML тэги
    stripers = {}
    table.insert(stripers, "{|.*|}");         -- исключаем таблицы
    if not nocomment then
    	table.insert(stripers, "%b()");       -- исключаем текст в круглых скобках
    end
    table.insert(stripers, "%[%[.-%]%]%S*");  -- исключаем внутренние ссылки
    table.insert(stripers, "%b[]");           -- исключаем внешние ссылки
    table.insert(stripers, "'''[^']-'''");    -- исключаем текст в тройных одинарных кавычках
    table.insert(stripers, "''[^']-''");      -- исключаем текст в двойных одинарных кавычках
    text = strip(text);     -- вырезаем по шаблону
    local l = 1;
    handlers[l] = { "\r?\n",                 parse }; l=l+1; -- исключаем разделители строк
    handlers[l] = { "%b<>",                  parse }; l=l+1; -- исключаем текст в угловых скобках
    handlers[l] = { "%s*[" .. delimiter .."]+%s*", myparser };     -- разделяем оставшееся
    text = parse(text, 1);  -- обрабатываем
    if stripid ~= 0 then
        text = unstrip(text); -- восстанавливаем вырезанное
    end;
    if hide_word ~= '' then
    	local cnt = 0;
    	-- удаление ссылки на слово
    	hide_word = mw.ustring.gsub( hide_word, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" ); -- маскируем спецсимволы
    	text, cnt = mw.ustring.gsub( text, '%[%[' .. hide_word ..'%|[^%]]*%]%]%w* *,? *', '' ); 
    	if cnt == 0 then
    		text, cnt = mw.ustring.gsub( text, '%[%[' .. hide_word ..'%]%]%w* *,? *', '' ); 
    	end
    	if cnt ~= 0 then
    		text = mw.ustring.gsub( text, '^%p+$', '' ); -- удаляем строку состоящую только из разделительных знаков
    		text = mw.ustring.gsub( text, '[,;] *;', ';' ); -- удаляем запятую перед точкой с запятой
    		text = mw.ustring.gsub( text, ', *$', '' ); -- удаляем запятую в конце строки
    		text = mw.ustring.gsub( text, '^[;,] *', '' ); -- удаляем запятую или точку с запятой в начале строки
		end
    end
    return text;
end

return p