連想配列とXMLの相互変換
前述のように、OSのバージョンアップに伴い、PHPのバージョンも5から7に上げた。
そうしたら、エラーが大量に発生してその対応に追われた。
ついでに、Noticeエラーもつぶしていったところで、大きな問題が発生した。
事情があってRDBMSが使えない環境だったため、DBの制御をXMLとCSVで行っていたのだが、
そのXMLの制御に、phpxml というライブラリを使っていたことで、エラーが発生した。
そこで、phpxmlのライブラリのバージョンが上がっているだろうということで、そのサイトに行ってみたら、まさかの404 not found.
http://keithdevens.com/software/phpxml
まじか。
というわけで、PHPでXMLを制御する方法を探してみた。どうやらsimplexmlを使うとよいらしい。
ところが、simplexmlでxmlを連想配列に変換ができても、連想配列からxmlに変換できない。
そもそも、xmlを連想配列に変換をしても、object形式になっているため、完全な連想配列ではない。
それを無理やり連想配列にするためにjson_decodeを使っていることが問題。
object形式なら多分またxmlに戻すことができそうだ。
と・こ・ろ・が、このシステムはPHP4時代に作られたもので、今更object形式に全部書き直すと、心が折れそうだ。しかも、連想配列の形式がphpxmlの仕様と違うし。
さらにWebで調べるとJAX.phpというライブラリを見つけた。
PHPでJSONとArrayとXMLを一発で相互変換するツール - JAX.php [ゼロと無限の間に]
ところが、これも配列からXMLに戻せない。
以上の理由により、phpxmlのような動作をするxmlと連想配列の相互変換クラスを作った。
残念ながら今回の場合属性は一切使わなかったので、それは想定していない。
xmlから連想配列に変換するところは、jax.phpのサイトからまるまる頂いた。
まだUTF-8が流行っていなかった上、運用環境が職場でWindowsしかなかったので、Shift_JISにしていた。
使用方法
今回作ったクラスは、もともとのプログラムがただの関数であり、インスタンス生成を記述するのが、面倒なのでstaticクラスにした。インスタンスを生成する非staticクラスにしようと思ったら、全ての関数のstaticを消して、 :: で記述している行をコメントアウトして、それの上の行にあるコメントを解除すればOK.
の部分は、適当に処理を。
$abc = XARY::_toArray($arg1,$arg2,$arg3);
$arg1:xmlの文字列
$arg2:文字エンコード変換後 省略可
$arg3:文字エンコード返還前 省略可
require_once "xml.php" //xmlを配列に $abc = XARY::_toArray(file_get_contents("./4.xml"), "UTF-8", "Shift_JIS"); print_r($abc); //連想配列をxmlに $xml = XARY::_toXml("diary", $abc); print $xml;
こうすると、
Array ( [diary] => Array ( [year] => 2016 [month] => 11 [day] => 4 [diarynum] => 3 [article] => Array ( [0] => Array ( [no] => Array ( ) [diarycount] => Array ( ) [title] => Array ( ) [category] => Array ( ) [contents] => Array ( ) [images] => Array ( [isoneline] => Array ( ) [image] => Array ( [0] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [1] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [2] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) ) ) [comments] => Array ( [commentnum] => Array ( ) [comment] => Array ( [0] => Array ( ) [1] => Array ( ) ) ) ) [1] => Array ( [no] => Array ( ) [diarycount] => Array ( ) [title] => Array ( ) [category] => Array ( ) [contents] => Array ( ) [images] => Array ( [isoneline] => Array ( ) [image] => Array ( [0] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [1] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [2] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) ) ) ) [2] => Array ( [no] => 108 [diarycount] => 1 [title] => Array ( ) [category] => IKFSOQET [contents] => ああアイフォン [images] => Array ( [isoneline] => Array ( ) [image] => Array ( [0] => Array ( [imagealt] => Array ( ) [imageurl] => 2016-11/img/0411.jpg ) [1] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [2] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) ) ) [issuperuser] => Array ( ) ) [3] => Array ( [no] => 109 [diarycount] => 2 [title] => Array ( ) [category] => IKFSOQET [contents] => Array ( ) [images] => Array ( [isoneline] => on [image] => Array ( [0] => Array ( [imagealt] => Array ( ) [imageurl] => 2016-11/img/0421.jpg ) [1] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [2] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) ) ) [issuperuser] => Array ( ) ) [4] => Array ( [no] => 110 [diarycount] => 3 [title] => Array ( ) [category] => IKFSOQET [contents] => Array ( ) [images] => Array ( [isoneline] => on [image] => Array ( [0] => Array ( [imagealt] => Array ( ) [imageurl] => 2016-11/img/0431.jpg ) [1] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) [2] => Array ( [imagealt] => Array ( ) [imageurl] => Array ( ) ) ) ) [issuperuser] => Array ( ) ) ) ) ) <?xml version="1.0" ?> <diary> <year>2016</year> <month>11</month> <day>4</day> <diarynum>3</diarynum> <article> <no> </no> <diarycount> </diarycount> <title> </title> <category> </category> <contents> </contents> <images> <isoneline> </isoneline> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> </images> <comments> <commentnum> </commentnum> <comment> </comment> <comment> </comment> </comments> </article> <article> <no> </no> <diarycount> </diarycount> <title> </title> <category> </category> <contents> </contents> <images> <isoneline> </isoneline> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> </images> </article> <article> <no>108</no> <diarycount>1</diarycount> <title> </title> <category>IKFSOQET</category> <contents>ああアイフォン</contents> <images> <isoneline> </isoneline> <image> <imagealt> </imagealt> <imageurl>2016-11/img/0411.jpg</imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> </images> <issuperuser> </issuperuser> </article> <article> <no>109</no> <diarycount>2</diarycount> <title> </title> <category>IKFSOQET</category> <contents> </contents> <images> <isoneline>on</isoneline> <image> <imagealt> </imagealt> <imageurl>2016-11/img/0421.jpg</imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> </images> <issuperuser> </issuperuser> </article> <article> <no>110</no> <diarycount>3</diarycount> <title> </title> <category>IKFSOQET</category> <contents> </contents> <images> <isoneline>on</isoneline> <image> <imagealt> </imagealt> <imageurl>2016-11/img/0431.jpg</imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> <image> <imagealt> </imagealt> <imageurl> </imageurl> </image> </images> <issuperuser> </issuperuser> </article> </diary>
こうなる。
以下ソースコード
<?php /* vim: set fdm=marker: */ print "<hr>XMLから配列へ."; //xmlを配列に $abc = XARY::_toArray(file_get_contents("./4.xml"), "UTF-8", "Shift_JIS"); print_r($abc); //連想配列をxmlに $xml = XARY::_toXml("diary", $abc); print $xml; /** XMLを連想配列に. 連想配列をxmlに相互変換する. * @version 0.1 * @see * @copyright ymlab * @license http://0-oo.net/pryn/MIT_license.txt (The MIT license) * see also * @see http://0-oo.net/sbox/php-tool-box/jax */ class XARY { /** xml文書を連想配列に変換する. UTF-8にしたほうが良い. * @param $data 連想配列に変換したいxml文書 * @return ret xml文書から連想配列に変換されたもの.xmlのrootタグは破棄される. */ public static function _toArray( $xmlStr, $target = "UTF-8", $source = "auto" ) { /** {{{*/ $xmlStr = mb_convert_encoding($xmlStr, $target, $source); $xmlStr = str_replace("?>", "?><abc>", $xmlStr); $xmlStr .= "</abc>"; $xmlData = simplexml_load_String($xmlStr); $ret = json_decode( json_encode($xmlData), true ); return $ret; } /**}}}*/ /** 配列からxmlへ変換し、さらにインデントを整えてxml文字列として返す. * <img />とかは知らん。 * @param $name, 一番最初のrootタグは配列置換時に消去されるので、 * プログラムから何をつけたらよいのかわからない。 * なので、それを指定する. * @param $data xmlにしたい連想配列 * @return ret 連想配列からxml文書に変換された文字列 */ public static function _toXml( $name, $data ) { /** {{{*/ $duplicate = array(); $ret = "<?xml version=\"1.0\" ?>\n"; //$ret .= $this->_toXmlString($name, $data); $ret .= self::_toXmlString($name, $data); preg_match_all('/<("[^"]*"|\'[^\']*\'|[^\'">])*>/',$ret,$matches); for ( $iCounter = 0; $iCounter < count($matches[0])-1; $iCounter++ ) { if ( strcmp( $matches[0][ $iCounter ], $matches[0][ $iCounter + 1 ] ) ) { //連続した重複タグなら.dupulicateの配列に追加. array_pushよりもこの方が早いらしい. $duplicate[] = $matches[0][ $iCounter ]; } } //タグが重なっていたら除去する. for ( $iCounter = 0; $iCounter < count($duplicate); $iCounter++ ) { $ret = str_replace( $duplicate[$iCounter].$duplicate[$iCounter], $duplicate[$iCounter], $ret); } $ret = str_replace("</${name}></${name}>","</${name}>", $ret); //not static//$ret = $this->parseXml($ret); $ret = self::parseXml($ret); return $ret; } /**}}}*/ /** Xml文書をインデントを整えて整形する. * @param $data XML文書 * @return $ret パースされたXml文書 */ private static function parseXml( $data ) { /** {{{*/ //preg_match_all('/(<("[^"]*"|\'[^\']*\'|[^\'">])*>)/', $data, $matches); $ret = ""; $tabs = 0; //インデントの数 $EndFlag = false; //今 終了タグ </ の中にいる。 for ( $i = 0; $i < strlen($data) - 2; $i++ ) { $ret .= $data[$i]; if ( $data[$i] == '<' && $data[$i+1] == '/' ) { $EndFlag = true; } else if ( $data[$i] == '>' ) { if ( $EndFlag == true ) { //終了タグの中の > を検出 $ret .= ""; $tabs--; $EndFlag = false; } } if ( $data[$i] == '>' && $data[$i+1] == '<' && $data[$i+2] != '/' ) { $ret .= "\n"; $tabs++; //not static//$ret .= $this->pushTabs( $tabs ); $ret .= self::pushTabs( $tabs ); } else if ( $data[$i] == '>' && $data[$i+1] == '<' && $data[$i+2] == '/' ) { $ret .= "\n"; //not static//$ret .= $this->pushTabs( $tabs ); $ret .= self::pushTabs( $tabs ); } } $ret .= $data[$i].$data[$i+1]; return $ret; } /**}}}*/ /** タブを回数分加えて返すだけ. * @param $tabs 欲しいタブの数 * @return $ret \t\t\t\t ←4の時 */ private static function pushTabs( $tabs ) { /** {{{*/ $ret = ""; for ( $i = 0; $i < $tabs; $i++ ) { $ret .= "\t"; } return $ret; } /**}}}*/ private static function _toXmlString($name, $data ) { /** {{{*/ $s = ''; if ( is_array( $data ) ) { foreach( $data as $k => $v ) { if ( is_numeric( $k ) ) { //not static//$s .= $this->_toXmlString( $name, $v ); $s .= self::_toXmlString( $name, $v ); } else { //not static//$s .= $this->_toXmlString( $k, $v ); $s .= self::_toXmlString( $k, $v ); } } } else { //not static//$s = $this->_xmlEscape( $data ); $s = self::_xmlEscape( $data ); } return "<$name>$s</$name>"; } /**}}}*/ private static function _xmlEscape( $value ) { /** {{{*/ return htmlSpecialChars( $value, ENT_QUOTES, 'UTF-8' ); } /** }}}*/ } ?>