正则表达式:使用负向前瞻代替不支持的负向后顾,并在拆分时捕获后顾字符。

3
我又在正则表达式方面遇到了困难。我一直在尝试添加一个转义字符以转义自定义标签,例如将<1>转义为<57>,将</1>转义为</57>。在Georg的这里帮助下,在尝试转义之前,以下表达式生成了所需的结果。 ('This is a <21>test</21> again.').split(/(<\/?(?:[1-9]|[1-4][0-9]|5[0-7])>)/); 生成 'This is a ', '<21>', 'test', '</21>', ' again.'

这个问题有一个建议,使用负向预查和OR来近似不支持的负向回顾。我修改了那个例子,以解决我认为更简单的问题;然而,我又被卡住了。

('This is a <21>test</21> again.').split(/(?:(?!\\).|^)(<\/?(?:[1-9]|[1-4][0-9]|5[0-7])>)/) );

生成'This is a','<21>','tes','</21>',' again.'因此,它不包括前一个字符<21></21>,当不是\时。我知道为什么会这样,因为使用了非捕获的?:

但是,如果删除它:

('This is a <21>test</21> again.').split(/((?!\\).|^)(<\/?(?:[1-9]|[1-4][0-9]|5[0-7])>)/) );

生成 'This is a', ' ', '<21>', 'tes', 't', '</21>', ' again.',并且前一个字符生成了一个单独的分割。

除了这个问题,转义的工作方式是,当前一个字符是 \ 时,标签不会生成字符串的分割。

请问是否有一种方法可以捕获前一个字符,但将其包含在前一个字符串的文本中而不是其自己的分割中?并且可能仅在存在 \ 时排除它?

当字符串为 'This is a <21>test</21> again.' 时,期望的结果是 'This is a ', '<21>', 'test', '</21>', ' again.'

当它是'This is a \<21>test</21> again.'时,期望的结果是'This is a <21>', 'test', '</21>', ' again.'

谢谢。

加法 最近,在thisMDN文档中了解到使用内联函数作为正则表达式replace操作的参数后,我开始想知道是否可以在这里做类似的事情。我不知道如何衡量性能,但Revo提供的正则表达式的复杂性以及他对我的效率评论的负面影响表示,否定向后看将是效率显著提高和减少RegExp引擎工作量的方法,而RegExp在幕后是一个黑盒子,这促使我尝试另一种方法。它是几行代码,但产生相同的结果并使用更短的正则表达式。它真正做的就是匹配标记,包括有和没有转义字符的标记,而不是尝试排除那些带有\转义的标记,然后忽略构建数组中具有转义字符的标记。下面是片段。
我不知道控制台日志中提供的时间是否代表性能,但如果是这样,在我运行的示例中,似乎在记录“start”和“a.split”之间的时间差作为百分比的差异要比在“exec”方法下记录数组“a”的最终日志之间的时间差异大得多。
另外,在while语句内部最内层的if块用于防止在字符串开头或结尾处有标记,或者两个标记之间没有空格时将""保存在数组中。
我很感激您能提供关于为什么或为什么不使用一种方法的任何见解,或者介绍一种更好的方法来处理无法访问真正的负向查找的情况。谢谢。

let a, i = 0, l, p, r,
    x = /\\?<\/?(?:[1-9]|[1-4]\d|5[0-7])>/g,
    T = '<1>This is a <21>test<21> of \\<22>escaped and \\> </ unescaped tags.<5>';

console.log('start');

a = T.split(/((?:[^<\\]+|\\+.?|<(?!\/?(?:[1-9]|[1-4]\d|5[0-7])>))+|<\/?(?:[1-9]|[1-4]\d|5[0-7])>)/).filter(Boolean);

      console.log(a);
      a=[];
      while ( ( r = x.exec( T ) ) !== null) {
        if ( r[0].charAt(0) !== '\\' )
          {
             if ( r.index === 0 || r.index === p )
               {
                 a[ i ] = r[0];
                 i = i + 1;
               }
             else 
               {
                 a[ i ] = T.substring( p, r.index );
                 a[ i + 1 ] = r[0];
                 i = i + 2;
               }; // end if
             p = x.lastIndex;
          }; // end if
      }; // next while

      if ( p !== T.length ) a[i] = T.substring( p );
      console.log(a)

1个回答

2

你需要按照所需的子字符串进行拆分,并使用捕获组将它们输出。这也可能会发生在不需要的子字符串上。你需要匹配它们并将其放入一个捕获组中以便输出。正则表达式如下:

Original Answer 翻译成“最初的回答”。

(undesired-part|desired-part)

应该先匹配不需要的子字符串的正则表达式,因为需要的部分可能包含在其中,例如<21>包含在\<21>中,所以我们应该先匹配后者。

您编写了需要的部分并且已经为我们所知:

最初的回答

(undesired-part|<\/?(?:[1-9]|[1-4]\d|5[0-7])>)

那么不希望的呢?这就是最初的回答:

(?:[^<\\]+|\\.?|<(?!\/?(?:[1-9]|[1-4]\d|5[0-7])>))+

让我们来分解一下:
  • (?: 开始一个非捕获组
    • [^<\\]+ 匹配除了 <\ 以外的任何字符
    • | 或者
    • \\.? 匹配转义字符
    • | 或者
    • <(?!\/?(?:[1-9]|[1-4]\d|5[0-7])>) 匹配一个不需要的 <
  • )+ 结束非捕获组,重复尽可能多的次数并至少出现一次

总的来说:


"最初的回答"
((?:[^<\\]+|\\.?|<(?!\/?(?:[1-9]|[1-4]\d|5[0-7])>))+|<\/?(?:[1-9]|[1-4]\d|5[0-7])>)

Js code:

console.log(
  'This is a \\<21>test</21> ag<ain\\.'.split(/((?:[^<\\]+|\\.?|<(?!\/?(?:[1-9]|[1-4]\d|5[0-7])>))+|<\/?(?:[1-9]|[1-4]\d|5[0-7])>)/).filter(Boolean)
);


谢谢你抽出时间去理解并给出解释。我在某种程度上理解,但并不完全明白。非期望的部分是除了1到57个标签之外的每一种可能性。它可能是没有标签或转义,一个转义或者一个标签而不是1到57。非期望的部分就像是宇宙减去所需部分;由于是由OR连接,似乎应该是所有内容都匹配。 我看不到否定或非部分,除了NCG。不过,我已经将其添加到我的代码中,并且看起来完美地运行。我找不到导致它提供非预期结果的表达式。谢谢。 - Gary
假设有支持负回溯,那么会更有效吗?或者,我应该问一下是否将所有东西匹配并不捕获最大部分会给计算机带来更多的工作量?我并不意味着你提供的方法不好,但我只是想理解一下。我不知道在搜索字符串时正则表达式是如何处理的;因此就我所知,这种方法可能与负回溯一样甚至更有效。谢谢。 - Gary
我刚刚注意到了一些事情。代码<212>在不应该的时候生成了一个分割。我认为是|之前的/d引起的。 - Gary
你得到了什么?你期望得到什么? - revo
我确实发现需要进行一项修改,虽然这个修改不太重要(因为我不知道为什么有人会以这种方式输入文本),但也许为了完整性而必要。如果一个标签前面有两个转义符,比如 '\<21>',那么它们都被视为常规文本,而 <21> 会在字符串中生成一个分割点。在 '|\.?|' 中加入 '+' 以使其变成 '|\+.?|',似乎已经解决了问题,而且没有破坏其他部分。谢谢。 - Gary
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接