Cách thoát văn bản cho biểu thức chính quy trong Java


320

Java có cách dựng sẵn để thoát văn bản tùy ý để có thể đưa nó vào một biểu thức chính quy không? Ví dụ: nếu người dùng của tôi nhập "$ 5", tôi muốn khớp chính xác hơn là "5" sau khi kết thúc đầu vào.

Câu trả lời:


450

Kể từ Java 1.5, có :

Pattern.quote("$5");

88
Xin lưu ý rằng điều này không thoát khỏi chuỗi, nhưng kết thúc bằng cách sử dụng \Q\E. Điều này có thể dẫn đến kết quả không mong muốn, ví dụ Pattern.quote("*.wav").replaceAll("*",".*")sẽ dẫn đến \Q.*.wav\Evà không .*\.wav, như bạn có thể mong đợi.
Matthias Rrid

11
@Paramaeleon Tại sao bạn mong đợi foo (x) .bar () == x.bar ()?
Michael

7
@Paramaeleon Tôi nghĩ bạn đang hiểu nhầm trường hợp sử dụng.
vikingsteve

18
Tôi chỉ muốn chỉ ra rằng cách thoát này cũng áp dụng lối thoát trên các biểu thức mà bạn giới thiệu sau đó . Điều này có thể gây ngạc nhiên. Nếu bạn làm điều "mouse".toUpperCase().replaceAll("OUS","ic")đó sẽ trở lại MicE. Bạn would't hy vọng nó sẽ trở lại MICEbởi vì bạn đã không áp dụng toUpperCase()vào ic. Trong ví dụ của tôi quote()cũng được áp dụng trên .*insertet replaceAll(). Bạn phải làm một cái gì đó khác, có lẽ .replaceAll("*","\\E.*\\Q")sẽ làm việc, nhưng đó là phản trực giác.
Matthias Rrid

2
@Paramaleon Nếu nó hoạt động bằng cách thêm các lối thoát riêng lẻ, ví dụ ban đầu của bạn vẫn không làm những gì bạn muốn ... nếu nó thoát các ký tự riêng lẻ, nó sẽ biến *.wavthành mô hình regex \*\.wav, và thay thế All sẽ biến nó thành \.*\.wav, nghĩa là nó sẽ biến thành khớp các tệp có tên bao gồm một số lượng thời gian tùy ý theo sau .wav. Bạn rất có thể cần phải làm replaceAll("\\*", ".*")như vậy nếu họ đã đi với việc triển khai mỏng manh hơn dựa vào việc nhận ra tất cả các trình duyệt regex hoạt động có thể có và thoát chúng riêng lẻ ... điều đó có dễ dàng hơn nhiều không?
Theodore Murdock

112

Sự khác biệt giữa Pattern.quoteMatcher.quoteReplacementkhông rõ ràng với tôi trước khi tôi thấy ví dụ sau

s.replaceFirst(Pattern.quote("text to replace"), 
               Matcher.quoteReplacement("replacement text"));

29
Cụ thể, Pattern.quotethay thế các ký tự đặc biệt trong chuỗi tìm kiếm regex, như. | + () V.v. và Matcher.quoteReplacementthay thế các ký tự đặc biệt trong chuỗi thay thế, như \ 1 cho các phản hồi.
Steven

9
Tôi không đồng ý. Pattern.quote kết thúc đối số của nó bằng \ Q và \ E. Nó không thoát khỏi các ký tự đặc biệt.
David Medinets

5
Matcher.quoteReplocation ("4 $ &% $") tạo ra "4 \ $ &% \ $". Nó thoát khỏi các nhân vật đặc biệt.
David Medinets

4
Nói cách khác: quoteReplacementchỉ quan tâm đến hai biểu tượng $\ ví dụ có thể được sử dụng trong các chuỗi thay thế như phản hồi $1hoặc \1. Do đó, nó không được sử dụng để thoát / trích dẫn một biểu thức chính quy.
SebastianH

1
Tuyệt vời. Dưới đây là một ví dụ mà chúng tôi muốn thay thế $Group$bằng T$UYO$HI. Các $biểu tượng là đặc biệt cả về mô hình và trong việc thay thế:"$Group$ Members".replaceFirst(Pattern.quote("$Group$"), Matcher.quoteReplacement("T$UYO$HI"))
Arun

29

Có thể quá muộn để trả lời, nhưng bạn cũng có thể sử dụng Pattern.LITERAL, sẽ bỏ qua tất cả các ký tự đặc biệt trong khi định dạng:

Pattern.compile(textToFormat, Pattern.LITERAL);

Điều này đặc biệt tốt vì bạn có thể kết hợp nó vớiPattern.CASE_INSENSITIVE
mj cameraec

13

Tôi nghĩ những gì bạn đang theo đuổi là \Q$5\E. Cũng thấyPattern.quote(s) giới thiệu trong Java5.

Xem mẫu javadoc để biết chi tiết.


Tôi tò mò liệu có bất kỳ sự khác biệt nào giữa việc này và sử dụng cờ LITITH không, vì javadoc nói rằng không có cờ nhúng để bật và tắt LITITH
chụp

15
Lưu ý rằng theo nghĩa đen sử dụng \ Q và \ E chỉ tốt nếu bạn biết đầu vào của mình. Pattern.quote (s) cũng sẽ xử lý trường hợp văn bản của bạn thực sự chứa các chuỗi này.
Jeremy Huiskamp

