PHPでXML文書をオブジェクトじゃなくて普通の配列としてパースしたり、普通の配列からXMLに変換する関数(成功編多分)

前回の記事でうまいこといったと思ったけど、
重複するタグが出てきた。

そういう訳で作り直した結果がこちら。
いろんな先人のコードを寄せ集めているだけなので、我ながらかなりずるい。

<?php  /* vim: set fdm=marker: */ 
/*
もともとのxml.phpは、内部的にutf-8で処理しているので、
ファイルの方で、毎回mb_convert_encodingしている。
従ってこっちのほうで、文字エンコードを変換しなくていい。
XML_unserialize( $buffer ); <- xmlからarray
@see  http://tk2-207-13211.vs.sakura.ne.jp/2015/08/278/
@see JAX http://0-oo.net/sbox/php-tool-box/jax
@see https://qiita.com/suin/items/6d0032fe7ecc140395d8
*/
//$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['diary']['article'][2]['contents'];

//simpleXML用に文字列を作成
//$newxml = XML_serialize($var, '');
$newxml = XML_serialize($var, "");
print $newxml;
$var = XML_unserialize($newxml);
print_r($var);


/**
* 配列を再帰的に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 = '') { /** {{{*/
	return array2xml($data, $name);
}
/**}}}*/

/**
 *	配列(連想配列)をXMLにする
 *	@param	string	$rootName	root要素名
 *	@param	array	$arr
 *	@return	string
 */
function array2xml(array $arr,$rootName ) { /** {{{*/
	$ret = "";
	$ret = _toXmlString($arr, $rootName);
	$ret = str_replace( "<>", "", $ret );
	$ret = str_replace( "</>", "", $ret );
	//重複したxmlタグを一つにする.
	$pattern = '/(<.+>)(\1)/';
	$replacement = '\1';
	$ret = preg_replace ( $pattern, $replacement, $ret  );
	return cleanUpXML(_str2xml($ret)->asXML());
}
/**}}}*/

/**
 *  再帰的に連想配列をXMLにする
 *  @param	string	$name
 *  @param	mixed	$data
 *  @return	string
 */
function _toXmlString($data, $name) { /** {{{*/
	$s = '';
	$attr = '';

	if (is_array($data)) {
		foreach ($data as $k => $v) {
			if ($k === '@') {
				foreach ($v as $attrName => $attrValue) {
					$attr .= " $attrName=" . '"' . _xmlEscape($attrValue) . '"';
				}
			} else if ($k === '#') {
				$s = _xmlEscape($v);
			} else if (is_numeric($k)) {
				$s .= _toXmlString($v, $name);
			} else {
				$s .= _toXmlString($v, $k);
			}
		}
	} else {
		$s = _xmlEscape($data);
	}

	return "<$name$attr>$s</$name>";
}
/** }}}*/

/**
 *	文字列からSimpleXMLElementオブジェクトを生成する
 *	@param	string	$xmlStr
 *	@param	string	$nameSpace	(Optional) XMLのNameSpace
 *	@return	SimpleXMLElement
 */
function _str2xml($xmlStr, $nameSpace = '') { /** {{{*/
	return new SimpleXMLElement(
		$xmlStr,
		LIBXML_COMPACT | LIBXML_NOERROR,
		false,
		$nameSpace
	);
}
/** }}}*/

/**
 *  XMLの文字列エスケープ
 *  @param	string	$value
 *  @return	string
 */
function _xmlEscape($value) { /** {{{*/
	return htmlSpecialChars($value, ENT_QUOTES, 'UTF-8');
}
/**}}}*/

/** 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;
}
/**}}}*/


/**
 * SimpleXMLのasXML()をきれいにインデントする関数
 * @package    Sites
 * @author     Suin <suinyeze@gmail.com>
 * @copyright  2011 Suin
 * @see 	   https://qiita.com/suin/items/6d0032fe7ecc140395d8
 * @license    MIT License
 */
function cleanUpXML($string) { /** {{{*/
    $string = preg_replace("/>\s*</", ">\n<", $string);
    $lines  = explode("\n", $string);
    $string = '';
    $indent = 0;

    foreach ( $lines as $line )
    {
        $increment = false;
        $decrement = false;

        if ( preg_match('#<\?xml.+\?>#', $line) == true )
        {
            // <?xml … 
        }
        elseif ( preg_match('#<[^/].+>.*</.+>#', $line) == true )
        {
            // Open Tag & Close Tag
        }
        elseif ( preg_match('#<.+/>#', $line) == true )
        {
            // Self-closing Tag
        }
        elseif ( preg_match('#<!.*>#', $line) == true )
        {
            // Comments and CDATA
        }
        elseif ( preg_match('#<[^/].+>#', $line) == true )
        {
            // Open Tag
            $increment = true;
        }
        elseif ( preg_match('#</.+>#', $line) == true )
        {
            // Close Tag
            $decrement = true;
        }
        else
        {
            // Others
        }

        if ( $decrement === true )
        {
            $indent -= 1;
        }

        $string .= str_repeat("\t", $indent).$line."\n";

        if ( $increment === true )
        {
            $indent += 1;
        }
    }

    return $string;
}
/**}}}*/
?>