Wie erkennt man UTF-8 in normalem C?

Lesezeit: 11 Minuten

Benutzeravatar von Konstantin
Konstantin

Ich suche nach einem Code-Snippet in einfachem altem C, das erkennt, dass die angegebene Zeichenfolge in UTF-8-Codierung vorliegt. Ich kenne die Lösung mit Regex, aber aus verschiedenen Gründen wäre es besser, in diesem speziellen Fall nichts anderes als einfaches C zu verwenden.

Lösung mit Regex sieht so aus (Achtung: diverse Checks entfallen):

#define UTF8_DETECT_REGEXP  "^([\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$"

const char *error;
int         error_off;
int         rc;
int         vect[100];

utf8_re = pcre_compile(UTF8_DETECT_REGEXP, PCRE_CASELESS, &error, &error_off, NULL);
utf8_pe = pcre_study(utf8_re, 0, &error);

rc = pcre_exec(utf8_re, utf8_pe, str, len, 0, 0, vect, sizeof(vect)/sizeof(vect[0]));

if (rc > 0) {
    printf("string is in UTF8\n");
} else {
    printf("string is not in UTF8\n")
}

  • Kannst du die Lösung mit der Regex posten?

    – Ludwig Weinzierl

    23. Juni 2009 um 9:59 Uhr

  • @Konstantin: Das Obige ist kein Kommentar, bitte bearbeiten Sie die Frage direkt und fügen Sie diese Details hinzu.

    – Anthony W Jones

    23. Juni 2009 um 10:04 Uhr

  • @Ludwig: Ja, aber das ist alles, was ich brauche.

    – Konstantin

    23. Juni 2009 um 10:22 Uhr

  • @Konstantin: Danke für die Regex. Wenn die Regex das tut nicht übereinstimmen eine Zeichenfolge bedeutet, dass die Zeichenfolge sicherlich kein gültiges UTF-8 ist. Das Umgekehrte gilt jedoch nicht. Wenn es mit der Zeichenfolge übereinstimmt, kann es sich um Müll handeln, der versehentlich keine illegalen UTF-8-Sequenzen enthält.

    – Ludwig Weinzierl

    23. Juni 2009 um 10:25 Uhr

  • @Konstantin: OK, es sollte möglich sein, die Regex in einfaches C zu übersetzen. Was ein bisschen unangenehm ist, sind die {2}s und die {3}. Schauen Sie sich Christophs Lösung an, das ist der richtige Weg.

    – Ludwig Weinzierl

    23. Juni 2009 um 10:40 Uhr

Benutzeravatar von Christoph
Christoph

Hier ist eine (hoffentlich fehlerfreie) Implementierung von dieser Ausdruck in normalem C:

