Jak mogę wykryć kodowanie / stronę kodową pliku tekstowego


295

W naszej aplikacji otrzymujemy pliki tekstowe ( .txt, .csvetc.) z różnych źródeł. Podczas czytania pliki te czasami zawierają śmieci, ponieważ pliki zostały utworzone na innej / nieznanej stronie kodowej.

Czy istnieje sposób (automatycznego) wykrycia strony kodowej pliku tekstowego?

detectEncodingFromByteOrderMarksNa StreamReaderkonstruktora, pracuje UTF8 i inne pliki Unicode oznakowane, ale szukam sposobu na wykrycie stron kodowych, jak ibm850, windows1252.


Dzięki za odpowiedzi, właśnie to zrobiłem.

Pliki, które otrzymujemy, pochodzą od użytkowników końcowych, nie mają pojęcia o stronach kodowych. Odbiorniki są również użytkownikami końcowymi, do tej pory wiedzą o stronach kodowych: Strony kodowe istnieją i są denerwujące.

Rozwiązanie:

  • Otwórz otrzymany plik w Notatniku, spójrz na zniekształcony fragment tekstu. Jeśli ktoś nazywa się François lub coś w tym rodzaju, dzięki swojej ludzkiej inteligencji możesz to odgadnąć.
  • Utworzyłem małą aplikację, za pomocą której użytkownik może otworzyć plik, i wprowadzić tekst, o którym wie, że pojawi się on w pliku, gdy zostanie użyta poprawna strona kodowa.
  • Zapętlaj wszystkie strony kodowe i wyświetlaj te, które dają rozwiązanie z tekstem dostarczonym przez użytkownika.
  • Jeśli pojawi się więcej niż jedna strona kodowa, poproś użytkownika o podanie większej ilości tekstu.

Odpowiedzi:


260

Nie możesz wykryć strony kodowej, musisz o tym powiedzieć. Możesz analizować bajty i zgadywać, ale może to dać dziwne (czasem zabawne) wyniki. Nie mogę go teraz znaleźć, ale jestem pewien, że Notatnik da się oszukać w języku angielskim w języku chińskim.

W każdym razie musisz przeczytać: Absolutne minimum Każdy programista absolutnie, pozytywnie musi wiedzieć o Unicode i zestawach znaków (bez wymówek!) .

W szczególności Joel mówi:

Najważniejszy fakt o kodowaniu

Jeśli całkowicie zapomnisz o wszystkim, co właśnie wyjaśniłem, pamiętaj o jednym niezwykle ważnym fakcie. Nie ma sensu mieć łańcucha bez znajomości używanego kodowania. Nie możesz już trzymać głowy w piasku i udawać, że „zwykły” tekst to ASCII. Nie ma czegoś takiego jak zwykły tekst.

Jeśli masz ciąg, w pamięci, w pliku lub w wiadomości e-mail, musisz wiedzieć, w jakim jest to kodowaniu, lub nie możesz go zinterpretować ani poprawnie wyświetlić użytkownikom.


43
Głosowałem za odpowiedzią z dwóch powodów. Po pierwsze, powiedzenie, że „trzeba powiedzieć”, nie jest pomocne. Kto by mi powiedział i za pomocą jakiego medium by to zrobili? Jeśli to ja zapisałem plik, kogo zapytam? Siebie? Po drugie, artykuł nie jest szczególnie pomocny jako źródło odpowiedzi na pytanie. Artykuł jest raczej historią kodowania napisanego w stylu Davida Sedarisa. Doceniam narrację, ale nie po prostu / bezpośrednio odpowiada na pytanie.
genorama

9
@geneorama, myślę, że artykuł Joela odnosi się do twoich pytań lepiej niż kiedykolwiek mogłem, ale oto idzie ... Medium z pewnością zależy od środowiska, w którym tekst jest odbierany. Lepiej, żeby plik (lub cokolwiek) zawierał te informacje (myślę, że HTML i XML). W przeciwnym razie osoba wysyłająca tekst powinna mieć możliwość dostarczenia tych informacji. Jeśli to Ty stworzyłeś ten plik, skąd możesz wiedzieć, jakiego używa kodowania?
JV.

4
@geneorama, ciąg dalszy ... Przypuszczam, że głównym powodem, dla którego artykuł nie odpowiada na pytanie, jest po prostu to, że nie ma prostej odpowiedzi na to pytanie. Gdyby pytanie brzmiało „Jak mogę zgadywać ...”, odpowiedziałbym inaczej.
JV.

