2008-12-17 48 views
33

Tôi đang trong quá trình di chuyển một ứng dụng từ PHP sang Java và có sử dụng nhiều biểu thức chính quy trong mã. Tôi đã chạy qua một cái gì đó trong PHP mà dường như không có một tương đương java:Java tương đương với preg_replace_callback của PHP

preg_replace_callback() 

01 Mỗi lần khớp trong regex, nó gọi một hàm được truyền văn bản khớp như một tham số. Cách sử dụng ví dụ:

$articleText = preg_replace_callback("/\[thumb(\d+)\]/",'thumbReplace', $articleText); 
# ... 
function thumbReplace($matches) { 
    global $photos; 
    return "<img src=\"thumbs/" . $photos[$matches[1]] . "\">"; 
} 

Cách lý tưởng để làm điều này trong Java là gì?

Trả lời

22

QUAN TRỌNG: Như được chỉ ra bởi Kip trong nhận xét, lớp này có lỗi vòng lặp vô hạn nếu đối sánh regex khớp với chuỗi thay thế. Tôi sẽ để nó như một bài tập để người đọc sửa nó, nếu cần thiết.


Tôi không biết bất cứ điều gì tương tự được tích hợp vào Java. Bạn có thể cuộn của riêng bạn mà không cần quá nhiều khó khăn, sử dụng lớp Matcher:

import java.util.regex.*; 

public class CallbackMatcher 
{ 
    public static interface Callback 
    { 
     public String foundMatch(MatchResult matchResult); 
    } 

    private final Pattern pattern; 

    public CallbackMatcher(String regex) 
    { 
     this.pattern = Pattern.compile(regex); 
    } 

    public String replaceMatches(String string, Callback callback) 
    { 
     final Matcher matcher = this.pattern.matcher(string); 
     while(matcher.find()) 
     { 
      final MatchResult matchResult = matcher.toMatchResult(); 
      final String replacement = callback.foundMatch(matchResult); 
      string = string.substring(0, matchResult.start()) + 
        replacement + string.substring(matchResult.end()); 
      matcher.reset(string); 
     } 
    } 
} 

Sau đó gọi:

final CallbackMatcher.Callback callback = new CallbackMatcher.Callback() { 
    public String foundMatch(MatchResult matchResult) 
    { 
     return "<img src=\"thumbs/" + matchResults.group(1) + "\"/>"; 
    } 
}; 

final CallbackMatcher callbackMatcher = new CallbackMatcher("/\[thumb(\d+)\]/"); 
callbackMatcher.replaceMatches(articleText, callback); 

Lưu ý rằng bạn có thể nhận được toàn bộ chuỗi phù hợp bằng cách gọi matchResults.group() hoặc matchResults.group(0), vì vậy nó không cần thiết để chuyển cuộc gọi lại trạng thái chuỗi hiện tại.

EDIT: Làm cho nó trông giống như chức năng chính xác của hàm PHP.

Dưới đây là bản gốc, vì Người hỏi đã thích nó:

public class CallbackMatcher 
{ 
    public static interface Callback 
    { 
     public void foundMatch(MatchResult matchResult); 
    } 

    private final Pattern pattern; 

    public CallbackMatcher(String regex) 
    { 
     this.pattern = Pattern.compile(regex); 
    } 

    public String findMatches(String string, Callback callback) 
    { 
     final Matcher matcher = this.pattern.matcher(string); 
     while(matcher.find()) 
     { 
      callback.foundMatch(matcher.toMatchResult()); 
     } 
    } 
} 

Đối với trường hợp sử dụng cụ thể này, nó có thể là tốt nhất chỉ đơn giản là xếp hàng mỗi trận đấu trong khi gọi lại, rồi sau đó chạy qua chúng về phía sau. Điều này sẽ ngăn chặn việc phải thay đổi các chỉ mục khi chuỗi được sửa đổi.

+0

Tôi thực sự thích câu trả lời ban đầu của bạn tốt hơn với xếp hàng chuỗi trở lại và lập chỉ mục. Sau đó áp dụng chúng ngược lại. Cách này đơn giản hơn, nhưng dường như làm được nhiều việc hơn, phải quét lại toàn bộ chuỗi cho mỗi trận đấu. Cám ơn vì sự gợi ý! – Mike

+0

Tôi đã thêm đề xuất ban đầu trở lại. Kích thước đầu vào được mong đợi sẽ tạo sự khác biệt về việc lưu lại hoặc xếp hàng sau đó thay thế sẽ hiệu quả hơn. Tôi cho rằng người ta cũng có thể có phương pháp thay thế xếp hàng cho họ, cùng với chuỗi thay thế ... – jdmichal

+0

Errr ... Misspoke. Rõ ràng xếp hàng luôn hiệu quả hơn về thời gian CPU. Sự khác biệt sẽ là liệu đó là một vấn đề đủ lớn để lo lắng. – jdmichal

-1

