Hans, tôi sẽ cắn câu và xác định câu trả lời trước đó của mình. Bạn nói rằng bạn muốn "một cái gì đó hoàn chỉnh hơn", vì vậy tôi hy vọng bạn sẽ không bận tâm đến câu trả lời dài dòng — chỉ đang cố gắng làm hài lòng. Hãy bắt đầu với một số nền tảng.
Trước hết, đây là một câu hỏi tuyệt vời. Thường có những câu hỏi về việc đối sánh các mẫu nhất định ngoại trừ trong một số ngữ cảnh nhất định (ví dụ: trong một khối mã hoặc bên trong dấu ngoặc đơn). Những câu hỏi này thường dẫn đến những giải pháp khá khó xử. Vì vậy, câu hỏi của bạn về nhiều ngữ cảnh là một thách thức đặc biệt.
Sự ngạc nhiên
Đáng ngạc nhiên là có ít nhất một giải pháp hiệu quả chung, dễ thực hiện và dễ duy trì. Nó hoạt động với tất cả các hương vị regex cho phép bạn kiểm tra các nhóm nắm bắt trong mã của mình. Và nó tình cờ trả lời một số câu hỏi phổ biến thoạt nghe có vẻ khác với câu hỏi của bạn: "khớp với mọi thứ ngoại trừ Bánh rán", "thay thế tất cả trừ ...", "khớp với tất cả các từ ngoại trừ những từ trong danh sách đen của mẹ tôi", "bỏ qua các thẻ "," khớp nhiệt độ trừ khi được in nghiêng "...
Đáng buồn thay, kỹ thuật này không được biết đến nhiều: Tôi ước tính rằng trong 20 câu hỏi SO có thể sử dụng nó, chỉ có một câu trả lời có một câu trả lời đề cập đến nó — có nghĩa là có thể một trong năm mươi hoặc sáu mươi câu trả lời. Xem cuộc trao đổi của tôi với Kobi trong phần bình luận. Kỹ thuật này được mô tả một cách chuyên sâu trong bài viết này và gọi nó (một cách lạc quan) là "thủ thuật regex tốt nhất từ trước đến nay". Không đi sâu vào chi tiết, tôi sẽ cố gắng cung cấp cho bạn một cách chắc chắn về cách thức hoạt động của kỹ thuật này. Để biết thêm chi tiết và các mẫu mã bằng nhiều ngôn ngữ khác nhau, tôi khuyến khích bạn tham khảo tài nguyên đó.
Một biến thể được biết đến nhiều hơn
Có một biến thể sử dụng cú pháp cụ thể cho Perl và PHP thực hiện tương tự. Bạn sẽ thấy nó trên SO dưới bàn tay của các bậc thầy regex như CasimiretHippolyte và HamZa . Tôi sẽ cho bạn biết thêm về điều này bên dưới, nhưng trọng tâm của tôi ở đây là giải pháp chung hoạt động với tất cả các hương vị regex (miễn là bạn có thể kiểm tra các nhóm nắm bắt trong mã của mình).
Cảm ơn vì tất cả nền, zx81 ... Nhưng công thức là gì?
Thực tế then chốt
Phương thức này trả về kết quả phù hợp trong chụp Nhóm 1. Nó không quan tâm chút nào đến trận đấu tổng thể.
Trên thực tế, mẹo là để khớp các ngữ cảnh khác nhau mà chúng ta không muốn (xâu chuỗi các ngữ cảnh này bằng cách sử dụng |
OR / luân phiên) để "vô hiệu hóa chúng". Sau khi phù hợp với tất cả các tình huống không mong muốn, phần cuối cùng của luân phiên phù hợp với những gì chúng ta làm muốn và chụp nó vào nhóm 1.
Công thức chung là
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
Điều này sẽ khớp Not_this_context
, nhưng theo nghĩa nào đó, trận đấu đó sẽ trở thành thùng rác, bởi vì chúng tôi sẽ không xem xét các trận đấu tổng thể: chúng tôi chỉ xem xét ảnh chụp của Nhóm 1.
Trong trường hợp của bạn, với các chữ số và ba ngữ cảnh cần bỏ qua, chúng tôi có thể làm:
s1|s2|s3|(\b\d+\b)
Lưu ý rằng vì chúng tôi thực sự đối sánh s1, s2 và s3 thay vì cố gắng tránh chúng bằng cách nhìn xung quanh, các biểu thức riêng lẻ cho s1, s2 và s3 có thể vẫn rõ ràng như ngày. (Chúng là biểu thức con ở mỗi bên của a |
)
Toàn bộ biểu thức có thể được viết như thế này:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
Xem bản trình diễn này (nhưng tập trung vào các nhóm chụp ở ngăn dưới bên phải.)
Nếu bạn cố gắng chia nhỏ regex này ở mỗi |
dấu phân cách, nó thực sự chỉ là một chuỗi bốn biểu thức rất đơn giản.
Đối với các hương vị hỗ trợ khoảng cách trống, điều này đặc biệt tốt.
(?mx)
### s1: Match line that ends with a period ###
^.*\.$
| ### OR s2: Match anything between parentheses ###
\([^\)]*\)
| ### OR s3: Match any if(...//endif block ###
if\(.*?//endif
| ### OR capture digits to Group 1 ###
(\b\d+\b)
Điều này đặc biệt dễ đọc và dễ bảo trì.
Mở rộng regex
Khi bạn muốn bỏ qua các tình huống khác s4 và s5, bạn thêm chúng vào các thay thế khác ở bên trái:
s4|s5|s1|s2|s3|(\b\d+\b)
Cái này hoạt động ra sao?
Các ngữ cảnh bạn không muốn sẽ được thêm vào danh sách thay thế ở bên trái: chúng sẽ khớp với nhau, nhưng những kết quả trùng khớp tổng thể này không bao giờ được kiểm tra, vì vậy việc so khớp chúng là một cách để đưa chúng vào "thùng rác".
Tuy nhiên, nội dung bạn muốn sẽ được chuyển vào Nhóm 1. Sau đó, bạn phải kiểm tra theo chương trình xem Nhóm 1 có được đặt và không trống hay không. Đây là một nhiệm vụ lập trình tầm thường (và sau này chúng ta sẽ nói về cách nó được thực hiện), đặc biệt là khi nó để lại cho bạn một regex đơn giản mà bạn có thể hiểu trong nháy mắt và sửa đổi hoặc mở rộng theo yêu cầu.
Tôi không phải lúc nào cũng thích hình dung, nhưng cái này làm rất tốt việc chỉ ra phương pháp đơn giản như thế nào. Mỗi "dòng" tương ứng với một trận đấu tiềm năng, nhưng chỉ dòng cuối cùng được đưa vào Nhóm 1.

Bản trình diễn gỡ lỗi
Biến thể Perl / PCRE
Trái ngược với giải pháp chung ở trên, tồn tại một biến thể cho Perl và PCRE thường thấy trên SO, ít nhất là trong tay của các Thần regex như @CasimiretHippolyte và @HamZa. Nó là:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
Trong trường hợp của bạn:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
Biến thể này dễ sử dụng hơn một chút vì nội dung phù hợp trong ngữ cảnh s1, s2 và s3 chỉ đơn giản là bỏ qua, vì vậy bạn không cần phải kiểm tra các ảnh chụp Nhóm 1 (lưu ý rằng dấu ngoặc đơn đã biến mất). Các trận đấu chỉ chứawhatYouWant
Lưu ý rằng (*F)
, (*FAIL)
và (?!)
tất cả đều giống nhau. Nếu bạn muốn mờ mịt hơn, bạn có thể sử dụng(*SKIP)(?!)
demo cho phiên bản này
Các ứng dụng
Dưới đây là một số vấn đề phổ biến mà kỹ thuật này thường có thể dễ dàng giải quyết. Bạn sẽ nhận thấy rằng lựa chọn từ có thể làm cho một số vấn đề này nghe có vẻ khác biệt trong khi trên thực tế, chúng hầu như giống hệt nhau.
- Làm cách nào để đối sánh foo ngoại trừ bất kỳ vị trí nào trong thẻ như thế
<a stuff...>...</a>
nào?
- Làm cách nào để đối sánh foo ngoại trừ trong
<i>
thẻ hoặc đoạn mã javascript (thêm điều kiện)?
- Làm cách nào để ghép tất cả các từ không có trong danh sách đen này?
- Làm thế nào tôi có thể bỏ qua bất cứ điều gì bên trong một khối SUB ... KẾT THÚC SUB?
- Làm cách nào để ghép mọi thứ ngoại trừ ... s1 s2 s3?
Cách lập trình Chụp nhóm 1
Bạn không phải đối với mã, nhưng để hoàn thành ... Mã để kiểm tra Nhóm 1 rõ ràng sẽ phụ thuộc vào ngôn ngữ bạn chọn. Ở bất kỳ mức độ nào, nó không nên thêm nhiều hơn một vài dòng vào mã bạn sẽ sử dụng để kiểm tra các kết quả phù hợp.
Nếu nghi ngờ, tôi khuyên bạn nên xem phần mẫu mã của bài viết đã đề cập trước đó, phần này trình bày mã cho khá nhiều ngôn ngữ.
Giải pháp thay thế
Tùy thuộc vào mức độ phức tạp của câu hỏi và công cụ regex được sử dụng, có một số lựa chọn thay thế. Đây là hai điều kiện có thể áp dụng cho hầu hết các trường hợp, bao gồm nhiều điều kiện. Theo quan điểm của tôi, s1|s2|s3|(whatYouWant)
công thức này gần như không hấp dẫn bằng , nếu chỉ vì sự rõ ràng luôn chiến thắng.
1. Replace then Match.
Một giải pháp tốt nghe có vẻ khó hiểu nhưng hoạt động tốt trong nhiều môi trường là thực hiện theo hai bước. Một regex đầu tiên vô hiệu hóa ngữ cảnh bạn muốn bỏ qua bằng cách thay thế các chuỗi có thể xung đột. Nếu bạn chỉ muốn đối sánh, thì bạn có thể thay thế bằng một chuỗi trống, sau đó chạy đối sánh của bạn ở bước thứ hai. Nếu bạn muốn thay thế, trước tiên bạn có thể thay thế các chuỗi bị bỏ qua bằng một thứ gì đó khác biệt, chẳng hạn bao quanh các chữ số của bạn bằng một chuỗi có chiều rộng cố định là @@@
. Sau lần thay thế này, bạn có thể tự do thay thế những gì bạn thực sự muốn, sau đó bạn sẽ phải hoàn nguyên các @@@
chuỗi đặc biệt của mình .
2. Cách nhìn.
Bài đăng ban đầu của bạn cho thấy rằng bạn hiểu cách loại trừ một điều kiện duy nhất bằng cách sử dụng cách xem xét. Bạn đã nói rằng C # là tuyệt vời cho điều này, và bạn đã đúng, nhưng nó không phải là lựa chọn duy nhất. Ví dụ, các phiên bản .NET regex được tìm thấy trong C #, VB.NET và Visual C ++, cũng như regex
mô-đun vẫn đang thử nghiệm để thay thế re
bằng Python, là hai công cụ duy nhất mà tôi biết hỗ trợ lookbehind chiều rộng vô hạn. Với những công cụ này, một điều kiện trong một cái nhìn sau có thể giúp bạn không chỉ nhìn về phía sau mà còn cả trận đấu và xa hơn trận đấu, tránh phải phối hợp với một cái nhìn trước. Thêm điều kiện? Nhiều cách nhìn hơn.
Tái chế regex bạn có cho s3 trong C #, toàn bộ mẫu sẽ giống như thế này.
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
Nhưng bây giờ bạn biết tôi không đề xuất điều này, phải không?
Xóa
@HamZa và @Jerry đã đề xuất tôi đề cập đến một thủ thuật bổ sung cho các trường hợp khi bạn tìm cách xóa WhatYouWant
. Bạn nhớ rằng công thức để phù hợp WhatYouWant
(nắm bắt nó vào Nhóm 1) s1|s2|s3|(WhatYouWant)
, phải không? Để xóa tất cả các phiên bản của WhatYouWant
, bạn thay đổi regex thành
(s1|s2|s3)|WhatYouWant
Đối với chuỗi thay thế, bạn sử dụng $1
. Điều xảy ra ở đây là đối với mỗi phiên bản s1|s2|s3
được so khớp, phần thay $1
thế sẽ thay thế phiên bản đó bằng chính nó (được tham chiếu bởi $1
). Mặt khác, khi WhatYouWant
được so khớp, nó được thay thế bằng một nhóm trống và không có gì khác - và do đó bị xóa. Hãy xem bản demo này , cảm ơn bạn @HamZa và @Jerry đã đề xuất bổ sung tuyệt vời này.
Thay thế
Điều này đưa chúng ta đến các thiết bị thay thế mà tôi sẽ đề cập ngắn gọn.
- Khi thay thế bằng không, hãy xem thủ thuật "Xóa" ở trên.
- Khi thay thế, nếu sử dụng Perl hoặc PCRE, hãy sử dụng
(*SKIP)(*F)
biến thể được đề cập ở trên để khớp chính xác những gì bạn muốn và thực hiện thay thế ngay lập tức.
- Trong các phiên bản khác, trong lệnh gọi hàm thay thế, hãy kiểm tra khớp bằng cách sử dụng lệnh gọi lại hoặc lambda và thay thế nếu Nhóm 1 được đặt. Nếu bạn cần trợ giúp về vấn đề này, bài viết đã được tham khảo sẽ cung cấp cho bạn mã bằng nhiều ngôn ngữ khác nhau.
Chúc vui vẻ!
Không, chờ đã, còn nhiều hơn nữa!
À, không, tôi sẽ để dành số đó cho hồi ký của mình thành hai mươi tập, sẽ được phát hành vào mùa xuân năm sau.
\K
không phải là cú pháp php đặc biệt. Hãy giải thích và làm rõ những gì bạn muốn nói. Nếu bạn muốn nói với chúng tôi rằng bạn không cần một giải pháp "phức tạp", bạn phải nói điều gì phức tạp đối với bạn và tại sao.