Wykryj kodowanie i uczyń wszystko UTF-8

304

Odczytywanie wielu tekstów z różnych kanałów RSS i wstawianie ich do mojej bazy danych.

Oczywiście istnieje kilka różnych kodowań znaków używanych w kanałach, np. UTF-8 i ISO 8859-1.

Niestety czasami występują problemy z kodowaniem tekstów. Przykład:

  1. „Ss” w „Fußball” powinien wyglądać następująco w mojej bazie danych: „Ÿ”. Jeśli jest to „Ÿ”, jest wyświetlane poprawnie.

  2. Czasami „ß” w „Fußball” wygląda następująco w mojej bazie danych: „ߟ”. Oczywiście jest to wyświetlane nieprawidłowo.

  3. W innych przypadkach „ß” jest zapisywane jako „ß” - więc bez żadnych zmian. Następnie jest wyświetlany niepoprawnie.

Co mogę zrobić, aby uniknąć przypadków 2 i 3?

Jak mogę ustawić to samo kodowanie, najlepiej UTF-8? Kiedy muszę użyć utf8_encode(), kiedy muszę użyć utf8_decode()(jasne jest, jaki jest efekt, ale kiedy muszę użyć funkcji?) I kiedy nie mogę nic zrobić z danymi wejściowymi?

Jak sprawić, by wszystko było tak samo kodujące? Być może z funkcją mb_detect_encoding()? Czy mogę napisać dla tego funkcję? Więc moje problemy to:

  1. Jak dowiedzieć się, jakiego kodowania używa tekst?
  2. Jak przekonwertować go na UTF-8 - bez względu na stare kodowanie?

Czy taka funkcja działałaby?

function correct_encoding($text) {
    $current_encoding = mb_detect_encoding($text, 'auto');
    $text = iconv($current_encoding, 'UTF-8', $text);
    return $text;
}

Testowałem to, ale to nie działa. Co jest z tym nie tak?

krakanie
źródło
36
„„ Ss ”w„ Fußball ”powinien wyglądać następująco w mojej bazie danych:„ Ÿ ”.”. Nie, to powinno wyglądać jak ß. Upewnij się, że sortowanie i połączenie są poprawnie skonfigurowane. W przeciwnym razie sortowanie i wyszukiwanie zostaną dla Ciebie zepsute.
Rich Bradshaw,
5
Twoja baza danych jest źle skonfigurowana. Jeśli chcesz przechowywać zawartość Unicode, po prostu ją skonfiguruj. Dlatego zamiast próbować obejść ten problem w kodzie PHP, należy najpierw naprawić bazę danych.
dolmen
2
UŻYJ: $ from = mb_detect_encoding ($ text); $ text = mb_convert_encoding ($ text, 'UTF-8', $ from);
Informate.it

Odpowiedzi:

363

Jeśli zastosujesz utf8_encode()do już napisanego UTF-8, zwróci zniekształcone wyjście UTF-8.

Stworzyłem funkcję, która rozwiązuje wszystkie te problemy. To się nazywa Encoding::toUTF8().

Nie musisz wiedzieć, jakie jest kodowanie swoich ciągów. Może to być Latin1 ( ISO 8859-1) , Windows-1252 lub UTF-8, lub ciąg może zawierać ich kombinację. Encoding::toUTF8()przekonwertuje wszystko na UTF-8.

Zrobiłem to, ponieważ usługa przekazała mi dane zepsute, mieszając UTF-8 i Latin1 w tym samym ciągu.

Stosowanie:

require_once('Encoding.php');
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::toUTF8($utf8_or_latin1_or_mixed_string);

$latin1_string = Encoding::toLatin1($utf8_or_latin1_or_mixed_string);

Pobieranie:

https://github.com/neitanod/forceutf8

Dołączyłem inną funkcję, Encoding::fixUFT8()która naprawi każdy ciąg UTF-8, który wygląda na zniekształcony.

Stosowanie:

require_once('Encoding.php');
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

Przykłady:

echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");

wyświetli:

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football

Przekształciłem funkcję ( forceUTF8) w rodzinę funkcji statycznych w klasie o nazwie Encoding. Nowa funkcja to Encoding::toUTF8().

