两个数字的Java正则表达式反向引用

时间:2017-01-10 06:34:23

标签: java regex

我正在使用正则表达式,我想在Java的String类的replaceAll方法中使用它。

我的正则表达式工作正常并且groupCount()返回11.因此,当我尝试使用指向第11组的反向引用替换我的文本时,我得到第一组带有" 1"附在上面,而不是十一组。

String regex = "(>[^<]*?)((\+?\d{1,4}[ \t\f\-\.](\d[ \t\f\-\.])?)?(\(\d{1,4}([\s-]\d{1,4})?\)[\.\- \t\f])?((\d{2,6}[\.\- \t\f])+\d{2,6})|(\d{6,16})([;,\.]{1,3}\d{3,}#?)?)([^<]*<)";
String text = "<span style=\"font-size:11.0pt\">675-441-3144;;;78888464#<o:p></o:p></span>":
String replacement = text.replaceAll(regex, $1<a href="tel:$2">$2</a>$11");

我希望得到以下结果:

<span style=\"font-size:11.0pt\"><a href=\"tel:675-441-3144;;;78888464#\">675-441-3144;;;78888464#</a><o:p></o:p></span>

但11美元的反向引用没有返回第11组,它返回第一组附加1,而是得到以下结果:

<span style="font-size:11.0pt"><a href="tel:675-441-3144">675-441-3144</a>>1o:p></o:p></span>

有人可以告诉我如何访问我的第11组模式吗?

感谢。

2 个答案:

答案 0 :(得分:2)

简答

您在替换中访问第11组比赛的方式是使用$11

说明:

正如corresponding Javadoc *所述:

  

替换字符串可能包含对捕获的子序列的引用   在上一场比赛中:每次出现${name}$g都是   替换为评估相应group(name)或的结果   分别为group(g)。对于$g$之后的第一个数字始终为   作为小组参考的一部分处理。随后的数字是   如果它们构成法律小组参考,则纳入g

所以一般来说,只要至少有11个组,那么"$11"将评估为group(11)。但是,如果您没有至少11个群组,则"$11"将评估为group(1) + "1"

* 此引文来自Matcher#appendReplacement(StringBuffer,String),这是String#replaceAll(String,String)引用的相关引用链。

实际答案

你的正则表达式不符合你的想法。

第1部分

问题

让我们将你的正则表达式划分为三个顶级组。这些分别是第1,2和11组。

  • 第1组:
    (>[^<]*?)
  • 第2组:
    ((\+?\d{1,4}[ \t\f\-\.](\d[ \t\f\-\.])?)?(\(\d{1,4}([\s-]\d{1,4})?\)[\.\- \t\f])?((\d{2,6}[\.\- \t\f])+\d{2,6})|(\d{6,16})([;,\.]{1,3}\d{3,}#?)?)
  • 第11组:
    ([^<]*<)

第2组是正则表达式的主体,它包含两个选项的顶级交替。这两个选项分别由3-8组和9-10组组成。

  • 第一个选项:
    ((\+?\d{1,4}[ \t\f\-\.](\d[ \t\f\-\.])?)?(\(\d{1,4}([\s-]\d{1,4})?\)[\.\- \t\f])?((\d{2,6}[\.\- \t\f])+\d{2,6})
  • 第二个选项:
    (\d{6,16})([;,\.]{1,3}\d{3,}#?)?)

现在,给定text字符串,这是正在发生的事情:

  1. 第1组执行。它与第一个">"匹配。
  2. 第2组执行。它按顺序评估其交替的选项。
    1. 执行第2组交替的第一个选项。它与"675-441-3144"匹配。
    2. 第2组交替在其中一个选项匹配时成功短路。
      • 第2组作为一个整体现在等于匹配的选项,即"675-441-3144"
      • 光标现在位于"675-441-3144"之后,紧接在";;;78888464#"之前。
  3. 第11组执行。它会匹配下一个"<"的所有内容,";;;78888464#<"
  4. 因此,您希望在第2组中的某些内容实际上是在第11组中。

    解决方案

    执行以下两项操作:

    • 转换第2组的内容
      option1|option2
      

      option1(option2)?|option2
      
    • 将替换模式中的$11更改为$12

    这将贪婪匹配一个或两个选项,而不是只有一个选项。对替换模式的修改是因为我们添加了一个组。

    第2部分

    问题

    现在我们已经修改了正则表达式,我们原来的&#34;选项2&#34;不再有意义。鉴于我们的新模式模板option1(option2)?|option2,第2组将无法匹配"675-441-3144;;;78888464#"。这是因为我们原来的选项1&#34;将匹配所有"675-441-3144",然后停止。我们的原始&#34;选项2&#34;然后将尝试匹配";;;78888464#",但无法匹配,因为它以6-10位数的强制捕获组开头:(\d{6,16}),但";;;78888464#"以分号开头。

    解决方案

    转换原始&#34;选项2&#34;的内容从

    (\d{6,16})([;,\.]{1,3}\d{3,}#?)?
    

    ([;,\.]{1,3}\d{3,}#?)?
    

    第3部分

    问题

    我们有一个最后要解决的问题。现在,我们原来的&#34;选项2&#34;仅包含具有?量词的单个组,它可以成功匹配零长度子字符串。因此,我们的模板模板option1(newoption2)?|newoption2可能导致零长度匹配,这不符合匹配电话号码的预期目的。

    解决方案

    执行以下两项操作:

    • 转换新&#34;选项2&#34;的内容从

      (〔;,] {1,3} \ d {3,}#)?

      〔;,] {1,3} \ d {3,}#

    • 将替换字符串中的$12更改为$10,因为我们现在已在两个位置删除了一个组。

    最终解决方案

    将所有内容放在一起,我们的最终解决方案如下。

    搜索正则表达式:

    (>[^<]*?)((\+?\d{1,4}[ \t\f\-\.](\d[ \t\f\-\.])?)?(\(\d{1,4}([\s-]\d{1,4})?\)[\.\- \t\f])?((\d{2,6}[\.\- \t\f])+\d{2,6})([;,\.]{1,3}\d{3,}#?)?|[;,\.]{1,3}\d{3,}#?)([^<]*<)
    

    替换正则表达式:

    $1<a href="tel:$2">$2</a>$10
    

    <强>爪哇:

    final String searchRegex = "(>[^<]*?)((\\+?\\d{1,4}[ \\t\\f\\-\\.](\\d[ \\t\\f\\-\\.])?)?(\\(\\d{1,4}([\\s-]\\d{1,4})?\\)[\\.\\- \\t\\f])?((\\d{2,6}[\\.\\- \\t\\f])+\\d{2,6})([;,\\.]{1,3}\\d{3,}#?)?|[;,\\.]{1,3}\\d{3,}#?)([^<]*<)";
    final String replacementRegex = "$1<a href=\"tel:$2\">$2</a>$10";
    
    String text = "<span style=\"font-size:11.0pt\">675-441-3144;;;78888464#<o:p></o:p></span>";
    String replacement = text.replaceAll(searchRegex, replacementRegex);
    

    Proof of correctness

答案 1 :(得分:-1)

好吧,在尝试使用replaceall做到没有成功之后,我不得不自己实现替换方法:

public static String parsePhoneNumbers(String html){
    StringBuilder regex = new StringBuilder(120);
    regex.append("(>[^<]*?)(")
       .append("((\+?\d{1,4}[ \t\f\-\.](\d[ \t\f\-\.])?)?")
       .append("(\(\d{1,4}([\s-]\d{1,4})?\)[\.\- \t\f])?")
       .append("((\d{2,6}[\.\- \t\f])+\d{2,6})|(\d{6,16})")
       .append("([;,\.]{1,3}\d{3,}#?)?)") 
       .append(")+([^<]*<)");

    StringBuilder mutableHtml = new StringBuilder(html.length());
    Pattern pattern = Pattern.compile(regex.toString());
    Matcher matcher = pattern.matcher(html);
    int start = 0;

    while(matcher.find()){
        mutableHtml.append(html.substring(start, matcher.start()));
        mutableHtml.append(matcher.group(1)).append("<a href=\"tel:")
                .append(matcher.group(2)).append("\">").append(matcher.group(2))
                .append("</a>").append(matcher.group(matcher.groupCount()));
        start = matcher.end();

    }
    mutableHtml.append(html.substring(start));
    return mutableHtml.toString();
}