Tôi cần một biểu thức chính quy để chọn tất cả văn bản giữa hai dấu ngoặc ngoài.
Thí dụ: some text(text here(possible text)text(possible text(more text)))end text
Kết quả: (text here(possible text)text(possible text(more text)))
Tôi cần một biểu thức chính quy để chọn tất cả văn bản giữa hai dấu ngoặc ngoài.
Thí dụ: some text(text here(possible text)text(possible text(more text)))end text
Kết quả: (text here(possible text)text(possible text(more text)))
Câu trả lời:
Biểu thức chính quy là công cụ sai cho công việc vì bạn đang xử lý các cấu trúc lồng nhau, tức là đệ quy.
Nhưng có một thuật toán đơn giản để làm điều này, mà tôi đã mô tả trong câu trả lời này cho một câu hỏi trước đó .
Tôi muốn thêm câu trả lời này cho nhanh chóng. Hãy cập nhật.
.NET Regex sử dụng các nhóm cân bằng .
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
Nơi c
được sử dụng như bộ đếm độ sâu.
Bản trình diễn tại Regexstorm.com
PCRE sử dụng mô hình đệ quy .
\((?:[^)(]+|(?R))*+\)
Demo tại regex101 ; Hoặc không có sự thay thế:
\((?:[^)(]*(?R)?)*+\)
Demo tại regex101 ; Hoặc trải ra để thực hiện:
\([^)(]*+(?:(?R)[^)(]*)*+\)
Demo tại regex101 ; Các mô hình được dán tại (?R)
đó đại diện (?0)
.
Perl, PHP, Notepad ++, R : perl = TRUE , Python : Gói Regex với (?V1)
hành vi Perl.
Ruby sử dụng các cuộc gọi subexpression .
Với Ruby 2.0 \g<0>
có thể được sử dụng để gọi mô hình đầy đủ.
\((?>[^)(]+|\g<0>)*\)
Demo tại Rubular ; Ruby 1.9 chỉ hỗ trợ chụp đệ quy nhóm :
(\((?>[^)(]+|\g<1>)*\))
Bản trình diễn tại Rubular ( nhóm nguyên tử kể từ Ruby 1.9.3)
API JavaScript :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\\(', '\\)', 'g');
JS, Java và các hương vị regex khác mà không cần đệ quy lên đến 2 cấp độ lồng nhau:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
Bản trình diễn tại regex101 . Làm tổ sâu hơn cần được thêm vào mô hình.
Để thất bại nhanh hơn trên dấu ngoặc đơn không cân bằng, hãy bỏ bộ +
định lượng.
Java : Một ý tưởng thú vị sử dụng các tài liệu tham khảo chuyển tiếp của @jaytea .
(?>[^)(]+|(?R))*+
cũng giống như viết (?:[^)(]+|(?R))*+
. Điều tương tự cho các mẫu tiếp theo. Về phiên bản chưa được kiểm soát, bạn có thể đặt bộ định lượng sở hữu ở đây: [^)(]*+
để ngăn chặn quay lại (trong trường hợp không có khung đóng).
(...(..)..(..)..(..)..(..)..)
) trong chuỗi chủ đề), bạn có thể sử dụng một nhóm không bắt giữ đơn giản và đặt tất cả trong một nhóm nguyên tử: (?>(?:[^)(]+|\g<1>)*)
( điều này hoạt động chính xác như một bộ định lượng sở hữu). Trong Ruby 2.x, bộ định lượng sở hữu có sẵn.
Bạn có thể sử dụng đệ quy regex :
\(([^()]|(?R))*\)
Unrecognized grouping construct
.
[^\(]*(\(.*\))[^\)]*
[^\(]*
khớp với mọi thứ không phải là dấu ngoặc mở ở đầu chuỗi, (\(.*\))
nắm bắt chuỗi con được yêu cầu kèm theo trong ngoặc và [^\)]*
khớp với mọi thứ không phải là dấu ngoặc đóng ở cuối chuỗi. Lưu ý rằng biểu thức này không cố gắng khớp dấu ngoặc; một trình phân tích cú pháp đơn giản (xem câu trả lời của dehmann ) sẽ phù hợp hơn cho điều đó.
(?<=\().*(?=\))
Nếu bạn muốn chọn văn bản giữa hai dấu ngoặc đơn phù hợp , bạn sẽ không gặp may với các biểu thức thông thường. Điều này là không thể (*) .
Regex này chỉ trả về văn bản giữa lần mở đầu tiên và dấu ngoặc đơn đóng cuối cùng trong chuỗi của bạn.
(*) Trừ khi công cụ regex của bạn có các tính năng như cân bằng các nhóm hoặc đệ quy . Số lượng động cơ hỗ trợ các tính năng như vậy đang tăng chậm, nhưng chúng vẫn không phải là phổ biến.
Câu trả lời này giải thích giới hạn lý thuyết về lý do tại sao các biểu thức chính quy không phải là công cụ phù hợp cho nhiệm vụ này.
Biểu thức thông thường không thể làm điều này.
Biểu thức chính quy dựa trên một mô hình điện toán được gọi là Finite State Automata (FSA)
. Như tên cho thấy, a chỉ FSA
có thể nhớ trạng thái hiện tại, nó không có thông tin về các trạng thái trước đó.
Trong sơ đồ trên, S1 và S2 là hai trạng thái trong đó S1 là bước bắt đầu và bước cuối cùng. Vì vậy, nếu chúng ta thử với chuỗi 0110
, quá trình chuyển đổi sẽ diễn ra như sau:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
Trong các bước trên, khi chúng ta đang ở thứ hai S2
tức là sau khi phân tích cú pháp 01
của 0110
, FSA không có thông tin về trước 0
trong 01
khi nó chỉ có thể nhớ hiện trạng và biểu tượng đầu vào tiếp theo.
Trong vấn đề trên, chúng ta cần biết không mở ngoặc đơn; điều này có nghĩa là nó phải được lưu trữ ở một nơi nào đó. Nhưng vì FSAs
không thể làm điều đó, một biểu thức thông thường không thể được viết.
Tuy nhiên, một thuật toán có thể được viết để thực hiện nhiệm vụ này. Các thuật toán thường rơi vào Pushdown Automata (PDA)
. PDA
là một cấp trên FSA
. PDA có một ngăn xếp bổ sung để lưu trữ một số thông tin bổ sung. Các thiết bị PDA có thể được sử dụng để giải quyết vấn đề trên, bởi vì chúng ta có thể ' push
' dấu ngoặc đơn mở trong ngăn xếp và ' pop
' chúng một khi chúng ta gặp dấu ngoặc đơn đóng. Nếu ở cuối, ngăn xếp trống, sau đó mở dấu ngoặc đơn và đóng dấu ngoặc đơn khớp. Nếu không thì không.
Thực sự có thể làm điều đó bằng cách sử dụng các biểu thức chính quy .NET, nhưng nó không tầm thường, vì vậy hãy đọc kỹ.
Bạn có thể đọc một bài viết tốt đẹp ở đây . Bạn cũng có thể cần phải đọc lên biểu thức chính quy .NET. Bạn có thể bắt đầu đọc ở đây .
Dấu ngoặc góc <>
được sử dụng vì chúng không yêu cầu thoát.
Biểu thức chính quy trông như thế này:
<
[^<>]*
(
(
(?<Open><)
[^<>]*
)+
(
(?<Close-Open>>)
[^<>]*
)+
)*
(?(Open)(?!))
>
Đây là regex dứt khoát:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
Thí dụ:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
lưu ý rằng '(pip'
được quản lý chính xác như chuỗi. (đã thử trong bộ điều chỉnh: http://sourceforge.net/projects/regulator/ )
Tôi đã viết một thư viện JavaScript nhỏ gọi là cân bằng để trợ giúp với nhiệm vụ này. Bạn có thể thực hiện điều này bằng cách làm
balanced.matches({
source: source,
open: '(',
close: ')'
});
Bạn thậm chí có thể thay thế:
balanced.replacements({
source: source,
open: '(',
close: ')',
replace: function (source, head, tail) {
return head + source + tail;
}
});
Đây là một ví dụ phức tạp và tương tác hơn JSFiddle .
Thêm vào câu trả lời của bong bóng bobble , có những hương vị regex khác trong đó các cấu trúc đệ quy được hỗ trợ.
Lua
Sử dụng %b()
( %b{}
/ %b[]
cho dấu ngoặc nhọn / dấu ngoặc vuông):
for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end
(xem bản demo )Perl6 :
Không trùng lặp nhiều dấu ngoặc đơn cân bằng:
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))」)
Chồng chéo nhiều dấu ngoặc đơn cân bằng khớp:
say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)
Xem bản demo .
re
Giải pháp Python không regex
Xem câu trả lời của poke để biết cách nhận biểu thức giữa các dấu ngoặc đơn cân bằng .
Giải pháp phi regex tùy biến Java
Đây là một giải pháp tùy biến cho phép các ký tự phân cách bằng ký tự đơn trong 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;
}
}
Sử dụng mẫu:
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)))]
Biểu thức chính quy sử dụng Ruby (phiên bản 1.9.3 trở lên):
/(?<match>\((?:\g<match>|[^()]++)*\))/
Bạn cần dấu ngoặc đơn đầu tiên và cuối cùng. Sử dụng một cái gì đó như thế này:
str.indexOf ('('); - nó sẽ cung cấp cho bạn lần xuất hiện đầu tiên
str.last IndexOf (')'); - cái cuối cùng
Vì vậy, bạn cần một chuỗi giữa,
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"
Câu trả lời phụ thuộc vào việc bạn có cần khớp các bộ ngoặc khớp hay chỉ đơn giản là lần mở đầu tiên đến lần đóng cuối cùng trong văn bản đầu vào.
Nếu bạn cần khớp các dấu ngoặc khớp, thì bạn cần một cái gì đó nhiều hơn các biểu thức thông thường. - xem @dehmann
Nếu nó chỉ mở lần đầu tiên để xem lần cuối, hãy xem @Zach
Quyết định những gì bạn muốn xảy ra với:
abc ( 123 ( foobar ) def ) xyz ) ghij
Bạn cần phải quyết định những gì mã của bạn cần phải phù hợp trong trường hợp này.
bởi vì js regex không hỗ trợ kết hợp đệ quy, tôi không thể tạo dấu ngoặc đơn cân bằng phù hợp với công việc.
Vì vậy, đây là một javascript đơn giản cho phiên bản vòng lặp làm cho chuỗi "method (arg)" thành mảng
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)
kết quả là như thế
[ { 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' } ]
Trong khi rất nhiều câu trả lời đề cập đến điều này trong một số hình thức bằng cách nói rằng regex không hỗ trợ kết hợp đệ quy và vân vân, lý do chính cho điều này nằm ở gốc rễ của Lý thuyết tính toán.
Ngôn ngữ của hình thức {a^nb^n | n>=0} is not regular
. Regex chỉ có thể phù hợp với những thứ tạo thành một phần của bộ ngôn ngữ thông thường.
Đọc thêm tại đây
Tôi đã không sử dụng regex vì rất khó để xử lý mã lồng nhau. Vì vậy, đoạn mã này sẽ có thể cho phép bạn lấy các phần của mã với dấu ngoặc cân bằng:
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
Tôi đã sử dụng điều này để trích xuất các đoạn mã từ một tệp văn bản.
Cái này cũng làm việc
re.findall(r'\(.+\)', s)
Điều này có thể hữu ích với một số:
Ở đây bạn có thể thấy regrec được tạo trong hành động
/**
* 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;
};
Điều này không giải quyết đầy đủ câu hỏi OP nhưng tôi mặc dù nó có thể hữu ích cho một số người đến đây để tìm kiếm regrec cấu trúc lồng nhau.