Мені потрібен регулярний вираз, щоб вибрати весь текст між двома зовнішніми дужками.
Приклад: some text(text here(possible text)text(possible text(more text)))end text
Результат: (text here(possible text)text(possible text(more text)))
Мені потрібен регулярний вираз, щоб вибрати весь текст між двома зовнішніми дужками.
Приклад: some text(text here(possible text)text(possible text(more text)))end text
Результат: (text here(possible text)text(possible text(more text)))
Відповіді:
Регулярні вирази - неправильний інструмент для роботи, оскільки ви маєте справу з вкладеними структурами, тобто рекурсією.
Але для цього є простий алгоритм, який я описав у цій відповіді на попереднє запитання .
Я хочу додати цю відповідь для швидкого перегляду. Не соромтеся оновлюватись.
.NET Regex з використанням балансуючих груп .
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
Де c
використовується як лічильник глибини.
Демонстрація на Regexstorm.com
PCRE з використанням рекурсивної схеми .
\((?:[^)(]+|(?R))*+\)
Демонстрація на regex101 ; Або без чергування:
\((?:[^)(]*(?R)?)*+\)
Демонстрація на regex101 ; Або скасовано для виконання:
\([^)(]*+(?:(?R)[^)(]*)*+\)
Демонстрація на regex101 ; Візерунок вставляється, на (?R)
якому зображено (?0)
.
Perl, PHP, Notepad ++, R : perl = TRUE , Python : пакет Regex з (?V1)
для поведінки Perl.
Рубін, використовуючи дзвінки під вираженою експресією .
З Ruby 2.0 \g<0>
можна використовувати для виклику повного шаблону.
\((?>[^)(]+|\g<0>)*\)
Демо на Rubular ; Ruby 1.9 підтримує лише фіксування групової рекурсії :
(\((?>[^)(]+|\g<1>)*\))
Демонстрація на Rubular ( атомне групування з Ruby 1.9.3)
JavaScript API :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\\(', '\\)', 'g');
JS, Java та інші ароматизатори без рекурсії до 2 рівнів введення:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
Демонстрація в regex101 . Для викрійки потрібно додати глибше гніздування .
Щоб швидше вийти з ладу в незбалансованих дужках, опустіть +
кількісний показник.
Java : цікава ідея з використанням прямих посилань від @jaytea .
(?>[^)(]+|(?R))*+
те саме, що писати (?:[^)(]+|(?R))*+
. Те саме для наступного шаблону. Щодо розкрученої версії, ви можете поставити тут присвійний кількісний коефіцієнт: [^)(]*+
щоб запобігти зворотному відстеженню (у випадку, якщо немає дужки закриття).
(...(..)..(..)..(..)..(..)..)
) в предметній рядку), ви можете використовувати просту групу, яка не захоплює, і вкладати все в атомну групу: (?>(?:[^)(]+|\g<1>)*)
( це поводиться точно як присвійний квантор). У Ruby 2.x доступний кількісний коефіцієнт.
Ви можете використовувати регекс :
\(([^()]|(?R))*\)
Unrecognized grouping construct
.
[^\(]*(\(.*\))[^\)]*
[^\(]*
відповідає всім, що не є початковим дужкою на початку рядка, (\(.*\))
захоплює необхідну підрядку, укладену в дужки, і [^\)]*
відповідає всім, що не є дужкою, що закривається в кінці рядка. Зауважте, що цей вираз не намагається відповідати дужкам; простий аналізатор (див . відповідь Демана ) був би більш підходящим для цього.
(?<=\().*(?=\))
Якщо ви хочете вибрати текст між двома відповідніми дужками, вам не пощастить з регулярними виразами. Це неможливо (*) .
Цей регулярний вираз просто повертає текст між першим відкриттям та останніми дужками, що закриваються, у вашому рядку.
(*) Якщо ваш механізм регулярних вивірок не має таких функцій, як балансуючі групи або рекурсія . Кількість двигунів, які підтримують такі функції, повільно зростає, але вони все ще не є загальнодоступними.
Ця відповідь пояснює теоретичне обмеження того, чому регулярні вирази не є правильним інструментом для цього завдання.
Регулярні вирази цього не можуть зробити.
Регулярні вирази засновані на обчислювальній моделі, відомій як Finite State Automata (FSA)
. Як вказує назва, a FSA
може запам'ятати тільки поточний стан, він не має інформації про попередні стани.
На наведеній діаграмі S1 і S2 - це два стани, де S1 є початковим і заключним кроком. Отже, якщо ми спробуємо з рядком 0110
, перехід відбувається наступним чином:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
У наведених вище кроків, коли ми знаходимося на другому , S2
тобто після розбору 01
з 0110
, АФН не має ніякої інформації про попередню 0
в 01
як він може згадати тільки поточний стан і наступний вхідний символ.
У наведеній вище проблемі нам потрібно знати відсутність дужок, що відкриваються; це означає, що його потрібно зберігати в якомусь місці. Але оскільки цього FSAs
не можна зробити, регулярного виразу не можна писати.
Однак можна написати алгоритм для виконання цього завдання. Алгоритми, як правило, підпадають під Pushdown Automata (PDA)
. PDA
знаходиться на один рівень вище FSA
. PDA має додатковий стек для зберігання деякої додаткової інформації. КПК можна використовувати для вирішення вищезазначеної проблеми, тому що ми можемо ' push
' відкриваючі дужки в стеку і ' pop
' їх, коли ми зустрінемо дужки, що закриваються. Якщо в кінці, стек порожній, тоді відкриваються дужки та закриваються дужки збігаються. Інакше ні.
Насправді це можливо зробити, використовуючи регулярні вирази .NET, але це не тривіально, тому уважно читайте.
Ви можете прочитати приємну статтю тут . Вам також може знадобитися прочитати регулярні вирази .NET. Ви можете почати читати тут .
Кутові дужки <>
були використані, оскільки вони не потребують втечі.
Регулярний вираз виглядає так:
<
[^<>]*
(
(
(?<Open><)
[^<>]*
)+
(
(?<Close-Open>>)
[^<>]*
)+
)*
(?(Open)(?!))
>
Це остаточний вираз:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
Приклад:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
зауважте, що '(pip'
правильно керується як рядок. (спробували в регуляторі: http://sourceforge.net/projects/regulator/ )
Я написав невелику бібліотеку JavaScript, яку називають збалансованою, щоб допомогти з цим завданням. Ви можете досягти цього, зробивши це
balanced.matches({
source: source,
open: '(',
close: ')'
});
Ви навіть можете робити заміни:
balanced.replacements({
source: source,
open: '(',
close: ')',
replace: function (source, head, tail) {
return head + source + tail;
}
});
Ось більш складний та інтерактивний приклад JSFiddle .
Додаючи до відповіді бульбашковий міхур , існують інші аромати регулярного вираження, де підтримуються рекурсивні конструкції.
Луа
Використовуйте %b()
( %b{}
/ %b[]
для фігурних дужок / квадратних дужок):
for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end
(див. демонстрацію )Perl6 :
Не збігаються декілька збалансованих дужок:
my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
# => (「(a(b)c)」 「((d)f(g))」)
Перекриття декількох збалансованих дужок збігів:
say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)
Дивіться демонстрацію .
re
Нерегексичне рішення Python
Дивіться відповідь Poke для того, як отримати вираз між збалансованими дужками .
Налаштування Java, що не піддається повторному вибору
Ось налаштоване рішення, що дозволяє однозначні обмежувачі символів на Java:
public static List<String> getBalancedSubstrings(String s, Character markStart,
Character markEnd, Boolean includeMarkers)
{
List<String> subTreeList = new ArrayList<String>();
int level = 0;
int lastOpenDelimiter = -1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == markStart) {
level++;
if (level == 1) {
lastOpenDelimiter = (includeMarkers ? i : i + 1);
}
}
else if (c == markEnd) {
if (level == 1) {
subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
}
if (level > 0) level--;
}
}
return subTreeList;
}
}
Використання зразка:
String s = "some text(text here(possible text)text(possible text(more text)))end text";
List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
System.out.println("Balanced substrings:\n" + balanced);
// => [(text here(possible text)text(possible text(more text)))]
Регулярний вираз за допомогою Ruby (версія 1.9.3 або вище):
/(?<match>\((?:\g<match>|[^()]++)*\))/
Вам потрібні перші та останні дужки. Використовуйте щось подібне:
str.indexOf ('('); - це дасть вам перше явище
str.lastIndexOf (')'); - останній
Тому вам потрібна рядок між,
String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
"""
Here is a simple python program showing how to use regular
expressions to write a paren-matching recursive parser.
This parser recognises items enclosed by parens, brackets,
braces and <> symbols, but is adaptable to any set of
open/close patterns. This is where the re package greatly
assists in parsing.
"""
import re
# The pattern below recognises a sequence consisting of:
# 1. Any characters not in the set of open/close strings.
# 2. One of the open/close strings.
# 3. The remainder of the string.
#
# There is no reason the opening pattern can't be the
# same as the closing pattern, so quoted strings can
# be included. However quotes are not ignored inside
# quotes. More logic is needed for that....
pat = re.compile("""
( .*? )
( \( | \) | \[ | \] | \{ | \} | \< | \> |
\' | \" | BEGIN | END | $ )
( .* )
""", re.X)
# The keys to the dictionary below are the opening strings,
# and the values are the corresponding closing strings.
# For example "(" is an opening string and ")" is its
# closing string.
matching = { "(" : ")",
"[" : "]",
"{" : "}",
"<" : ">",
'"' : '"',
"'" : "'",
"BEGIN" : "END" }
# The procedure below matches string s and returns a
# recursive list matching the nesting of the open/close
# patterns in s.
def matchnested(s, term=""):
lst = []
while True:
m = pat.match(s)
if m.group(1) != "":
lst.append(m.group(1))
if m.group(2) == term:
return lst, m.group(3)
if m.group(2) in matching:
item, s = matchnested(m.group(3), matching[m.group(2)])
lst.append(m.group(2))
lst.append(item)
lst.append(matching[m.group(2)])
else:
raise ValueError("After <<%s %s>> expected %s not %s" %
(lst, s, term, m.group(2)))
# Unit test.
if __name__ == "__main__":
for s in ("simple string",
""" "double quote" """,
""" 'single quote' """,
"one'two'three'four'five'six'seven",
"one(two(three(four)five)six)seven",
"one(two(three)four)five(six(seven)eight)nine",
"one(two)three[four]five{six}seven<eight>nine",
"one(two[three{four<five>six}seven]eight)nine",
"oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
"ERROR testing ((( mismatched ))] parens"):
print "\ninput", s
try:
lst, s = matchnested(s)
print "output", lst
except ValueError as e:
print str(e)
print "done"
Відповідь залежить від того, чи потрібно відповідати збіжним наборам дужок або просто першим, відкритим до останнього закриття у вхідному тексті.
Якщо вам потрібно відповідати вкладеним дужкам, вам знадобиться щось більше, ніж звичайні вирази. - див. @dehmann
Якщо це тільки перший відкрито для останнього закриття см @Zach
Вирішіть, що ви хочете з цим:
abc ( 123 ( foobar ) def ) xyz ) ghij
Вам потрібно вирішити, який код повинен відповідати в цьому випадку.
оскільки js regex не підтримує рекурсивну відповідність, я не можу зробити збалансовані дужки, які відповідають роботі.
тож це простий javascript для версії циклу, яка перетворює рядок "method (arg)" в масив
push(number) map(test(a(a()))) bass(wow, abc)
$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
const parser = str => {
let ops = []
let method, arg
let isMethod = true
let open = []
for (const char of str) {
// skip whitespace
if (char === ' ') continue
// append method or arg string
if (char !== '(' && char !== ')') {
if (isMethod) {
(method ? (method += char) : (method = char))
} else {
(arg ? (arg += char) : (arg = char))
}
}
if (char === '(') {
// nested parenthesis should be a part of arg
if (!isMethod) arg += char
isMethod = false
open.push(char)
} else if (char === ')') {
open.pop()
// check end of arg
if (open.length < 1) {
isMethod = true
ops.push({ method, arg })
method = arg = undefined
} else {
arg += char
}
}
}
return ops
}
// const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)
console.log(test)
результат схожий
[ { method: 'push', arg: 'number' },
{ method: 'map', arg: 'test(a(a()))' },
{ method: 'bass', arg: 'wow,abc' } ]
[ { method: '$$', arg: 'groups' },
{ method: 'filter',
arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
{ method: 'pickBy', arg: '_id,type' },
{ method: 'map', arg: 'test()' },
{ method: 'as', arg: 'groups' } ]
Хоча так багато відповідей згадують це в якійсь формі, кажучи, що регулярний вираз не підтримує рекурсивне узгодження тощо, головна причина цього криється в коренях теорії обчислення.
Мова форми {a^nb^n | n>=0} is not regular
. Regex може відповідати лише тим, що входять до звичайного набору мов.
Детальніше @ тут
Я не використовував регулярний вираз, оскільки важко мати справу з вкладеним кодом. Тож цей фрагмент повинен мати можливість дозволяти вам захоплювати розділи коду з збалансованими дужками:
def extract_code(data):
""" returns an array of code snippets from a string (data)"""
start_pos = None
end_pos = None
count_open = 0
count_close = 0
code_snippets = []
for i,v in enumerate(data):
if v =='{':
count_open+=1
if not start_pos:
start_pos= i
if v=='}':
count_close +=1
if count_open == count_close and not end_pos:
end_pos = i+1
if start_pos and end_pos:
code_snippets.append((start_pos,end_pos))
start_pos = None
end_pos = None
return code_snippets
Я використовував це для витягування фрагментів коду з текстового файлу.
Цей теж працював
re.findall(r'\(.+\)', s)
Це може бути корисно для деяких:
Тут ви можете бачити генерований регулярний вираз
/**
* get param content of function string.
* only params string should be provided without parentheses
* WORK even if some/all params are not set
* @return [param1, param2, param3]
*/
exports.getParamsSAFE = (str, nbParams = 3) => {
const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
const params = [];
while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
str = str.replace(nextParamReg, (full, p1) => {
params.push(p1);
return '';
});
}
return params;
};
Це не повністю стосується питання щодо ОП, але я хоч може бути корисним для деяких, хто приїжджає сюди, щоб шукати вкладену структуру regexp.