Модуль:Regex
| Regex |
Выполняет поиск или замену с помощью регулярного выражения
- Автор:
- Александр Машин
1:- Строка для поиска или замены
flavour:- разновидность регулярного выражения (pcre2, posix, и т.д.)
subpattern:- номер захвата, который должен возвратить успешный поиск
sep:- разделитель между найденными подстроками
template:- шаблон, которым должны быть обёрнуты найденные значения
intro:- строка, вставляемая перед найденными значеняими
outro:- строка, подставляемая после найденных значений
default:- значение по умолчанию, возвращаемое вместо найденных значений
limit:- максимальное количество возвращённых совпадений
Найденные значения или строка, в которой произведены замены
Синтаксис:{{#invoke:Regex|Regex
| Строка для поиска или замены
| flavour = разновидность регулярного выражения (pcre2, posix, и т.д.)
| subpattern = номер захвата, который должен возвратить успешный поиск
| sep = разделитель между найденными подстроками
| template = шаблон, которым должны быть обёрнуты найденные значения
| intro = строка, вставляемая перед найденными значеняими
| outro = строка, подставляемая после найденных значений
| default = значение по умолчанию, возвращаемое вместо найденных значений
| limit = максимальное количество возвращённых совпадений
}} Функция, производящая поиск по регулярному выражению в переданной строке, или поик и замену, в том числе, последовательную.
Знак | в регулярном выражении надо заменить на {{!}}. Знак = надо заменить на {{=}} или использовать синтаксис 2 = /regex/.
В строке замены захваты начинаются с %.
Примеры
| Викитекст | Результат |
|---|---|
| Поиск | |
{{#invoke:Regex|regex|а,б,в,г|%[аб]%u|sep=,}} |
String "%[аб]%u" is not expected here. |
{{#invoke:Regex|regex|one, two, three|%\w+%|template=lang-en|sep=, <nowiki />}} |
англ. one, англ. two, англ. three |
{{#invoke:Regex|regex|а,б,в,г|%(?:^{{!}},)(.*?)(?:,{{!}}$)%u}} |
,)(.*?)(?:,|$)%u" is not expected here. |
| Замена | |
{{#invoke:Regex|regex|а,б,в,г|%[аб]%u|[[%0]]|limit=1}} |
String "%[аб]%u" is not expected here. |
{{#invoke:Regex|regex|а,б,в,г|%[аб]%u|[[%0]]}} |
String "%[аб]%u" is not expected here. |
{{#invoke:Regex|regex|а,б,в,г|%(?:^{{!}},)(.*?)(?:,{{!}}$)%|[[%1]]}} |
абвг |
| Многострочный синтаксис | |
{{#invoke:Regex|regex|Александр, Константин, Николай, Михаил
| /(
Александр
{{!}} Николай
)/x = царь
| /Михаил/ = великий князь
| /Константин/ = цесаревич
}}
|
царь, цесаревич, царь, великий князь
|
local concat = table.concat
local wrap, yield = coroutine.wrap, coroutine.yield
local function ordered_pairs (tbl)
return wrap (function ()
for key, value in ipairs (tbl) do
yield (key, value)
end
for key, value in pairs (tbl) do
if type (key) ~= 'number' then
yield (key, value)
end
end
end)
end
local function shallow (tbl)
local cloned = {}
for key, value in ordered_pairs (tbl) do
cloned [key] = value
end
return cloned
end
local flavours = {}
for _, flavour in ipairs { 'posix', 'gnu', 'pcre', 'pcre2', 'onig', 'tre' } do
flavours [flavour] = _G ['rex_' .. flavour]
end
flavours.pcre = flavours.pcre or flavours.pcre2
flavours.pcre2 = flavours.pcre or flavours.pcre2
local pcre = flavours.pcre.new
local param_names = {
'flavour',
'subpattern', 'sep', 'template',
'intro', 'outro', 'default', 'limit'
}
local flag_letters = {
a = 'ANCHORED',
i = 'CASELESS',
m = 'MULTILINE',
n = 'NO_AUTO_CAPTURE',
s = 'DOTALL',
u = 'UTF8',
x = 'EXTENDED',
A = 'ANCHORED',
D = 'DOLLAR_ENDONLY',
U = 'UNGREEDY',
X = 'EXTRA'
}
local function allowed_flags (flavour)
local constants = flavour.flags()
local flags = {}
for flag, const in pairs (flag_letters) do
if constants [const] then
flags [#flags + 1] = flag
end
end
return concat (flags)
end
local function flags2options (consts, flags)
-- To handle UTF8 correctly:
if consts.UTF8 and consts.UCP then
consts.UTF8 = consts.UTF8 + consts.UCP
end
local options = 0
for flag in flags:gmatch '.' do
options = options + consts [flag_letters [flag]]
end
return options
end
local function regex_pattern (flavour)
return pcre (([==[
^
(?<delim>[\W\\\S])
(?<search>.*?)
(?:
\k<delim>
(?<replace>.*)
)?
\k<delim> \s*
(?<flags>[%s]*)
$
]==]):format (allowed_flags (flavour)), 'sx')
end
local function regex (args, expander)
local subject
local params = {}
for _, param in ipairs (param_names) do
params [param] = args [param]
args [param] = nil
end
local flavour = flavours [params [flavour]] or flavours.pcre
local new, gsub, tfind = flavour.new, flavour.gsub, flavour.tfind
local regex_pattern = regex_pattern (flavour)
local subs = {}
for left, right in ordered_pairs (args) do
if left == 1 then
subject = right
else
local search, replace, flags
if type (left) == 'number' then
-- /search/replace/, or /search/, or /search/, replace syntax:
local start, _, captures = regex_pattern:tfind (right)
if start then
search, replace, opts = captures.search, captures.replace, flags2options (flavour.flags(), captures.flags)
else
-- Could be /search/, replace syntax at replace:
replace = right
end
else
-- /search/ = replace syntax:
local start, _, captures = regex_pattern:tfind (left)
if start then
search, replace, opts = captures.search, right, flags2options (flavour.flags(), captures.flags)
end
end
if search then
local regex = new (search, opts)
if not regex then
return '<span class="error">Regular expression /'
.. search
.. '/ does not compile</span>'
end
subs [#subs + 1] = { search = regex, replace = replace }
else
if subs [#subs] and subs [#subs].search and not subs [#subs].replace then
subs [#subs].replace = replace
else
return '<span class="error">String "'
.. ( replace or '' )
.. '" is not expected here.</span>'
end
end
end
end
local found = false
for _, subst in ipairs (subs) do
if subst.replace then
subject = gsub (subject, subst.search, subst.replace, params.limit)
else
local matches, start = {}, 1
while start and (not params.limit or #matches < params.limit) do
local captures, finish = {}, nil
start, finish, captures = subst.search:tfind (subject, start)
if start then
if params.subpattern then
matches [#matches + 1] = captures [params.subpattern]
else
matches [#matches + 1] = subject:sub (start, finish)
end
captures [0] = matches [#matches]
if params.template then
if not captures [1] then
captures [1] = captures [0]
end
matches [#matches] = expander (params.template, captures)
end
start = finish + 1
found = true
end
end
subject = (found and params.intro or '')
.. (#matches > 0 and concat (matches, params.sep or '') or params.default or '')
.. (found and params.outro or '')
end
end
return subject
end
return {
regex = function (tbl)
local expander = tbl.expandTemplate and function (template, args)
return tbl:expandTemplate { title = template, args = args }
end or function (template, args)
return '{{' .. template .. '|' .. concat (args, '|') .. '}}' -- @todo.
end
return regex (tbl.args and shallow (tbl.args) or tbl, expander)
end
}