Как преобразовать текст с HTML-символами и недопустимыми символами в его эквивалент UTF-8? - PullRequest
4 голосов
/ 09 февраля 2012

Я меняю заголовок, потому что не знал о специальных символах разбитых окон, которые вызывали у меня проблемы, из-за чего вопрос выглядел как дубликат.

Как конвертировать HTML-сущности, символьные ссылки типа & # [0-9] +; и & # x [a-fA-F0-9] + ;, недопустимые ссылки на символы - и недопустимые символы windows chr (151) для их эквивалентов UTF-8?

Как очистить очень плохой текст от переменной кодировки и сохранить его как UTF-8?

оригинальный вопрос ниже

Преобразовать & # [0-9] +; и & # x [a-fA-F0-9] +; ссылки на эквиваленты UTF-8?

например

—
—

в -

как браузер, но с php.

edit: даже нестандартные, сделанные в Windows, но браузеры по-прежнему отображаются.

Ответы [ 2 ]

5 голосов
/ 09 февраля 2012

Отвечая на мой вопрос с помощью решения, которое я использовал в конце

Проблема:

Мне нужно было заменить html-сущности и десятичные и шестнадцатеричные ссылки на символы, которые выглядели так ‚ и ‚ и &#emdash;, на их эквиваленты UTF-8, как это делал бы обычный браузер, и преобразовать текст в UTF- 8.

Проблема заключалась в том, что часто встречались ссылки в диапазоне 130-150 и x82-x9F, которые, как выяснили тридцат , были недопустимые символы слова windows , которые люди используйте вместе с текстом ASCII для специальных символов, таких как emdashes, которые не поддерживаются html_entity_decode php.

Можно подумать, что эти недопустимые символы не будут работать в браузерах, но похоже, что браузеры заключили молчаливое недокументированное соглашение, чтобы исправить эти символы и правильно их отобразить.

Пытаясь исправить эти ссылки, я также обнаружил, что использовались и такие символы, как <?php echo chr(151);?>, которые, вероятно, были непосредственно скопированы из слова и вызывали всевозможные проблемы, поэтому мне нужно было их исправить тоже .

В большинстве ответов, которые я нашел относительно кодировок, не упоминается, что решение проблем, связанных с кодированием, часто во многом зависит от используемой кодировки. Вот пример:

Недопустимый символ Windows chr(151) будет работать с закодированным текстом «ISO-8859-1», и Джош Б упоминает в соответствии с предложением Юкки Корпеласа , что вы должны исправить их следующим образом:

$str = str_replace(chr(151),'--',$str);

Что он делает, так это заменяет символ windows на безопасную альтернативу ASCII, но, зная, что текст будет сохранен в UTF-8, я не хотел терять оригинальные символы. Изменять их так нельзя, потому что ASCII не поддерживает правильный символ Unicode:

$str = str_replace(chr(151),chr(8218),$str);

Поэтому вместо этого я сначала заменил символ на его ссылку html (хотя $ str был закодирован как «ISO-8859-1»:

$str = str_replace(chr(151),'&#8218;'),$str);

Затем я меняю кодировку

$str = iconv('ISO-8859-1', 'UTF-8//IGNORE', $str);//convert to UTF-8

И, наконец, я превращаю все сущности и ссылки на символы в чистый UTF-8 с помощью своей функции "html_character_reference_decode", которая в значительной степени основана на Gumbos решении , которое также исправляет плохие окна ссылки, но использует только preg_replace_callback для перехода по плохим символам Windows.

function fix_char_mapping($match){
    if (strtolower($match[1][0]) === "x") {
        $codepoint = intval(substr($match[1], 1), 16);
    } else {
        $codepoint = intval($match[1], 10);
    }
    $mapping = array(8218,402,8222,8230,8224,8225,710,8240,352,8249,338,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,157,158,376); 
    $codepoint = $mapping[$codepoint-130];
    return '&#'.$codepoint.';';
}
function html_character_reference_decode($string, $encoding='UTF-8', $fixMappingBug=true){
    if($fixMappingBug){
        $string = preg_replace_callback('/&#(1[3-5][0-9]|x8[2-9a-f]|x9[0-9a-f]);/i','fix_char_mapping',$string);
    }
    return html_entity_decode($string, ENT_QUOTES, 'UTF-8');
}
header('Content-Type: text; charset=UTF-8');
echo  html_character_reference_decode('dash &#151; and another dash &#x97; text &#x5D5; and more tests &#x5E0;&#x5D5;&#x5E3; ');

Итак, если ваш текст закодирован в формате ISO-8859-1, полное решение выглядит следующим образом:

