Нет описания правки Метка: sourceedit |
Нет описания правки |
||
Строка 163: | Строка 163: | ||
return result |
return result |
||
end |
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.arguments = {} |
library.arguments = {} |
Версия от 12:40, 31 марта 2018
Для документации этого модуля может быть создана страница Модуль:Библиотека/doc
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.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