Đây là kết quả cuối cùng của những gì tôi đã làm với đề xuất của bạn. Tôi nghĩ sẽ tốt hơn nếu có ai đó gặp vấn đề tương tự. Mã kết quả gọi trông giống như:

content = ReplaceCallback.find(content, regex, new ReplaceCallback.Callback() { 
    public String matches(MatchResult match) { 
     // Do something special not normally allowed in regex's... 
     return "newstring" 
    } 
}); 

Toàn bộ danh sách lớp sau:

import java.util.regex.MatchResult; 
import java.util.regex.Pattern; 
import java.util.regex.Matcher; 
import java.util.Stack; 

/** 
* <p> 
* Class that provides a method for doing regular expression string replacement by passing the matched string to 
* a function that operates on the string. The result of the operation is then used to replace the original match. 
* </p> 
* <p>Example:</p> 
* <pre> 
* ReplaceCallback.find("string to search on", "/regular(expression/", new ReplaceCallback.Callback() { 
*  public String matches(MatchResult match) { 
*   // query db or whatever... 
*   return match.group().replaceAll("2nd level replacement", "blah blah"); 
*  } 
* }); 
* </pre> 
* <p> 
* This, in effect, allows for a second level of string regex processing. 
* </p> 
* 
*/ 
public class ReplaceCallback { 
    public static interface Callback { 
     public String matches(MatchResult match); 
    } 

    private final Pattern pattern; 
    private Callback callback; 

    private class Result { 
     int start; 
     int end; 
     String replace; 
    } 

    /** 
    * You probably don't need this. {@see find(String, String, Callback)} 
    * @param regex  The string regex to use 
    * @param callback An instance of Callback to execute on matches 
    */ 
    public ReplaceCallback(String regex, final Callback callback) { 
     this.pattern = Pattern.compile(regex); 
     this.callback = callback; 
    } 

    public String execute(String string) { 
     final Matcher matcher = this.pattern.matcher(string); 
     Stack<Result> results = new Stack<Result>(); 
     while(matcher.find()) { 
      final MatchResult matchResult = matcher.toMatchResult(); 
      Result r = new Result(); 
      r.replace = callback.matches(matchResult); 
      if(r.replace == null) 
       continue; 
      r.start = matchResult.start(); 
      r.end = matchResult.end(); 
      results.push(r); 
     } 
     // Improve this with a stringbuilder... 
     while(!results.empty()) { 
      Result r = results.pop(); 
      string = string.substring(0, r.start) + r.replace + string.substring(r.end); 
     } 
     return string; 
    } 

    /** 
    * If you wish to reuse the regex multiple times with different callbacks or search strings, you can create a 
    * ReplaceCallback directly and use this method to perform the search and replace. 
    * 
    * @param string The string we are searching through 
    * @param callback A callback instance that will be applied to the regex match results. 
    * @return The modified search string. 
    */ 
    public String execute(String string, final Callback callback) { 
     this.callback = callback; 
     return execute(string); 
    } 

    /** 
    * Use this static method to perform your regex search. 
    * @param search The string we are searching through 
    * @param regex  The regex to apply to the string 
    * @param callback A callback instance that will be applied to the regex match results. 
    * @return The modified search string. 
    */ 
    public static String find(String search, String regex, Callback callback) { 
     ReplaceCallback rc = new ReplaceCallback(regex, callback); 
     return rc.execute(search); 
    } 
} 
+0

Tôi sẽ không sử dụng một biến mẫu để lưu trữ gọi lại, nhưng thay vì chuyển nó thành một tham số. Lưu trữ nó như là một biến thể hiện làm cho lớp của bạn có hành vi bất ngờ khi được gọi từ các luồng riêng biệt cùng một lúc. (Cuộc gọi lại thứ hai sẽ nhận được các trận đấu từ đầu tiên và thứ hai). – jdmichal

51

Đang cố gắng để bắt chước tính năng gọi lại PHP dường như một awful nhiều công việc khi bạn chỉ có thể sử dụng appendReplacement() và appendTail () trong một vòng lặp:

StringBuffer resultString = new StringBuffer(); 
Pattern regex = Pattern.compile("regex"); 
Matcher regexMatcher = regex.matcher(subjectString); 
while (regexMatcher.find()) { 
    // You can vary the replacement text for each match on-the-fly 
    regexMatcher.appendReplacement(resultString, "replacement"); 
} 
regexMatcher.appendTail(resultString); 
+3

Tôi nghĩ rằng một số lớp JDK có các tính năng mạnh mẽ nhưng các tính năng này đôi khi ẩn đằng sau các tên lớp lạ hoặc tên phương thức lạ ... Mặc dù chiến lược 'appendReplacement/appendTail', như được sử dụng ở đây, yêu cầu ít mã hơn, chiến lược' callback' (Câu trả lời được chọn của OP) rõ ràng hơn, rõ ràng hơn! – Stephan

+0

Điều gì sẽ xảy ra nếu tôi cần phải khớp chuỗi để có được sự thay thế phù hợp? Nói subjectString có thể chứa "foo bar" nhưng tôi cần thay "foo" bởi "Jan" và "bar" bởi "Goyvaerts"? – ALOToverflow

