【问题标题】:How to convert text with HTML entites and invalid characters to it's UTF-8 equivalent?如何将带有 HTML 实体和无效字符的文本转换为等效的 UTF-8?
【发布时间】:2012-02-09 12:01:52
【问题描述】:

我正在更改标题,因为我不知道导致我出现问题的特殊损坏的 windows 字符,使问题看起来像重复。

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

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

下面的原始问题

转换 [0-9]+;和 [a-fA-F0-9]+;引用 UTF-8 等价物?

例如

—
—

到 ——

就像浏览器一样,但使用 php。

编辑:即使是非标准的窗口制作但浏览器仍然显示。

【问题讨论】:

  • @Gumbo 除了上述问题没有解决 $#[0-9]+;字符
  • @Gumbo 我不知道该说什么,但它对我不起作用......它根本没有改变它。
  • @Gumbo 我的意思不是“重命名它以使其不再重复”,而是要有一个更清晰的标题,并可能处理其他将数字字符转换为 unicode 的情况。
  • @thirtydot 现在我明白你在说什么了。不知道。我很惊讶浏览器供应商确实采用了这种奇怪的行为。在那种情况下,我不会认为这个问题是重复的。

标签: php html utf-8 character-encoding iconv


【解决方案1】:

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

问题:

我需要将 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 = '&#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;
}

它已经在各种情况下进行了测试,并且看起来很有效。

让我们看一下包含错误 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 字符替换为其编码的对应字符,然后替换坏字符,然后解码好字符,从而保留一切:)

像这样:

  1. 11100010:10000000:10010100 替换为类似的编码 &amp;#8212;
  2. 然后将10010100 替换为适当的破折号&amp;mdash;
  3. 然后将&amp;#8212;解码回11100010:10000000:10010100

但是您必须写下每个包含与坏字符匹配的字节的多字节字符才能实现这一点。

相关:What is the difference between EM Dash #151; and #8212;?

【讨论】:

    【解决方案2】:

    这比我写答案时想象的要复杂得多。

    Gumbo 更新了他对一个非常相似的问题的回答,所以请阅读:

    How can I convert HTML character references (&#x5E3;) to regular UTF-8?

    【讨论】:

    • html_entity_decode():仅适用于 get_html_translation_table() 中的字符,这是一个非常小的引用和字符列表。
    • 不,它也适用于数字实体。看看 PHP 源代码。 This 导致 this 包含 this。我不确定为什么它不适用于&amp;#151;&amp;#x97;,但它确实适用于&amp;#8212;。这三个都意味着。我见过的所有解决方案都无法解码&amp;#151;&amp;#x97;
    • @thirtybot 他们都没有,这正是我发布这个问题的原因。
    • 我正在阅读:cs.tut.fi/~jkorpela/www/windows-chars.html。它开始变得有意义了。 PHP 显然没有像浏览器那样处理这种奇怪的事情。
    • @thirtydot - 字符覆盖映射在此处的 HTML5 规范中正式指定:dev.w3.org/html5/spec/tokenization.html#table-charref-overrides
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-28
    • 2011-03-02
    • 1970-01-01
    • 2015-02-25
    • 1970-01-01
    • 2018-09-14
    相关资源
    最近更新 更多