代码之家  ›  专栏  ›  技术社区  ›  Mike

Java相当于PHP的PREGGRESPLATE回调

  •  33
  • Mike  · 技术社区  · 16 年前

    我正在把一个应用程序从PHP移动到Java,在代码中大量使用正则表达式。我在PHP中遇到了一些似乎没有Java等价的东西:

    preg_replace_callback()
    

    对于regex中的每个匹配,它调用一个函数,该函数将匹配文本作为参数传递。作为示例用法:

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

    用Java做这件事的理想方法是什么?

    6 回复  |  直到 7 年前
        1
  •  22
  •   Community CDub    8 年前

    重要的 :正如所指出的 Kip 在注释中,如果匹配的regex与替换字符串匹配,则此类具有无限循环错误。如果必要的话,我将把它作为练习留给读者来解决。


    我不知道任何类似于Java的东西。使用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);
            }
        }
    }
    

    然后呼叫:

    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);
    

    请注意,您可以通过调用 matchResults.group() matchResults.group(0) ,因此不需要将回调传递到当前字符串状态。

    编辑: 使它看起来更像PHP函数的确切功能。

    这是原版,因为提问者喜欢:

    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());
            }
        }
    }
    

    对于这个特定的用例,最好是简单地将回调中的每个匹配项排队,然后向后运行它们。这将防止在修改字符串时重新映射索引。

        2
  •  54
  •   Jan Goyvaerts    16 年前

    当您可以在循环中使用appendreplacement()和appendtail()时,尝试模拟php的回调功能似乎是一项非常困难的工作:

    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
  •  3
  •   Kip    15 年前

    我对这里的任何解决方案都不太满意。我想要一个无状态的解决方案。如果我的替换字符串恰好与模式匹配,我不想以无限循环结束。当我在那里的时候,我增加了对 limit 参数和返回的 count 参数。(我用过 AtomicInteger 模拟通过引用传递整数。)我移动了 callback 参数到参数列表的末尾,以便于定义匿名类。

    以下是用法示例:

    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);
    

    下面是我的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();
      }
    }
    
        4
  •  0
  •   jevon JesseBuesking    15 年前

    我发现,如果您返回的字符串可以再次匹配,jdmichal的答案将是无限循环;下面是一个修改,防止无限循环进行这种匹配。

    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;
    }
    
        5
  •  0
  •   holmis83    7 年前
    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();
    }
    

    使用实例:

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

    将产生返回值:

    一条狗两条狗在院子里

        6
  •  -1
  •   community wiki Mike    16 年前

    这是我对你的建议所做的最后结果。我想如果有人也有同样的问题的话,到这里来会很好。产生的调用代码如下:

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

    整个类列表如下:

    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);
        }
    }