在XML Amazon MWS ListOrders响应的一部分中,我们在一个元素中得到了一个转义的UTF-8字符:
<Address><Name>Ramírez Jones</Name></Address>
这个名字应该是拉米雷斯。变音符号字符í
是 UTF-8 字符U+00ED
(文字'xc3'xad
;请参阅此图表以供参考)。
然而,PHP 的 SimpleXML 函数破坏了这个字符串(你可以看到,因为我只是粘贴了),将其转换为
拉姆雷斯·琼斯
进入这里的编辑器框(显然 stackoverflow 的 ASP.NET 基础与 PHP 做同样的事情)。
现在,当这个被破坏的字符串被保存到,然后从MongoDB中提取出来时,它就变成了
拉姆雷兹·琼斯
出于某种原因,在那里插入了一个连字符,尽管信不信由你,如果您选择上面的粗体文本,然后将其粘贴回 StackOverflow 编辑器窗口中,它将简单地显示为RamÃrez
(连字符神秘地消失了,至少在 OS X 10.8.5 上)!
下面是一些示例代码来显示此问题:
$xml = "<Address><Name>Ramírez Jones</Name></Address>";
$elem = new SimpleXMLAddressent($xml);
$bad_string = $elem->Name;
echo mb_detect_encoding($bad_string)."'n";
echo $elem->Name->__toString()."'n";
echo iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $elem->Name->__toString());
下面是上面示例代码的输出,在onlinephpfunction.com的沙箱上运行:
UTF-8
拉姆雷斯·琼斯
拉玛-雷兹·琼斯
我们如何避免这个问题?这真的把事情搞砸了。
编辑:
让我补充一点,虽然XML中的名字应该是Ramírez Jones,但我需要将其音译为Ramirez Jones(去掉í的变音标记)。
修订后的最终解决方案:
它与下面的正确答案不同,但这是我找到的最优雅的解决方案。只需将示例的最后一行替换为以下内容:
echo iconv('UTF-8','ASCII//TRANSLIT', html_entity_decode($xml));
这是有效的"í"
因为它们是 HTML 实体。
替代解决方案
奇怪的是,这也有效:
$xml = '<?xml version="1.0"?><Address><Name>Ramírez Jones</Name></Address>';
$xml= str_replace('<?xml version="1.0"?>', '<?xml version="1.0" encoding="ISO-8859-1"?>' , $xml);
$domdoc = new DOMDocument();
$domdoc->loadXML($xml);
$xml = iconv('UTF-8','ASCII//TRANSLIT',$domdoc->saveXML());
$elem = new SimpleXMLElement($xml);
echo $elem->Name;
它不起作用,因为它被编码了两次。字符í
具有代码U+00ED
,应将其编码为 &#ED;
的 XML
您可以使用以下任一方法修复其编码:
$name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT//IGNORE', $elem->Name->__toString());
或
$name = mb_convert_encoding($elem->Name->__toString(), 'ISO-8859-1', 'UTF-8');
更新:
上面建议的两种方法都可以修复编码(它们实际上将字符串的编码从 UTF-8
转换为 ISO-8859-1
,顺便解决了手头的问题)。
@Hazzit提供的解决方案也有效。
这两种解决方案(以及您的代码)的真正挑战是检测接收到的数据是否以错误的方式编码,并仅在该情况下应用这些修复程序,以便在 Amazon 修复编码问题时让代码正常工作。我希望他们会这样做。
以最小的信息损失去除重音
修复编码后,为了将重音字母替换为 ASCII 子集中的类似字母,您必须使用 iconv()
(因为只有 iconv()
可以提供帮助),就像您在示例代码中所做的那样。
$nameAscii = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name);
第二个参数的解释可以在iconv()
的文档页面中找到;也请阅读用户评论。
SimpleXML 不会解码十六进制实体并将结果理解为 UTF-8,因为这不是 XML 或 UTF-8 实际工作的方式。但是,如果亚马逊产生这样的废话,您需要在将其解析为XML之前更正该错误。
function decode_hexentities($xml) {
return
preg_replace_callback(
'~&#x([0-9a-fA-F]+);~i',
function ($matches) { return chr(hexdec($matches[1])); },
$xml
);
}
$xml = "<Address><Name>Ramírez Jones</Name></Address>";
$xml = decode_hexentities($xml);
$elem = new SimpleXMLElement($xml);
$bad_string = $elem->Name;
echo mb_detect_encoding($bad_string)."'n";
echo $elem->Name->__toString()."'n";
echo iconv('UTF-8', 'ASCII//TRANSLIT', $elem->Name->__toString());
结果在:
UTF-8
Ramírez Jones
Ramirez Jones