从正则表达式中删除捕获组

时间:2011-09-20 17:50:22

标签: ruby regex

我有一个正则'简单',我想用它作为另一个正则表达式'复杂'的构建块。麻烦的是,“简单”中的捕获组正在干扰“复杂”。这些低级捕获组是我不关心的细节。我希望在正则表达式消耗之前删除它们。

问题是:如何?

换句话说,在代码中,这种方法效果不佳:

simple = /(a)bc/
complex = /(#{simple}) - (#{simple})/
complex.match("abc - abc").captures # => ["abc", "a", "abc", "a"]
// when I need ["abc","abc"]

我更愿意写:

simple = /(a)bc/
complex = /(#{simple.without_capture}) - (#{simple.without_capture})/
complex.match("abc - abc").captures # => ["abc", "abc"]

我一直坚持如何做到这一点,但我打赌以前做过。 Regex#without_capture的实现当然需要考虑非捕获组,向前/向后等等。因此,仅仅删除所有()是不够的。此外,找到捕获组的匹配似乎有点挑战。

思想?

编辑:我忘了提。我不想手动创建两个简单版本(捕获和非捕获)。在我的实际情况中,维护这两个版本是不切实际的。能够动态切换捕获效果要好得多。

5 个答案:

答案 0 :(得分:4)

嗯,最好的方法是创建两个版本的“简单”,但既然你表示你不想这样做,你可以尝试通过这个正则表达式运行“简单”:

/\((?!\?)/

...并替换与(?:匹配的任何内容。但是,我想强调的是,尝试使用正则表达式处理正则表达式让我非常紧张。我无法保证上述模式不会产生误报,具体取决于您输入的内容。

我知道它无法正确处理转义的开括号(即\(意味着被解释为文字(字符)。您可以使用/(^|[^\\])\((?!\?)/替换它,并将其替换为$1(?:,但如果反斜杠本身被转义(即\\(,则会产生错误的否定被解释为字面反斜杠和组的开头。)

对此的真正解决方案是/(?<!(^|[^\\])(\\\\)*\\)\((?!\?)/来检查奇数编号的反斜杠字符串,但由于Ruby不支持lookbehinds,我会说/(^|[^\\])\((?!\?)/或者其他什么对你来说似乎最明白。

答案 1 :(得分:1)

好吧,我不知道在哪种情况下这会失败,但这是我的尝试:

class MatchData
    alias_method :captures_old, :captures
    def captures(other = false)
        unless other
            self.captures_old
        else
            self.captures_old - other.match(self.to_s).captures_old
        end
    end
end

#example
basic = /(a)/
simple = /#{basic}b(c)/
complex = /(#{simple}) - (#{simple})/

#usual behavior
p basic.match("abc - abc").captures
p simple.match("abc - abc").captures
p complex.match("abc - abc").captures
#removes those from simple which also contain those from basic
p complex.match("abc - abc").captures(simple)

答案 2 :(得分:1)

这比我想象的要难。如果我改变一个要求,而不是旋转更多的轮子一切似乎都很容易而不是尝试替换任何捕获组,而只替换命名的捕获组。

感谢@JustinMorgan和@TimPietzcker让我这么做。

这就是我想出来的:

class Regexp
  # replaces all named capture groups with non-capturing groups
  # in other words, it replaces all (?<*>...) with (?:...)
  def without_named_captures
      named_captures = %r{\(\?<[^>]+>}
      pattern = self.source.gsub(named_captures, "(?:")
      Regexp.new(pattern)
  end
end

通过了这个规范:

describe "Regexp Extensions" do
  describe "#without_named_captures" do
    it "should replace named captures with non-captures" do
      p1 = /(?<a>.*) - (?<b>.*)/
      p2 = p1.without_named_captures

      p2.should == /(?:.*) - (?:.*)/

      # sanity check
      p1.match('abc - def').should have_exactly(3).items
      p2.match('abc - def').should have_exactly(1).items
    end
  end
end

处理递归,转义和所有其他垃圾,当令牌比单个'(')更复杂时,它就会消失。如果我在任何地方使用命名捕获,我可以使用这种方法。如果我不这样做,事情表现正常。

已经很晚了,所以我不知道我是否遗漏了任何东西,但我认为这样做会有效。

感谢大家的帮助。

答案 3 :(得分:0)

您可以将特定正则表达式中的所有捕获组切换为非捕获 我真的不太了解Ruby正则表达式的味道,买你应该得到jist
用这个Perl例子。我用一个超集正则表达式以图形方式注释捕获 正则表达式中的缓冲区。

这是轻型版本,并且适用于普通的正则表达式 它通常使用回调进行全局替换,测试捕获缓冲区 确定我们的匹配类型。

很抱歉,如果这有点复杂。

编辑请注意,这最初用作使用全局
的注释正则表达式 搜索,没有替代品。转向非捕获群体可能会破坏这一目标 原始正则表达式在对非命名捕获组的引用时的意图。

use strict;
use warnings;

#
 my $rxgroup = qr/
    (?:
        (?<!\\)   # Not an escape behind us

        ( (?:\\.)* )  ## CaptGRP 1 - 0 or more "escape + any char"

        ( ## CaptGRP 2

             # Exclude character class'
              \[
                 \]?
                 (?: \\.| \[:[a-z]*:\] | [^\]\n] )*
                 \n?
                 (?: \\.| \[:[a-z]*:\] | [^\]] )*
              \]
           |
             (?# Exclude extended comments )
               \(\?\# [^)]* \)
           |
             # Exclude free comments
              \# [^\n]*

           |
             # Start of a literal capture group
             ( \(  )      ## CaptGRP 3
              (?:
                  (?!\?)    # unnamed: not a ? in front of us

                ## block for annotation only  
                ##  |           # or (Perl 5.10 and above)
                ##              # named: a ?<name> or ?'name' is ok
                ##    (?= \?[<'][^\W\d][\w]*['>] )
              )
        )
     )
  /x;

#
 my @samples = (

  qr/ \(\$th(\\(?:.) [(] \\\\(.\)\\\(.)(i(s))\t(i(s)) ] )/x,
  qr/
     \(\$th(\\(?:.) [(]
     (?# Extended lines
         of comment
     )
     \\\\(.\)\\\(.)(i(s))\t(i(s)) ] )
    /x,
  $rxgroup
 );

#
 for (@samples)
 {
    print "\n\n", '='x20, "\nold: \n\n$_\n\n", '-'x10, "\n";
    s/$rxgroup/ defined $3 ? "$1(?:" : "$1$2" /eg;
    print "new: \n\n$_\n";
 }

输出:

====================
old:

(?x-ism: \(\$th(\\(?:.) [(] \\\\(.\)\\\(.)(i(s))\t(i(s)) ] ))

----------
new:

(?x-ism: \(\$th(?:\\(?:.) [(] \\\\(?:.\)\\\(.)(?:i(?:s))\t(?:i(?:s)) ] ))


====================
old:

(?x-ism:
     \(\$th(\\(?:.) [(]
     (?# Extended lines
         of comment
     )
     \\\\(.\)\\\(.)(i(s))\t(i(s)) ] )
    )

----------
new:

(?x-ism:
     \(\$th(?:\\(?:.) [(]
     (?# Extended lines
         of comment
     )
     \\\\(?:.\)\\\(.)(?:i(?:s))\t(?:i(?:s)) ] )
    )


====================
old:

(?x-ism:
    (?:
        (?<!\\)   # Not an escape behind us

        ( (?:\\.)* )  ## CaptGRP 1 - 0 or more "escape + any char"

        ( ## CaptGRP 2

             # Exclude character class'
              \[
                 \]?
                 (?: \\.| \[:[a-z]*:\] | [^\]\n] )*
                 \n?
                 (?: \\.| \[:[a-z]*:\] | [^\]] )*
              \]
           |
             (?# Exclude extended comments )
               \(\?\# [^)]* \)
           |
             # Exclude free comments
              \# [^\n]*

           |
             # Start of a literal capture group
             ( \(  )      ## CaptGRP 3
              (?:
                  (?!\?)    # unnamed: not a ? in front of us

                ## block for annotation only
                ##  |           # or (Perl 5.10 and above)
                ##              # named: a ?<name> or ?'name' is ok
                ##    (?= \?[<'][^\W\d][\w]*['>] )
              )
        )
     )
  )

----------
new:

(?x-ism:
    (?:
        (?<!\\)   # Not an escape behind us

        (?: (?:\\.)* )  ## CaptGRP 1 - 0 or more "escape + any char"

        (?: ## CaptGRP 2

             # Exclude character class'
              \[
                 \]?
                 (?: \\.| \[:[a-z]*:\] | [^\]\n] )*
                 \n?
                 (?: \\.| \[:[a-z]*:\] | [^\]] )*
              \]
           |
             (?# Exclude extended comments )
               \(\?\# [^)]* \)
           |
             # Exclude free comments
              \# [^\n]*

           |
             # Start of a literal capture group
             (?: \(  )      ## CaptGRP 3
              (?:
                  (?!\?)    # unnamed: not a ? in front of us

                ## block for annotation only
                ##  |           # or (Perl 5.10 and above)
                ##              # named: a ?<name> or ?'name' is ok
                ##    (?= \?[<'][^\W\d][\w]*['>] )
              )
        )
     )
  )

答案 4 :(得分:0)

我知道这是一个古老的问题,但是,我为解析器项目写了一个改进,其中包含许多带有捕获组以供扫描的表达式,这些表达式需要相同的非捕获副本进行拆分。

refine Regexp do
  def decapture
    Regexp.new(to_s.gsub(/\(\?<\w+>|(?<![^\\]\\)\((?!\?)/, '(?:'))
  end
end

它适用于捕获组以及命名的捕获组,支持表达式选项,特殊组和文字反斜杠/括号对。这是测试(Ruby 2.5):

describe :decapture do
  it "should replace capture groups with non-capture groups" do
    /(foo) baz (bar)/.decapture.must_equal /(?-mix:(?:foo) baz (?:bar))/
    /(foo) baz (bar)/i.decapture.must_equal /(?i-mx:(?:foo) baz (?:bar))/
  end

  it "should replace named capture groups with non-capture groups" do
    /(?<a>foo) baz (?<b>bar)/.decapture.must_equal /(?-mix:(?:foo) baz (?:bar))/
    /(?<a>foo) baz (?<b>bar)/i.decapture.must_equal /(?i-mx:(?:foo) baz (?:bar))/
  end

  it "should not replace special groups" do
    /(?:foo) (?<=baz) bar/.decapture.must_equal /(?-mix:(?:foo) (?<=baz) bar)/
  end

  it "should not replace literal round brackets" do
    /\(foo\)/.decapture.must_equal /(?-mix:\(foo\))/
  end

  it "should replace literal backslash followed by literal round brackets" do
    /\\(foo\\)/.decapture.must_equal /(?-mix:\\(?:foo\\))/
  end
end