ФЭНДОМ


local library = {}
 
library.toTable = function( var )
    if type( var ) == 'table' then
        return var
    else
        return { var }
    end
end
library.errorPrint = function( err )
    local wrapper = mw.html.create( 'div' )
    wrapper:css( 'color', 'red' )
    wrapper:wikitext( err..'[[Категория:Pages with script errors]]' )
    return wrapper
end
library.string = {}
--ФУНКЦИИ ДЛЯ СТРОК
--[[
    разбиение строки на массив подстрок по определенной группе символов
    str - строка, подлежащая разбиению,
    separator - строка или, если pattern == tue, образец
    возвращает массив, с подстроками если str == separator, то пустой массив
    два сепаратора, идущие подряд в строке, рассматриваются как один
]]--
library.string.split = function( str, separator, pattern, condition )
    local result = {}
    if pattern then
        pattern = false
    else
        pattern = true
    end
    local s, e, pE, pS = 0, 0, 0, 0
    local length = mw.ustring.len( str )
    while e ~= length do
        s, e = mw.ustring.find( str, separator, pE + 1, pattern )
        if ( not s or not e ) then
            result[ #result + 1 ] = mw.ustring.sub( str, pS + 1, length )
            break
        elseif not condition or condition( str, {s, e, pE, pS} ) then
            local substring = mw.ustring.sub( str, pS + 1, s - 1 )
            if substring ~= ''  then
                result[ #result + 1 ] = substring
            end
            pS = e
        end
        pE = e
    end
    return result
end
library.string.join = function( arr, separator, condition, associative )
    local result = ''
    local checkAndAddition = function( val, key )
        if not condition or condition( val, key ) then
            if result ~= '' then
                result = result..separator
            end
            result = result..val
        end
    end
    if associative then
        for key, val in pairs( arr ) do
            checkAndAddition( val, key )
        end
    else
        for i, val in ipairs( arr ) do
            checkAndAddition( val, i )
        end
    end
    return result
end
 
library.parseLinks = function( text, jsep, ssep )
    if not jsep then
        jsep = ''
    end
    if not ssep then
        ssep = ','
    end
    local fragments = {}
    local cS, cE, pE = 0, 0, 0
    local length = mw.ustring.len( text )
    while cE ~= length do
        cS, cE = mw.ustring.find( text, '[^\\]'..ssep, cE + 1 )
        if not cS then
            fragments[ #fragments + 1 ] = mw.ustring.sub( text, pE + 1, length )
            break
        end
        fragments[ #fragments + 1 ] = mw.ustring.sub( text, pE + 1, cE - 1 )
        pE = cE
    end
    local result = {}
    for i, val in ipairs( fragments ) do
        --снос сепараторов вначале и пробелов в начале
        local str = mw.ustring.gsub( val, '^['..ssep..'%s]*', '' )
        --снос пробелов в конце
        str = mw.ustring.gsub( str, '%s*$', '' )
        if str ~= '' then
            if mw.ustring.sub( str, 1, 2 ) == '\\w' then
                result[ #result + 1 ] = mw.ustring.sub( str, 3 )
            else
                --замена разделителей
                str = mw.ustring.gsub( str, '\\!', '|' )
                --снос неэкранированных обратных слешей
                str = mw.ustring.gsub( str, '([^\\])[\\]', '%1' )
                if mw.ustring.sub( str, 1, 1 ) == '\\' then
                    str = mw.ustring.sub( str, 2 )
                end
                result[ #result + 1 ] = '[['..str..']]'
            end
        end
    end
    return library.string.join( result, jsep )
end
 
--ФУНКЦИИ ДЛЯ ТАБЛИЦ
library.table = {}
library.table.indexOf = function( table, searchElement, fromIndex )
    local startIndex
    if fromIndex then
        startIndex = fromIndex
    else
        startIndex = 1
    end
    for i = startIndex, #table do
        if table[ i ] == searchElement then
            return i
        end
    end
    return nil
end
library.table.getKeys = function( asTable )
    local keys = {}
    for key, val in pairs( asTable ) do
        table.insert( keys, key )
    end
    return keys
end
library.table.mergeTables = function( ... )
    local result = {}
    local recursion
    local length = #arg
    if type(arg[ length] ) == 'number' or arg[ length ] == true then
        recursion = arg[ length ]
    end
    for i, table in ipairs( arg ) do
        if i == length and recursion then
            break
        end
        for key, val in pairs( table ) do
            if recursion and recursion ~= 0 and type( val ) == 'table' and type( result[key] ) == 'table' then
                local childRecursion
                if type( recursion ) == 'number' then
                    childRecursion = recursion - 1
                else
                    childRecursion = true
                end
                result[ key ] = library.table.mergeTables( childRecursion, result[key], val )
            else
                result[ key ] = val
            end
        end
    end
    return result
end
library.table.compare = function( table1, table2 )
    for key, value in pairs( table2 ) do
        if table1[ key ] == nil then
            return false
        end
    end
    for key, value1 in pairs( table1 ) do
        local value2 = table2[ key ]
        if type( value1 ) ~= type( value2 ) then
            return false
        end
        if type( value1 ) == 'table' then
            if not library.table.compare( value1, value2 ) then
                return false
            end
        else
            if value1 ~= value2 then
                return false
            end
        end
    end
    return true
end
library.table.find = function( table, sought, all )
    local found
    for key, value in pairs( table ) do
        if value == sought then
            if all then
                found[ #found + 1 ] = key
            else
                return key
            end
        end
    end
    if all then
        return found
    end
    return nil
end
library.table.check = function( table, sought )
    if type(sought) == 'table' then
        for i, value in pairs( sought ) do
            if not library.table.find( table, value ) then
                return false
            end
        end
    else
        if not library.table.find( table, value ) then
            return false
        end
    end
    return true
end
 
--ФУНКЦИИ ДЛЯ АРГУМЕНТОВ
library.arguments = {}
library.arguments.err = function( argName, listAllowVals )
    if not listAllowVals then
        error( 'Параметр "'..argName..'" должен быть определён' )
    else
        listAllowVals = library.toTable( listAllowVals )
        local allowVals = library.string.join( listAllowVals, ' or ', nil, true )
        error( 'Параметр "'..argName..'" должен быть: "'..allowVals..'"' )
    end
end
library.arguments.check = function( argName, allowVals, forbidNil )
    local self = library.arguments
    local arg = mw.getCurrentFrame().args[ argName ]
    if not arg then
        if not forbidNil then
            return
        else
            self.err( argName )
        end
    end
    if not allowVals then
        return
    end
    allowVals = library.toTable( allowVals )
    for i, val in ipairs( allowVals ) do
        if arg == val then
            return
        end
    end
    self.err( argName, allowVals )
end
library.arguments.checkAndReturn = function( argName, allowArgs, default, forbidNil )
    library.arguments.check( argName, allowArgs, forbidNil )
    local arg = mw.getCurrentFrame().args[ argName ]
    if arg then
        return arg
    else
        return default
    end
end
library.arguments.checkAndTranslate = function( argName, dictionary, default, forbidNil )
    local allowList = library.table.getKeys( dictionary )
    library.arguments.check( argName, allowList, forbidNil )
    local arg = mw.getCurrentFrame().args[ argName ]
    if arg then
        return dictionary[ arg ]
    else
        return default
    end
end
library.arguments.translate = function( argName, dictionary, default )
    local arg = mw.getCurrentFrame().args[ argName ]
    if arg and dictionary[ arg ] then
        return dictionary[ arg ]
    else
        return default
    end
end
 
--ПАРСЕР
library.parser = {}
--[[
Создание настройки:
1. создание объекта с нуля
1.1 создание обекта шаблонов - patterns. все свойства строковые. все свойства обязательны.
1.1.1. patterns.esc - экранирующий символ.
1.1.2. patterns.skip - пропуск фрагмента.
1.1.3. patterns.skipAll - пропуск всей строки.
1.1.4. patterns.seq1 - начальная часть последовательностей.
1.1.5. patterns.seq2 - конечная часть последовательностей.
1.1.6. patterns.sep1 - начальная часть сепараторов.
1.1.7. patterns.sep2 - конечная часть сепараторов.
1.2  sequences - управляющие последовательности - ассоциативный массив. значение - строка, либо функция, возвращающая строку. В функцию передается сама последовательность.
1.3 separators - массив разделителей.Состоит из массивов по первым элементам которых, строка делится на фрагменты, а по вторым склеивается. Сначала строка делится на фрагменты по первому сепаратору, потом полученные фрагменты делятся по следующему сепаратору и т.д. Склеивание происходит в обратном порядке.
1.4 textHandler - функция, которая вызывается для каждого фрагмента деления по последнему сепаратору, перед склейкой.
Объект должен в себя включать patterns. Прочие значения необязательны.
2. создание объекта на базе имеющегося.
2.1 baseSettingName - имя базовой настройки. строка.
2.2 patterns - содержащиеся свойства заменяют свойства базовой настройки.
2.3 actions - действия, совершаемые над объектами sequences, separators и textHandler. Для sequences и separators: set - редактирование, replace - замена. Для textHandler - remove удаление, replace - замена. Если действие пропущено, соответствующий объект не изменяется.
2.4 sequences - при замене создается новый объект, при редактировании, если свойство строка - добавляется новое свойство, либо заменяется текущее, если свойство - false.
2.5 separators - при замене создается новый массив, при редактировании, если свойство объект - добавляется новое свойство, либо заменяется текущее, если свойство - false. при этом, все элементы массива, номером выше удаляемого, становятся недоступны.
Объект должен включать в себя baseSettingName, прочие значения необязательны.
Для добавления нового объекта используется функция addSetting, первый аргумент - настройка setting, а вторая - название.
 
В случае пропуска фрагмента, он не обрабатывается и остается как есть. Исключение - экранированные сепараторы, стоящие перед ними экранирующие символы обрабатываются в общем порядке.
]]--
 
--настройки
library.parser._settings = {}
library.parser._settings.standart = {
    patterns = {
        esc = '\\',
        skip = 'w',
        skipAll = 'W',
        seq1 = '\\',
        seq2 = '',
        sep1 = '',
        sep2 = ''
    },
    sequences = {
        ['!'] = '|',
        ['n'] = '<br />'
    },
    separators = {
        [1] = {',', ' • ' }
    },
    textHandler = function( text )
        local featuredText = mw.ustring.gsub( text, '^%s*', '' )
        featuredText = mw.ustring.gsub( featuredText, '%s*$', '' )
        return '[['..featuredText..']]'
    end
}
library.parser._settings.test1 = {
    baseSettingName = 'standart',
    actions = {
        sequences = 'replace',
        separators = 'set',
        textHandler = 'remove'
    },
    patterns = {
        seq2 = ' ',
        sep1 = '_',
        sep2 = '_'
    },
    sequences = {--удаление - false
        ['!'] = '?',
        ['*'] = '#'
    },
    separators = {--удаление - false все элементы старше номером удаляемого, будут недоступны.
        [2] = { ':', '--' }
    }
}
function library.parser.addSetting( setting, settingName )
    local newSetting = mw.clone( setting )
    if not setting.sequences then
        setting.sequences = {}
    end
    if not setting.separators then
        setting.separators = {}
    end
    if setting.baseSettingName and not setting.actions then
        setting.actions = {}
    end
    library.parser._settings[ settingName ] = setting
end
function library.parser.checkSetting( settingName )
    if library.parser._settings[ settingName ] then
        return true
    end
end
function library.parser.getSetting( setting, selfOnly )
    local settings = library.parser._settings
    local currentSetting
    if type( setting ) == 'table' then
        currentSetting = setting
    elseif type( setting ) == 'string' and settings[ setting ] then
        currentSetting = settings[ setting ]
    end
    if selfOnly then
        return mw.clone( settings )
    end
    local newSetting = {}
    if currentSetting.baseSettingName then
        --1. погружение до корневой настройки
        newSetting = library.parser.getSetting( currentSetting.baseSettingName )
        --3.1 редактирование корневой настройки на каждом этапе всплытия
        if currentSetting.patterns then
            for key, pattern in pairs( currentSetting.patterns ) do
                newSetting.patterns[ key ] = pattern
            end
        end
        if currentSetting.actions.sequences == 'set' then
            for key, sequence in pairs( currentSetting.sequences ) do
                if sequence then
                    newSetting.sequences[ key ] = sequence
                else
                    newSetting.sequences[ key ] = nil
                end
            end
        elseif currentSetting.actions.sequences == 'replace' then
            newSetting.sequences = mw.clone( currentSetting.sequences )
        end
        if currentSetting.actions.separators == 'set' then
            for key, separator in pairs( currentSetting.separators ) do
                if separator then
                    newSetting.separators[ key ] = separator
                else
                    newSetting.separators[ key ] = nil
                end
            end
        elseif currentSetting.actions.separators == 'replace' then
            newSetting.separators = mw.clone( currentSetting.separators )
        end
        if currentSetting.actions.textHandler == 'replace' then
            newSetting.textHandler = currentSetting.textHandler
        elseif currentSetting.actions.textHandler == 'remove' then
            newSetting.textHandler = nil
        end
    else
        --2. взятие корневой настройка
        newSetting.patterns = mw.clone( currentSetting.patterns )
        newSetting.sequences = mw.clone( currentSetting.sequences )
        newSetting.separators = mw.clone( currentSetting.separators )
        newSetting.textHandler = currentSetting.textHandler
    end
    --3. всплытие до требуемой настройки и возврат
    return newSetting
end
function library.parser.create( setting )
    local self = library.parser
    local obj = {}
    obj._library = library
    obj._settings = library.parser._settings
    setmetatable( obj, self )
    self.__index = self
    if not setting then
        obj:loadSettings( 'standart' )
    else
        obj:loadSettings( setting )
    end
    return obj
end
function library.parser:loadSettings( setting )
    local newSetting = library.parser.getSetting( setting )
    self._sequences = newSetting.sequences
    self._separators = newSetting.separators
    self._textHandler = newSetting.textHandler
    self:_loadPatterns( newSetting.patterns )
end
function library.parser:_loadPatterns( patterns )
    local pat = patterns
    local esc = '('..pat.esc..'*)'
    self._patterns = {
        seq1 = esc..pat.seq1,
        seq2 = pat.seq2,
        skip1 = esc..pat.seq1..pat.skip,
        skip2 = pat.seq2,
        skipAll = esc..pat.seq1..pat.skipAll..pat.seq2,
        sep1 = pat.sep1,
        sep2 = pat.sep2,
        esc = pat.esc
    }
end
function library.parser:_checkScreening( arg1, arg2 )
    if arg2 then
        local text = arg1
        local symbolPosition = arg2
        local numberOfScrs = 0
        local currentScrPosition = symbolPosition - 1
        local scrSymbol = mw.ustring.sub( text, currentScrPosition, currentScrPosition )
        while currentScrPosition > 0 and scrSymbol == self._patterns.esc do
            numberOfScrs = numberOfScrs + 1
            currentScrPosition = currentScrPosition - 1
            scrSymbol = mw.ustring.sub( text, currentScrPosition, currentScrPosition )
        end
        if numberOfScrs % 2 ~= 0 then
            return true
        end
    else
        local esc = arg1
        local length = mw.ustring.len( esc )
        if length % 2 ~= 0 then
            return true
        end
    end
end
--проверка на отключение парсера и удаление управляющих этим элементов разметки
function library.parser:_checkSkip( text, pattern )
    local skip = false
    local finalText = mw.ustring.gsub(text, pattern, function( esc )
        if not self:_checkScreening( esc ) and not skip then
            skip = true
            return esc
        end
    end)
    return finalText, skip
end
function library.parser:parse( text, data )
--высший уровень
    --проверка на полное отключение парсера для строки
    local finalText, skipAll = self:_checkSkip( text, self._patterns.skipAll )
    --если отключен, возврат строки без изменений
    if skipAll then
        return finalText
    end
    --отправка строки на нижний уровень
    return self:_parse( text, 1, data )
end
function library.parser:_parse( text, no, data )
    local pat = self._patterns
    local sep = self._separators[no]
    local seq = self._sequences
    --Проверка на отключение парсера для участка строки
    local finalText, globalSkip = self:_checkSkip( text, pat.skip1..no..pat.skip2 )
    --если отключен, вернуть на верхний уровень без изменений
    if globalSkip then
        return finalText
    end
    --если есть разделитель
    if sep then
        --разделение строки
        local fragments = self._library.string.split(text, pat.sep1..sep[1]..pat.sep2, false, function(text, points)
            return not self:_checkScreening( text, points[1] )
        end)
        --проброска на нижний уровень
        local results = {}
        for i, fragment in ipairs( fragments ) do
            --очистка символов, экранирующих незадействованные разделители 
            fragment = mw.ustring.gsub(fragment, '('..pat.esc..'+)('..sep[1]..')', function( escSymbs, sep )
                local symbsLen = mw.ustring.len( escSymbs )
                local finalSymbsLen  = math.floor( symbsLen / 2 )
                if finalSymbsLen == 0 then
                    return sep
                else
                    return mw.ustring.sub( escSymbs, 1, finalSymbsLen )..sep
                end
            end)
            results[ #results + 1 ] = self:_parse( fragment, no + 1, data )
        end
        --склейка строки из результатов работы функций нижнего уровня
        return self._library.string.join(results, sep[2] , function( text )
            if text ~= '' then
                return true
            end
        end)
    --если разделителя нету обработка строки на низшем уровне
    else
        --проверка на отключение парсера для участка строки
        local finalText, localSkip = self:_checkSkip( text, pat.skip1..pat.skip2 )
        --если отключен, вернуть на верхний уровень без обработки
        if localSkip then
            return finalText
        end
        --обработка управляющих последовательностей
        local optionParametres = {}
        for key, val in pairs( seq ) do
            finalText = mw.ustring.gsub(finalText, pat.seq1..'('..key..')'..pat.seq2, function( esc, seq )
                if self:_checkScreening( esc ) then
                    return
                end
                optionParametres[ key ] = seq
                if type( val ) == 'function' then
                    return esc..val( seq, data )
                else
                    return esc..val
                end
            end)
        end
        --очистка лишних экранирующих символов
        finalText = mw.ustring.gsub(finalText, pat.esc..'*', function( escSymbols )
            local symbsLen = mw.ustring.len( escSymbols )
            local finalSymbsLen  = math.floor( symbsLen / 2 )
            if finalSymbsLen == 0 then
                return ''
            else
                return mw.ustring.sub( escSymbols, 1, finalSymbsLen )
            end
        end)
        --обработка результата в функции, если она есть, и возврат его на верхний уровень
        if self._textHandler then
            return self._textHandler( finalText, optionParametres, data )
        else
            return finalText
        end
    end
end
 --тесты
function library:test()
    local test3 = {
        patterns = {
            esc = '\\',
            skip = 'w',
            skipAll = 'W',
            seq1 = '\\',
            seq2 = '',
            sep1 = '',
            sep2 = ''
        },
        sequences = {
            ['!'] = '|'
        },
        separators = {
            [1] = {',', ' • ' }
        },
        textHandler = function( text )
            return '[['..text..']]'
        end
    }
    local test2 = {
        baseSettingName = 'test3',
        actions = {
            sequences = 'set',
            separators = 'set'
        },
        patterns = {
            seq2 = ' ',
            sep1 = '_',
            sep2 = '_'
        },
        sequences = {--удаление - false
            ['!'] = false
        },
        separators = {--удаление - false все элементы старше номером удаляемого, будут недоступны.
            [1] = false
        },
        textHandler = function( text )
            return '['..text..']'
        end
    }
    local test4 = {
        baseSettingName = 'standart',
        actions = {
            separators = 'set'
        },
        separators = {
            [2] = {':', ' ! ' }
        }
    }
    self.parser.addSetting( test3, 'test3' )
    self.parser.addSetting( test2, 'test2' )
    self.parser.addSetting( test4, 'test4' )
    local p = self.parser.create('test4')
    local s = '    1 1 1\\!          ,             2 2 2            '
    local s2 = '\\* \\! 111\\! _,_33_:_222_-_33'
    local s3 = '\\w 1\\\\\\:11:22\\,2,333:444'
    return p:parse(s3)
end
function library.test1()
    local a = {1,{1,2},3}
    local b = {1,{3},3}
    return library.table.mergeTables(1,a,b)
end
 --старое
 function library:split( str, separator, pattern, condition )
    local result = {}
    if pattern then
        pattern = false
    else
        pattern = true
    end
    local s, e, pE, pS = 0, 0, 0, 0
    local length = mw.ustring.len( str )
    while e ~= length do
        s, e = mw.ustring.find( str, separator, pE + 1, pattern )
        if ( not s or not e ) then
            result[ #result + 1 ] = mw.ustring.sub( str, pS + 1, length )
            break
        elseif not condition or condition( str, {s, e, pE, pS} ) then
            local substring = mw.ustring.sub( str, pS + 1, s - 1 )
            if substring ~= ''  then
                result[ #result + 1 ] = substring
            end
            pS = e
        end
        pE = e
    end
    return result
end
function library:join( arr, separator, condition )
    local str = ''
    for i, val in ipairs( arr ) do
        if not condition or condition( val, i ) then
            if str ~= '' then
                str = str..separator
            end
            str = str..val
        end
    end
    return str
end
 function library:indexOf( table, searchElement, fromIndex )
    local startIndex
    if fromIndex then
        startIndex = fromIndex
    else
        startIndex = 1
    end
    for i = startIndex, #table do
        if table[ i ] == searchElement then
            return i
        end
    end
    return nil
end
 
return library
Материалы сообщества доступны в соответствии с условиями лицензии CC-BY-SA , если не указано иное.