使用函数替换正则表达式的子匹配

19

假设我有像下面这样的字符串:

input := `bla bla b:foo="hop" blablabla b:bar="hu?"`

我想用一个函数替换在b:foo="hop"b:bar="hu?"引号中的部分。

可以轻松构建正则表达式以获取匹配和子匹配,例如:

r := regexp.MustCompile(`\bb:\w+="([^"]+)"`)

然后调用ReplaceAllStringFunc,但问题是回调函数接收的是整个匹配项而不是子匹配项:

fmt.Println(r.ReplaceAllStringFunc(input, func(m string) string {
    // m is the whole match here. Damn.
}))

我该如何替换子匹配?

目前,我还没有找到比在回调函数中使用正则表达式分解m并在处理子匹配后重建字符串更好的解决方案。

如果在Go语言中有正向零宽断言,我会使用另一种方法来处理,但那不是现实情况(而且也不应该是必要的)。

我该怎么办呢?


编辑:这是我的当前解决方案,我希望能够简化:

func complexFunc(s string) string {
   return "dbvalue("+s+")" // this could be more complex
}
func main() {
        input := `bla bla b:foo="hop" blablabla b:bar="hu?"`
        r := regexp.MustCompile(`(\bb:\w+=")([^"]+)`)
        fmt.Println(r.ReplaceAllStringFunc(input, func(m string) string {
                parts := r.FindStringSubmatch(m)
                return parts[1] + complexFunc(parts[2])
        }))
}

(playground链接)

让我困扰的是我必须要两次应用正则表达式。这不太对。


1
如果您认为这是一个设计错误,请随时提交错误报告 - fuz
@FUZxxl 我不确定这是否是一个设计错误。对我来说,它看起来不像是一个最优的设计,但我可能错过了一个简单的一次遍历解决方案。如果其他人在这里对此发表评论,我可能会提交一个错误报告,但我需要意见。 - Denys Séguret
1
@FUZxxl 我提交了一个问题:http://code.google.com/p/go/issues/detail?id=5690 - Denys Séguret
@zanona 我没有看到任何新的东西。 - Denys Séguret
1
@zanona 我也这么认为,使用可变参数函数作为参数就像在js中一样,确实会使代码更简洁。 - Denys Séguret
显示剩余4条评论
3个回答

4

看起来提问者已经为此创建了一个问题,但截至本帖发布时,仍未实现https://github.com/golang/go/issues/5690

幸运的是,看起来其他网站上的某个人已经提供了自己的函数来实现这一点https://gist.github.com/elliotchance/d419395aa776d632d897

func ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
    result := ""
    lastIndex := 0

    for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
        groups := []string{}
        for i := 0; i < len(v); i += 2 {
            groups = append(groups, str[v[i]:v[i+1]])
        }

        result += str[lastIndex:v[0]] + repl(groups)
        lastIndex = v[1]
    }

    return result + str[lastIndex:]
}

3
我不喜欢下面的代码,但它似乎做了你想要做的事情:
package main

import (
        "fmt"
        "regexp"
)

func main() {
        input := `bla bla b:foo="hop" blablabla b:bar="hu?"`
        r := regexp.MustCompile(`\bb:\w+="([^"]+)"`)
        r2 := regexp.MustCompile(`"([^"]+)"`)
        fmt.Println(r.ReplaceAllStringFunc(input, func(m string) string {
                return r2.ReplaceAllString(m, `"${2}whatever"`)
        }))
}

Playground


输出

bla bla b:foo="whatever" blablabla b:bar="whatever"

EDIT: 第二次尝试。
package main

import (
        "fmt"
        "regexp"
)

func computedFrom(s string) string {
        return fmt.Sprintf("computedFrom(%s)", s)
}

func main() {
        input := `bla bla b:foo="hop" blablabla b:bar="hu?"`
        r := regexp.MustCompile(`\bb:\w+="([^"]+)"`)
        r2 := regexp.MustCompile(`"([^"]+)"`)
        fmt.Println(r.ReplaceAllStringFunc(input, func(m string) string {
                match := string(r2.Find([]byte(m)))
                return r2.ReplaceAllString(m, computedFrom(match))
        }))
}

Playground


输出:

bla bla b:foo=computedFrom("hop") blablabla b:bar=computedFrom("hu?")

问题在于我想用一个复杂的函数(以此子匹配作为输入)计算子匹配的替换,这就是为什么我不能简单地在内部使用ReplaceAllString。至于优雅性,我担心正确的解决方案会更糟糕 :( - Denys Séguret
+1 对于另外一个解决方案,但像我的这个一样,它强制使用了两个正则表达式,虽然从技术上讲第一个包含了所有内容。 - Denys Séguret

0

试试这个:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    input := `bla bla b:foo="hop" blablabla b:bar="hu?"`
    r := regexp.MustCompile(`\b(b:\w+)="([^"]+)"`)
    fmt.Println(r.ReplaceAllString(input, "${1}=whatever"))
}

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