XML読み込みエラー時の詳細情報を取得する

XMLファイルを読み込む時、タグが欠けてるとか正しくないXMLだった場合に、どこそこでエラーだよって出したいわけだが、DOMDocument::load()はTRUE/FALSEしか返してくれないのでエラーが起きたことしかわからない。
なんだよこれどうすんだよ……と思ったらどうやらlibxmlの関数を呼んで取得するらしい。


やり方はこんな感じ。

<?php
require_once 'XMLException.php';

// ユーザ(アプリ)によるエラー処理を有効にする
libxml_use_internal_errors(true);

$doc = new DOMDocument();
try {
    $res = $doc->load('test.xml');
    if ($res === false) {
        // エラー情報を取得(LibXMLErrorオブジェクトが返ってくる)
        $error = libxml_get_last_error();
        libxml_clear_errors();
        throw new XMLException($error->message, $error);
    }

    // 色々な処理
    // ...
} catch (XMLException $e) {
    $error = $e->getCause();
    echo 'エラー発生:';
    print_r($error);
    exit();
}
?>

ポイントはlibxml_use_internal_errors()というやつで、これを有効にしてやるとプログラム側でエラー処理を行えるようになる。
デフォルトはFALSEになっていて、この場合は勝手にWarningを吐いてくれる。が、この場合でもいちおうエラー情報は返してくれるようだ。


何かエラーが起きたらlibxml_get_last_error()libxml_get_errors()を呼ぶと、エラー情報の入ったLibXMLErrorオブジェクトが返ってくるので好きなように料理すればよい。
オブジェクトといってもcode, column, file, level, line, messageという6つのプロパティだけしかない単純なもの。
エラー処理後はlibxml_clear_errors()でエラーバッファをクリアしておくとよい。


ちなみになんとなく例外にしたかったのでPEAR_Exceptionみたいなノリで$causeを取れるXMLExceptionをこしらえてみた(コードは想像に任せる)。
DOM操作に関するエラーはDOMException投げてくれるんだから、どうせならこの辺も例外にしてくれればいいのに。

論理的エラーをどう表現するか

実はこっちが課題だったりして。


XMLの構造に関するエラーは上記の方法で行番号なんかも取れたりして問題ないんだが、アプリケーションの論理的なエラーに対して行番号を表示するにはどうすればいいんだろう。
たとえばXMLでデータをインポートする機能があったとして、「複数のデータ間でメールアドレスが重複している」みたいなやつ。
DOMでは基本的に行番号が取れないのでSAXで処理するしかないように思える。眩暈がする。でもどこかわからないと修正しようがないし。

/itemlist/item[3]/emailの値が重複しています。

みたいにXPathで表現するというのが実装コストを加味すると今のところ唯一の案なんだけど、お客さんはXPathなんてわからないし。
単純な構造ならディレクトリとかURLっぽくてなんとなくわかってくれるだろうけど、複雑になってくるとたぶんわからないよな。単純だったとしてもデータ量が多いと位置を特定するのが大変そう。
うーん、どうしたらいいんだ。XMLエディタ使ってくださいってか。