清理错误的UTF-8字符串

My gRPC service failed to send a request due to malformed user-data. Turns out the HR user-data has a bad UTF-8 string and gRPC could not encode it. I narrowed the bad field down to this string:

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

So I want a way to sanitized such inputs should they contain bad UTF-8 encodings.

Not seeing any obvious sanitization functions in the unicode/utf8 standard package, here's my first naïve attempt:

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

Output:

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

Is there a better or more standard way to salvage as much valid data from a bad UTF-8 string?


The reason I have pause here is because while iterating the string and the bad (3rd) character is encountered, utf8.ValidRune(rune) returns true: https://play.golang.org/p/_FZzeTRLVls

So my follow-up question is, will iterating a string - one rune at a time - will the rune value always be valid? Even though the underlying source string encoding was malformed?


EDIT:

Just to clarify, this data is coming from an LDAP source: 500K user records. Of those 500K records only 15 (fifteen) i.e. ~0.03% return a uf8.ValidString(...) of false.

As @kostix and @peterSO have pointed out, the values may be valid if converted from another encoding (e.g. Latin-1) to UTF-8. Applying this theory to these outlier samples:

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
drhzn3911
drhzn3911 我对Q进行了更多详细的编辑:这是糟糕的UTF-8,但值得庆幸的是,它所占的比例很小。
11 个月之前 回复
doushi5752
doushi5752 我认为UTF-8不错。它不是UTF-8。我认为此人的名字叫格雷戈里·史密斯(GrégorySmith),这就是您的名字。谁以UTF-8格式读取它,都会出错。解决这个问题,您就不会有问题,也不会破坏此人的名字。
11 个月之前 回复
duanpu1064
duanpu1064 根据@hobbs所说的内容,如果c=='\uFFFD'{继续;},并称之为一天。
11 个月之前 回复
doue9730
doue9730 我没有适合您的解决方案,但是问题是,在字符串上使用范围已经假定它应该是有效的,因此它用U+FFFD“替换字符”符文替换了任何无效的字节序列。对于ValidRune而言,这是有效的,因为可以对其进行编码。因此,您需要一种不会尝试在字符串范围内扩展的方法。
11 个月之前 回复

3个回答



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

 程序包main 

import(
” fmt“
” strings“

funcnotSoNaïveSanitizer(s string)string {
var b strings.Builder
for _ ,c:= range s {
如果c =='\ uFFFD'{
继续
}
b.WriteRune(c)
}
返回b.String()
}

func main(){
fmt.Println(notSoNaïveSanitizer(“ Gr \ 351gory Smith”))
}
</ code> </ pre>

游乐场。</ p>

问题是 \ 351 </ code>是字符 é在拉丁1 中。</ p>

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

所以我会 验证您是否真的在处理Latin-1(或其他任何东西),如果是,
< code> golang.org/x/text/encoding </ code> 提供了完整的工具,可以将传统编码重新编码为UTF-8(或其他格式)。</ p>

( 附带说明一下,您可能也不会碰巧明确要求您的数据源为您提供UTF-8编码的数据。)</ p>
</ div>

展开原文

原文

You could improve your "sanitiser" by dropping invalid runes:

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.

The problem though is that \351 is the character é in Latin-1.

@PeterSO pointed out it also happens to be at the same position in the Unicode's BMP, and that is correct but Unicode is not an encoding, and your data is supposedly encoded, so I think you just have an incorrect assumption about the encoding of your data and it's not UTF-8 but rather Latin-1 (or something compatible with regard to Latin accented letters).

So I'd verify you really are dealing with Latin-1 (or whatever) and if so, golang.org/x/text/encoding provides complete tooling for re-encoding from legacy encodings to UTF-8 (or whatever).

(On a side note, you might as well just not happen to explicitly ask your data source to provide you with UTF-8-encoded data.)

dongshungai4857
dongshungai4857 显然,此数据不是以UTF8开头的事实,因此输入错误。 是的,N看起来很奇怪。 在此异常值上,我尝试了其他一些编码转换,但均未成功。 我认为这几乎可以解决坏的(数据)问题。
11 个月之前 回复
dongwolu5275
dongwolu5275 BaÑon对我来说很奇怪。 这是错字(错误输入的大写字母)吗?
11 个月之前 回复
duanpu1111
duanpu1111 您的Latin-1理论取得了成果! play.golang.org/p/9BA7W7qQcV3
11 个月之前 回复
dtdvlazd56637
dtdvlazd56637 数据源是LDAP,我可以看到有效的UTF-8字符串,例如“ Jean-Fran \ 303 \ 247ois Smith”(即“Jean-FrançoisSmith”游乐场)。 当我评论@PeterSO的答案时,像这样的500K记录中有15条记录。 我将研究您的Latin-1编码理论-但错误率<%0.03,我怀疑输入软件不好。
11 个月之前 回复



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

  func sanitize  (s字符串)字符串{
返回字符串。ToValidUTF8(s,“”)
}
</ code> </ pre>

我什至都不认为应该使用它自己的功能。 在游乐场上尝试。</ p>

如果 输入恰好是一个字节片,您可以使用类似的 bytes.ToValidUTF8()</ 代码> 函数。</ p>

还请注意,如果您不只是想不加任何输入就丢弃输入中的某些数据,则可以使用任何替换字符(或多个字符) ),当调用 strings.ToValidUTF8()</ code>时,例如:</ p>

 返回字符串。ToValidUTF8(in,“❗”)
