4

SimpleXML を使用して、宣言を含む大きな XML ファイルを解析しようとしています。<!ENTITY残念ながら、SimpleXML は先に進んでこれらのエンティティを展開することに熱心すぎるようです。エンティティ シンボルは短く、簡単に解析可能であり、理論的には新しいバージョンのファイルでは変更されないため、むしろそうしませんでした。エンティティは変更される可能性のある英文です。SimpleXML を無効にするように指示する方法はありますか?

ファイルの内容を XML パーサーに渡す前に、XML ファイルを "事前解析" して<!ENTITYビットを取り除くことを考えましたが、それはハッキリしているように感じます。できるだけ。

(上記の間違った用語をご容赦ください。このレベルの XML 作業は、かなり長い間行っていません。)

4

1 に答える 1

6

そう思われるかもしれませんが、そうではありません (コードで何をしているのかを示していないにもかかわらず、フラグを指定しない限り)。->asXML()to-string-implementation 経由ではないメソッドを使用している場合にのみ、SimpleXML がそれを返すことができるというだけです。

それがどのように機能するかを示すために、いくつかの例を見てみましょう。DTD から次の単純なエンティティを選択しました。

<!ENTITY n "noun (common) (futsuumeishi)">

エンティティ<pos>が含まれているため、最初の要素を選択しましょう。&n;

$xml = simplexml_load_file($file);
$pos = $xml->entry->sense->pos;

変数$posは、<pos>要素ノードの SimpleXMLElement になりました。&n;それを出力して、パーサーがエンティティに対して何をするかを見てみましょう。

echo  "SimpleXML value (string): ", $pos         , "\n"
    , "SimpleXML value (XML)   : ", $pos->asXML(), "\n";

出力は次のとおりです。

SimpleXML value (string): noun (common) (futsuumeishi)
SimpleXML value (XML)   : <pos>&n;</pos>

この例が示すように、&n;はまだ存在します ( )。<pos>&n;</pos>文字列値としてアクセスした瞬間に展開されるだけです ( noun (common) (futsuumeishi))。

ちなみに、これはまったく問題ありません。XML 仕様では、これらのエンティティを展開するかどうかはパーサー次第であるとされています。SimpleXML が設計されている目的のために、これは文字列値を読み取るときに拡張されることが完全に予想されます。

LIBXML_NOENTオプションを指定することで、この動作を制御することもできます。

$xml = simplexml_load_file($file, NULL, LIBXML_NOENT);

これは実際にあなたが想定することを行います.エンティティは展開され、XML出力にはエンティティが含まれなくなります:

SimpleXML value (string): noun (common) (futsuumeishi)
SimpleXML value (XML)   : <pos>noun (common) (futsuumeishi)</pos>

では、二重の疑問符は、あなたが探していることをどのように行うのですか? 実際にエンティティーのモデルを持つ PHP の XML パーサーは DOMDocument です。これは SimpleXML の姉妹ライブラリであり、内部的には両方とも同じメモリ オブジェクトを共有しています。を使用しない場合と使用する場合の 2 つのモードの同じオブジェクト (より正確には、その唯一の子ノード) の出力を次に示しますLIBXML_NOENT

Mode 1:
DOMDocument Class       : DOMEntityReference
DOMDocument value(XML)  : &n;
DOMDocument ->nodeName  : n

Mode 2 (LIBXML_NOENT):
DOMDocument Class       : DOMText
DOMDocument value(XML)  : noun (common) (futsuumeishi)
DOMDocument ->nodeName  : #text

これは、指定された出力の背後にあるものをより見やすくする次のコードによって作成されます。

$node   = dom_import_simplexml($pos);
$doc    = $node->ownerDocument;
$entity = $node->firstChild;

echo  "DOMDocument Class       : ", get_class($entity)    , "\n"
    , "DOMDocument value(XML)  : ", $doc->saveXML($entity), "\n"
    , "DOMDocument ->nodeName  : ", $entity->nodeName     , "\n";

書かれているように、それは姉妹ライブラリであり、問​​題のエンティティ参照であることがわかっているその子をトラバースする必要があるライブラリにdom_import_simplexml変わります。$posDOMElement

したがって、これは完全に理にかなっています。SimpleXML はEntity Referenceを表すことができないため、展開された文字列値またはエンティティを含む XML のみを提供できます。

そうでなければ、文字列値を区別する方法は何でしょう

<pos>&n;</pos>
<pos><![CDATA[&n;]]></pos>

? したがって、あなたが求めることは限られた意味しかありません。しかし、それは私たちがそれを処理できなかったという意味ではありません。そのため、SimpleXML をだまして拡張することでそれを実行させることができます。単一のエンティティのみを含む各子要素がそう返す必要があるとしましょう。それ以外の場合は、標準の SimpleXML 文字列化を使用する必要があります。

/**
 * Class EntityPreserveXML
 */
class EntityPreserveXML extends SimpleXMLElement
{
    /**
     * @return string
     */
    public function __toString()
    {
        $dom = dom_import_simplexml($this);
        if (
            !$dom instanceof DOMElement
            || $dom->childNodes->length !== 1
            || ! $dom->firstChild instanceof DOMEntityReference
        ) {
            return parent::__toString();
        }

        return $dom->ownerDocument->saveXML($dom->firstChild);
    }
}

上記の例で実行してみましょう。

require('EntityPreserveXML.php');
$xml = simplexml_load_file($file, 'EntityPreserveXML');
$pos = $xml->entry->sense->pos;

echo  "SimpleXML value (string): ", $pos         , "\n"
    , "SimpleXML value (XML)   : ", $pos->asXML(), "\n";

SimpleXML は拡張クラスを使用するようになり、期待どおりに次のようになります。

SimpleXML value (string): &n;
SimpleXML value (XML)   : <pos>&n;</pos>

唯一の&n;子であるため、SimpleXMLElement の文字列への変換で保持されるようになりました。ただし、これが機能するからといって、これを使用する必要があるというわけではありません。テキスト形式の解析済み XML と、ドキュメント モデルの意味での単なる XML との間のエンコーディング境界が壊れます。

おそらく DOMDocument を探しているだけですか? DOMEntityReferenceあればすぐに使える、よりディテールにこだわったモデルです。

于 2013-11-03T17:01:04.830 に答える