如何将此 UTF-8 转义字符串从亚马逊 MWS 响应转换为正确的 UTF-8


How to convert this UTF-8 escaped string from an Amazon MWS response to proper UTF-8?

在XML Amazon MWS ListOrders响应的一部分中,我们在一个元素中得到了一个转义的UTF-8字符:

<Address><Name>Ram&#xC3;&#xAD;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&#xC3;&#xAD;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));

这是有效的"&#xC3;&#xAD;"因为它们是 HTML 实体。

替代解决方案

奇怪的是,这也有效:

$xml = '<?xml version="1.0"?><Address><Name>Ram&#xC3;&#xAD;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&#xC3;&#xAD;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