連想配列とXMLの相互変換

前述のように、OSのバージョンアップに伴い、PHPのバージョンも5から7に上げた。
そうしたら、エラーが大量に発生してその対応に追われた。

ついでに、Noticeエラーもつぶしていったところで、大きな問題が発生した。

事情があってRDBMSが使えない環境だったため、DBの制御をXMLCSVで行っていたのだが、
そのXMLの制御に、phpxml というライブラリを使っていたことで、エラーが発生した。

そこで、phpxmlのライブラリのバージョンが上がっているだろうということで、そのサイトに行ってみたら、まさかの404 not found.
http://keithdevens.com/software/phpxml
まじか。

というわけで、PHPXMLを制御する方法を探してみた。どうやら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' );
	}
	/** }}}*/
}

?>