ПРОБЛЕМА : Мне нужен XML-файл, полный кодированный UTF8; то есть без сущности, представляющей символы, все символы, подпадающие под UTF8, за исключением только трех, которые зарезервированы для XML, «&» (amp), «<» (lt) и «>» (gt). И мне нужна встроенная функция, которая делает это быстро : преобразовывать сущности в реальные символы UTF8 (без развращения моего XML).
PS: это «проблема реального мира» (!); в PMC / журналах , например, есть 2,8 млн. научных статей, связанных с специальным XML DTD (также известным как формат JATS ) … Для обработки как «обычного XML-UTF8-текста» нам нужно перейти от числового объекта к UTF8 голец.
ПОПЫТНОЕ РЕШЕНИЕ : естественной функцией для этой задачи является html_entity_decode , но она разрушает XML-код (!), Преобразуя зарезервированные 3 зарезервированных символа XML.
предполагать
$xmlFrag ='<p>Hello world! Let A<B and A=∬dxdy</p>';
Если объекты 160 (nbsp) и x222C (двойной интеграл) должны быть преобразованы в UTF8, а XML-зарезервировано lt
not. Текст XML будет (после преобразования),
$ xmlFrag = ' <p>
Привет, мир! Пусть A <
B и A = ∬dxdy </p>
';
Текст «A <B» нуждается в символе с сохранением XML, поэтому ДОЛЖЕН оставаться как A<B
Я пытаюсь использовать html_entity_decode
для решения (напрямую!) Проблемы … Итак, я обновил свой PHP до версии 5.5, чтобы попытаться использовать параметр ENT_XML1
,
$s = html_entity_decode($xmlFrag, ENT_XML1, 'UTF-8'); // not working // as I expected
Возможно, еще один вопрос: «ПОЧЕМУ нет другого способа делать то, что я ожидал?» – это важно для многих других приложений XML (!), причем не только для меня.
Мне не нужен обходной путь в качестве ответа … Хорошо, я показываю свою уродливую функцию, возможно, это помогает понять проблему,
function xml_entity_decode($s) { // here an illustration (by user-defined function) // about how the hypothetical PHP-build-in-function MUST work static $XENTITIES = array('&','>','<'); static $XSAFENTITIES = array('#_x_amp#;','#_x_gt#;','#_x_lt#;'); $s = str_replace($XENTITIES,$XSAFENTITIES,$s); //$s = html_entity_decode($s, ENT_NOQUOTES, 'UTF-8'); // any php version $s = html_entity_decode($s, ENT_HTML5|ENT_NOQUOTES, 'UTF-8'); // PHP 5.3+ $s = str_replace($XSAFENTITIES,$XENTITIES,$s); return $s; } // you see? not need a benchmark: // it is not so fast as direct use of html_entity_decode; if there // was an XML-safe option was ideal.
PS: исправлено после этого ответа . Должен быть флаг ENT_HTML5
, для конвертирования действительно всех названных объектов .
Используйте DTD при загрузке XML-документа JATS, так как он определит любое сопоставление от именованных объектов к символам Unicode, а затем установите кодировку в UTF-8 при сохранении:
$doc = new DOMDocument; $doc->load($inputFile, LIBXML_DTDLOAD | LIBXML_NOENT); $doc->encoding = 'UTF-8'; $doc->save($outputFile);
Этот вопрос создает время от времени «ложный ответ» (см. Ответы). Возможно, это связано с тем, что люди не обращают внимания, и потому что нет НИКАКОГО ОТВЕТА : есть недостаток встроенного в PHP решения .
… Итак, повторите мое обходное решение (это НЕ ответ !), Чтобы не создавать больше путаницы:
Обращать внимание:
xml_entity_decode()
ниже – лучший (по сравнению с любым другим) способом обхода . function xml_entity_decode($s) { // illustrating how a (hypothetical) PHP-build-in-function MUST work static $XENTITIES = array('&','>','<'); static $XSAFENTITIES = array('#_x_amp#;','#_x_gt#;','#_x_lt#;'); $s = str_replace($XENTITIES,$XSAFENTITIES,$s); $s = html_entity_decode($s, ENT_HTML5|ENT_NOQUOTES, 'UTF-8'); // PHP 5.3+ $s = str_replace($XSAFENTITIES,$XENTITIES,$s); return $s; }
Чтобы протестировать и продемонстрировать, что у вас есть лучшее решение, сначала проверьте этот простой benckmark:
$countBchMk_MAX=1000; $xml = file_get_contents('sample1.xml'); // BIG and complex XML string $start_time = microtime(TRUE); for($countBchMk=0; $countBchMk<$countBchMk_MAX; $countBchMk++){ $A = xml_entity_decode($xml); // 0.0002 /* 0.0014 $doc = new DOMDocument; $doc->loadXML($xml, LIBXML_DTDLOAD | LIBXML_NOENT); $doc->encoding = 'UTF-8'; $A = $doc->saveXML(); */ } $end_time = microtime(TRUE); echo "\n<h1>END $countBchMk_MAX BENCKMARKs WITH ", ($end_time - $start_time)/$countBchMk_MAX, " seconds</h1>";
public function entity_decode($str, $charset = NULL) { if (strpos($str, '&') === FALSE) { return $str; } static $_entities; isset($charset) OR $charset = $this->charset; $flag = is_php('5.4') ? ENT_COMPAT | ENT_HTML5 : ENT_COMPAT; do { $str_compare = $str; // Decode standard entities, avoiding false positives if ($c = preg_match_all('/&[az]{2,}(?![az;])/i', $str, $matches)) { if ( ! isset($_entities)) { $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset)); // If we're not on PHP 5.4+, add the possibly dangerous HTML 5 // entities to the array manually if ($flag === ENT_COMPAT) { $_entities[':'] = ':'; $_entities['('] = '('; $_entities[')'] = '&rpar'; $_entities["\n"] = '&newline;'; $_entities["\t"] = '&tab;'; } } $replace = array(); $matches = array_unique(array_map('strtolower', $matches[0])); for ($i = 0; $i < $c; $i++) { if (($char = array_search($matches[$i].';', $_entities, TRUE)) !== FALSE) { $replace[$matches[$i]] = $char; } } $str = str_ireplace(array_keys($replace), array_values($replace), $str); } // Decode numeric & UTF16 two byte entities $str = html_entity_decode( preg_replace('/(&#(?:x0*[0-9a-f]{2,5}(?![0-9a-f;]))|(?:0*\d{2,4}(?![0-9;])))/iS', '$1;', $str), $flag, $charset ); } while ($str_compare !== $str); return $str; }
У меня была та же проблема, потому что кто-то использовал HTML-шаблоны для создания XML вместо использования SimpleXML. вздох … Во всяком случае, я придумал следующее. Это не так быстро, как ваш, но это не на порядок медленнее, и он менее взломан. Ваш будет непреднамеренно конвертировать #_x_amp#;
до $amp;
, однако маловероятно его присутствие в исходном XML.
Примечание. Я предполагаю, что кодировка по умолчанию – UTF-8
// Search for named entities (strings like "&abc1;"). echo preg_replace_callback('#&[A-Z0-9]+;#i', function ($matches) { // Decode the entity and re-encode as XML entities. This means "&" // will remain "&" whereas "€" becomes "€". return htmlentities(html_entity_decode($matches[0]), ENT_XML1); }, "<Foo>€&foo Ç</Foo>") . "\n"; /* <Foo>€&foo Ç</Foo> */
Кроме того, если вы хотите заменить специальные символы нумерованными объектами (если вы не хотите XML UTF-8), вы можете легко добавить функцию к вышеуказанному коду:
// Search for named entities (strings like "&abc1;"). $xml_utf8 = preg_replace_callback('#&[A-Z0-9]+;#i', function ($matches) { // Decode the entity and re-encode as XML entities. This means "&" // will remain "&" whereas "€" becomes "€". return htmlentities(html_entity_decode($matches[0]), ENT_XML1); }, "<Foo>€&foo Ç</Foo>") . "\n"; echo mb_encode_numericentity($xml_utf8, [0x80, 0xffff, 0, 0xffff]); /* <Foo>€&foo Ç</Foo> */
В вашем случае вы хотите, чтобы все было наоборот. Кодировать нумерованные объекты как UTF-8:
// Search for named entities (strings like "&abc1;"). $xml_utf8 = preg_replace_callback('#&[A-Z0-9]+;#i', function ($matches) { // Decode the entity and re-encode as XML entities. This means "&" // will remain "&" whereas "€" becomes "€". return htmlentities(html_entity_decode($matches[0]), ENT_XML1); }, "<Foo>€&foo Ç</Foo>") . "\n"; // Encodes (uncaught) numbered entities to UTF-8. echo mb_decode_numericentity($xml_utf8, [0x80, 0xffff, 0, 0xffff]); /* <Foo>€&foo Ç</Foo> */
Я добавил контрольный показатель для хорошей оценки. Это также демонстрирует недостаток вашего решения для ясности. Ниже приведена введенная строка.
<Foo>€&foo Ç é #_x_amp#; ∬</Foo>
php -r '$q=["&",">","<"];$y=["#_x_amp#;","#_x_gt#;","#_x_lt#;"]; $s=microtime(1); for(;++$i<1000000;)$r=str_replace($y,$q,html_entity_decode(str_replace($q,$y,"<Foo>€&foo Ç é #_x_amp#; ∬</Foo>"),ENT_HTML5|ENT_NOQUOTES)); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";' <Foo>€&foo Ç é & ∬</Foo> ===== Time taken: 2.0397531986237
php -r '$s=microtime(1); for(;++$i<1000000;)$r=preg_replace_callback("#&[A-Z0-9]+;#i",function($m){return htmlentities(html_entity_decode($m[0]),ENT_XML1);},"<Foo>€&foo Ç é #_x_amp#; ∬</Foo>"); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";' <Foo>€&foo Ç é #_x_amp#; ∬</Foo> ===== Time taken: 4.045273065567
php -r '$s=microtime(1); for(;++$i<1000000;)$r=mb_encode_numericentity(preg_replace_callback("#&[A-Z0-9]+;#i",function($m){return htmlentities(html_entity_decode($m[0]),ENT_XML1);},"<Foo>€&foo Ç é #_x_amp#; ∬</Foo>"),[0x80,0xffff,0,0xffff]); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";' <Foo>€&foo Ç é #_x_amp#; ∬</Foo> ===== Time taken: 5.4407880306244
php -r '$s=microtime(1); for(;++$i<1000000;)$r=mb_decode_numericentity(preg_replace_callback("#&[A-Z0-9]+;#i",function($m){return htmlentities(html_entity_decode($m[0]),ENT_XML1);},"<Foo>€&foo Ç é #_x_amp#;</Foo>"),[0x80,0xffff,0,0xffff]); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";' <Foo>€&foo Ç é #_x_amp#; ∬</Foo> ===== Time taken: 5.5400078296661
Попробуйте эту функцию:
function xmlsafe($s,$intoQuotes=1) { if ($intoQuotes) return str_replace(array('&','>','<','"'), array('&','>','<','"'), $s); else return str_replace(array('&','>','<'), array('&','>','<'), html_entity_decode($s)); }
пример использования:
echo '<k nid="'.$node->nid.'" description="'.xmlsafe($description).'"/>';
также: https://stackoverflow.com/a/9446666/2312709
этот код, используемый в производстве, кажется, что никаких проблем с UTF-8