1
@JV Później dowiedziałem się, że xml / html może określać kodowanie znaków, dzięki za wzmiankę o tym przydatnym smakołyku.
genorama

1
@JV „Utwórz plik” może być złym wyborem słów. Zakładam, że użytkownik może określić kodowanie pliku generowanego przez użytkownika. Ostatnio „utworzyłem” plik z klastra Hadoop za pomocą Hive i przekazałem go do FTP przed pobraniem go na różne komputery klienckie. Rezultat zawierał trochę śmieci unicode, ale nie wiem, który krok spowodował problem. Nigdy nie określałem wyraźnie kodowania. Chciałbym móc sprawdzić kodowanie na każdym kroku.
genorama

31

Jeśli chcesz wykryć kodowanie inne niż UTF (tj. Brak BOM), zasadniczo sprowadzasz się do heurystyki i analizy statystycznej tekstu. Możesz przyjrzeć się artykułowi Mozilli na temat uniwersalnego wykrywania zestawów znaków (ten sam link, z lepszym formatowaniem za pomocą Wayback Machine ).


9
Co zabawne, moja instalacja Firefoksa 3.05 wykrywa tę stronę jako UTF-8, wyświetlając wiele glifów znaku zapytania w rombie, chociaż źródło ma metatag dla Windows-1252. Ręczna zmiana kodowania znaków powoduje prawidłowe wyświetlenie dokumentu.
devstuff

5
Twoje zdanie „Jeśli chcesz wykryć kodowanie inne niż UTF (tj. Brak BOM)” jest nieco mylące; standard Unicode nie zaleca dodawania BOM do dokumentów utf-8! (a to zalecenie lub jego brak jest źródłem wielu bólów głowy). ref: en.wikipedia.org/wiki/Byte_order_mark#UTF-8
Tao

Odbywa się to, aby można było łączyć łańcuchy UTF-8 bez gromadzenia zbędnych BOM. Poza tym znak kolejności bajtów nie jest potrzebny dla UTF-8, w przeciwieństwie do UTF-16 na przykład.
sashoalm,

26

Czy próbowałeś portu C # dla Mozilla Universal Charset Detector

Przykład z http://code.google.com/p/ude/

public static void Main(String[] args)
{
    string filename = args[0];
    using (FileStream fs = File.OpenRead(filename)) {
        Ude.CharsetDetector cdet = new Ude.CharsetDetector();
        cdet.Feed(fs);
        cdet.DataEnd();
        if (cdet.Charset != null) {
            Console.WriteLine("Charset: {0}, confidence: {1}", 
                 cdet.Charset, cdet.Confidence);
        } else {
            Console.WriteLine("Detection failed.");
        }
    }
}    

1
Działa bezbłędnie dla systemu Windows-1252.
seebiscuit

I jak możesz go użyć do odczytania pliku tekstowego do napisu przy użyciu tego? CharsetDetector zwraca nazwę kodowania w formacie łańcuchowym i to wszystko ...
Bartosz

@Bartosz private Encoding GetEncodingFromString(string encoding) { try { return Encoding.GetEncoding(encoding); } catch { return Encoding.ASCII; } }
PrivatePyle

15

Nie możesz wykryć strony kodowej

To jest oczywiście nieprawda. Każda przeglądarka internetowa ma jakiś uniwersalny wykrywacz zestawów znaków, który radzi sobie ze stronami, które nie mają żadnego oznaczenia kodowania. Firefox ma jeden. Możesz pobrać kod i zobaczyć, jak to robi. Zobacz trochę dokumentacji tutaj . Zasadniczo jest to heurystyka, ale działa naprawdę dobrze.

Biorąc pod uwagę rozsądną ilość tekstu, można nawet wykryć język.

Oto kolejny, który właśnie znalazłem za pomocą Google:


39
„heurystyka” - więc przeglądarka go nie wykrywa, zgaduje. „działa naprawdę dobrze” - więc nie działa przez cały czas? Wydaje mi się, że jesteśmy zgodni.
JV.

10
Standard HTML określa, że ​​jeśli zestaw znaków nie jest zdefiniowany w dokumencie, to należy go traktować jako zakodowany jako UTF-8.
Jon Trauntvein

