用我最后使用的解决方案回答我自己的问题
问题:
我需要将 html 实体以及看起来像 ‚ 和 ‚ 和 &#emdash; 的十进制和十六进制字符引用替换为它们的 UTF-8 等效项,就像普通浏览器一样,并将文本转换为 UTF- 8.
问题在于,通常存在 130-150 和 x82-x9F 范围内的引用,正如 thirtydot 发现的那样,人们将 invalid windows word characters 与 ASCII 文本一起用于特殊字符(如 emdashes),其中不受 php 的 html_entity_decode 支持。
您会认为这些无效字符在浏览器中不起作用,但看起来浏览器达成了一个无声的无证协议来修复这些字符并正确显示它们。
在尝试修复这些引用的同时,我还发现像<?php echo chr(151);?>这样的实际字符也被使用了,它们可能是直接从word中复制而来的,会导致各种问题,所以我也需要修复它们.
我发现的关于编码的大多数答案都没有提到,编码相关问题的解决方案通常在很大程度上取决于所使用的编码。
这是一个例子:
无效的 windows 字符 chr(151) 将与“ISO-8859-1”编码文本一起使用,Josh B mentions as per Jukka Korpelas suggestion 您应该像这样修复它们:
$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
最后,我使用“html_character_reference_decode”函数将所有实体和字符引用转换为纯 UTF-8,该函数主要基于 Gumbos solution,它也修复了错误的 Windows 引用,但仅使用 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;
}
它已经在各种情况下进行了测试,并且看起来很有效。
让我们看一下包含错误 Windows 字符的 UTF-8 编码文本的相同情况。
测试是否存在错误字符或“格式错误的 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 文本中的坏 Windows 字符,让我解释一下原因。
让我们使用一个完全有效的 unicode 字符 $str = "—".chr(151); 和一个错误的 windows emdash 的字符串。
我不知道 UTF-8 字符串中可能存在哪些错误的 Windows 字符,只知道它们可能存在。
使用str_replace 尝试修复上述有效 emdash 字符串中甚至不包含任何双引号的坏 windows 字符 chr(148)(右双引号)会导致字符乱码,起初我以为@ 987654345@ 可能不是多字节安全的,并尝试使用 mb_eregi_replace 但问题是一样的。
php 网站和 stackoverflow 上的 cmets 提到 str_replace 是二进制安全的,并且可以很好地处理 格式良好的 UTF-8 文本,因为 UTF-8 的设计方式。
为什么会坏
认为坏windows字符chr(148)由以下位“10010100”组成,而
(破折号字符)(http://www.fileformat.info/info/unicode/char/2014/index.htm),根据文件格式网站由 3 个字节组成:“11100010:10000000:10010100”
请注意,完全有效的 UTF-8 字符中最后一个字节中的位与错误窗口右双引号中的位相匹配,因此 str_replace 只是替换最后一个字节,破坏了 UTF-8 字符。
很多 unicode 字符都会出现这个问题,例如,会打乱俄语文本中的很多字符。
ASCII 文本不会发生这种情况,因为每个字符总是由一个字节组成。
所以当你得到一个包含任意数量多字节字符的 UTF-8 字符串时,你不能再安全地修复坏的 windows 字符,我找到的唯一解决方案是用 iconv 去除它们
$str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
我能想到的唯一解决方案
尽管您总是可以将包含一个字节的坏字符的有效 unicode 字符替换为其编码的对应字符,然后替换坏字符,然后解码好字符,从而保留一切:)
像这样:
- 将
11100010:10000000:10010100 替换为类似的编码
&#8212;
- 然后将
10010100 替换为适当的破折号&mdash;
- 然后将
&#8212;解码回11100010:10000000:10010100
但是您必须写下每个包含与坏字符匹配的字节的多字节字符才能实现这一点。
相关:What is the difference between EM Dash #151; and #8212;?