如何将带有HTML entites和无效字符的文本转换为UTF-8等效字符?

Tim*_*nen 4 html php utf-8 character-encoding iconv

我正在更改标题,因为我不知道特殊的破窗口字符导致我的问题,使问题看起来像一个副本.

如何转换HTML实体,类型&#[0-9] +的字符引用; 和&#x [a-fA-F0-9] +;,无效的字符引用 - 和无效的windows字符chr(151)到它们的UTF-8等价物?

基本上如何清理一些非常糟糕的变量编码文本并将其保存为UTF-8?

原始问题如下

转换&#[0-9] +; 和&#x [a-fA-F0-9] +; 参考UTF-8等值?

例如

—
—
Run Code Online (Sandbox Code Playgroud)

至 -

像浏览器一样,但用PHP.

编辑:甚至是Windows制作的非标准版,但浏览器仍然显示.

Tim*_*nen 5

用我最后使用的解决方案回答我自己的问题

问题:

我需要替换HTML实体,并十进制和看起来像这样十六进制字符引用‚,并‚&#emdash;他们的UTF-8 equvalents,像一个正常的浏览器将和文本转换为UTF-8.

问题是通常有130-150和x82-x9F范围内的引用,正如thirtydot发现的那样是无效的windows字符,人们使用ASCII文本来表示像emdashes这样的特殊字符,这些字符不受支持php的html_entity_decode.

您会认为这些无效字符在浏览器中不起作用,但看起来浏览器制作了一个无声的未记录协议来修复这些字符并无论如何正确显示它们.

在尝试修复这些引用的同时,我还发现实际的字符<?php echo chr(151);?>也被使用,可能直接从word中复制,并且会引起各种各样的问题,所以我也需要修复它们.

我发现的关于编码的大多数答案都没有提到,编码相关问题的解决方案通常很大程度上取决于所使用的编码.这是一个例子:

无效的Windows字符chr(151)将与"ISO-8859-1"编码文本一起使用, Josh B根据Jukka Korpelas建议您应该像这样修复它们:

$str = str_replace(chr(151),'--',$str);
Run Code Online (Sandbox Code Playgroud)

它的作用是将windows字符替换为安全的ASCII替代品,但是知道文本将以UTF-8存储,我不想丢失原始字符.虽然这样更改它们不是一个选项,因为ASCII不支持正确的Unicode字符:

$str = str_replace(chr(151),chr(8218),$str);
Run Code Online (Sandbox Code Playgroud)

所以我做的是首先将字符替换为其html引用(而$ str是"ISO-8859-1"编码:

$str = str_replace(chr(151),'&#8218;'),$str);
Run Code Online (Sandbox Code Playgroud)

然后我改变编码

$str = iconv('ISO-8859-1', 'UTF-8//IGNORE', $str);//convert to UTF-8
Run Code Online (Sandbox Code Playgroud)

最后,我使用我的"html_character_reference_decode"函数将所有实体和字符引用转换为纯UTF-8,该函数主要基于Gumbos 解决方案,该解决方案还修复了坏的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 &#151; and another dash &#x97; text &#x5D5; and more tests &#x5E0;&#x5D5;&#x5E3; ');
Run Code Online (Sandbox Code Playgroud)

因此,如果您的文本是"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;
}
Run Code Online (Sandbox Code Playgroud)

它经过了广泛的测试,看起来很有效.

让我们看看包含坏窗口字符的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;
}
Run Code Online (Sandbox Code Playgroud)

这几乎是我能想到的最好的,因为我找不到合理的方法来查找和替换UTF-8文本中的坏窗口字符,让我解释一下原因.

让一个字符串具有完全有效的unicode字符$str = "—".chr(151);和一个坏的windows emdash.

我不知道UTF-8字符串中可能存在哪些坏的Windows字符,只是它们可能存在.

使用str_replace尝试和修复坏Windows字符chr(148)在上述有效emdash字符串,甚至不包含任何双引号将导致scrambeled字符(右双引号),起初我以为str_replace可能不是多字节安全的,并尝试使用mb_eregi_replace,但问题是一样的.

关于php网站和stackoverflow的评论提到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);
Run Code Online (Sandbox Code Playgroud)

我能想到的唯一解决方案

虽然你总是可以将包含坏字符字节的有效unicode字符替换为它们的编码对应字符,然后替换坏字符然后解码好字符,从而保留所有内容:)

像这样:

  1. 11100010:10000000:10010100用编码 代替&#8212;
  2. 然后10010100用适当的em破折号替换&mdash;
  3. 然后解码&#8212;回来11100010:10000000:10010100

但是你必须写下包含与坏字符匹配的字节的每个多字节字符来实现这一点.

相关:EM Dash#151之间的区别是什么?和#8212;?