_Bool is_utf8(const char * string)
{
    if(!string)
        return 0;

    const unsigned char * bytes = (const unsigned char *)string;
    while(*bytes)
    {
        if( (// ASCII
             // use bytes[0] <= 0x7F to allow ASCII control characters
                bytes[0] == 0x09 ||
                bytes[0] == 0x0A ||
                bytes[0] == 0x0D ||
                (0x20 <= bytes[0] && bytes[0] <= 0x7E)
            )
        ) {
            bytes += 1;
            continue;
        }

        if( (// non-overlong 2-byte
                (0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
                (0x80 <= bytes[1] && bytes[1] <= 0xBF)
            )
        ) {
            bytes += 2;
            continue;
        }

        if( (// excluding overlongs
                bytes[0] == 0xE0 &&
                (0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF)
            ) ||
            (// straight 3-byte
                ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
                    bytes[0] == 0xEE ||
                    bytes[0] == 0xEF) &&
                (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF)
            ) ||
            (// excluding surrogates
                bytes[0] == 0xED &&
                (0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF)
            )
        ) {
            bytes += 3;
            continue;
        }

        if( (// planes 1-3
                bytes[0] == 0xF0 &&
                (0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
                (0x80 <= bytes[3] && bytes[3] <= 0xBF)
            ) ||
            (// planes 4-15
                (0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
                (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
                (0x80 <= bytes[3] && bytes[3] <= 0xBF)
            ) ||
            (// plane 16
                bytes[0] == 0xF4 &&
                (0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
                (0x80 <= bytes[3] && bytes[3] <= 0xBF)
            )
        ) {
            bytes += 4;
            continue;
        }

        return 0;
    }

    return 1;
}

Bitte beachten Sie, dass dies eine getreue Übersetzung des vom W3C für die Formularvalidierung empfohlenen regulären Ausdrucks ist, der tatsächlich einige gültige UTF-8-Sequenzen ablehnt (insbesondere solche, die ASCII-Steuerzeichen enthalten).

Auch nachdem dies durch die im Kommentar erwähnte Änderung behoben wurde, wird immer noch von einer Nullterminierung ausgegangen, was das Einbetten von NUL-Zeichen verhindert, obwohl dies technisch gesehen legal sein sollte.

Als ich versuchte, meine eigene String-Bibliothek zu erstellen, entschied ich mich für modifiziertes UTF-8 (dh Codierung von NUL als eine überlange Zwei-Byte-Sequenz) – Sie können es gerne verwenden diese Überschrift als Vorlage zum Bereitstellen einer Validierungsroutine, die nicht unter den oben genannten Mängeln leidet.

  • Sehr schön. Ich habe nur meine verschachtelten ifs gehackt, aber du warst schneller. Ich habe Ihre Lösung nicht getestet, aber sie sieht für mich gut aus.

    – Ludwig Weinzierl

    23. Juni 2009 um 10:41 Uhr

  • Da Sie Byte +1, +2, +3 lesen und nur dieses Byte überprüfen! = 0, kann dieser Code über das Ende der Zeichenfolge hinaus lesen. Auch wenn es nullterminiert ist.

    – Lukas

    13. Dezember 2009 um 17:30 Uhr

  • @Lucas: nein, kann es nicht: die && wird diesen Fall kurzschließen, weil 0 befindet sich nicht im Bereich einer gültigen Multibyte-Sequenz

    – Christoph

    14. Dezember 2009 um 1:29 Uhr

  • Das war erstaunlich, vielen Dank für die Bereitstellung dieses Codes.

    – Tyler Brock

    16. Mai 2012 um 20:03 Uhr

  • @DanW; Dannys IsUTF8Bytes() überprüft nur, ob die Bytezeichenfolge dem UTF-8-Codierungsschema entspricht (siehe Stefans Antwort), während meins eine zusätzliche Validierung durchführt, indem es Nicht-Zeichen und ASCII-Steuersequenzen ausschließt; Letztere sind tatsächlich gültiges UTF-8 – siehe Kommentare oben

    – Christoph

    12. Oktober 2012 um 6:59 Uhr

Benutzeravatar von Joakim
Joakim

Dieser Decoder von Björn Hörmann ist der einfachste, den ich gefunden habe. Es funktioniert auch, indem es ein einzelnes Byte füttert und einen Zustand beibehält. Der Status ist sehr nützlich, um UTF8 zu analysieren, das in Blöcken über das Netzwerk eingeht.

http://bjoern.hoehrmann.de/utf-8/decoder/dfa/

// Copyright (c) 2008-2009 Bjoern Hoehrmann <[email protected]>
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.

#define UTF8_ACCEPT 0
#define UTF8_REJECT 1

static const uint8_t utf8d[] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
  8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
  0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
  0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
  0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
  1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
  1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
  1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
};

uint32_t inline
decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
  uint32_t type = utf8d[byte];

  *codep = (*state != UTF8_ACCEPT) ?
    (byte & 0x3fu) | (*codep << 6) :
    (0xff >> type) & (byte);

  *state = utf8d[256 + *state*16 + type];
  return *state;
}

Ein einfacher Validator/Detektor benötigt den Codepunkt nicht, also könnte er so geschrieben werden (Anfangszustand ist gesetzt auf UTF8_ACCEPT):

uint32_t validate_utf8(uint32_t *state, char *str, size_t len) {
   size_t i;
   uint32_t type;

    for (i = 0; i < len; i++) {
        // We don't care about the codepoint, so this is
        // a simplified version of the decode function.
        type = utf8d[(uint8_t)str[i]];
        *state = utf8d[256 + (*state) * 16 + type];

        if (*state == UTF8_REJECT)
            break;
    }

    return *state;
}

Wenn der Text gültig ist, utf8 UTF8_ACCEPT ist zurück gekommen. Wenn es ungültig ist UTF8_REJECT. Wenn mehr Daten benötigt werden, wird eine andere Ganzzahl zurückgegeben.

Anwendungsbeispiel mit Einspeisung von Daten in Chunks (z. B. aus dem Netzwerk):

char buf[128];
size_t bytes_read;
uint32_t state = UTF8_ACCEPT;

// Validate the UTF8 data in chunks.
while ((bytes_read = get_new_data(buf, sizeof(buf))) {
    if (validate_utf8(&state, buf, bytes_read) == UTF8_REJECT)) {
        fprintf(stderr, "Invalid UTF8 data!\n");
        return -1;
    }
}

// If everything went well we should have proper UTF8,
// the data might instead have ended in the middle of a UTF8
// codepoint.
if (state != UTF8_ACCEPT) {
    fprintf(stderr, "Invalid UTF8, incomplete codepoint\n");
}

Sie können nicht erkennen, ob eine bestimmte Zeichenfolge (oder Bytesequenz) ein UTF-8-codierter Text ist, da beispielsweise jede einzelne Serie von UTF-8-Oktetten auch eine gültige (wenn auch unsinnige) Serie von Latin-1 (oder einer anderen Codierung) ist. Oktette. Allerdings sind nicht alle Serien gültiger Latin-1-Oktette gültige UTF-8-Serien. So können Sie Zeichenfolgen ausschließen, die nicht dem UTF-8-Codierungsschema entsprechen:

U+0000-U+007F    0xxxxxxx
U+0080-U+07FF    110yyyxx    10xxxxxx
U+0800-U+FFFF    1110yyyy    10yyyyxx    10xxxxxx
U+10000-U+10FFFF 11110zzz    10zzyyyy    10yyyyxx    10xxxxxx   

Sie müssten die Zeichenfolge als UTF-8 analysieren, siehe http://www.rfc-editor.org/rfc/rfc3629.txt Es ist sehr einfach. Wenn die Analyse fehlschlägt, handelt es sich nicht um UTF-8. Es gibt mehrere einfache UTF-8-Bibliotheken, die dies tun können.

Es könnte vielleicht vereinfacht werden, wenn Sie kennt die Zeichenfolge ist entweder einfaches altes ASCII oder Es enthält Zeichen außerhalb von ASCII, die UTF-8-codiert sind. In diesem Fall müssen Sie sich oft nicht um den Unterschied kümmern. Das Design von UTF-8 war, dass vorhandene Programme, die mit ASCII umgehen können, in den meisten Fällen transparent mit UTF-8 umgehen können.

Denken Sie daran, dass ASCII in UTF-8 als es selbst codiert ist, sodass ASCII gültiges UTF-8 ist.

AC-String kann alles sein, ist das Problem, das Sie lösen müssen, dass Sie nicht wissen, ob der Inhalt ASCII, GB 2312, CP437, UTF-16 oder eine der anderen Dutzend Zeichenkodierungen ist, die einem Programm das Leben schwer machen. ?

Es ist unmöglich zu erkennen, dass ein bestimmtes Byte-Array ein UTF-8-String ist. Sie können zuverlässig feststellen, dass es sich nicht um gültiges UTF-8 handeln kann (was nicht bedeutet, dass es sich nicht um ungültiges UTF-8 handelt); und Sie können feststellen, dass es sich möglicherweise um eine gültige UTF-8-Sequenz handelt, dies jedoch zu Fehlalarmen führt.

Verwenden Sie für ein einfaches Beispiel einen Zufallszahlengenerator, um ein Array aus 3 zufälligen Bytes zu generieren, und verwenden Sie es zum Testen Ihres Codes. Dies sind zufällige Bytes und daher kein UTF-8, sodass jede Zeichenfolge, die Ihr Code für “möglicherweise UTF-8” hält, ein falsches Positiv ist. Ich vermute, dass (unter diesen Bedingungen) Ihr Code in über 12 % der Fälle falsch sein wird.

Sobald Sie erkennen, dass dies unmöglich ist, können Sie darüber nachdenken, ein Konfidenzniveau zurückzugeben (zusätzlich zu Ihrer Vorhersage). Beispielsweise könnte Ihre Funktion etwas wie „Ich bin mir zu 88 % sicher, dass dies UTF-8 ist“ zurückgeben.

Führen Sie dies nun für alle anderen Datentypen durch. Beispielsweise könnten Sie eine Funktion haben, die überprüft, ob die Daten UTF-16 sind, die möglicherweise “Ich bin zu 95 % sicher, dass dies UTF-16 ist” zurückgibt und dann entscheidet, dass dies der Fall ist (weil 95 % höher als 88 %). wahrscheinlicher, dass die Daten UTF-16 und nicht UTF-8 waren.

Der nächste Schritt besteht darin, Tricks hinzuzufügen, um das Vertrauen zu erhöhen. Wenn die Zeichenfolge beispielsweise hauptsächlich Gruppen gültiger Silben zu enthalten scheint, die durch Leerzeichen getrennt sind, können Sie viel sicherer sein, dass es sich tatsächlich um UTF-8 handelt. Wenn es sich bei den Daten möglicherweise um HTML handelt, können Sie auf die gleiche Weise nach Dingen suchen, die gültiges HTML-Markup sein könnten, und dies verwenden, um Ihr Vertrauen zu stärken.

Gleiches gilt natürlich auch für andere Arten von Daten. Wenn die Daten beispielsweise einen gültigen PE32- oder ELF-Header oder einen korrekten BMP-, JPG- oder MP3-Header haben, können Sie viel sicherer sein, dass es sich überhaupt nicht um UTF-8 handelt.

Ein weitaus besserer Ansatz besteht darin, die eigentliche Ursache des Problems zu beheben. Beispielsweise kann es möglich sein, eine Art “Dokumenttyp”-Kennung am Anfang aller Dateien hinzuzufügen, die Sie interessieren, oder vielleicht zu sagen: “Diese Software geht von UTF-8 aus und unterstützt nichts anderes”; damit Sie nicht erst zweifelhafte Vermutungen anstellen müssen.

Benutzeravatar von shoosh
tschüss

Du kannst den … benutzen UTF-8-Detektor in Firefox integriert. Es befindet sich im universellen Zeichensatzdetektor und ist so ziemlich eine eigenständige C++-Bibliothek. Es sollte extrem einfach sein, die Klasse zu finden, die UTF-8 erkennt, und nur diese zu nehmen.
Diese Klasse erkennt im Wesentlichen Zeichenfolgen, die für UTF-8 eindeutig sind.

  • Holen Sie sich den neuesten Firefox-Trunk
  • gehe zu \mozilla\extensions\universalchardet\
  • Finden Sie die UTF-8-Detektorklasse (ich erinnere mich nicht genau, wie der genaue Name ist)

Grundsätzlich überprüfe ich, ob der angegebene Schlüssel (eine Zeichenfolge mit maximal 4 Zeichen) mit dem Format von diesem Link übereinstimmt: http://www.fileformat.info/info/unicode/utf8.htm

/*
** Checks if the given string has all bytes like: 10xxxxxx
** where x is either 0 or 1
*/

static int      chars_are_folow_uni(const unsigned char *chars)
{
    while (*chars)
    {
        if ((*chars >> 6) != 0x2)
            return (0);
        chars++;
    }
    return (1);
}

int             char_is_utf8(const unsigned char *key)
{
    int         required_len;

    if (key[0] >> 7 == 0)
        required_len = 1;
    else if (key[0] >> 5 == 0x6)
        required_len = 2;
    else if (key[0] >> 4 == 0xE)
        required_len = 3;
    else if (key[0] >> 5 == 0x1E)
        required_len = 4;
    else
        return (0);
    return (strlen(key) == required_len && chars_are_folow_uni(key + 1));
}

Funktioniert gut für mich:

unsigned char   buf[5];

ft_to_utf8(L'歓', buf);
printf("%d\n", char_is_utf8(buf)); // => 1

1394220cookie-checkWie erkennt man UTF-8 in normalem C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy