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

是否有一种使用正则表达式的方法来匹配引号外文本的模式?

  •  3
  • rjzii  · 技术社区  · 16 年前

    如标题中所述,是否有一种方法可以使用正则表达式来匹配出现在引号之外的文本的文本模式。理想情况下,在下面的示例中,我希望能够匹配引号之外的逗号,但不能匹配引号中的逗号。

    这是一些文本,后跟“文本,引号!”

    这是一些文本,后跟“文本,引号”和“更多”文本,引号!

    另外,如果表达式像下面的示例一样尊重嵌套引号,那就更好了。然而,如果这在技术上不适用于正则表达式,那么很容易知道情况是否如此。

    程序员从桌上抬起头来,“这不行,”他喊道,“系统说‘找不到文件’。”

    我找到了一些匹配引号中的内容的表达式,但是对于引号之外的内容没有找到合适的表达式。

    9 回复  |  直到 16 年前
        1
  •  2
  •   Chris Lutz    16 年前

    这可以用现代的regex来完成,因为存在大量对regex引擎的黑客攻击,但是让我做一个发布“不要用正则表达式做这件事”答案的人。

    这是 正则表达式的作业。这是一个全面的解析器的工作。作为一个不能用(经典)正则表达式处理的示例,请考虑:

    ()(())(()())
    

    没有(经典的)regex可以确定这些括号是否正确匹配,但是在没有regex的情况下这样做是很简单的:

    /* C code */
    
    char string[] = "()(())(()())";
    int parens = 0;
    for(char *tmp = string; tmp; tmp++)
    {
      if(*tmp == '(') parens++;
      if(*tmp == ')') parens--;
    }
    if(parens > 0)
    {
      printf("%s too many open parenthesis.\n", parens);
    }
    else if(parens < 0)
    {
      printf("%s too many closing parenthesis.\n", -parens);
    }
    else
    {
      printf("Parenthesis match!\n");
    }
    
    # Perl code
    
    my $string = "()(())(()())";
    my $parens = 0;
    for(split(//, $string)) {
      $parens++ if $_ eq "(";
      $parens-- if $_ eq ")";
    }
    die "Too many open parenthesis.\n" if $parens > 0;
    die "Too many closing parenthesis.\n" if $parens < 0;
    print "Parenthesis match!";
    

    看看编写一些非regex代码为您完成这项工作有多简单?

    编辑:好的,看《冒险乐园》回来。:)试试这个(用Perl编写,注释后帮助你理解我在做什么,如果你不了解Perl的话):

    # split $string into a list, split on the double quote character
    my @temp = split(/"/, $string);
    
    # iterate through a list of the number of elements in our list
    for(0 .. $#temp) {
    
      # skip odd-numbered elements - only process $list[0], $list[2], etc.
      # the reason is that, if we split on "s, every other element is a string
      next if $_ & 1;
    
      if($temp[$_] =~ /regex/) {
        # do stuff
      }
    
    }
    

    另一种方法是:

    my $bool = 0;
    my $str;
    my $match;
    
    # loop through the characters of a string
    for(split(//, $string)) {
    
      if($_ eq '"') {
        $bool = !$bool;
        if($bool) {
    
          # regex time!
          $match += $str =~ /regex/;
    
          $str = "";
        }
      }
    
      if(!$bool) {
    
        # add the current character to our test string
        $str .= $_;
      }
    }
    
    # get trailing string match
    $match += $str =~ /regex/;
    

    (我给出两个答案是因为,在另一种语言中,一种解决方案可能比另一种更容易实现,而不仅仅是因为有多种方法可以做到这一点)

    当然,当您的问题变得越来越复杂时,构建一个完整的解析器会带来一些好处,但这是另一匹马。现在,这就足够了。

        2
  •  4
  •   Markus Jarderot    16 年前

    最简单的方法是同时匹配逗号和带引号的字符串,然后过滤掉带引号的字符串。

    /"[^"]*"|,/g
    

    如果您真的不能匹配引号,可以这样做:

    /,(?=[^"]*(?:"[^"]*"[^"]*)*\Z)/g
    

    这可能会变慢,因为对于每个逗号,它必须查看剩余的字符并计算引号的数量。 \Z 匹配字符串的结尾。类似 $ ,但永远不会匹配行尾。

    如果您不介意额外的捕获组,可以这样做:

    /\G((?:[^"]*"[^"]*")*?[^"]*?)(,)/g
    

    这将只扫描字符串一次。它从字符串的开头开始计算引号。 \G 将匹配上次匹配结束的位置。


    最后一个模式可能需要一个例子。

    Input String: 'This is, some text, followed by "text, in quotes!" and more ,-as'
    Matches:
    1. ['This is', ',']
    2. [' some text', ',']
    3. [' and followed by "text, in quotes!" and more ', ',']
    

    它匹配逗号前面的字符串以及逗号。

        3
  •  1
  •   Community CDub    7 年前

    如前所述, regexp cannot match any nested pattern ,因为它不是 Context-free language .

    所以如果你有任何嵌套的引号,你不会用正则表达式来解决这个问题。
    (除了 balancing group “.NET regex引擎的功能-如前所述” Daniel L 在评论-中,但是我没有在这里对regex的味道做任何假设)

    除非您添加了进一步的规范,例如必须转义引号中的引号。

    在这种情况下,以下内容:

    text before string "string with \escape quote \" still
    within quote" text outside quote "within quote \" still inside" outside "
    inside" final outside text
    

    将成功匹配:

    (?ms)((?:\\(?=")|[^"])+)(?:"((?:[^"]|(?<=\\)")+)(?<!\\)")?
    
    • 第1组:引用文本前面的文本
    • 第2组:双引号内的文本,即使 \" 在里面。
        4
  •  0
  •   Mitchel Sellers    16 年前

    这里有一个得到匹配的表达式,但它并不完美,因为它得到的第一个匹配是整个字符串,删除了最后一个”。

    [^"].*(,).*[^"]
    

    我一直在用我的 Free RegEx tester 看看有什么效果。

    试验结果

    Group Match Collection # 1
    Match # 1
    Value: This is some text, followed by "text, in quotes!
    Captures: 1
    
    Match # 2
    Value: ,
    Captures: 1
    
        5
  •  0
  •   Gumbo    16 年前

    您最好构建一个简单的解析器(伪代码):

    quoted := False
    FOR char IN string DO
        IF char = '"'
            quoted := !quoted
        ELSE
            IF char = "," AND !quoted
                // not quoted comma found
            ENDIF
        ENDIF
    ENDFOR
    
        6
  •  0
  •   simon    16 年前

    这取决于是否允许嵌套引号。

    理论上,使用嵌套引号是不能这样做的(常规语言不能计数)

    在实践中,如果可以限制深度,则可以进行管理。当你增加复杂性的时候,它会变得越来越难看。这就是人们经常用正规的表达方式陷入悲伤的原因(尝试去匹配一些通常不正规的东西)。

    请注意,一些“regex”库/语言添加了非常规功能。

    如果这类事情变得足够复杂,您就必须为它编写/生成一个解析器。

        7
  •  0
  •   JP Alioto    16 年前

    你需要更多的描述。您想要任何一组可能的带引号的字符串和非带引号的字符串吗?

    洛伦伊普桑“多洛尔坐”阿美,“神圣的阿迪皮斯”精英。

    …或者只是你想要的模式?我觉得这很接近…

    (?<outside>.*?)(?<inside>(?=\"))
    

    但它确实捕获了“”。

        8
  •  0
  •   fredrik alexandresoli    16 年前

    也许你可以分两步做?
    首先替换引用的文本:

    ("[^"]*")
    

    然后从剩余的字符串中提取所需的内容

        9
  •  0
  •   Alan Moore Chris Ballance    16 年前
    ,(?=(?:[^"]*"[^"]*")*[^"]*\z)
    

    正则表达式可能无法计数,但它们可以确定某个数是奇数还是偶数。查找逗号后,lookahead断言,如果前面有引号,则有偶数,这意味着逗号是 在一组引号中。

    如果需要的话,可以调整它来处理转义引号,尽管最初的问题没有提到这一点。另外,如果您的regex风格支持它们,我会添加原子组或所有格量词来检查回溯。