Дезинфекция плохих струн UTF-8 - PullRequest
1 голос
/ 19 сентября 2019
Служба

My 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

версия игровой площадки

Есть лилучший или более стандартный способ получить как можно больше допустимых данных из неверной строки UTF-8?


Причина, по которой я здесь остановился, заключается в том, что при итерации строки и встречается плохой (3-й) символ,utf8.ValidRune(rune) возвращает true: https://play.golang.org/p/_FZzeTRLVls

Итак, мой следующий вопрос: будет ли повторение строки - по одной руне за раз - всегда ли значение руны будет действительным?Даже если исходное кодирование строки источника было искажено?


РЕДАКТИРОВАТЬ:

Просто для пояснения, эти данные поступают из источника LDAP: 500K пользовательских записей.Из этих 500K записей только 15 (пятнадцать), т. Е. ~ 0,03%, возвращают uf8.ValidString(...) из false.

Как указали @kostix и @peterSO, значения могут быть действительными, если преобразовать из другое кодирование (например, 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 ]

2 голосов
/ 19 сентября 2019

Вы можете улучшить свой «дезинфицирующее средство», отбросив недопустимые руны:

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"))
}

Детская площадка .

Однако проблема в том, что \351 является персонажем éв Latin-1 .

@ PeterSO указал, что он также находится в той же позиции в BMP Unicode, и это правильно, но Unicode не является кодировкой, и ваши данныепредположительно закодированы, так что я думаю, что у вас просто неверное предположение о кодировке ваших данных, и это не UTF-8, а скорее Latin-1 (или что-то совместимое с латинскими буквами с акцентом).

Итак, я 'd убедитесь, что вы действительно имеете дело с Latin-1 (или чем-то еще), и если так, golang.org/x/text/encoding предоставляет полный набор инструментов для перекодирования из устаревших кодировок в UTF-8 (или что-то еще).

(Кстати, вы можете просто не попросить источник данных предоставить вам данные в кодировке UTF-8.)

2 голосов
/ 19 сентября 2019

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 .

1 голос
/ 19 сентября 2019

Исправьте вашу проблему.\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
...