【问题标题】:Check if replacement string is valid检查替换字符串是否有效
【发布时间】:2016-08-22 22:27:48
【问题描述】:

这是我检查replacement string 是否有效的实用方法:

public static boolean isValidReplacementString(String regex, String replacement) {
    try {
        "".replaceFirst(regex, replacement);
        return true;
    } catch (IllegalArgumentException | NullPointerException e) {
        return false;
    }
}

我想在执行真正的替换之前检查一下,因为获取源字符串是昂贵的 (I/O)。

我觉得这个解决方案很老套。我缺少的标准库中是否已有方法?


编辑: As pointed out by sln,如果找不到匹配项,这甚至不起作用。


编辑: Following shmosel's answer,我想出了这个“解决方案”:

private static boolean isLower(char c) {
    return c >= 'a' && c <= 'z';
}

private static boolean isUpper(char c) {
    return c >= 'A' && c <= 'Z';
}

private static boolean isDigit(char c) {
    return isDigit(c - '0');
}

private static boolean isDigit(int c) {
    return c >= 0 && c <= 9;
}

@SuppressWarnings("unchecked")
public static void checkRegexAndReplacement(String regex, String replacement)  {
    Pattern parentPattern = Pattern.compile(regex);
    Map<String, Integer> namedGroups;
    int capturingGroupCount;

    try {
        Field namedGroupsField = Pattern.class.getDeclaredField("namedGroups");
        namedGroupsField.setAccessible(true);
        namedGroups = (Map<String, Integer>) namedGroupsField.get(parentPattern);
        Field capturingGroupCountField = Pattern.class.getDeclaredField("capturingGroupCount");
        capturingGroupCountField.setAccessible(true);
        capturingGroupCount = capturingGroupCountField.getInt(parentPattern);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new RuntimeException("That's what you get for using reflection!", e);
    }

    int groupCount = capturingGroupCount - 1;

    // Process substitution string to replace group references with groups
    int cursor = 0;

    while (cursor < replacement.length()) {
        char nextChar = replacement.charAt(cursor);
        if (nextChar == '\\') {
            cursor++;
            if (cursor == replacement.length())
                throw new IllegalArgumentException(
                        "character to be escaped is missing");
            nextChar = replacement.charAt(cursor);
            cursor++;
        } else if (nextChar == '$') {
            // Skip past $
            cursor++;
            // Throw IAE if this "$" is the last character in replacement
            if (cursor == replacement.length())
                throw new IllegalArgumentException(
                        "Illegal group reference: group index is missing");
            nextChar = replacement.charAt(cursor);
            int refNum = -1;
            if (nextChar == '{') {
                cursor++;
                StringBuilder gsb = new StringBuilder();
                while (cursor < replacement.length()) {
                    nextChar = replacement.charAt(cursor);
                    if (isLower(nextChar) ||
                            isUpper(nextChar) ||
                            isDigit(nextChar)) {
                        gsb.append(nextChar);
                        cursor++;
                    } else {
                        break;
                    }
                }
                if (gsb.length() == 0)
                    throw new IllegalArgumentException(
                            "named capturing group has 0 length name");
                if (nextChar != '}')
                    throw new IllegalArgumentException(
                            "named capturing group is missing trailing '}'");
                String gname = gsb.toString();
                if (isDigit(gname.charAt(0)))
                    throw new IllegalArgumentException(
                            "capturing group name {" + gname +
                                    "} starts with digit character");
                if (namedGroups == null || !namedGroups.containsKey(gname))
                    throw new IllegalArgumentException(
                            "No group with name {" + gname + "}");
                refNum = namedGroups.get(gname);
                cursor++;
            } else {
                // The first number is always a group
                refNum = (int)nextChar - '0';
                if (!isDigit(refNum))
                    throw new IllegalArgumentException(
                            "Illegal group reference");
                cursor++;
                // Capture the largest legal group string
                boolean done = false;
                while (!done) {
                    if (cursor >= replacement.length()) {
                        break;
                    }
                    int nextDigit = replacement.charAt(cursor) - '0';
                    if (!isDigit(nextDigit)) { // not a number
                        break;
                    }
                    int newRefNum = (refNum * 10) + nextDigit;
                    if (groupCount < newRefNum) {
                        done = true;
                    } else {
                        refNum = newRefNum;
                        cursor++;
                    }
                }
            }
            if (refNum < 0 || refNum > groupCount) {
                throw new IndexOutOfBoundsException("No group " + refNum);
            }
        } else {
            cursor++;
        }
    }
}

如果此方法抛出,则正则表达式或替换字符串无效。

这甚至比replaceAllreplaceFirst 更严格,因为如果找不到匹配项,这些方法将不会调用appendReplacement,因此会“丢失”无效的组引用。

【问题讨论】:

  • 如果不匹配,我不确定引擎是否会检查替换字符串,我可能是错的。话虽如此,替换时的一些错误可能是在正则表达式中未定义的无效捕获组反向引用。
  • 在替换前使用 apache StringUtils.isNotNull 方法检查 null。
  • @sln 你是对的。 isValidReplacementString("test", "$") 正在返回 true,因为找不到匹配项。所以我的方法甚至不能正常工作。
  • @amitmah 检查null 只是一个小方面。这更多的是关于语法和语义(例如,引用不存在的捕获组)。
  • java的String.replace方法需要什么语法检查?

标签: java regex replace


【解决方案1】:

我想说你最好的办法是复制Matcher.appendReplacement() 中实现的过程,删除与源字符串或结果字符串有关的任何逻辑。这不可避免地意味着您将无法进行某些验证,例如验证组名和索引,但您应该能够应用其中的大部分。

【讨论】:

  • 我采用了appendReplacement 的主体并使其可运行(在问题中编辑)。出乎你的意料,现在这个更严格了,如果没有找到匹配项也会抛出。这可能是它所能得到的最好的了。
  • @xehpuk 没错,我将源字符串与正则表达式混淆了。
猜你喜欢
  • 2019-05-05
  • 2019-10-04
  • 1970-01-01
  • 2013-02-13
  • 2017-01-20
  • 2014-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多