6

問題: UTF8 で「完全にエンコードされた」XML ファイルが必要です。つまり、シンボルを表すエンティティがなく、すべてのシンボルが UTF8 でエンコードされています。ただし、XML で予約されている「&」(amp)、「<」(lt)、および「">」(gt) の 3 つのみを除きます。そして、エンティティを実際の UTF8 文字に (XML を破壊することなく) 変換するため に、それを高速に行う組み込み関数が必要です。
  PS: これは「現実世界の問題」です (!)。たとえば、PMC/journals には、特別な XML DTD (JATS 形式とも呼ばれます) でエンコードされた 280 万の科学記事があります... 通常のXML-UTF8-テキスト」として処理するには、数値エンティティから UTF8 に変更する必要があります。文字。

試行された解決策: このタスクの自然な関数はhtml_entity_decodeですが、XML コード (!) を破棄し、予約済みの 3 つの XML 予約済みシンボルを変換します。

問題の説明

仮定する

  $xmlFrag ='<p>Hello world! &#160;&#160; Let A&lt;B and A=&#x222C;dxdy</p>';

エンティティ 160 (nbsp) および x222C (二重積分) は UTF8 に変換する必要があり、XML 予約は変換しltないでください。XML テキストは (変換後)、

$xmlFrag = '<p>こんにちは! A &lt;B とし、A=∬dxdy </p>';

テキスト「A<B」には XML 予約文字が必要なため、そのままにしておく必要がありますA&lt;B


欲求不満の解決策

私は問題を(直接!)解決するために使用しようとしています...そのため、オプションhtml_entity_decodeを使用するためにPHPをv5.5に更新しました。ENT_XML1

  $s = html_entity_decode($xmlFrag, ENT_XML1, 'UTF-8'); // not working
                                                        // as I expected

おそらく別の質問は、「私が期待したことを行うための他のオプションがないのはなぜですか?」ということです。-- これは、私だけでなく、他の多くの XML アプリケーション (!) にとって重要です。


答えとして回避策は必要ありません... OK、醜い関数を示します。問題を理解するのに役立つかもしれません。

  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('&amp;','&gt;','&lt;');
    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を変換するには、フラグである必要があります。

4

6 に答える 6

6

この質問は、時間ごとに「誤った回答」を作成しています(回答を参照)。これはおそらく、人々が注意を払っていないことと、答えがないためです。PHP 組み込みソリューションが不足しています

...だから、私の回避策を繰り返しましょう(それは答えではありません!)、これ以上混乱しないようにします:

最善の回避策

注意を払う:

  1. xml_entity_decode()以下の関数は、(他のどの方法よりも) 最善の回避策です。
  2. 以下の関数は、現在の質問に対する回答ではなく、回避策にすぎません。
  function xml_entity_decode($s) {
  // illustrating how a (hypothetical) PHP-build-in-function MUST work
    static $XENTITIES = array('&amp;','&gt;','&lt;');
    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;
 }  

より良い解決策があることをテストして実証するには、まず次の簡単なベンチマークでテストしてください。

  $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>";
  
于 2014-11-10T16:28:40.547 に答える
2

SimpleXML を使用する代わりに HTML テンプレートを使用して XML を作成したため、同じ問題が発生しました。はあ… とにかく、私は次のことを思いつきました。それはあなたのものほど速くはありませんが、桁違いに遅くはなく、ハックも少なくなります。あなたのものはうっかり に変換#_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 "&amp;"
    // will remain "&amp;" whereas "&euro;" becomes "€".
    return htmlentities(html_entity_decode($matches[0]), ENT_XML1);
}, "<Foo>&euro;&amp;foo &Ccedil;</Foo>") . "\n";

/* <Foo>€&amp;amp;foo Ç</Foo> */

また、特殊文字を番号付きエンティティに置き換えたい場合 (UTF-8 XML が必要ない場合)、上記のコードに関数を簡単に追加できます。

// 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 "&amp;"
    // will remain "&amp;" whereas "&euro;" becomes "€".
    return htmlentities(html_entity_decode($matches[0]), ENT_XML1);
}, "<Foo>&euro;&amp;foo &Ccedil;</Foo>") . "\n";

echo mb_encode_numericentity($xml_utf8, [0x80, 0xffff, 0, 0xffff]);

/* <Foo>&#8364;&amp;foo &#199;</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 "&amp;"
    // will remain "&amp;" whereas "&euro;" becomes "€".
    return htmlentities(html_entity_decode($matches[0]), ENT_XML1);
}, "<Foo>&euro;&amp;foo &Ccedil;</Foo>") . "\n";

// Encodes (uncaught) numbered entities to UTF-8.
echo mb_decode_numericentity($xml_utf8, [0x80, 0xffff, 0, 0xffff]);

/* <Foo>€&amp;amp;foo Ç</Foo> */

基準

適切な測定のためにベンチマークを追加しました。これは、明確にするためにソリューションの欠陥も示しています。以下は、私が使用した入力文字列です。

<Foo>&euro;&amp;foo &Ccedil; &eacute; #_x_amp#; &#8748;</Foo>

あなたの方法

php -r '$q=["&amp;","&gt;","&lt;"];$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>&euro;&amp;foo &Ccedil; &eacute; #_x_amp#; &#8748;</Foo>"),ENT_HTML5|ENT_NOQUOTES)); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";'

<Foo>€&amp;amp;foo Ç é &amp; ∬&lt;/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>&euro;&amp;foo &Ccedil; &eacute; #_x_amp#; &#8748;</Foo>"); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";'

<Foo>€&amp;amp;foo Ç é #_x_amp#; &#8748;</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>&euro;&amp;foo &Ccedil; &eacute; #_x_amp#; &#8748;</Foo>"),[0x80,0xffff,0,0xffff]); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";'

<Foo>&#8364;&amp;foo &#199; &#233; #_x_amp#; &#8748;</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>&euro;&amp;foo &Ccedil; &eacute; #_x_amp#;</Foo>"),[0x80,0xffff,0,0xffff]); $t=microtime(1)-$s; echo"$r\n=====\nTime taken: $t\n";'

<Foo>€&amp;amp;foo Ç é #_x_amp#; ∬&lt;/Foo>
=====
Time taken: 5.5400078296661
于 2016-02-15T14:01:41.743 に答える
2

名前付きエンティティから Unicode 文字へのマッピングを定義するため、JATS XML ドキュメントをロードするときに DTD を使用し、保存時にエンコーディングを UTF-8 に設定します。

$doc = new DOMDocument;
$doc->load($inputFile, LIBXML_DTDLOAD | LIBXML_NOENT);
$doc->encoding = 'UTF-8';
$doc->save($outputFile);
于 2013-11-21T14:46:47.463 に答える
1
    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('/&[a-z]{2,}(?![a-z;])/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[':'] = '&colon;';
                    $_entities['('] = '&lpar;';
                    $_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;
}
于 2014-11-09T11:51:07.440 に答える