PHPでXML文書をオブジェクトじゃなくて普通の配列としてパースしたり、普通の配列からXMLに変換する関数(失敗編)
本題の結論
・PHP環境が変わったときはその都度対応しよう.
動機
昔昔のPHP4時代に開発したWeblogシステムがある。
RDBMSも使用せず、何故かCSVやXML、iniファイルを、何故かDBと同じ階層で無理矢理
DBコントローラー用のファイル(not クラス, but ファイル[PHP4時代のクラスの機能はやる意味あんの?という水準だった。])で制御するという、ある意味とんんでもないことなのだが、今のところ正常に動作している(ようにみえる。)。
それが、何年か前に本番環境がPHP5に、いつの間にか変更になり、Notice:が出まくっていたが、対処療法的に警告さえでなけりゃいっか!というノリでいじって済ませていた。
ところがそれから何年かして、開発用の自宅サーバで利用していた無料のDDNSであるno-ipが有料化になり、別の無料DDNSを導入しようとしてhttpd.confやssl.confをいじっているとApacheが壊れた。
で、portからreinstallとかdeinstallとかしてみても、conflictが大量に発生して依存関係でデッドロックしてしまった。
で、開発用の自宅サーバのFreeBSDのバージョンを9から11に上げると、portsには、PHP7がある。
新しいものを入れたいと思うのが人の性なので当然最新バージョンを導入した。
そしたら、なんとPHP7では、PHP4時代に重宝していたxml.phpライブラリがFatal Errorを吐きまくった。まじで。
で、もしかしたらxml.phpのバージョンアップバージョンがあるかも!?と思ってサイトを除くとまさかの404。
http://keithdevens.com/software/phpxml
元ソースは私には難解すぎてどうしてもエラーを消せず断念。
色々最近PHPを調べてみるとsimple_xmlなんていう素晴らしいクラスがあることを知り試してみるが、配列がなんかよくわからないオブジェクト形式になっていた。
これまでのデータ制御関係のソースコードを全て書き換えるのは、心が折れるのでxml.phpと同じ動作をする関数を作ろうと思ったのがきっかけ。
本職がプログラマではないので、勤務時間中にコーディングする時間がないのもあった。
使わせてもらった先人の知恵
変更点
本来はid:ichiy さんのような、上記の方法で全く問題ないのでだが、如何せん私のミッションは、元のソースコードを一切いじらず完全互換のphp.xmlを作ることだったので、このままでは動かなかった。
まず、クラスを使わず、関数にしないといけない。これはすぐできた。$thisとかselfとか ::とかそこらへんをいじるだけでOK.
問題はsimple_xml。
simple_xmlは、xmlから配列にする時や、配列からxmlにする時に先頭のタグが消されてしまう。
具体的には、
<?xml version="1.0"?> <abc> <def> <ghi> a </ghi> <ghi> b </ghi> </def> <abc>
は、
array( [def] [0] [ghi] a [1] [ghi] b
となってしまう。
そこで、無理矢理 xmlのルートタグにダミータグを挿入する必要がある。
タグの検出には、昔買ったオライリーの詳説正規表現に載っていたが、コードに入れるのがめんどくさいのでやめた。でもいい本で、買って良かったと思う。
詳説正規表現第3版 [ ジェフリー・E.F.フリードル ] |
コード
使い方の説明は、コメント参照または、先に紹介した方々のサイトを参照。
<?php /* vim: set fdm=marker: */ /* もともとのxml.phpは、内部的にutf-8で処理しているので、 ファイルの方で、毎回mb_convert_encodingしている。 従ってこっちのほうで、文字エンコードを変換しなくていい。 XML_unserialize( $buffer ); <- xmlからarray */ //$var = Libs::parseAPI('', array('zn' => '1600000'), 'GET'); //$var = Libs::XMLtoArray("./5.xml"); //$fBuf = mb_convert_encoding($fBuf, "UTF-8", "sjis-win"); $xml = mb_convert_encoding(file_get_contents("./5.xml"), "UTF-8","sjis-win"); $var = XML_unserialize($xml); print_r($var); print $var['dary']['article'][2]['contents']; //simpleXML用に文字列を作成 //$newxml = XML_serialize($var, ''); $newxml = XML_serialize($var); print $newxml; /** * 配列を再帰的にXML用の文字列に変換 * * foreachの$valueの値が配列でなくなるまで再帰 * ※ ['key' => array()]という場合も考慮 * @see http://tk2-207-13211.vs.sakura.ne.jp/2015/08/278/ * @param array or string $data 配列もしくは要素の中身 * @param string $name 要素の名前 例えば、これに"ABC"とすると、<abc>$dataをxmlにしたやつ</abc>になる。 * @return string $str */ function XML_serialize($data, $name = '') { $ret = XML_serialize_2($data, $name); $ret = "<?xml version=\"1.0\"?>\n".$ret; return $ret; } /** XML_serializeから受けて呼ばれる. * 面倒だがXML_serialize_2は再帰関数なので、最後に<?xml version="1.0"?>を入れたいから仕方なく. * @param $data XML_serializeの$data * @param $name XML_serializeの$name * @ret <?xml version = "1.0" ?>なしのxmlデータ */ function XML_serialize_2($data, $name = '') { /** {{{*/ $str = ''; if(!empty($name)) { $str .= "<".$name.">"; } if(!is_array($data)) { $str .= $data; } else { foreach ($data as $key => $val) { if(is_numeric($key)) { $str .= XML_serialize_2($val, ''); } else { if(is_array($val) && !empty($val)) { $str .= XML_serialize_2($val, $key); } else { $str .= "<".$key.">"; $str .= (empty($val)) ? "" : $val; $str .= "</".$key.">"; } } } } if(!empty($name)) { $str .= "</".$name.">"; } return $str; } /**}}}*/ /** XML文書を解析して、配列として返す. * XML文書を、simplexml_load_string($xml)を利用して、配列に変換する. * その関数の構造上一番先頭のXMLタグは消去されてしまうため、 * 無理矢理dummyタグを入れ子にしている.これでダミータグが消えるだけですむので、 * ルートタグを含めて配列にできる. * @param $xml xml[必ずUTF-8] */ function XML_unserialize( $xml ) { /** {{{*/ //無理やり. //$xml = file_get_contents($fXmlPath); //一番先頭のタグの名前を取得する. //$rootTag = preg_match("正規表現のパタン", "入力文字列","検索結果"); preg_match("/<([a-z0-9]+)>/", $xml, $data_match); $rootTag = $data_match[1]; $xml = preg_replace("/<$rootTag>/", "<dummy><$rootTag>", $xml, 1);//1回だけ置換する. $xml = preg_replace("/<.$rootTag>/", "</$rootTag></dummy>", $xml, 1);//1回だけ置換する. // 帰ってきたXMLをパース $parse_xml = simplexml_load_string($xml); if (false === $parse_xml) return false; return xml2arr($parse_xml); } /**}}}*/ /** */ function xml2arr($xmlobj) { /** {{{*/ $arr = array(); if (is_object($xmlobj)) { $xmlobj = get_object_vars($xmlobj); } else { $xmlobj = $xmlobj; } foreach ($xmlobj as $key => $val) { if (is_object($xmlobj[$key])) { $arr[$key] = xml2arr($val); } else if (is_array($val)) { foreach($val as $k => $v) { if (is_object($v) || is_array($v)) { $arr[$key][$k] = xml2arr($v); } else { $arr[$key][$k] = $v; } } } else { $arr[$key] = $val; } } return $arr; } /**}}}*/ ?>