Sebastián Grignoli
źródło
1
Cóż, jeśli spojrzysz na kod, fixUTF8 po prostu wywołuje forceUTF8 raz za razem, aż łańcuch zostanie zwrócony bez zmian. Jedno wywołanie fixUTF8 () zajmuje co najmniej dwa razy więcej czasu niż wywołanie forceUTF8 (), więc jest znacznie mniej wydajne. Zrobiłem fixUTF8 () tylko po to, aby utworzyć program wiersza polecenia, który naprawi pliki „uszkodzone przez kodowanie”, ale w środowisku na żywo jest to rzadko potrzebne.
Sebastián Grignoli
3
W jaki sposób konwertuje to znaki spoza UTF8 na UTF8, nie wiedząc, od jakiego kodowania mają zacząć nieprawidłowe znaki?
philfreo
4
Zakłada ISO-8859-1, odpowiedź już to mówi. Jedyna różnica między forceUTF8 () a utf8_encode () polega na tym, że forceUTF8 () rozpoznaje znaki UTF8 i utrzymuje je bez zmian.
Sebastián Grignoli,
28
„Nie musisz wiedzieć, jakie jest kodowanie swoich ciągów”. - Bardzo się nie zgadzam. Zgadywanie i próby mogą działać, ale zawsze prędzej czy później napotkasz przypadkowe przypadki, w których to nie działa.
deceze
4
W pełni się zgadzam. W zasadzie nie chciałem powiedzieć, że z reguły wyjaśnij, że ta klasa może ci pomóc, jeśli znajdziesz się w takiej sytuacji.
Sebastián Grignoli
74

Najpierw musisz wykryć, jakie kodowanie zostało użyte. Podczas analizowania źródeł danych RSS (prawdopodobnie przez HTTP) należy odczytać kodowanie z charsetparametru Content-Typepola nagłówka HTTP . Jeśli nie jest obecny, przeczytaj kodowanie z encodingatrybutu instrukcji przetwarzania XML . Jeśli tego również brakuje, użyj UTF-8 zgodnie ze specyfikacją .


Edytuj    Oto, co prawdopodobnie zrobiłbym:

Chciałbym użyć cURL do wysyłania i pobierania odpowiedź. Pozwala to ustawić określone pola nagłówka i pobrać również nagłówek odpowiedzi. Po pobraniu odpowiedzi musisz przeanalizować odpowiedź HTTP i podzielić ją na nagłówek i treść. Nagłówek powinien następnie zawierać Content-Typepole nagłówka zawierające typ MIME i (mam nadzieję) charsetparametr również z kodowaniem / zestawem znaków. Jeśli nie, przeanalizujemy XML PI pod kątem obecności encodingatrybutu i stamtąd uzyskamy kodowanie. Jeśli tego również brakuje, specyfikacje XML określają użycie UTF-8 jako kodowania.

$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';

$accept = array(
    'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
    'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
    'Accept: '.implode(', ', $accept['type']),
    'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
    // error fetching the response
} else {
    $offset = strpos($response, "\r\n\r\n");
    $header = substr($response, 0, $offset);
    if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
        // error parsing the response
    } else {
        if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
            // type not accepted
        }
        $encoding = trim($match[2], '"\'');
    }
    if (!$encoding) {
        $body = substr($response, $offset + 4);
        if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
            $encoding = trim($match[1], '"\'');
        }
    }
    if (!$encoding) {
        $encoding = 'utf-8';
    } else {
        if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
            // encoding not accepted
        }
        if ($encoding != 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $encoding);
        }
    }
    $simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
    if (!$simpleXML) {
        // parse error
    } else {
        echo $simpleXML->asXML();
    }
}
Gumbo
źródło
Dzięki. To byłoby łatwe. Ale czy to naprawdę zadziała? W nagłówkach HTTP lub atrybutach XML często występują błędne kodowania.
caw
25
Znowu: To nie twój problem. Ustanowiono standardy, aby uniknąć takich problemów. Jeśli inni ich nie przestrzegają, to ich problem, nie twój.
Gumbo
Ok, myślę, że w końcu mnie teraz przekonałeś. :)
caw
Dzięki za kod. Ale dlaczego po prostu tego nie użyć? paste.bradleygill.com/index.php?paste_id=9651 Twój kod jest znacznie bardziej złożony, co jest z nim lepszego?
caw
Najpierw wysyłasz dwa żądania, jedno dla nagłówka HTTP i jedno dla danych. Po drugie, szukasz dowolnym wyglądzie charset=i encoding=nie tylko na odpowiednich pozycjach. Po trzecie, nie sprawdzasz, czy zadeklarowane kodowanie jest akceptowane.
Gumbo
39

Wykrywanie kodowania jest trudne.

mb_detect_encodingdziała na zasadzie zgadywania na podstawie liczby zdanych kandydatów. W niektórych kodowaniach niektóre sekwencje bajtów są niepoprawne, dlatego można rozróżnić różnych kandydatów. Niestety istnieje wiele kodowań, w których te same bajty są poprawne (ale różne). W takich przypadkach nie ma możliwości ustalenia kodowania; Możesz zaimplementować własną logikę, aby zgadywać w takich przypadkach. Na przykład dane pochodzące z japońskiej witryny mogą mieć kodowanie japońskie.