10

Trước hết, nếu

  • bạn sử dụng thay thế All ()
  • bạn KHÔNG sử dụng Matcher.quoteReplocation ()
  • văn bản được thay thế bao gồm $ 1

nó sẽ không đặt 1 ở cuối Nó sẽ xem xét biểu thức tìm kiếm cho nhóm phù hợp đầu tiên và THAT phụ. Đó là $ 1, $ 2 hoặc $ 3 có nghĩa là gì trong văn bản thay thế: nhóm phù hợp từ mẫu tìm kiếm.

Tôi thường xuyên cắm các chuỗi văn bản dài vào các tệp .properations, sau đó tạo các chủ đề và nội dung email từ các tệp đó. Thật vậy, điều này dường như là cách mặc định để thực hiện i18n trong Spring Framework. Tôi đặt các thẻ XML, với tư cách là các trình giữ chỗ, vào các chuỗi và tôi sử dụng thay thế All () để thay thế các thẻ XML bằng các giá trị khi chạy.

Tôi gặp phải một vấn đề trong đó người dùng nhập số đô la và xu, với ký hiệu đô la. thayTất cả () nghẹn ngào với nó, với phần sau hiển thị trong một chuỗi:

java.lang.IndexOutOfBoundsException: No group 3
at java.util.regex.Matcher.start(Matcher.java:374)
at java.util.regex.Matcher.appendReplacement(Matcher.java:748)
at java.util.regex.Matcher.replaceAll(Matcher.java:823)
at java.lang.String.replaceAll(String.java:2201)

Trong trường hợp này, người dùng đã nhập "$ 3" ở đâu đó trong đầu vào của họ và thay thế All () đã tìm kiếm trong biểu thức tìm kiếm cho nhóm khớp thứ ba, không tìm thấy và bị loại.

Được:

// "msg" is a string from a .properties file, containing "<userInput />" among other tags
// "userInput" is a String containing the user's input

thay thế

msg = msg.replaceAll("<userInput \\/>", userInput);

với

msg = msg.replaceAll("<userInput \\/>", Matcher.quoteReplacement(userInput));

Đã giải quyết vấn đề. Người dùng có thể đặt bất kỳ loại ký tự nào, kể cả ký hiệu đô la, mà không có vấn đề. Nó hành xử chính xác theo cách bạn mong đợi.


6

Để có mẫu được bảo vệ, bạn có thể thay thế tất cả các ký hiệu bằng "\\\\", ngoại trừ chữ số và chữ cái. Và sau đó, bạn có thể đặt vào mẫu được bảo vệ đó các ký hiệu đặc biệt của mình để làm cho mẫu này hoạt động không giống như văn bản trích dẫn ngu ngốc, nhưng thực sự giống như một patten, nhưng là của riêng bạn. Không có biểu tượng người dùng đặc biệt.

public class Test {
    public static void main(String[] args) {
        String str = "y z (111)";
        String p1 = "x x (111)";
        String p2 = ".* .* \\(111\\)";

        p1 = escapeRE(p1);

        p1 = p1.replace("x", ".*");

        System.out.println( p1 + "-->" + str.matches(p1) ); 
            //.*\ .*\ \(111\)-->true
        System.out.println( p2 + "-->" + str.matches(p2) ); 
            //.* .* \(111\)-->true
    }

    public static String escapeRE(String str) {
        //Pattern escaper = Pattern.compile("([^a-zA-z0-9])");
        //return escaper.matcher(str).replaceAll("\\\\$1");
        return str.replaceAll("([^a-zA-Z0-9])", "\\\\$1");
    }
}

Bạn không phải trốn không gian. Vì vậy, bạn có thể chọn mẫu của bạn thành "([^ a-zA-z0-9])".
Erel Segal-Halevi

5
Lỗi đánh máy nhỏ, hậu quả lớn: "([^ a-zA-z0-9])" cũng không khớp (nghĩa là không thoát) [, \,], ^ mà bạn chắc chắn muốn thoát! Lỗi đánh máy là 'z' thứ hai phải là 'Z', nếu không, mọi thứ từ ASCII 65 đến ASCII 122 đều được bao gồm
Zefiro

3

Pattern.quote ("blabla") hoạt động độc đáo.

Pattern.quote () hoạt động độc đáo. Nó kèm theo câu với các ký tự " \ Q " và " \ E " và nếu nó thoát "\ Q" và "\ E". Tuy nhiên, nếu bạn cần thực hiện thoát biểu thức chính quy thực (hoặc thoát tùy chỉnh), bạn có thể sử dụng mã này:

String someText = "Some/s/wText*/,**";
System.out.println(someText.replaceAll("[-\\[\\]{}()*+?.,\\\\\\\\^$|#\\\\s]", "\\\\$0"));

Phương thức này trả về: Một số / \ s / wText * / \, **

Mã ví dụ và kiểm tra:

String someText = "Some\\E/s/wText*/,**";
System.out.println("Pattern.quote: "+ Pattern.quote(someText));
System.out.println("Full escape: "+someText.replaceAll("[-\\[\\]{}()*+?.,\\\\\\\\^$|#\\\\s]", "\\\\$0"));

-2

Biểu tượng ^ (Phủ định) được sử dụng để khớp với thứ không thuộc nhóm nhân vật.

Đây là liên kết đến Biểu thức chính quy

Dưới đây là thông tin hình ảnh về phủ định:

Thông tin về phủ định

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.