</ code> </ pre>

游乐场上尝试一下。 </ p>
</ div>

展开原文

原文

Go 1.13 introduces strings.ToValidUTF8(), so sanitizer() should simply be:

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

Which I don't even think deserves its own function. Try it on the Go Playground.

If your input happens to be a byte slice, you may use the similar bytes.ToValidUTF8() function.

Also note that if you don't just want to discard some data from your input without a trail, you may use any replacement character (or multiple characters) when calling strings.ToValidUTF8(), for example:

return strings.ToValidUTF8(in, "❗")

Try this one on the Go Playground.

dsnpjz6907
dsnpjz6907 没关系。 如果他的答案对解决您的问题最有帮助,那么他的答案应该是被接受的(如接受答案的工具提示所建议)。
11 个月之前 回复
doukang8949
doukang8949 的Latin-1编码建议可实现更整洁的数据修复。
11 个月之前 回复
dongshenyu4638
dongshenyu4638 是的我同意。 您可以传递任何字符(或字符串)以代替无效序列,例如 strings.ToValidUTF8(in,“â—”)。
11 个月之前 回复
dongluolie3487
dongluolie3487 同意。 在更坏的情况下,我会用“?”替换位旋转的字节。 留下一些面包屑的痕迹,那就是有些事情是不正确的。 我认为@kostix的建议正确,我的流氓字符是Latin-1编码的。 即将更新问题...
11 个月之前 回复
douxiandiyo58855
douxiandiyo58855 在现实世界中,安静地丢弃数据很少是一个好主意。
11 个月之前 回复



解决您的问题。 \ 351 </ code>是Unicode代码点é</ code>的八进制值。</ p>

 包main 

import“ fmt” \ n
func main(){
fmt.Println(string(rune(0351)))
全名:=“GrégorySmith” //“ Gr \ 351gory Smith”
fmt.Println(全名)
} \ n </ code> </ pre>

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

输出:</ p>

 é
格雷戈里·史密斯
</ code> </ pre>
</ div>

展开原文

原文

Fix your problem. \351 is the octal value of Unicode code point é.

package main

import "fmt"

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

Playground: https://play.golang.org/p/WigFZk3iSK1

Output:

é
Grégory Smith

dqitk20644
dqitk20644 而且可以正确读取LDAP数据,因为我可以看到有效的UTF-8字符串,例如“ Jean-Fran \ 303 \ 247ois Smith”(即“Jean-FrançoisSmith”)
11 个月之前 回复
drz49609
drz49609 欣赏到代码点错误的见解,但这不能自动解决问题。 这些字符串来自HR LDAP数据:总共50万条记录。 扫描整个LDAP树,我发现15条记录的UTF-8字段错误。 我需要一种自动清除这些字段的方法,以便可以对它们进行处理,即,我不想跳过记录,因为HR搞砸了。
11 个月之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