Tak długo, jak masz do czynienia tylko z językami Europy Zachodniej, trzy główne kodowania do rozważenia to utf-8: iso-8859-1i cp-1252. Ponieważ są to ustawienia domyślne dla wielu platform, najprawdopodobniej zostaną również błędnie zgłoszone. Na przykład. jeśli ludzie używają różnych kodowań, prawdopodobnie będą szczerzy, ponieważ ich oprogramowanie bardzo często się psuje. Dlatego dobrą strategią jest zaufanie do dostawcy, chyba że kodowanie zostanie zgłoszone jako jedno z tych trzech. Nadal powinieneś dokładnie sprawdzić, czy rzeczywiście jest poprawne, używając mb_check_encoding(pamiętaj, że poprawne to nie to samo, co bycie - te same dane wejściowe mogą być poprawne dla wielu kodowań). Jeśli jest to jeden z nich, możesz użyćmb_detect_encodingrozróżniać między nimi. Na szczęście jest to dość deterministyczne; Musisz tylko użyć właściwej sekwencji wykrywania, która jest UTF-8,ISO-8859-1,WINDOWS-1252.

Po wykryciu kodowania musisz przekonwertować je na wewnętrzną reprezentację ( UTF-8jest to jedyny rozsądny wybór). Funkcja utf8_encodeprzekształca ISO-8859-1się w UTF-8, więc może być używana tylko dla tego określonego typu danych wejściowych. W przypadku innych kodowań użyj mb_convert_encoding.

troelskn
źródło
Dziękuję Ci bardzo! Co jest lepszego: MB-Convert-Encoding () lub Iconv ()? Nie wiem jakie są różnice. Tak, będę musiał analizować tylko języki zachodnioeuropejskie, zwłaszcza angielski, niemiecki i francuski.
caw
7
Właśnie widziałem: mb-wykrywanie-kodowanie () jest bezużyteczne. Obsługuje tylko UTF-8, UTF-7, ASCII, EUC-JP, SJIS, eucJP-win, SJIS-win, JIS i ISO-2022-JP. Najważniejsze dla mnie, ISO-8859-1 i WINDOWS-1252, nie są obsługiwane. Więc nie mogę użyć mb-detektuj-kodowanie ().
caw
1
Mój masz rację. Minęło trochę czasu, odkąd go użyłem. Musisz wtedy napisać własny kod wykrywający lub użyć zewnętrznego narzędzia. UTF-8 można dość wiarygodnie ustalić, ponieważ jego sekwencje ucieczki są dość charakterystyczne. wp-1252 i iso-8859-1 można rozróżnić, ponieważ wp-1252 może zawierać bajty, które są nielegalne w iso-8859-1. Skorzystaj z Wikipedii, aby uzyskać szczegółowe informacje, lub zajrzyj do sekcji komentarzy php.net w ramach różnych funkcji związanych z zestawem znaków.
troelskn
Myślę, że można rozróżnić różne kodowania, patrząc na formy, w których pojawiają się specjalne piosenki: niemiecki „ß” pojawia się w różnych formach: czasami „Ÿ”, czasami „Ÿ”, a czasem „ß”. Dlaczego?
caw
Tak, ale zanim zaczniesz porównywać, musisz znać zawartość łańcucha, a ten rodzaj w pierwszej kolejności pokonuje cel. Niemiecki ß wygląda inaczej, ponieważ ma różne wartości w różnych kodowaniach. Niektóre znaki są reprezentowane w ten sam sposób w różnych kodowaniach (np. Wszystkie znaki w zestawie znaków ascii są kodowane w ten sam sposób w utf-8, iso-8859- * i wp-1252), o ile używasz tylko te postacie, wszystkie wyglądają tak samo. Dlatego są czasami nazywane kompatybilnymi z ASCII.
troelskn
14

Naprawdę dobry sposób, aby wdrożyć isUTF8-function można znaleźć na php.net :

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}
harpax
źródło
16
Niestety działa to tylko wtedy, gdy ciąg znaków składa się tylko ze znaków zawartych w ISO-8859-1. Ale to może zadziałać: @iconv ('utf-8', 'utf-8 // IGNORE', $ str) == $ str
Christian Davén
@Christian: Rzeczywiście, tak też polecają autorzy High Performance MySQL.
Alix Axel,
1
Nie działa poprawnie: echo (int) isUTF8 ('z'); # 1 echo (int) isUTF8 (NULL); # 1
Yousha Aleayoub
1
Chociaż nie jest to idealne, myślę, że jest to dobry sposób na wdrożenie szkicowego testu UTF-8.
Mateng
1
mb_check_encoding($string, 'UTF-8')
deceze
13

Ten ściąg zawiera listę typowych ostrzeżeń związanych z obsługą UTF-8 w PHP: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet

