我正在更改标题,因为我不知道特殊的破窗口字符导致我的问题,使问题看起来像一个副本.
如何转换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.
编辑:甚至是Windows制作的非标准版,但浏览器仍然显示.
解决方法:
用我最后使用的解决方案回答我自己的问题
问题:
我需要替换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);?>也被使用,可能直接从单词复制,并会导致各种问题,所以我也需要修复它们.
我发现的关于编码的大多数答案都没有提到,编码相关问题的解决方案通常很大程度上取决于所使用的编码.
这是一个例子:
无效的窗口字符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;
}
它经过了广泛的测试,看起来很有效.
让我们看看包含坏窗口字符的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文本中的坏窗口字符,让我解释一下原因.
让一个带有完全有效的unicode字符的字符串$str =“ – ”.chr(151);和一个糟糕的窗户emdash.
我不知道UTF-8字符串中可能存在哪些坏的Windows字符,只是它们可能存在.
使用str_replace尝试修复坏窗口字符chr(148)(右双引号)在上面有效的emdash字符串中甚至不包含任何双引号将导致一个有碎片的字符,起初我认为str_replace可能不是多字节的安全,并尝试使用mb_eregi_replace但问题是一样的.
关于php网站和*的评论提到str_replace是二进制安全的,并且由于UTF-8的设计方式,可以很好地使用格式良好的UTF-8文本.
为什么它会破裂
它表明坏窗口字符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字符.
这个问题发生在很多unicode字符上,并且例如在俄语文本中会乱写很多字符.
ASCII文本不会发生这种情况,因为每个字符总是由一个字节组成.
因此,当您获得包含任意数量的多字节字符的UTF-8字符串时,您无法再安全地修复错误的Windows字符,我找到的唯一解决方案是使用iconv剥离它们
$str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
我能想到的唯一解决方案
虽然你总是可以将包含坏字符字节的有效unicode字符替换为它们的编码对应字符,然后替换坏字符然后解码好字符,从而保留所有内容:)
像这样:
>用编码替换11100010:10000000:10010100
&安培;#8212;
>然后用适当的em dash& mdash替换10010100;
>然后解码—回到11100010:10000000:10010100
但是你必须写下包含与坏字符匹配的字节的每个多字节字符来实现这一点.
相关:What is the difference between EM Dash #151; and #8212;?