<?php
header('Content-Type: text/plain; charset=utf-8');
ini_set("default_charset", 'utf-8');
error_reporting(-1);
$encoding = 'ISO-8859-1';//put encoding here
$str = '&#x9F; &#x9C; bad&#150;string: '.chr(151);//ASCII
if($encoding==='ISO-8859-1'){
//fix bad windows characters
$badchars = array(
'&#130;'=>chr('130'),//',' baseline single quote
'&#131;'=>chr('131'),//'NLG' florin
'&#132;'=>chr('132'),//'"' baseline double quote
'&#133;'=>chr('133'),//'...' ellipsis
'&#134;'=>chr('134'),//'**' dagger (a second footnote)
'&#135;'=>chr('135'),//'***' double dagger (a third footnote)
'&#136;'=>chr('136'),//'^' circumflex accent
'&#137;'=>chr('137'),//'o/oo' permile
'&#138;'=>chr('138'),//'Sh' S Hacek
'&#139;'=>chr('139'),//'<' left single guillemet
'&#140;'=>chr('140'),//'OE' OE ligature
'&#145;'=>chr('145'),//"'" left single quote
'&#146;'=>chr('146'),//"'" right single quote
'&#147;'=>chr('147'),//'"' left double quote
'&#148;'=>chr('148'),//'"' right double quote
'&#149;'=>chr('149'),//'-' bullet
'&#150;'=>chr('150'),//'-' endash
'&#151;'=>chr('151'),//'--' emdash
'&#152;'=>chr('152'),//'~' tilde accent
'&#153;'=>chr('153'),//'(TM)' trademark ligature
'&#154;'=>chr('154'),//'sh' s Hacek
'&#155;'=>chr('155'),//'>' right single guillemet
'&#156;'=>chr('156'),//'oe' oe ligature
'&#159;'=>chr('159'),//'Y' Y Dieresis
);
$str = str_replace(array_values($badchars),array_keys($badchars),$str);
$str = iconv('ISO-8859-1', 'UTF-8//IGNORE', $str);//convert to UTF-8
$str = html_character_reference_decode($str);//fixes bad entities above
echo $str;die;
}

Он был протестирован в широком диапазоне ситуаций и выглядит так, как будто он работает.

Давайте посмотрим на ту же ситуацию с текстом в кодировке UTF-8, который содержит плохие символы Windows.

Один надежный способ проверить наличие плохих символов или «плохо сформированного UTF-8» - это использовать iconv, это медленно, но было более надежно, чем использование preg_match в моих тестах:

$cleaned = iconv('UTF-8','UTF-8//IGNORE',$str);
if ($cleaned!==$str){
    //contains bad characters, use cleaned version where the bad characters were stripped
    $str = $cleaned;
}

Это было самое лучшее, что я мог придумать, так как я не нашел разумного способа найти и заменить плохие символы окон в тексте UTF-8, позвольте мне объяснить, почему.

Давайте возьмем строку с совершенно правильным символом Unicode $str = "—".chr(151); и плохой окном Windows.

Я не знаю, какие плохие символы Windows могут присутствовать в строке UTF-8, только то, что они могут присутствовать.

Использование str_replace для исправления некорректного символа Windows chr(148) (правая двойная кавычка) в приведенной выше допустимой строке emdash, которая даже не содержит двойных кавычек, приведет к появлению символа с шифрованием, сначала я подумал, что str_replace может быть небезопасным, и попытался использовать mb_eregi_replace, но проблема была та же.

В комментариях на сайте php и stackoverflow упоминается, что str_replace является бинарно-безопасным и прекрасно работает с правильно сформированным текстом UTF-8 благодаря способу, которым был разработан UTF-8.

Почему ломается

Показывает, что плохой символ Windows chr(148) состоит из следующих битов " 10010100 ", в то время каксимвол (emdash) (http://www.fileformat.info/info/unicode/char/2014/index.htm),, который, согласно веб-сайту fileformat, состоит из 3 байтов: "11100010: 10000000: 10010100 "

Обратите внимание, что биты в последнем байте в совершенно правильном символе UTF-8 совпадают с битами в двойных кавычках неправильных окон, поэтому str_replace просто заменяет последний байт, разбивая символ UTF-8. Эта проблема возникает с большим количеством символов Юникода, и, например, закодирует много символов в русском тексте.

Этого не может быть с текстом ASCII, потому что каждый символ всегда состоит из одного байта.

Таким образом, когда вы получаете строку UTF-8, которая содержит любое количество многобайтовых символов, вы больше не можете безопасно исправлять плохие символы Windows, и единственное решение, которое я нашел, было убрать их с помощью iconv

$str = iconv('UTF-8', 'UTF-8//IGNORE', $str);

Единственное решение, которое я могу придумать

Хотя вы всегда можете заменить действительные символы Юникода, содержащие байт плохих символов, на их закодированные аналоги, затем заменить плохие символы и затем декодировать хорошие символы, сохраняя все:)

вот так:

  1. заменить 11100010:10000000:10010100 на кодировку &#8212;
  2. затем замените 10010100 на правильный тире &mdash;
  3. затем декодировать &#8212; обратно на 11100010:10000000:10010100

Но для этого нужно записать каждый многобайтовый символ, содержащий байты, которые соответствуют плохим символам.

Похожие: В чем разница между EM Dash # 151; и # 8212;?

1 голос
/ 09 февраля 2012

Это намного сложнее, чем я думал, когда писал свой ответ.

Гамбо обновил свой ответ на очень похожий вопрос, поэтому просто прочитайте это:

Как преобразовать ссылки на символы HTML (& # x5E3;) в обычный UTF-8?

...