+0

Sử dụng 'foo | bar' làm regex của bạn và truy vấn 'regexMatcher.group()' bên trong vòng lặp để xem bạn cần nối thêm thay thế nào. –

0

Tôi thấy rằng câu trả lời của jdmichal sẽ lặp vô hạn nếu chuỗi trả về của bạn có thể được khớp lại; dưới đây là một sửa đổi ngăn chặn các vòng lặp vô hạn từ kết hợp này.

public String replaceMatches(String string, Callback callback) { 
    String result = ""; 
    final Matcher matcher = this.pattern.matcher(string); 
    int lastMatch = 0; 
    while(matcher.find()) 
    { 
     final MatchResult matchResult = matcher.toMatchResult(); 
     final String replacement = callback.foundMatch(matchResult); 
     result += string.substring(lastMatch, matchResult.start()) + 
      replacement; 
     lastMatch = matchResult.end(); 
    } 
    if (lastMatch < string.length()) 
     result += string.substring(lastMatch); 
    return result; 
} 
3

Tôi không hài lòng với bất kỳ giải pháp nào ở đây. Tôi muốn một giải pháp không quốc tịch. Và tôi không muốn kết thúc trong một vòng lặp vô hạn nếu chuỗi thay thế của tôi đã xảy ra để phù hợp với mô hình. Trong khi tôi ở đó, tôi đã thêm hỗ trợ cho thông số limit và tham số trả về count.(Tôi đã sử dụng AtomicInteger để mô phỏng truyền số nguyên theo tham chiếu.) Tôi đã di chuyển tham số callback vào cuối danh sách tham số, để dễ dàng xác định một lớp ẩn danh hơn.

Dưới đây là một ví dụ về việc sử dụng:

final Map<String,String> props = new HashMap<String,String>(); 
props.put("MY_NAME", "Kip"); 
props.put("DEPT", "R&D"); 
props.put("BOSS", "Dave"); 

String subjectString = "Hi my name is ${MY_NAME} and I work in ${DEPT} for ${BOSS}"; 
String sRegex = "\\$\\{([A-Za-z0-9_]+)\\}"; 

String replacement = ReplaceCallback.replace(sRegex, subjectString, new ReplaceCallback.Callback() { 
    public String matchFound(MatchResult match) { 
    String group1 = match.group(1); 
    if(group1 != null && props.containsKey(group1)) 
     return props.get(group1); 
    return match.group(); 
    } 
}); 

System.out.println("replacement: " + replacement); 

Và đây là phiên bản của tôi về lớp ReplaceCallback:

import java.util.concurrent.atomic.AtomicInteger; 
import java.util.regex.*; 

public class ReplaceCallback 
{ 
    public static interface Callback { 
    /** 
    * This function is called when a match is made. The string which was matched 
    * can be obtained via match.group(), and the individual groupings via 
    * match.group(n). 
    */ 
    public String matchFound(MatchResult match); 
    } 

    /** 
    * Replaces with callback, with no limit to the number of replacements. 
    * Probably what you want most of the time. 
    */ 
    public static String replace(String pattern, String subject, Callback callback) 
    { 
    return replace(pattern, subject, -1, null, callback); 
    } 

    public static String replace(String pattern, String subject, int limit, Callback callback) 
    { 
    return replace(pattern, subject, limit, null, callback); 
    } 

    /** 
    * @param regex The regular expression pattern to search on. 
    * @param subject The string to be replaced. 
    * @param limit The maximum number of replacements to make. A negative value 
    *     indicates replace all. 
    * @param count If this is not null, it will be set to the number of 
    *     replacements made. 
    * @param callback Callback function 
    */ 
    public static String replace(String regex, String subject, int limit, 
      AtomicInteger count, Callback callback) 
    { 
    StringBuffer sb = new StringBuffer(); 
    Matcher matcher = Pattern.compile(regex).matcher(subject); 
    int i; 
    for(i = 0; (limit < 0 || i < limit) && matcher.find(); i++) 
    { 
     String replacement = callback.matchFound(matcher.toMatchResult()); 
     replacement = Matcher.quoteReplacement(replacement); //probably what you want... 
     matcher.appendReplacement(sb, replacement); 
    } 
    matcher.appendTail(sb); 

    if(count != null) 
     count.set(i); 
    return sb.toString(); 
    } 
} 
0
public static String replace(Pattern pattern, Function<MatchResult, String> callback, CharSequence subject) { 
    Matcher m = pattern.matcher(subject); 
    StringBuffer sb = new StringBuffer(); 
    while (m.find()) { 
     m.appendReplacement(sb, callback.apply(m.toMatchResult())); 
    } 
    m.appendTail(sb); 
    return sb.toString(); 
} 

Cách sử dụng Ví dụ:

replace(Pattern.compile("cat"), mr -> "dog", "one cat two cats in the yard") 

sẽ tạo ra giá trị trả về:

một con chó hai con chó trong sân

Các vấn đề liên quan