未定義の特殊文字の実体参照が含まれるXMLファイルを無理矢理パースする

RSSを始めとするXMLで使用できるる特殊文字の実体参照は、以下の5つだけです。
(何の定義も与えられていない場合)
  • &lt; → <
  • &gt; → >
  • &amp; → &
  • &apos; → '
  • &quot; → "
これ以外の特殊文字の実体参照(例えば&times → &timesや、∞ → &infin;)が含まれている場合、パース時にパースエラーとなって読み込めないことがあります。ブラウザで表示しようとしてもそこで途切れてしまったり、返り値がfalseになったり(例えばSimpleXML)。動作としてはそれで正しいのですが、
  1. RSSを配信しているのが自分ではない
  2. 何らかの処理のために無理矢理読み込んでパースしたい
と言うときがあります。

本来であれば、パース処理の方に手を加えるべきなのでしょうが、面倒なので次のような過程を踏んでみました。

手順

  1. HTTP_Clientなどでファイルの内容を読み込む
  2. 不正な部分を変換する
  3. XML文字列として読み込む(simple_load_string)



1. HTTP_Clientなどでファイルの内容を読み込む

たまたまHTTP_Clientを使いましたが、HTTP_Requestでもfile_get_contentsでも何でも良いです。

コード例:

require_once 'HTTP/Client.php';
$client = new HTTP_Client();
$client->get('http://movapic.com/feed/user/nobodyplace');
$result = $client->currentResponse();

ちなみに上で読み込んでいるのは「携帯百景」のRSSですね。よくこの問題が起きるので。



2. 不正な部分を変換する

ここが面倒くさい。
まぁしかし何とかして読み込めればいいので…次のような作戦を立てました。
  1. 実体参照に含まれる&と;を数値参照に変換する
  2. ただし、&lt;、&gt;、&amp;、&apos;、&quot;は除く

コード例:

$str = preg_replace_callback('/(&[^;]+;)/'
, create_function('$matches', 'return mb_encode_numericentity($matches[1], array(0x0000, 0xffff, 0, 0xffff));')
, $result['body'])
;

本当のことを言うとこれは完全な解決ではありません。
上の処理はこういうことをしているんですが、
  • &times; → &times;
やりたいことは本来そうではなくて、こう。
  • &times; (→ ×)→ ×
コード表をきちんと把握していればそれも出来るような気がしますが、いまいち理解が追いついて無くて出来なかったのでとりあえずこれで。でもまぁこれでは変換とは言えないし、後で何か処理が必要なんじゃないのかと思ってた…のに、SimpleXmlオブジェクトとして読み込んでから出力してみるとちゃんと表示されてる!不思議!(なんで?)



3. XML文字列として読み込む(simple_load_string)

というわけで話が前後しますが、読み込み。

コード例:

$xml = simplexml_load_string($str);

後は煮るなり焼くなり。



結論

何で上手く言ってるのかよくわかんないけど、結果オーライ!

なんでだろ?




オマケ

考えてみれば自分で何回もRSSを出力してきましたけど…この辺のことをルーズにしてることが自分でもあるかも。あんまり自信ない。手抜きでよければ、<![CDATA[ ]]>を使えば良いんですけどね(何でもかんでもそれで良いのかはわかんない)。

XMLに実体参照が含まれてパースエラーになってしまう件。