Pomocna może być również funkcja wykrywająca znaki wielobajtowe w ciągu znaków ( źródło ):


function detectUTF8($string)
{
    return preg_match('%(?:
        [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
        |\xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
        |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
        |\xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
        |\xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
        |[\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
        |\xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
        )+%xs', 
    $string);
}

miek
źródło
2
Myślę, że to nie działa poprawnie: wykrywanie echaUTF8 ('3٣3'); # 1
Yousha Aleayoub
10

Trochę do góry. Powiedziałeś, że „ß” powinno być wyświetlane w bazie danych jako „Ÿ”.

Jest tak prawdopodobnie dlatego, że używasz bazy danych z kodowaniem znaków Latin-1 lub być może twoje połączenie PHP-MySQL jest ustawione nieprawidłowo, to znaczy, P uważa, że ​​MySQL jest ustawiony na używanie UTF-8, więc wysyła dane jako UTF-8 , ale Twój MySQL wierzy, że PHP wysyła dane zakodowane jako ISO 8859-1, więc może ponownie spróbować zakodować przesłane dane jako UTF-8, powodując tego rodzaju problemy.

Spójrz na mysql_set_charset . To może ci pomóc.

Krynble
źródło
4

Twoje kodowanie wygląda jak zakodowane dwukrotnie w UTF-8 ; to znaczy, z innego kodowania, do UTF-8 i ponownie do UTF-8. Tak jakbyś miał ISO 8859-1, przekonwertowany z ISO 8859-1 na UTF-8 i traktował nowy ciąg jako ISO 8859-1 w celu kolejnej konwersji na UTF-8.

Oto pseudokod tego, co zrobiłeś:

$inputstring = getFromUser();
$utf8string = iconv($current_encoding, 'utf-8', $inputstring);
$flawedstring = iconv($current_encoding, 'utf-8', $utf8string);

Powinieneś spróbować:

  1. wykryć kodowanie za pomocą mb_detect_encoding() lub cokolwiek chcesz użyć
  2. jeśli jest to UTF-8, przekonwertuj na ISO 8859-1 i powtórz krok 1
  3. na koniec przekonwertuj z powrotem na UTF-8

Zakłada się, że w „środkowej” konwersji użyłeś ISO 8859-1. Jeśli korzystasz z systemu Windows-1252, przekonwertuj go na Windows-1252 (latin1). Oryginalne kodowanie źródłowe nie jest ważne; ten, którego użyłeś w wadliwym, drugim nawróceniu jest.

Zgaduję, co się stało; niewiele można zrobić, aby uzyskać cztery bajty zamiast jednego rozszerzonego bajtu ASCII.

Język niemiecki używa również ISO 8859-2 i Windows-1250 (Latin-2).

Ivan Vučica
źródło
3

Interesującą rzeczą mb_detect_encodingi mb_convert_encodingto, że kolejność kodowań proponujesz ma znaczenia:

// $input is actually UTF-8

mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8");
// ISO-8859-9 (WRONG!)

mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9");
// UTF-8 (OK)

Możesz więc użyć określonej kolejności przy określaniu oczekiwanego kodowania. Należy jednak pamiętać, że nie jest to niezawodne.

Halil Özgür
źródło
2
Dzieje się tak, ponieważ ISO-8859-9 w praktyce akceptuje wszelkie dane binarne. To samo dotyczy Windows-1252 i przyjaciół. Najpierw musisz przetestować kodowania, które mogą nie zaakceptować danych wejściowych.
Mikko Rantalainen,
@MikkoRantalainen, tak, chyba ta część dokumentów mówi coś podobnego: php.net/manual/en/function.mb-detect-order.php#example-2985
Halil Özgür
Biorąc pod uwagę, że specyfikacja WHATWG HTML definiuje Windows 1252 jako domyślne kodowanie, należy założyć, że jest całkiem bezpieczny if ($input_is_not_UTF8) $input_is_windows1252 = true;. Zobacz także: html.spec.whatwg.org/multipage/…
Mikko Rantalainen
3

Musisz przetestować zestaw znaków na wejściu, ponieważ odpowiedzi mogą pochodzić z różnych kodowań.

Wymuszam przesłanie całej zawartości do UTF-8, wykonując wykrywanie i tłumaczenie przy użyciu następującej funkcji:

function fixRequestCharset()
{
  $ref = array(&$_GET, &$_POST, &$_REQUEST);
  foreach ($ref as &$var)
  {
    foreach ($var as $key => $val)
    {
      $encoding = mb_detect_encoding($var[$key], mb_detect_order(), true);
      if (!$encoding)
        continue;
      if (strcasecmp($encoding, 'UTF-8') != 0)
      {
        $encoding = iconv($encoding, 'UTF-8', $var[$key]);
        if ($encoding === false)
          continue;
        $var[$key] = $encoding;
      }
    }
  }
}

Ta procedura zamieni wszystkie zmienne PHP pochodzące ze zdalnego hosta w UTF-8.

Lub zignoruj ​​wartość, jeśli kodowania nie można wykryć ani przekonwertować.

Możesz dostosować go do swoich potrzeb.

Po prostu wywołaj go przed użyciem zmiennych.

Cavila
źródło
jaki jest cel używania mb_detect_order () bez przekazanej listy kodowania?
giorgio79,
Celem jest zwrócenie skonfigurowanej przez system tablicy uporządkowanych kodowań zdefiniowanych w użytym pliku php.ini. Jest to wymagane przez mb_detect_encoding do wypełnienia trzeciego parametru.
cavila,
2

Opracowanie kodowania znaków w kanałach RSS wydaje się skomplikowane . Nawet normalne strony internetowe często pomijają lub kłamią na temat ich kodowania.

Możesz więc spróbować użyć prawidłowego sposobu wykrycia kodowania, a następnie wrócić do jakiejś formy automatycznego wykrywania (zgadywania).

Kevin ORourke
źródło
Nie chcę odczytać kodowania z informacji o kanale. Więc jest równy, jeśli informacje o pliku danych są błędne. Chciałbym wykryć kodowanie z tekstu.
caw
@ marco92w: To nie twój problem, jeśli zadeklarowane kodowanie jest nieprawidłowe. Nie ustanowiono standardów zabawy.
Gumbo
1
@Gumbo: ale jeśli pracujesz w prawdziwym świecie, musisz być w stanie poradzić sobie z takimi sprawami, jak niepoprawnie zadeklarowane kodowanie. Problem polega na tym, że bardzo trudno odgadnąć (poprawnie) kodowanie tylko z jakiegoś tekstu. Standardy są wspaniałe, ale wiele (większość?) Tamtejszych stron / kanałów jest z nimi niezgodnych.
Kevin ORourke
@Kevin ORourke: Dokładnie tak. To mój problem. @Gumbo: Tak, to mój problem. Chcę czytać kanały i agregować je. Więc muszę poprawić nieprawidłowe kodowanie.
caw
@ marco92w: Ale nie możesz poprawić kodowania, jeśli nie znasz poprawnego kodowania i bieżącego kodowania. I właśnie do tego służy charset/ encodingdeklaracja, jeśli: opisz kodowanie, w którym dane są zakodowane.
Gumbo
2

Wiem, że to starsze pytanie, ale wydaje mi się, że użyteczna odpowiedź nigdy nie boli. Miałem problemy z kodowaniem między aplikacją komputerową, SQLite i zmiennymi GET / POST. Niektóre będą w UTF-8, inne w ASCII i zasadniczo wszystko popieprzy się, gdy w grę wejdą zagraniczne postacie.

Oto moje rozwiązanie. Przeszukuje GET / POST / REQUEST (pominąłem pliki cookie, ale możesz je dodać w razie potrzeby) przy każdym ładowaniu strony przed przetwarzaniem. Działa dobrze w nagłówku. PHP wyrzuca ostrzeżenia, jeśli nie może automatycznie wykryć kodowania źródłowego, więc ostrzeżenia te są pomijane za pomocą @.

//Convert everything in our vars to UTF-8 for playing nice with the database...
//Use some auto detection here to help us not double-encode...
//Suppress possible warnings with @'s for when encoding cannot be detected
try
{
    $process = array(&$_GET, &$_POST, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = $v;
                $process[] = &$process[$key][@mb_convert_encoding($k,'UTF-8','auto')];
            } else {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = @mb_convert_encoding($v,'UTF-8','auto');
            }
        }
    }
    unset($process);
}
catch(Exception $ex){}
żartobliwy
źródło
Dzięki za odpowiedź, żartobliwy. Funkcja mb_convert_encoding () jest tym, co już tutaj mamy, prawda? ;) Więc jedyną nową rzeczą w twojej odpowiedzi są pętle do zmiany kodowania we wszystkich zmiennych.
caw
2

