【问题标题】:Regex that can match empty string is breaking the javascript regex engine可以匹配空字符串的正则表达式正在破坏 javascript 正则表达式引擎
【发布时间】:2016-12-16 01:48:12
【问题描述】:

我写了以下正则表达式:/\D(?!.*\D)|^-?|\d+/g

我认为它应该这样工作:

\D(?!.*\D)    # match the last non-digit
|             # or
^-?           # match the start of the string with optional literal '-' character
|             # or
\d+           # match digits

但是,它没有:

var arrTest = '12,345,678.90'.match(/\D(?!.*\D)|^-?|\d+/g);
console.log(arrTest);

var test = arrTest.join('').replace(/[^\d-]/, '.');
console.log(test);

但是,当使用 PCRE(php)-flavour 在线玩时,Regex101。就像我描述的那样工作。

我不知道我是否认为它应该以一种它不起作用的方式工作。或者如果 javascript regex-flavour 中不允许使用某些模式。

【问题讨论】:

  • 您的预期结果是什么?
  • @anubhava。我希望它返回12345678.90
  • 我认为您可以使用var res = '-12,345,678.90'.replace(/(\D)(?!.*\D)|^-?|\D/g, function($0,$1) { return $1 ? "." : ""; }); - 单个替换操作。
  • @WiktorStribiżew。伙计,这很聪明,您只需要一种替换方法就可以完成所有工作
  • JS 的工作方式与 PCRE 不同。关键是 JS 正则表达式引擎并不能很好地处理零长度匹配,索引只是手动递增,并跳过零长度匹配后的下一个字符。

标签: javascript regex


【解决方案1】:

JS 的工作方式与 PCRE 不同。关键是 JS 正则表达式引擎不能很好地处理零长度匹配,索引只是手动递增,并跳过零长度匹配后的下一个字符。 ^-? 可以匹配一个空字符串,它匹配12,345,678.90 开头,跳过1

如果我们查看String#match documentation,我们会看到在找到零长度匹配后,每次使用全局正则表达式调用match 都会增加正则表达式对象的lastIndex

  1. 否则,全局true
    一种。使用参数 "lastIndex" 和 0 调用 rx 的 [[Put]] 内部方法。
    湾。假设 A 是一个新数组,就像由表达式 new Array() 创建的一样,其中 Array 是具有该名称的标准内置构造函数。
    C。让 previousLastIndex 为 0。
    d。让 n 为 0。
    e.让 lastMatchtrue
    F。重复,而 lastMatchtrue
    一世。令 result 为调用 exec 的 [[Call]] 内部方法的结果,其中 rx 作为 this包含 S 的值和参数列表。
    ii.如果 resultnull,则将 lastMatch 设置为 false
    iii.否则,result 不是 null
    1. 令 thisIndex 为调用 rx 的 [[Get]] 内部方法的结果,参数为“lastIndex”。
    2. 如果 thisIndex = previousLastIndex 那么
    一种。使用参数 "lastIndex" 和 thisIndex+1 调用 rx 的 [[Put]] 内部方法。
    湾。将 previousLastIndex 设置为 thisIndex+1。

所以,匹配过程从 8a8f 初始化辅助结构,然后进入一个 while 块(重复直到 lastMatch true,内部 exec 命令匹配字符串开头的空格(8fi -> 8fiii) ,并且由于结果不是 nullthisIndex 被设置为上一个成功匹配的 lastIndex,并且匹配为零长度(基本上,thisIndex = previousLastIndex),previousLastIndex 设置为 thisIndex+1 - 即零长度匹配成功后跳过当前位置

您实际上可以在 replace 方法中使用更简单的正则表达式并使用回调来使用适当的替换:

var res = '-12,345,678.90'.replace(/(\D)(?!.*\D)|^-|\D/g, function($0,$1) {
   return $1 ? "." : "";
});
console.log(res);

模式详情

  • (\D)(?!.*\D) - 一个非数字(捕获到第 1 组),后面不跟 0+ 个字符,换行符和另一个非数字
  • | - 或
  • ^- - 字符串开头的连字符
  • | - 或
  • \D - 非数字

请注意,这里您甚至不必将开头的连字符设为可选。

【讨论】:

  • 我花了 16 分钟来添加(和格式化)ECMA 参考,并逐步解释为什么会发生这种情况。希望它能解释这种奇怪的 JS global 正则表达式匹配行为。请注意,PCRE 只是不会在零长度匹配后直接增加位置,它会正确地重新检查下一个字符。
  • 谢谢 :) ...我最终选择了.replace(/(^-)|\D(?=.*\D)|(\D)/g, function($0, $1, $2) { return $1 || ($2 ? '.' : ''); }); 以避免替换负号。
  • 如果你不需要替换那个减号,为什么要使用替代分支:)?只需从模式中删除 (^-)| 并从回调函数中删除 $2 并调整其主体。
【解决方案2】:

您可以重新排序您的交替模式并在 JS 中使用它来使其工作:

var arrTest = '12,345,678.90'.match(/\D(?!.*\D)|\d+|^-?/g);
console.log(arrTest);

var test = arrTest.join('').replace(/\D/, '.');

console.log(test);

//=> 12345678.90

RegEx Demo

这是 Javascript 和 PHP(PCRE) 正则表达式行为之间的区别。

在 Javascript 中:

'12345'.match(/^|.+/gm)
//=> ["", "2345"]

在 PHP 中:

preg_match_all('/^|.+/m', '12345', $m);
print_r($m);
Array
(
    [0] => Array
        (
            [0] =>
            [1] => 12345
        )
    )

因此,当您在 Javascript 中匹配 ^ 时,正则表达式引擎会向前移动一个位置,而在替换 | 之后的任何内容都会从输入中的第二个位置开始匹配。

【讨论】:

  • 这是因为当您在 JS 正则表达式中匹配 ^ 时,引擎会向前移动一个位置,而在交替 | 之后的任何内容都匹配输入中的第二个位置。使用'12345'.match(/^|.+/gm) 进行测试,得到["", "2345"]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-05
  • 2013-12-25
  • 1970-01-01
相关资源
最近更新 更多