Как преобразовать эту экранированную строку UTF-8 из ответа Amazon MWS в соответствующий 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 искажает эту строку (которую вы можете видеть, потому что я просто вставил), превращая ее в

Рамирес Джонс

здесь, в окне редактора (очевидно, основы ASP.NET в stackoverflow делают то же самое, что и 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 должно быть Рамирес Джонс, Мне нужно транслитерировать это Рамирес Джонс (снимите диакритическую метку с í).

ПЕРЕСМОТРЕННОЕ ЗАКЛЮЧИТЕЛЬНОЕ РЕШЕНИЕ:

Это отличается от правильного ответа ниже, но это было самое элегантное решение, которое я нашел. Просто замените последнюю строку примера следующим образом:

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;

2

Решение

SimpleXML не декодирует шестнадцатеричные объекты а также понимать результат как UTF-8, потому что это не так, как на самом деле работает XML или UTF-8. Тем не менее, если Amazon производит такую ​​ерунду, вам нужно исправить эту ошибку, прежде чем анализировать ее как 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
1

Другие решения

Это не работает, потому что кодируется дважды. Характер í имеет код U+00ED и это должно быть закодировано в XML как &#ED;,

Вы можете исправить его кодировку, используя:

$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(); Пожалуйста, прочитайте также комментарии пользователей.

2