5
Co jest fajne, chyba że czytamy niestandardowe dokumenty HTML. Lub dokumenty inne niż HTML.
Kos,

2
Ta odpowiedź jest zła, więc musiałem głosować. Mówienie, że byłoby fałszywe, że nie można wykryć strony kodowej, jest błędne. Możesz zgadywać, a twoje domysły mogą być raczej dobre, ale nie możesz „wykryć” strony kodowej.
z80crew,

1
@JonTrauntvein Zgodnie ze specyfikacjami HTML5 a character encoding declaration is required even if the encoding is US-ASCII - brak deklaracji powoduje użycie algorytmu heurystycznego, a nie powrót do UTF8.
z80crew

9

Wiem, że jest już za późno na to pytanie i to rozwiązanie nie spodoba się niektórym (ze względu na jego ukierunkowanie na język angielski i brak testów statystycznych / empirycznych), ale działało bardzo dobrze dla mnie, szczególnie w przypadku przetwarzania przesłanych danych CSV:

http://www.architectshack.com/TextFileEncodingDetector.ashx

Zalety:

  • Wbudowane wykrywanie BOM
  • Domyślne / zapasowe kodowanie można dostosować
  • dość wiarygodne (z mojego doświadczenia) dla plików zachodnioeuropejskich zawierających egzotyczne dane (np. francuskie nazwy) z mieszanką plików w stylu UTF-8 i Latin-1 - w zasadzie większość środowisk amerykańskich i zachodnioeuropejskich.

Uwaga: to ja napisałem tę klasę, więc oczywiście weź to z odrobiną soli! :)



7

Szukając innego rozwiązania, znalazłem to

https://code.google.com/p/ude/

to rozwiązanie jest dość ciężkie.

Potrzebowałem podstawowej detekcji kodowania, opartej na 4 pierwszych bajtach i prawdopodobnie wykrywaniu zestawu znaków xml - więc wziąłem przykładowy kod źródłowy z Internetu i dodałem nieco zmodyfikowaną wersję

http://lists.w3.org/Archives/Public/www-validator/2002Aug/0084.html

napisane dla Java.

    public static Encoding DetectEncoding(byte[] fileContent)
    {
        if (fileContent == null)
            throw new ArgumentNullException();

        if (fileContent.Length < 2)
            return Encoding.ASCII;      // Default fallback

        if (fileContent[0] == 0xff
            && fileContent[1] == 0xfe
            && (fileContent.Length < 4
                || fileContent[2] != 0
                || fileContent[3] != 0
                )
            )
            return Encoding.Unicode;

        if (fileContent[0] == 0xfe
            && fileContent[1] == 0xff
            )
            return Encoding.BigEndianUnicode;

        if (fileContent.Length < 3)
            return null;

        if (fileContent[0] == 0xef && fileContent[1] == 0xbb && fileContent[2] == 0xbf)
            return Encoding.UTF8;

        if (fileContent[0] == 0x2b && fileContent[1] == 0x2f && fileContent[2] == 0x76)
            return Encoding.UTF7;

        if (fileContent.Length < 4)
            return null;

        if (fileContent[0] == 0xff && fileContent[1] == 0xfe && fileContent[2] == 0 && fileContent[3] == 0)
            return Encoding.UTF32;

        if (fileContent[0] == 0 && fileContent[1] == 0 && fileContent[2] == 0xfe && fileContent[3] == 0xff)
            return Encoding.GetEncoding(12001);

        String probe;
        int len = fileContent.Length;

        if( fileContent.Length >= 128 ) len = 128;
        probe = Encoding.ASCII.GetString(fileContent, 0, len);

        MatchCollection mc = Regex.Matches(probe, "^<\\?xml[^<>]*encoding[ \\t\\n\\r]?=[\\t\\n\\r]?['\"]([A-Za-z]([A-Za-z0-9._]|-)*)", RegexOptions.Singleline);
        // Add '[0].Groups[1].Value' to the end to test regex

        if( mc.Count == 1 && mc[0].Groups.Count >= 2 )
        {
            // Typically picks up 'UTF-8' string
            Encoding enc = null;

            try {
                enc = Encoding.GetEncoding( mc[0].Groups[1].Value );
            }catch (Exception ) { }

            if( enc != null )
                return enc;
        }

        return Encoding.ASCII;      // Default fallback
    }

