Golang正则表达式替换不包括引用的字符串

时间:2016-04-19 17:16:53

标签: regex go

我正在尝试从this Javascript implementation在Golang中实现removeComments函数。我希望删除文中的任何评论。例如:

/* this is comments, and should be removed */

However, "/* this is quoted, so it should not be removed*/"

在Javascript实现中,引用匹配不会在组中捕获,因此我可以轻松地将其过滤掉。然而,在Golang中,似乎不容易判断匹配的部分是否在一个组中被捕获。那么如何在Golang中实现与Javascript版本相同的removeComments逻辑呢?

6 个答案:

答案 0 :(得分:3)

背景

执行任务的正确方法是匹配和捕获引用的字符串(记住可以在内部转义实体),然后匹配多行注释。

REGEX IN-CODE DEMO

以下是处理该问题的代码:

package main
import (
    "fmt"
    "regexp"
)
func main() {
    reg := regexp.MustCompile(`("[^"\\]*(?:\\.[^"\\]*)*")|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`)
        txt := `random text
            /* removable comment */
            "but /* never remove this */ one"
             more random *text*`
        fmt.Println(reg.ReplaceAllString(txt, "$1"))
}

请参阅Playground demo

说明

我建议的正则表达式是用Best Regex Trick Ever概念编写的,包含两个选项:

  • ("[^"\\]*(?:\\.[^"\\]*)*") - 双引号字符串文字正则表达式 - 第1组(请参阅由外部非转义括号组成的capturing group,稍后可通过replacement backreferences访问)匹配可以包含转义序列的双引号字符串文字。这部分匹配:
    • " - 领先的双引号
    • [^"\\]* - 除"\之外的0 +个字符(因为[^...]构造是negated character class,它匹配任何字符,但在其中定义的字符除外) (*零个或多个匹配quantifier的匹配项
    • (?:\\.[^"\\]*)*" - 0+序列(请参阅最后*non-capturing group仅用于子模式而不形成捕获)转义序列( \\.匹配文字\后跟任何字符),后跟0+ "\
    • 以外的字符
  • | - 或
  • /\*[^*]*\*+(?:[^/*][^*]*\*+)*/ - 多行注释正则表达式部分匹配*而不形成捕获组(因此,通过反向引用从替换模式中无法使用)和匹配
    • / - /文字斜杠
    • \* - 文字星号
    • [^*]* - 除星号外的零个或多个字符
    • \*+ - 1个或更多(+一个或多个匹配量词的出现次数)asterisks
    • (?:[^/*][^*]*\*+)* - 除了/*(参见[^/*])之外的任何字符的0+序列(非捕获,我们以后不再使用),除了星号外有0+个字符(请参阅[^*]*),然后是1 +星号(参见\*+)。
    • / - 一个字面(尾随,结束)斜杠。

注意 此多行注释正则表达式是我测试过的最快的。同样适用于双引号文字正则表达式{{1在编写时考虑到unroll-the-loop technique:没有替换,只有具有"[^"\\]*(?:\\.[^"\\]*)*"*量词的字符类按特定顺序使用才能实现最快的匹配。

关于模式增强的说明

如果您计划扩展到匹配单引号文字,那么没有什么比这更容易了,只需通过重复使用双引号字符串文字正则表达式并将双引号替换为单引号,将另一个备选项添加到第一个捕获组:

+

以下是single- and double-quoted literal supporting regex demo removing the miltiline comments

添加单行注释支持类似:只需添加reg := regexp.MustCompile(`("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`) ^-------------------------^ 替代结尾:

//[^\n\r]*

这是single- and double-quoted literal supporting regex demo removing the miltiline and singleline comments

答案 1 :(得分:2)

我从未在Go中读过/写过任何内容,所以请耐心等待。幸运的是,我知道正则表达式。我对Go正则表达式进行了一些研究,看起来它们缺乏大多数现代功能(例如引用)。

尽管如此,我已经开发了一个似乎正是你正在寻找的正则表达式。我假设所有字符串都是单行。这是:

reg := regexp.MustCompile(`(?m)^([^"\n]*)/\*([^*]+|(\*+[^/]))*\*+/`)

txt := `random text
        /* removable comment */
            "but /* never remove this */ one"
        more random *text*`

fmt.Println(reg.ReplaceAllString(txt, "${1}"))

变体:上述版本不会删除引号后发生的注释。这个版本会,但可能需要多次运行。

reg := regexp.MustCompile(
   `(?m)^(([^"\n]*|("[^"\n]*"))*)/\*([^*]+|(\*+[^/]))*\*+/`
)
txt := `
   random text
   what /* removable comment */
   hi "but /* never remove this */ one" then /*whats here*/ i don't know /*what*/
   more random *text*
`
newtxt := reg.ReplaceAllString(txt, "${1}")
fmt.Println(newtxt)
newtxt = reg.ReplaceAllString(newtxt, "${1}")
fmt.Println(newtxt)

解释

  • (?m)表示多行模式。 Regex101给出了一个很好的解释:

      

    ^和$ anchors现在分别在每行的开头/结尾匹配,而不是整个字符串的开头/结尾。

    需要将其锚定到每一行的开头(使用^)以确保报价尚未开始。

  • 第一个正则表达式有:[^"\n]*。从本质上讲,它匹配的所有内容都不是"\n。我添加了括号,因为这些东西不是评论,所以需要放回。

  • 第二个正则表达式有:(([^"\n]*|("[^"\n]*"))*)。具有此语句的正则表达式可以匹配[^"\n]*(就像第一个正则表达式那样),或者|)它可以匹配一对引号(以及它们之间的内容)与"[^"\n]*" 。它是重复的,所以当有多个报价对时,它可以正常工作。请注意,与更简单的正则表达式一样,正在捕获此非注释内容。

  • 两个正则表达式都使用此:/\*([^*]+|(\*+[^/]))*\*+/。它匹配/*后跟任意数量的任何一个:

    • [^*]+*字符

    • \*+[^/]一个或多个*未被/跟踪。
  • 然后它与结束*/

  • 相匹配
  • 在替换过程中,${1}会引用已捕获的非评论内容,因此会将其重新插入字符串中。

答案 2 :(得分:1)

演示

Play golang demo

(输出每个阶段的工作,通过向下滚动可以看到最终结果。)

方法

一些"技巧"习惯于使用Golang的somewhat limited regex syntax

  1. 使用唯一字符替换开始引号和结束引号。至关重要的是,用于识别开头和结尾引号的字符必须彼此不同,并且极不可能出现在正在处理的文本中。
  2. 替换所有的评论启动者(/*),其前面是未终止的起始引号,其中包含一个或多个字符的唯一序列。
  3. 同样,将一个的所有注释表单(*/)替换为之前没有开头引号的结束引号,并使用一个或多个字符的不同唯一序列
  4. 删除所有剩余的/*...*/个评论序列。
  5. 取消屏蔽之前的"屏蔽"通过撤消上面步骤2和3中所做的替换来评论启动者/启动者。
  6. 限制

    目前的演示并未解决评论中出现双引号的可能性,例如: /* Not expected: " */注意:我的感觉是这可以处理 - 只是还没有付出努力 - 所以,如果你认为这可能是一个问题,请告诉我,我会调查它。

答案 3 :(得分:1)

只是为了另一种方法,将最小的词法分析器作为状态机实现,受到Rob Pike talk http://cuddle.googlecode.com/hg/talk/lex.html的启发和描述。代码更冗长,但更具可读性,可理解性和可破解性,然后是regexp。它也可以与任何Reader和Writer一起使用,而不仅仅是字符串,所以不要消耗RAM,甚至应该更快。

type stateFn func(*lexer) stateFn

func run(l *lexer) {
    for state := lexText; state != nil; {
        state = state(l)
    }
}

type lexer struct {
    io.RuneReader
    io.Writer
}
func lexText(l *lexer) stateFn {
    for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() {
        switch r {
        case '"':
            l.Write([]byte(string(r)))
            return lexQuoted
        case '/':
            r, _, err = l.ReadRune()
            if r == '*' {
                return lexComment
            } else {
                l.Write([]byte("/"))
                l.Write([]byte(string(r)))
            }
        default:
            l.Write([]byte(string(r)))
        }
    }
    return nil
}
func lexQuoted(l *lexer) stateFn {
    for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() {
        if r == '"' {
            l.Write([]byte(string(r)))
            return lexText
        }
        l.Write([]byte(string(r)))
    }

    return nil
}

func lexComment(l *lexer) stateFn {
    for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() {
        if r == '*' {
            r, _, err = l.ReadRune()
            if r == '/' {
                return lexText
            }
        }
    }

    return nil
}

您可以看到它有效http://play.golang.org/p/HyvEeANs1u

答案 4 :(得分:1)

这些不保留格式

首选方式(如果组1不匹配则产生NULL)
在golang游乐场工作 -

     # https://play.golang.org/p/yKtPk5QCQV
     # fmt.Println(reg.ReplaceAllString(txt, "$1"))
     # (?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//[^\n]*(?:\n|$))|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|[\S\s][^/"'\\]*)

     (?:                              # Comments 
          /\*                              # Start /* .. */ comment
          [^*]* \*+
          (?: [^/*] [^*]* \*+ )*
          /                                # End /* .. */ comment
       |  
          //  [^\n]*                       # Start // comment
          (?: \n | $ )                     # End // comment
     )
  |  
     (                                # (1 start), Non - comments 
          "
          [^"\\]*                          # Double quoted text
          (?: \\ [\S\s] [^"\\]* )*
          "
       |  
          '
          [^'\\]*                          # Single quoted text
          (?: \\ [\S\s] [^'\\]* )*
          ' 
       |  [\S\s]                           # Any other char
          [^/"'\\]*                        # Chars which doesn't start a comment, string, escape, or line continuation (escape + newline)
     )                                # (1 end)

替代方式(组1始终匹配,但可以为空)
在golang游乐场工作 -

 # https://play.golang.org/p/7FDGZSmMtP
 # fmt.Println(reg.ReplaceAllString(txt, "$1"))
 # (?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//[^\n]*(?:\n|$))?((?:"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|[\S\s][^/"'\\]*)?)     

 (?:                              # Comments 
      /\*                              # Start /* .. */ comment
      [^*]* \*+
      (?: [^/*] [^*]* \*+ )*
      /                                # End /* .. */ comment
   |  
      //  [^\n]*                       # Start // comment
      (?: \n | $ )                     # End // comment
 )?
 (                                # (1 start), Non - comments 
      (?:
           "
           [^"\\]*                          # Double quoted text
           (?: \\ [\S\s] [^"\\]* )*
           "
        |  
           '
           [^'\\]*                          # Single quoted text
           (?: \\ [\S\s] [^'\\]* )*
           ' 
        |  [\S\s]                           # Any other char
           [^/"'\\]*                        # Chars which doesn't start a comment, string, escape, or line continuation (escape + newline)
      )?
 )                                # (1 end)

Cadilac - 保留格式

(不幸的是,这不能在Golang中完成,因为Golang不能做断言)
发布,如果您转移到另一个正则表达式引擎。

     # raw:   ((?:(?:^[ \t]*)?(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|/\*|//)))?|//(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|/\*|//))|(?=\r?\n))))+)|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|(?:\r?\n|[\S\s])[^/"'\\\s]*)
     # delimited:  /((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|(?:\r?\n|[\S\s])[^\/"'\\\s]*)/

     (                                # (1 start), Comments 
          (?:
               (?: ^ [ \t]* )?                  # <- To preserve formatting
               (?:
                    /\*                              # Start /* .. */ comment
                    [^*]* \*+
                    (?: [^/*] [^*]* \*+ )*
                    /                                # End /* .. */ comment
                    (?:                              # <- To preserve formatting 
                         [ \t]* \r? \n                                      
                         (?=
                              [ \t]*                  
                              (?: \r? \n | /\* | // )
                         )
                    )?
                 |  
                    //                               # Start // comment
                    (?:                              # Possible line-continuation
                         [^\\] 
                      |  \\ 
                         (?: \r? \n )?
                    )*?
                    (?:                              # End // comment
                         \r? \n                               
                         (?=                              # <- To preserve formatting
                              [ \t]*                          
                              (?: \r? \n | /\* | // )
                         )
                      |  (?= \r? \n )
                    )
               )
          )+                               # Grab multiple comment blocks if need be
     )                                # (1 end)

  |                                 ## OR

     (                                # (2 start), Non - comments 
          "
          [^"\\]*                          # Double quoted text
          (?: \\ [\S\s] [^"\\]* )*
          "
       |  
          '
          [^'\\]*                          # Single quoted text
          (?: \\ [\S\s] [^'\\]* )*
          ' 
       |  
          (?: \r? \n | [\S\s] )            # Linebreak or Any other char
          [^/"'\\\s]*                      # Chars which doesn't start a comment, string, escape,
                                           # or line continuation (escape + newline)
     )                                # (2 end)

答案 5 :(得分:0)

试试这个例子..

play golang