MySQL — потерянные буквы 'ш' и 'И' — решение

Недавно пришлось восстанавливать сайт на Wordpress, и дамп MySQL'ной базы оказался дважды перекодирован из Windows-1251 в UTF-8. После того, как я его перекодировал взад, оказалось, что из русского текста пропали две буквы: 'ш' и 'И' — на их месте стояли кракозябылы.

Порылся в сети — оказалось, что бага известная и периодически случающаяся. И большей частью с ней борются, практически, вручную, по крайней мере мне не удалось найти никакого скрипта, который бы я мог использовать для решения этой проблемы. В итоге, я написал свой :).

Скачать: SHI-convert.zip (1 Кб).

Скрипт будет работать, скорее всего, только для двойного преобразования Windows-1251 → UTF-8. Результат работы скрипта — текст в UTF-8 с поставленными на место буквами 'ш' и 'И'.

Шлите ваши комментарии :).

<?php
/**
    Восстанавливатель текстов, подвергшихся двойной перекодировки  
    из Windows-1251 в UTF-8. Казалось бы, что сложного в том, чтобы 
    сделать обратное преобразование? Однако, проблема в том, что 
    по неизвестным причинам в исходном тексте теряются две буквы: 
    'ш' и 'И'. 

    Особенно часто этот глюк встречается при экспорте базы из MySQL 
    при криво настроенных свойствах кодировки таблиц и экспорта.

    Как использовать:

    php SHI-converter.php {имя файла} > {имя нового файла}


    Комментарии, предложения, замечания? 
    http://outcorp-ru.blogspot.com/2011/08/mysql.html
    
    Copyleft, Стас Давыдов и Outcorp, 2011. 
    stas@motivateme.ruhttp://stasdavydov.comhttp://outcorp-ru.blogspot.com/
*/

define("DEBUG"FALSE);

function 
get_bytes($str) {
    
$len strlen($str);
    
$bytes '';
    for(
$i 0$i $len$i++) {
        
$bytes .= sprintf('%2X'ord($str[$i]));
        if (
$i $len 1)
            
$bytes .= ' ';
    }
    return 
$bytes;
}

function 
fix_bytes(&$str$start 0$len 0) {
    if (
ord($str[$start $len]) == 0xD0) {
        
$str[$start $len 1] = chr(0x98);
    } else if (
ord($str[$start $len]) == 0xD1) {
        
$str[$start $len 1] = chr(0x88);
    } else {
        
$str[$start $len] = ' ';
    }

}

function 
fix_string(&$s) {
    
$converted = @iconv('utf-8''windows-1251'$s);
    if (
strlen($converted) > 0) {
        
$converted iconv('windows-1251''utf-8'$converted);
        
$sub strlen($converted);
        
$start strpos($s$converted);

        if(
DEBUG)
            echo 
$converted;

        if(
DEBUG) {
            echo 
'['.get_bytes(substr($s$start $sub)).']'."\n";
        }

        
fix_bytes($s$start$sub);

    } else {
        if (
DEBUG)
            echo 
'['.get_bytes($s).']'."\n";

        
fix_bytes($s);
    }
}

if (
$_SERVER['argc'] != 2) {
    echo 
'Usage: '.pathinfo(__FILE__PATHINFO_FILENAME).".php <name of file to recover>\n";
    exit;
}

$filename $_SERVER['argv'][1];

$str file_get_contents($filename);
$utf8str iconv('utf-8''windows-1251'$str);
foreach(
preg_split('/( |\t)/'$utf8str) as $s) {
    while(! 
mb_check_encoding($s'utf-8')) {
        
fix_string($s);

        if (
DEBUG)
            echo 
$s."\n";
    }

    if (! 
DEBUG)
        echo 
$s.' ';
}
?>

5 комментариев:

Stas Davydov комментирует...

Такой изврат с разбиением текста на слова и последовательный прогон через mb_check_encoding связан с тем, что простой заменой тут не обойтись - фиг его знает, где еще может встретиться нужная нам последовательность символов в испорченных 'И' и 'ш' - мультибайт ведь.

Режим DEBUG оставлен на случай, если пропадут еще какие-нибудь символы - к ним так легче будет подобрать восстанавливающий паттерн.

Анонимный комментирует...

Спасибо, помогло :)

Stas Davydov комментирует...

Пожалуйста :)

Анонимный комментирует...

Очень помогло! Два дня голову ломал! Огромное спасибо!

Анонимный комментирует...

Очень помогло! Два дня голову ломал! Огромное спасибо!