Od wieków szukałem rozwiązań dla kodowania , a ta strona jest prawdopodobnie końcem wielu lat poszukiwań! Przetestowałem niektóre z podanych przez ciebie sugestii i oto moje notatki:

To jest mój ciąg testowy:

jest to ciąg „wròng wrìtten”, ale nie było mi potrzebne specjalne spotkanie, aby je zobaczyć, przekonwertowane przez fùnctìon !! & Otóż to!

Wykonuję INSERT, aby zapisać ten ciąg w bazie danych w polu ustawionym jako utf8_general_ci

Zestaw znaków mojej strony to UTF-8.

Jeśli wykonam INSERT właśnie w ten sposób, w mojej bazie danych prawdopodobnie mam postacie pochodzące z Marsa ...

Muszę więc przekonwertować je na jakiś „zdrowy” UTF-8. próbowałemutf8_encode() , ale znaki kosmitów atakowały moją bazę danych ...

Próbowałem więc użyć funkcji forceUTF8zamieszczonej pod numerem 8, ale w bazie danych zapisany ciąg wygląda następująco:

jest to napis „wròng wrìtten”, który zabrałem na specjalne święta, aby zobaczyć je, przekonwertowane przez fn.oncton !! & Otóż to!

Więc zbierając więcej informacji na tej stronie i łącząc je z innymi informacjami na innych stronach, rozwiązałem problem z tym rozwiązaniem:

