Saya perlu ekspresi reguler untuk memilih semua teks di antara dua kurung luar.
Contoh: some text(text here(possible text)text(possible text(more text)))end text
Hasil: (text here(possible text)text(possible text(more text)))
Saya perlu ekspresi reguler untuk memilih semua teks di antara dua kurung luar.
Contoh: some text(text here(possible text)text(possible text(more text)))end text
Hasil: (text here(possible text)text(possible text(more text)))
Jawaban:
Ekspresi reguler adalah alat yang salah untuk pekerjaan itu karena Anda berurusan dengan struktur bersarang, yaitu rekursi.
Tetapi ada algoritma sederhana untuk melakukan ini, yang saya jelaskan dalam jawaban untuk pertanyaan sebelumnya .
Saya ingin menambahkan jawaban ini untuk referensi cepat. Jangan ragu untuk memperbarui.
.NET Regex menggunakan grup penyeimbang .
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
Di mana c
digunakan sebagai penghitung kedalaman.
PCRE menggunakan pola rekursif .
\((?:[^)(]+|(?R))*+\)
Demo di regex101 ; Atau tanpa pergantian:
\((?:[^)(]*(?R)?)*+\)
Demo di regex101 ; Atau belum dibuka untuk kinerja:
\([^)(]*+(?:(?R)[^)(]*)*+\)
Demo di regex101 ; Pola disisipkan di (?R)
mana mewakili (?0)
.
Perl, PHP, Notepad ++, R : perl = BENAR , Paket Python : Regex dengan (?V1)
untuk perilaku Perl.
Ruby menggunakan panggilan subexpression .
Dengan Ruby 2.0 \g<0>
dapat digunakan untuk memanggil pola penuh.
\((?>[^)(]+|\g<0>)*\)
Demo di Rubular ; Ruby 1.9 hanya mendukung pengambilan rekursi grup :
(\((?>[^)(]+|\g<1>)*\))
Demo di Rubular ( pengelompokan atom sejak Ruby 1.9.3)
JavaScript API :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\\(', '\\)', 'g');
JS, Java, dan rasa regex lainnya tanpa rekursi hingga 2 level bersarang:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
Demo di regex101 . Sarang yang lebih dalam perlu ditambahkan ke pola.
Untuk gagal lebih cepat pada tanda kurung tidak seimbang, jatuhkan +
quantifier.
Java : Gagasan menarik menggunakan referensi ke depan oleh @jaytea .
(?>[^)(]+|(?R))*+
sama dengan menulis (?:[^)(]+|(?R))*+
. Hal yang sama untuk pola selanjutnya. Tentang versi yang belum dibuka, Anda dapat meletakkan quantifier posesif di sini: [^)(]*+
untuk mencegah kemunduran (jika tidak ada braket penutup).
(...(..)..(..)..(..)..(..)..)
) dalam string subjek), Anda dapat menggunakan grup non-tangkapan sederhana dan melampirkan semuanya dalam grup atom: (?>(?:[^)(]+|\g<1>)*)
( ini berperilaku persis seperti quantifier posesif). Di Ruby 2.x, kuantifier posesif tersedia.
Anda dapat menggunakan rekursi regex :
\(([^()]|(?R))*\)
Unrecognized grouping construct
.
[^\(]*(\(.*\))[^\)]*
[^\(]*
cocok dengan semua yang bukan braket pembuka di awal string, (\(.*\))
menangkap substring yang diperlukan yang terlampir dalam tanda kurung, dan [^\)]*
cocok dengan semua yang bukan braket penutup di akhir string. Perhatikan bahwa ungkapan ini tidak berusaha mencocokkan tanda kurung; pengurai sederhana (lihat jawaban dehmann ) akan lebih cocok untuk itu.
(?<=\().*(?=\))
Jika Anda ingin memilih teks di antara dua tanda kurung yang cocok , Anda kurang beruntung dengan ekspresi reguler. Ini tidak mungkin (*) .
Regex ini hanya mengembalikan teks antara pembukaan pertama dan kurung tutup terakhir di string Anda.
(*) Kecuali jika mesin regex Anda memiliki fitur seperti kelompok penyeimbang atau rekursi . Jumlah mesin yang mendukung fitur-fitur tersebut perlahan-lahan bertambah, tetapi mereka masih belum tersedia secara umum.
Jawaban ini menjelaskan batasan teoretis mengapa ekspresi reguler bukan alat yang tepat untuk tugas ini.
Ekspresi reguler tidak dapat melakukan ini.
Ekspresi reguler didasarkan pada model komputasi yang dikenal sebagai Finite State Automata (FSA)
. Seperti namanya, a FSA
hanya dapat mengingat keadaan saat ini, ia tidak memiliki informasi tentang keadaan sebelumnya.
Dalam diagram di atas, S1 dan S2 adalah dua negara di mana S1 adalah langkah awal dan akhir. Jadi jika kita mencoba dengan string 0110
, transisi berjalan sebagai berikut:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
Dalam langkah di atas, ketika kita berada di kedua S2
yaitu setelah parsing 01
dari 0110
, FSA tidak memiliki informasi tentang sebelumnya 0
di 01
karena hanya bisa mengingat keadaan saat ini dan simbol input berikutnya.
Dalam masalah di atas, kita perlu mengetahui no dari tanda kurung buka; ini berarti harus disimpan di suatu tempat. Tetapi karena FSAs
tidak bisa melakukan itu, ekspresi reguler tidak dapat ditulis.
Namun, suatu algoritma dapat ditulis untuk melakukan tugas ini. Algoritma umumnya jatuh di bawah Pushdown Automata (PDA)
. PDA
adalah satu tingkat di atas FSA
. PDA memiliki tumpukan tambahan untuk menyimpan beberapa informasi tambahan. PDA dapat digunakan untuk memecahkan masalah di atas, karena kita dapat ' push
' membuka tanda kurung di tumpukan dan ' pop
' mereka begitu kita menemukan tanda kurung penutup. Jika pada akhirnya, tumpukan kosong, lalu buka tanda kurung dan tutup tanda kurung cocok. Kalau tidak, tidak.
Sebenarnya mungkin untuk melakukannya menggunakan .NET regular expressions, tetapi tidak sepele, jadi baca dengan cermat.
Anda dapat membaca artikel yang bagus di sini . Anda juga mungkin perlu membaca di .NET regular expressions. Anda dapat mulai membaca di sini .
Kurung sudut <>
digunakan karena mereka tidak perlu keluar.
Ekspresi reguler terlihat seperti ini:
<
[^<>]*
(
(
(?<Open><)
[^<>]*
)+
(
(?<Close-Open>>)
[^<>]*
)+
)*
(?(Open)(?!))
>
Ini adalah regex definitif:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
Contoh:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
perhatikan bahwa '(pip'
dikelola dengan benar sebagai string. (dicoba di regulator: http://sourceforge.net/projects/regulator/ )
Saya telah menulis perpustakaan JavaScript kecil yang disebut seimbang untuk membantu tugas ini. Anda dapat melakukannya dengan melakukan
balanced.matches({
source: source,
open: '(',
close: ')'
});
Anda bahkan dapat melakukan penggantian:
balanced.replacements({
source: source,
open: '(',
close: ')',
replace: function (source, head, tail) {
return head + source + tail;
}
});
Berikut ini contoh JSFiddle yang lebih kompleks dan interaktif .
Menambahkan ke jawaban gelembung berbandul , ada rasa regex lain di mana konstruksi rekursif didukung.
Lua
Gunakan %b()
( %b{}
/ %b[]
untuk kurung kurawal / kurung kotak):
for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end
(lihat demo )Perl6 :
Kecocokan beberapa tanda kurung yang tidak tumpang tindih:
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))」)
Tumpang tindih dengan beberapa tanda kurung yang seimbang:
say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)
Lihat demo .
re
Solusi non-regex Python
Lihat jawaban poke untuk Cara mendapatkan ekspresi di antara tanda kurung yang seimbang .
Java solusi non-regex yang dapat disesuaikan
Berikut adalah solusi yang dapat disesuaikan yang memungkinkan pembatas literal karakter tunggal di Jawa:
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;
}
}
Penggunaan sampel:
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)))]
Ekspresi reguler menggunakan Ruby (versi 1.9.3 atau lebih tinggi):
/(?<match>\((?:\g<match>|[^()]++)*\))/
Anda membutuhkan tanda kurung pertama dan terakhir. Gunakan sesuatu seperti ini:
str.indexOf ('('); - itu akan memberi Anda kejadian pertama
str.lastIndexOf (')'); - terakhir
Jadi, Anda perlu string antara,
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"
Jawabannya tergantung pada apakah Anda harus mencocokkan set kurung yang cocok, atau hanya yang terbuka pertama ke penutup terakhir dalam teks input.
Jika Anda harus mencocokkan tanda kurung bersarang, maka Anda memerlukan sesuatu yang lebih dari ekspresi reguler. - lihat @dehmann
Jika hanya terbuka pertama untuk terakhir tutup lihat @ Zach
Putuskan apa yang Anda inginkan terjadi:
abc ( 123 ( foobar ) def ) xyz ) ghij
Anda perlu memutuskan kode apa yang perlu Anda cocokkan dalam kasus ini.
karena js regex tidak mendukung kecocokan rekursif, saya tidak dapat membuat pencocokan tanda kurung yang seimbang berfungsi.
jadi ini adalah javascript sederhana untuk versi loop yang membuat string "method (arg)" menjadi array
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)
hasilnya seperti
[ { 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' } ]
Sementara begitu banyak jawaban menyebutkan hal ini dalam beberapa bentuk dengan mengatakan bahwa regex tidak mendukung pencocokan rekursif dan sebagainya, alasan utama untuk ini terletak pada akar Teori Komputasi.
Bahasa formulir {a^nb^n | n>=0} is not regular
. Regex hanya dapat mencocokkan hal-hal yang merupakan bagian dari rangkaian bahasa reguler.
Baca lebih lanjut @ sini
Saya tidak menggunakan regex karena sulit untuk berurusan dengan kode bersarang. Jadi cuplikan ini harus memungkinkan Anda untuk mengambil bagian kode dengan tanda kurung seimbang:
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
Saya menggunakan ini untuk mengekstrak cuplikan kode dari file teks.
Yang ini juga berhasil
re.findall(r'\(.+\)', s)
Ini mungkin berguna untuk beberapa:
Struktur pertandingan seperti:
Di sini Anda dapat melihat regexp yang dihasilkan beraksi
/**
* 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;
};
Ini tidak sepenuhnya menjawab pertanyaan OP tapi saya pikir mungkin berguna untuk beberapa yang datang ke sini untuk mencari struktur regexp bersarang.