清理错误的UTF-8字符串

时间:2019-09-19 18:59:43

标签: string go utf-8 utf

我的gRPC服务由于用户数据格式错误而无法发送请求。原来HR用户数据的UTF-8字符串错误,gRPC无法对其进行编码。我将坏字段缩小为该字符串:

"Gr\351gory Smith" // Gr�gory Smith  (this is coming from an LDAP source)

因此,我希望有一种方法可以清理此类输入,如果它们包含错误的UTF-8编码。

unicode/utf8标准软件包中看不到任何明显的清理功能,这是我的首次尝试:

func naïveSanitizer(in string) (out string) {
    for _, rune := range in {
        out += string(rune)
    }
    return
}

输出:

Before: Valid UTF-8? false  Name: 'Gr�gory Smith' Byte-Count:  13
After:  Valid UTF-8? true   Name: 'Gr�gory Smith' Byte-Count:  15

Playground version

是否有更好或更标准的方法来从错误的UTF-8字符串中挽救尽可能多的有效数据?


我在这里暂停的原因是,当迭代字符串并遇到错误的(第3个)字符时,utf8.ValidRune(rune)返回truehttps://play.golang.org/p/_FZzeTRLVls

因此,我的后续问题是,是否会迭代一个字符串-一次一个符文-符文值始终有效吗?即使底层的源字符串编码格式不正确?


编辑:

请澄清一下,该数据来自LDAP来源:500K用户记录。在这50万条记录中,只有15(十五)条记录,即约0.03%,返回uf8.ValidString(...)中的false

正如@kostix和@petSO指出的那样,如果将这些值从另一编码(例如Latin-1)转换为UTF-8,则这些值可能是有效的。将此理论应用于这些离群样本:

https://play.golang.org/p/9BA7W7qQcV3

Name:     "Jean-Fran\u00e7ois Smith" : (good UTF-8) :            : Jean-François Smith
Name:                   "Gr\xe9gory" : (bad  UTF-8) : Latin-1-Fix: Grégory
Name:               "Fr\xe9d\xe9ric" : (bad  UTF-8) : Latin-1-Fix: Frédéric
Name:                 "Fern\xe1ndez" : (bad  UTF-8) : Latin-1-Fix: Fernández
Name:                     "Gra\xf1a" : (bad  UTF-8) : Latin-1-Fix: Graña
Name:                     "Mu\xf1oz" : (bad  UTF-8) : Latin-1-Fix: Muñoz
Name:                     "P\xe9rez" : (bad  UTF-8) : Latin-1-Fix: Pérez
Name:                    "Garc\xeda" : (bad  UTF-8) : Latin-1-Fix: García
Name:                  "Gro\xdfmann" : (bad  UTF-8) : Latin-1-Fix: Großmann
Name:                     "Ure\xf1a" : (bad  UTF-8) : Latin-1-Fix: Ureña
Name:                    "Iba\xf1ez" : (bad  UTF-8) : Latin-1-Fix: Ibañez
Name:                     "Nu\xf1ez" : (bad  UTF-8) : Latin-1-Fix: Nuñez
Name:                     "Ba\xd1on" : (bad  UTF-8) : Latin-1-Fix: BaÑon
Name:                  "Gonz\xe1lez" : (bad  UTF-8) : Latin-1-Fix: González
Name:                    "Garc\xeda" : (bad  UTF-8) : Latin-1-Fix: García
Name:                 "Guti\xe9rrez" : (bad  UTF-8) : Latin-1-Fix: Gutiérrez
Name:                      "D\xedaz" : (bad  UTF-8) : Latin-1-Fix: Díaz
Name:               "Encarnaci\xf3n" : (bad  UTF-8) : Latin-1-Fix: Encarnación

3 个答案:

答案 0 :(得分:2)

您可以通过删除无效的符文来改善您的“消毒剂”:

package main

import (
    "fmt"
    "strings"
)

func notSoNaïveSanitizer(s string) string {
    var b strings.Builder
    for _, c := range s {
        if c == '\uFFFD' {
            continue
        }
        b.WriteRune(c)
    }
    return b.String()
}

func main() {
    fmt.Println(notSoNaïveSanitizer("Gr\351gory Smith"))
}

Playground

问题是\351Latin-1中的字符é。

@PeterSO指出它也恰好在Unicode的BMP中的同一位置,这是正确的,但是Unicode不是一种编码,并且您的数据据称已编码,所以我认为您对此的假设不正确。数据编码,它不是UTF-8,而是Latin-1(或与拉丁字母重读字母兼容的东西)。

因此,我确认您确实在处理Latin-1(或其他),如果是这样, golang.org/x/text/encoding提供了完整的工具,可以将传统编码重新编码为UTF-8(或其他格式)。

(顺便说一句,您可能也不会碰巧明确要求您的数据源为您提供UTF-8编码的数据。)

答案 1 :(得分:2)

Go 1.13引入了strings.ToValidUTF8(),因此sanitizer()应该简单地是:

func sanitize(s string) string {
    return strings.ToValidUTF8(s, "")
}

我什至不认为应该发挥自己的作用。在Go Playground上尝试一下。

如果您的输入恰好是字节片,则可以使用类似的bytes.ToValidUTF8()函数。

还要注意,如果您不只是想丢弃输入中的某些数据而没有任何痕迹,则可以在调用strings.ToValidUTF8()时使用任何替换字符(或多个字符),例如:

return strings.ToValidUTF8(in, "❗")

Go Playground上尝试这个。

答案 2 :(得分:1)

解决您的问题。 \351是Unicode代码点é的八进制值。

package main

import "fmt"

func main() {
    fmt.Println(string(rune(0351)))
    fullname := "Grégory Smith" // "Gr\351gory Smith"
    fmt.Println(fullname)
}

游乐场:https://play.golang.org/p/WigFZk3iSK1

输出:

é
Grégory Smith