$finallyIDidIt = mb_convert_encoding(
  $string,
  mysql_client_encoding($resourceID),
  mb_detect_encoding($string)
);

Teraz w mojej bazie danych mam ciąg z poprawnym kodowaniem.

UWAGA: działa tylko uwaga do załatwienia mysql_client_encoding! Musisz być podłączony do bazy danych, ponieważ ta funkcja chce mieć identyfikator zasobu jako parametr.

Ale cóż, po prostu wykonuję to ponowne kodowanie przed moim INSERT, więc dla mnie to nie jest problem.

Mauro
źródło
1
Dlaczego nie używasz po prostu UTF-8kodowania klienta dla mysql? Nie potrzebowałbym ręcznej konwersji w ten sposób
Esailija,
2

To proste: kiedy dostajesz coś, co nie jest UTF-8, musisz zakodować to w UTF-8.

Tak więc, gdy pobierasz określony plik danych, który jest ISO 8859-1, przeanalizuj go utf8_encode.

Jeśli jednak pobierasz kanał UTF-8, nie musisz nic robić.

Seb
źródło
Dzięki! OK, mogę dowiedzieć się, jak kodowany jest kanał za pomocą mb-detektuj-kodowanie (), prawda? Ale co mogę zrobić, jeśli plik danych to ASCII? utf8-encode () istnieje tylko dla ISO-8859-1 do UTF-8, prawda?
caw
ASCII jest podzbiorem ISO-8859-1 I UTF-8, więc użycie utf8-encode () nie powinno wprowadzać zmian - JEŻELI jest to po prostu ASCII
Michael Borgwardt
Więc zawsze mogę użyć utf8_encode, jeśli nie jest to UTF-8? To byłoby naprawdę łatwe. Tekst, który był ASCII zgodnie z mb-wykrycie-kodowanie () zawierał „& # 228;”. Czy to znak ASCII? A może HTML?
caw
To jest HTML. Właściwie jest to zakodowane, więc po wydrukowaniu go na danej stronie pokazuje się dobrze. Jeśli chcesz, możesz najpierw ut8_encode (), a następnie html_entity_decode ().
Seb
1
Znak ß jest zakodowany w UTF-8 za pomocą sekwencji bajtów 0xC39F. Ta interpretowana w Windows-1252 sekwencja reprezentuje dwa znaki  (0xC3) i Ÿ (0x9F). A jeśli ponownie zakodujesz tę sekwencję bajtów za pomocą UTF-8, otrzymasz 0xC383 0xC29F, co reprezentuje ß w Windows-1252. Więc twoim błędem jest traktowanie tych danych zakodowanych w UTF-8 jako czegoś innego niż kodowanie UTF-8. To, że ta sekwencja bajtów jest prezentowana jako postać, którą widzisz, jest tylko kwestią interpretacji. Jeśli użyjesz innego kodowania / zestawu znaków, prawdopodobnie zobaczysz inne znaki.
Gumbo
1

php.net/mb_detect_encoding

echo mb_detect_encoding($str, "auto");

lub

echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");

Naprawdę nie wiem, jakie są wyniki, ale sugeruję, abyś wziął kilka swoich kanałów z innym kodowaniem i spróbował, czy mb_detect_encodingdziała, czy nie.


auto update jest skrótem od „ASCII, JIS, UTF-8, EUC-JP, SJIS”. zwraca wykryty zestaw znaków, którego można użyć do konwersji ciągu znaków na utf-8 za pomocą iconv .

<?php
function convertToUTF8($str) {
    $enc = mb_detect_encoding($str);

    if ($enc && $enc != 'UTF-8') {
        return iconv($enc, 'UTF-8', $str);
    } else {
        return $str;
    }
}
?>

nie przetestowałem tego, więc nie ma gwarancji. i może jest prostszy sposób.

