2008-09-26 25 views
7

我需要替换字符(比如说)x与字符(比如说)P在一个字符串中,但仅当它包含在引用子字符串中时。 的一个例子使之更清晰:Can Regex可以用于这个特定的字符串操作吗?

axbx'cxdxe'fxgh'ixj'k -> axbx'cPdPe'fxgh'iPj'k 

假设,为了简单起见,报价总是成对出现。

最显而易见的方法是只一次处理(一个简单的状态机的方法)将字符串一个字符;然而,我想知道是否可以使用正则表达式一次完成所有处理。

我的目标语言是C#,但我想我的问题是指具有内置任何语言或正则表达式库支持。

+0

删除 '规则表达式' 标签 – 2008-09-26 16:10:59

回答

8

我可以使用Python做到这一点:

>>> import re 
>>> re.sub(r"x(?=[^']*'([^']|'[^']*')*$)", "P", "axbx'cxdxe'fxgh'ixj'k") 
"axbx'cPdPe'fxgh'iPj'k" 

这样做的是使用非获取匹配(= ...?)检查字符x在引用字符串内。它会查找一些直到下一个引号的非引号字符,然后查找单个字符或带引号的字符组的序列,直到字符串结束。

这依赖于你的假设,即引号总是平衡的。这也不是很有效。

+0

也考虑到它的应用re.sub在替换 'X' 由 'P'( )函数遍历字符串。正则表达式本身只匹配引号内的第一个x。 – 2008-09-26 10:33:31

+0

我无法想象如何使用像re.sub()这样的东西来解决这个问题。毕竟,正则表达式本身只是匹配,而原始问题是关于替换的问题。 – 2008-09-26 10:36:52

1

不是用普通的正则表达式。正则表达式没有“记忆”,因此它们无法区分是“内部”还是“外部”引号。

您使用gema这将是简单明了需要更强大的东西,例如:

'<repl>'=$0 
repl:x=P 
0

不好意思去打破你的希望,但你需要一个下推自动机来做到这一点。这里有更多的信息: Pushdown Automaton

简而言之,规则表达式是有限状态机只能读取并且没有内存,而下推自动机具有堆栈和操作能力。

编辑:拼写...

1

约平衡文本类似的讨论取代:Can regular expressions be used to match nested patterns?

虽然你可以试试这个在Vim的,但它运作良好,只有当字符串是在一行上,而且也只有一对的的。

:%s:\('[^']*\)x\([^']*'\):\1P\2:gci 

如果还有一对或甚至不平衡',那么它可能会失败。那样,我在ex命令中包含了c又名确认标志。

同样可以使用sed来完成,没有互动 - 或awk这样你就可以添加一些互动。

一个可行的办法是打破对'当时的你可以用vim的解决方案做线。

9

我将Greg Hewgill的python代码转换为C#,它工作正常!

[Test] 
public void ReplaceTextInQuotes() 
{ 
    Assert.AreEqual("axbx'cPdPe'fxgh'iPj'k", 
    Regex.Replace("axbx'cxdxe'fxgh'ixj'k", 
     @"x(?=[^']*'([^']|'[^']*')*$)", "P")); 
} 

该测试通过。

1
Pattern:  (?s)\G((?:^[^']*'|(?<=.))(?:'[^']*'|[^'x]+)*+)x 
Replacement: \1P 
  1. \G —锚每个匹配在先前一个或字符串的开始的端部。
  2. (?:^[^']*'|(?<=.)) —如果它在字符串的开头,则匹配到第一个引号。
  3. (?:'[^']*'|[^'x]+)*+ —匹配任何未加引号的字符块或任何(非引号)字符到'x'。

一次扫过槽源字符串,除了单个字符后视。

2

诀窍是使用非捕获组字符串的以下匹配(字符X)我们正在搜索的部分相匹配。 试图匹配高达x的字符串只会查找第一个或最后一个发生,具体取决于是否使用非贪婪量词。 这里是格雷格的想法转置到Tcl的评论。

 
set strIn {axbx'cxdxe'fxgh'ixj'k} 
set regex {(?x)      # enable expanded syntax 
            # - allows comments, ignores whitespace 
      x      # the actual match 
      (?=      # non-matching group 
       [^']*'    # match to end of current quoted substring 
            ## 
            ## assuming quotes are in pairs, 
            ## make sure we actually were 
            ## inside a quoted substring 
            ## by making sure the rest of the string 
            ## is what we expect it to be 
            ## 
       (
        [^']*   # match any non-quoted substring 
        |    # ...or... 
        '[^']*'   # any quoted substring, including the quotes 
       )*     # any number of times 
       $     # until we run out of string :) 
      )      # end of non-matching group 
} 

#the same regular expression without the comments 
set regexCondensed {(?x)x(?=[^']*'([^']|'[^']*')*$)} 

set replRegex {P} 
set nMatches [regsub -all -- $regex $strIn $replRegex strOut] 
puts "$nMatches replacements. " 
if {$nMatches > 0} { 
    puts "Original: |$strIn|" 
    puts "Result: |$strOut|" 
} 
exit 

此打印:

3 replacements. 
Original: |axbx'cxdxe'fxgh'ixj'k| 
Result: |axbx'cPdPe'fxgh'iPj'k| 
2
#!/usr/bin/perl -w 

use strict; 

# Break up the string. 
# The spliting uses quotes 
# as the delimiter. 
# Put every broken substring 
# into the @fields array. 

my @fields; 
while (<>) { 
    @fields = split /'/, $_; 
} 

# For every substring indexed with an odd 
# number, search for x and replace it 
# with P. 

my $count; 
my $end = $#fields; 
for ($count=0; $count < $end; $count++) { 
    if ($count % 2 == 1) { 
     $fields[$count] =~ s/a/P/g; 
    }  
} 

这是不是块做的工作?

2

允许非配对引号的更一般(更简单)的解决方案。

  1. 查找引用字符串
  2. 字符串

    #!/usr/bin/env python 
    import re 
    
    text = "axbx'cxdxe'fxgh'ixj'k" 
    
    s = re.sub("'.*?'", lambda m: re.sub("x", "P", m.group(0)), text) 
    
    print s == "axbx'cPdPe'fxgh'iPj'k", s 
    # -> True axbx'cPdPe'fxgh'iPj'k 
    
相关问题