Wystarczy odczytać prawdopodobnie pierwsze 1024 bajty z pliku, ale ładuję cały plik.


7

Jeśli ktoś szuka rozwiązania 93,9%. To działa dla mnie:

public static class StreamExtension
{
    /// <summary>
    /// Convert the content to a string.
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <returns></returns>
    public static string ReadAsString(this Stream stream)
    {
        var startPosition = stream.Position;
        try
        {
            // 1. Check for a BOM
            // 2. or try with UTF-8. The most (86.3%) used encoding. Visit: http://w3techs.com/technologies/overview/character_encoding/all/
            var streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true);
            return streamReader.ReadToEnd();
        }
        catch (DecoderFallbackException ex)
        {
            stream.Position = startPosition;

            // 3. The second most (6.7%) used encoding is ISO-8859-1. So use Windows-1252 (0.9%, also know as ANSI), which is a superset of ISO-8859-1.
            var streamReader = new StreamReader(stream, Encoding.GetEncoding(1252));
            return streamReader.ReadToEnd();
        }
    }
}

Bardzo fajne rozwiązanie. Można łatwo owinąć treść ReadAsString () w pętlę dozwolonych kodowań, jeśli powinno być dozwolone więcej niż 2 kodowania (UTF-8 i ASCI 1252).
ViRuSTriNiTy

Po wypróbowaniu mnóstwa przykładów, w końcu dotarłem do twojego. Jestem teraz w szczęśliwym miejscu. lol Dziękuję !!!!!!!
Sedrick

To może nie być odpowiedź na to, jak wykryć 1252 vs 1250, ale absolutnie powinna to być odpowiedź na „Jak wykryć UTF-8” z BOM lub bez !!
chuckc

4

Zrobiłem coś podobnego w Pythonie. Zasadniczo potrzebujesz wielu przykładowych danych z różnych kodowań, które są rozkładane przez przesuwne dwubajtowe okno i przechowywane w słowniku (hash), kluczowanym na parach bajtów zapewniających wartości list kodowania.

Biorąc pod uwagę ten słownik (skrót), bierzesz tekst wejściowy i:

  • jeśli zaczyna się dowolnym znakiem BOM („\ xfe \ xff” dla UTF-16-BE, „\ xff \ xfe” dla UTF-16-LE, „\ xef \ xbb \ xbf” dla UTF-8 itp.), I traktuj to jak zasugerowano
  • jeśli nie, to weź wystarczająco dużą próbkę tekstu, weź wszystkie pary bajtów próbki i wybierz kodowanie, które jest najmniej powszechne sugerowane ze słownika.

Jeśli próbkowałeś również teksty zakodowane w UTF, które nie zaczynają się od żadnej BOM, drugi krok obejmie te, które wymknęły się z pierwszego kroku.

Jak dotąd działa dla mnie (dane przykładowe i kolejne dane wejściowe są napisami w różnych językach) ze zmniejszającymi się wskaźnikami błędów.


4

Narzędzie „uchardet” robi to dobrze, wykorzystując modele dystrybucji częstotliwości znaków dla każdego zestawu znaków. Większe pliki i bardziej „typowe” pliki mają większą pewność (oczywiście).

Na Ubuntu, po prostu apt-get install uchardet.

W innych systemach pobierz źródło, użycie i dokumenty tutaj: https://github.com/BYVoid/uchardet


Na Macu przez homebrew:brew install uchardet
Paul B

3

Konstruktor klasy StreamReader przyjmuje parametr „wykryj kodowanie”.


Jest to po prostu link „kodujący” tutaj, a opis mówi, że musimy podać kodowanie.
SurajS

@SurajS: Spójrz na inne przeciążenia.
leppie

oryginalny autor chce wykryć kodowanie pliku, który potencjalnie nie miałby znacznika BOM. StreamReader wykrywa kodowanie z nagłówka BOM zgodnie z sygnaturą. public StreamReader (Strumień strumienia, wykrywanie boolEncodingFromByteOrderMarks)
ibondre

1

Jeśli możesz połączyć się z biblioteką C, możesz użyć libenca. Zobacz http://cihar.com/software/enca/ . Ze strony podręcznika:

Enca czyta podane pliki tekstowe lub standardowe dane wejściowe, gdy nie są podane, i korzysta z wiedzy o ich języku (musi być przez ciebie obsługiwany) oraz mieszance parsowania, analizy statystycznej, zgadywania i czarnej magii w celu określenia ich kodowania.