stefs
źródło
Dziękuję Ci. Jaka jest różnica między „auto” a „UTF-8, ASCII, ISO-8859-1” jako drugim argumentem? Czy „auto” zawiera więcej kodowań? W takim razie lepiej byłoby użyć „auto”, prawda? Jeśli to naprawdę działa bez żadnych błędów, muszę tylko zmienić „ASCII” lub „ISO-8859-1” na „UTF-8”. W jaki sposób?
caw
2
Twoja funkcja nie działa dobrze we wszystkich przypadkach. Czasami pojawia się błąd: Uwaga: iconv (): Wykryłem niedozwolony znak w ciągu wejściowym w ...
caw
1

@harpax, który pracował dla mnie. W moim przypadku jest to wystarczająco dobre:

if (isUTF8($str)) { 
    echo $str; 
}
else
{
    echo iconv("ISO-8859-1", "UTF-8//TRANSLIT", $str);
}
PJ Brunet
źródło
0

Po uporządkowaniu skryptów php nie zapomnij powiedzieć mysql, jaki zestaw znaków przekazujesz i chciałbyś go odzyskać.

Przykład: ustaw zestaw znaków utf8

Przekazywanie danych utf8 do tabeli latin1 w sesji I / O latin1 daje te paskudne ptasie odchody. Widzę to co drugi dzień w sklepach z oscommerce. W czwartej i czwartej może się to wydawać właściwe. Ale phpmyadmin pokaże prawdę. Mówiąc mysqlowi, jaki przekazany zestaw znaków obsłuży dla ciebie konwersję danych mysql.

Jak odzyskać istniejące zaszyfrowane dane mysql to kolejny wątek do omówienia. :)

tim
źródło
0

Ta wersja jest dla języka niemieckiego, ale możesz modyfikować $ CHARSETS i $ TESTCHARS

class CharsetDetector
{
private static $CHARSETS = array(
"ISO_8859-1",
"ISO_8859-15",
"CP850"
);
private static $TESTCHARS = array(
"€",
"ä",
"Ä",
"ö",
"Ö",
"ü",
"Ü",
"ß"
);
public static function convert($string)
{
    return self::__iconv($string, self::getCharset($string));
}
public static function getCharset($string)
{
    $normalized = self::__normalize($string);
    if(!strlen($normalized))return "UTF-8";
    $best = "UTF-8";
    $charcountbest = 0;
    foreach (self::$CHARSETS as $charset) {
        $str = self::__iconv($normalized, $charset);
        $charcount = 0;
        $stop   = mb_strlen( $str, "UTF-8");

        for( $idx = 0; $idx < $stop; $idx++)
        {
            $char = mb_substr( $str, $idx, 1, "UTF-8");
            foreach (self::$TESTCHARS as $testchar) {

                if($char == $testchar)
                {

                    $charcount++;
                    break;
                }
            }
        }
        if($charcount>$charcountbest)
        {
            $charcountbest=$charcount;
            $best=$charset;
        }
        //echo $text."<br />";
    }
    return $best;
}
private static function __normalize($str)
{

$len = strlen($str);
$ret = "";
for($i = 0; $i < $len; $i++){
    $c = ord($str[$i]);
    if ($c > 128) {
        if (($c > 247)) $ret .=$str[$i];
        elseif ($c > 239) $bytes = 4;
        elseif ($c > 223) $bytes = 3;
        elseif ($c > 191) $bytes = 2;
        else $ret .=$str[$i];
        if (($i + $bytes) > $len) $ret .=$str[$i];
        $ret2=$str[$i];
        while ($bytes > 1) {
            $i++;
            $b = ord($str[$i]);
            if ($b < 128 || $b > 191) {$ret .=$ret2; $ret2=""; $i+=$bytes-1;$bytes=1; break;}
            else $ret2.=$str[$i];
            $bytes--;
        }
    }
}
return $ret; 
}
private static function __iconv($string, $charset)
{
    return iconv ( $charset, "UTF-8" , $string );
}
}

Lukas Gottschall
źródło
0

Pobierz kodowanie z nagłówków i przekonwertuj je na utf-8.

$post_url='http://website.domain';

/// Get headers ////////////////////////////////////////////////////////////
function get_headers_curl($url) 
{ 
    $ch = curl_init(); 

    curl_setopt($ch, CURLOPT_URL,            $url); 
    curl_setopt($ch, CURLOPT_HEADER,         true); 
    curl_setopt($ch, CURLOPT_NOBODY,         true); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_TIMEOUT,        15); 

    $r = curl_exec($ch); 
    return $r; 
}
$the_header = get_headers_curl($post_url);
/// check for redirect /////////////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
    $arr = explode('Location:', $the_header);
    $location = $arr[1];

    $location=explode(chr(10), $location);
    $location = $location[0];

$the_header = get_headers_curl(trim($location));
}
/// Get charset /////////////////////////////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
    $arr = explode('charset=', $the_header);
    $charset = $arr[1];

    $charset=explode(chr(10), $charset);
    $charset = $charset[0];
    }
