Отвечая на мой вопрос с помощью решения, которое я использовал в конце
Проблема:
Мне нужно было заменить 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),'‚'),$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 — and another dash — text ו and more tests נוף ');
Итак, если ваш текст закодирован в формате 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 = 'Ÿ œ bad–string: '.chr(151);//ASCII
if($encoding==='ISO-8859-1'){
//fix bad windows characters
$badchars = array(
'‚'=>chr('130'),//',' baseline single quote
'ƒ'=>chr('131'),//'NLG' florin
'„'=>chr('132'),//'"' baseline double quote
'…'=>chr('133'),//'...' ellipsis
'†'=>chr('134'),//'**' dagger (a second footnote)
'‡'=>chr('135'),//'***' double dagger (a third footnote)
'ˆ'=>chr('136'),//'^' circumflex accent
'‰'=>chr('137'),//'o/oo' permile
'Š'=>chr('138'),//'Sh' S Hacek
'‹'=>chr('139'),//'<' left single guillemet
'Œ'=>chr('140'),//'OE' OE ligature
'‘'=>chr('145'),//"'" left single quote
'’'=>chr('146'),//"'" right single quote
'“'=>chr('147'),//'"' left double quote
'”'=>chr('148'),//'"' right double quote
'•'=>chr('149'),//'-' bullet
'–'=>chr('150'),//'-' endash
'—'=>chr('151'),//'--' emdash
'˜'=>chr('152'),//'~' tilde accent
'™'=>chr('153'),//'(TM)' trademark ligature
'š'=>chr('154'),//'sh' s Hacek
'›'=>chr('155'),//'>' right single guillemet
'œ'=>chr('156'),//'oe' oe ligature
'Ÿ'=>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);
Единственное решение, которое я могу придумать
Хотя вы всегда можете заменить действительные символы Юникода, содержащие байт плохих символов, на их закодированные аналоги, затем заменить плохие символы и затем декодировать хорошие символы, сохраняя все:)
вот так:
- заменить
11100010:10000000:10010100
на кодировку
—
- затем замените
10010100
на правильный тире —
- затем декодировать
—
обратно на 11100010:10000000:10010100
Но для этого нужно записать каждый многобайтовый символ, содержащий байты, которые соответствуют плохим символам.
Похожие: В чем разница между EM Dash # 151; и # 8212;?