To GPL v2.


0

Masz ten sam problem, ale nie znalazłem jeszcze dobrego rozwiązania do jego automatycznego wykrycia. Teraz używam do tego PsPad (www.pspad.com);) Działa dobrze


0

Ponieważ zasadniczo sprowadza się to do heurystyki, pomocne może być użycie kodowania wcześniej odebranych plików z tego samego źródła jako pierwszej wskazówki.

Większość ludzi (lub aplikacji) za każdym razem robi rzeczy w tej samej kolejności, często na tej samej maszynie, więc jest całkiem prawdopodobne, że kiedy Bob utworzy plik .csv i wyśle ​​go do Mary, zawsze będzie korzystał z Windows-1252 lub cokolwiek domyślnie ma jego maszyna.

Tam gdzie to możliwe, odrobina szkoleń dla klientów nigdy nie boli :-)


0

Tak naprawdę szukałem ogólnego, nie programistycznego sposobu wykrywania kodowania plików, ale jeszcze go nie znalazłem. Testując przy użyciu różnych kodowań, znalazłem, że mój tekst to UTF-7.

Więc gdzie po raz pierwszy robiłem: StreamReader file = File.OpenText (pełna nazwa pliku);

Musiałem to zmienić na: Plik StreamReader = nowy StreamReader (pełna nazwa pliku, System.Text.Encoding.UTF7);

OpenText zakłada, że ​​jest to UTF-8.

możesz również utworzyć StreamReader, jak ten nowy StreamReader (pełna nazwa pliku, prawda), drugi parametr oznaczający, że powinien spróbować wykryć kodowanie z bajtuordermark pliku, ale to nie zadziałało w moim przypadku.


@JohnMachin Zgadzam się, że jest to rzadkie, ale jest to obowiązkowe np. W niektórych częściach protokołu IMAP. Jeśli tam właśnie jesteś, nie będziesz musiał zgadywać.
tripleee

0

Otwórz plik w AkelPad (lub po prostu skopiuj / wklej zniekształcony tekst), przejdź do Edycja -> Wybór -> Przekoduj ... -> zaznacz „Autodetekcja”.


0

Jako dodatek do postu ITmeze, użyłem tej funkcji do konwersji wyjścia portu C # dla Mozilla Universal Charset Detector

    private Encoding GetEncodingFromString(string codePageName)
    {
        try
        {
            return Encoding.GetEncoding(codePageName);
        }
        catch
        {
            return Encoding.ASCII;
        }
    }

MSDN



-1

Używam tego kodu do wykrywania domyślnej strony kodowej ansi Windows i Unicode podczas czytania pliku. W przypadku innych kodowań konieczna jest kontrola treści, ręcznie lub przez programowanie. Można to wykorzystać do zapisania tekstu z takim samym kodowaniem, jak w momencie jego otwarcia. (Używam VB.NET)

'Works for Default and unicode (auto detect)
Dim mystreamreader As New StreamReader(LocalFileName, Encoding.Default) 
MyEditTextBox.Text = mystreamreader.ReadToEnd()
Debug.Print(mystreamreader.CurrentEncoding.CodePage) 'Autodetected encoding
mystreamreader.Close()

-1

Minęło 10 lat (!), Odkąd o to zapytano, a mimo to nie widzę wzmianki o dobrym, nie- GPL'owym rozwiązaniu MS : IMultiLanguage2 API.

Większość bibliotek już wspomnianych opiera się na UDE Mozilli - i wydaje się rozsądne, że przeglądarki już rozwiązały podobne problemy. Nie wiem, jakie jest rozwiązanie chrome, ale od czasu wydania IE 5.0 MS, i jest to:

  1. Bez problemów licencyjnych podobnych do GPL,
  2. Wspierane i utrzymywane prawdopodobnie na zawsze,
  3. Daje bogaty wynik - wszyscy ważni kandydaci do kodowania / stron kodowych wraz z wynikami ufności,
  4. Zaskakująco łatwy w użyciu (jest to jedno wywołanie funkcji).

Jest to natywne wywołanie COM, ale oto bardzo fajna praca Carstena Zeumera, która obsługuje bałagan interop dla użycia .net. Istnieje kilka innych, ale ogólnie rzecz biorąc, ta biblioteka nie przyciąga uwagi, na jaką zasługuje.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.