///////////////////////////////////////////////////////////////////////////////
// echo $charset;

if($charset && $charset!='UTF-8') { $html = iconv($charset, "UTF-8", $html); }
Arsen
źródło
0

Ÿjest Mojibake dla ß. W twojej bazie danych możesz mieć hex

DF if the column is "latin1",
C39F if the column is utf8 -- OR -- it is latin1, but "double-encoded"
C383C5B8 if double-encoded into a utf8 column

Należy nie używać żadnych kodowanie / dekodowanie funkcje w PHP; zamiast tego należy poprawnie skonfigurować bazę danych i połączenie z nią.

Jeśli dotyczy MySQL, zobacz: Problemy ze znakami utf8; to, co widzę, nie jest tym, co zapisałem

Rick James
źródło
0

Znajduję rozwiązanie tutaj http://deer.org.ua/2009/10/06/1/

class Encoding
{
    /**
     * http://deer.org.ua/2009/10/06/1/
     * @param $string
     * @return null
     */
    public static function detect_encoding($string)
    {
        static $list = ['utf-8', 'windows-1251'];

        foreach ($list as $item) {
            try {
                $sample = iconv($item, $item, $string);
            } catch (\Exception $e) {
                continue;
            }
            if (md5($sample) == md5($string)) {
                return $item;
            }
        }
        return null;
    }
}

$content = file_get_contents($file['tmp_name']);
$encoding = Encoding::detect_encoding($content);
if ($encoding != 'utf-8') {
    $result = iconv($encoding, 'utf-8', $content);
} else {
    $result = $content;
}

Myślę, że @ to zła decyzja i dokonaj pewnych zmian w rozwiązaniu z deer.org.ua;

outdead
źródło
0

Najczęściej głosowana odpowiedź nie działa. Oto moje i mam nadzieję, że to pomoże.

function toUTF8($raw) {
    try{
        return mb_convert_encoding($raw, "UTF-8", "auto"); 
    }catch(\Exception $e){
        return mb_convert_encoding($raw, "UTF-8", "GBK"); 
    }
}
ch271828n
źródło
1
Czy masz wgląd w to, dlaczego lub jak różniły się Twoje pliki? Które części nie działały dla Ciebie? Na przykład: wielkie niemieckie znaki nie zostały poprawnie przekonwertowane. Ciekawe, co to jest „GBK”?
SherylHohman
-1

Podczas próby obsługi wielu języków, takich jak japoński i koreański, możesz mieć kłopoty. mb_convert_encoding z parametrem „auto” nie działa dobrze. Ustawienie mb_detect_order („ASCII, UTF-8, JIS, EUC-JP, SJIS, EUC-KR, UHC”) nie pomaga, ponieważ nieprawidłowo wykryje EUC- *.

Doszedłem do wniosku, że dopóki łańcuchy wejściowe pochodzą z HTML, powinien używać „charset” w elemencie meta. Używam prostego parsera DOM HTML, ponieważ obsługuje on nieprawidłowy HTML.

Poniższy fragment wyodrębnia element tytułowy ze strony internetowej. Jeśli chcesz przekonwertować całą stronę, możesz usunąć niektóre wiersze.

<?php
require_once 'simple_html_dom.php';

echo convert_title_to_utf8(file_get_contents($argv[1])), PHP_EOL;

function convert_title_to_utf8($contents)
{
    $dom = str_get_html($contents);
    $title = $dom->find('title', 0);
    if (empty($title)) {
        return null;
    }
    $title = $title->plaintext;
    $metas = $dom->find('meta');
    $charset = 'auto';
    foreach ($metas as $meta) {
        if (!empty($meta->charset)) { // html5
            $charset = $meta->charset;
        } else if (preg_match('@charset=(.+)@', $meta->content, $match)) {
            $charset = $match[1];
        }
    }
    if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
        $charset = 'auto';
    }
    return mb_convert_encoding($title, 'UTF-8', $charset);
}
Nobu
źródło
-1

Miałem ten sam problem z phpQuery ( ISO-8859-1 zamiast UTF-8 ) i ten hack pomógł mi:

$html = '<?xml version="1.0" encoding="UTF-8" ?>' . $html;

mb_internal_encoding('UTF-8'), phpQuery::newDocumentHTML($html, 'utf-8'), mbstring.internal_encodingI inne manipulacje nie miała żadnego wpływu.

użytkownik2448995
źródło
-1

Spróbuj bez „auto”

To jest:

mb_detect_encoding($text)

zamiast:

mb_detect_encoding($text, 'auto')

Więcej informacji można znaleźć tutaj: mb_detect_encoding

tkartas
źródło