ゼロと無限の間に

フリーでオープンソースなJavaScriptとかPHPとか。

ユーザ用ツール

サイト用ツール


サイドバー

何かありましたら、メールで連絡いただくか、ブログのどこかにコメント入れてくださいね ^_^

Menu

ゼロと無限の間に

はじめに

作った主なサイト

作った主な便利ツール(無料)

ログ (Blog)

php-tool-box:jax

PHPでJSONとArrayとXMLを一発で相互変換するツール - JAX.php

PHPでデータを扱うなら配列を使うだろう。PHPの配列は連想配列でもあるので、なんでもかんでも放り込める。foreachで回せば楽々取り扱える。
外部とのやり取りなら手軽なJSONを使いたい。でもちょっと古いシステムが相手だとXMLを要求されることもある。
そんなJSONとArrayとXMLを相互に変換できるツールを作ってみた。Json, Array and Xml。略してJAX

ポイント

下記のようなことを考えると、必要となったときにはこのJAX.phpを使うか、あるいはチュートリアルとして参考にするのが楽だと思う。

JSONと配列の変換

JSONと配列の変換は、標準関数としてjson_encode()/json_decode()があるので簡単。なのだが、きちんとやろうとすると意外に面倒。ポイントは下記の2つ。

  1. json_encode()の第2引数をいろいろ指定することにより、セキュリティ的に強固にした方が良い
  2. json_encode()/json_decode()でエラーが発生した場合、そのエラーの取得はjson_last_error()で行う。取得したエラーの値はJSON_ERROR_XXXXといった感じのPHPの定数と一致するはず

XMLと配列の変換

PHPにはXMLをパースする機能が標準的にいくつか提供されていて、中でもSimpleXMLが分かりやすい。しかし、XMLと配列/連想配列には下記のように埋め難い溝があるので、どうしても変換は「条件付き」になってしまう。

  1. XMLは同じ要素名の要素が同じレベルに複数並ぶことができるが、連想配列ではそれはできない。なので配列の場合、要素名をキーとする連想配列の値として配列を持ち、その配列にデータを並列で並べることになる
  2. XMLは子要素の他に属性(attribute)として値を持つことができる。連想配列でそれを表そうとすると、要素名をキーとする連想配列の値として属性と子要素の2種類を持たなければならない。属性は“@“というキーの下に連想配列で属性名と値を持たせて、子要素は要素名をキーにした連想配列化することにより持ち分けることができる(XMLの要素名として”@“というのはありえないので)が、そうすると子要素がテキストノード(XML要素でない、普通の文字列)だった場合にもわざわざ連想配列で値を持たなければならなくなる。
    JAXでは苦肉の策として下記のようにした。
    • 属性が無く子要素がテキストノードの場合は、連想配列の値にテキストノードをそのまま入れる
    • 属性があり子要素がテキストノードの場合は、連想配列の値を連想配列にして、その中の”@“というキーの値に属性をさらに連想配列にして入れる。テキストノードは”@“と同じレベルに”#“というキーを作り、その値としてテキストノードの文字列を入れる

さらにオプション(コンストラクタで指定)として、属性が無い場合でも必ずテキストノードは”#“をキーとする連想配列の値として入れるようにすることもできるようにした。

(なんか書いているうちに何を説明しているか分からなくなってきた m( データを入れて動かしてみるのが一番わかり易い。)

ライセンス

MITライセンス

ソースコード

<?php
/**
 *  JAX.php
 *
 *  JSON、Array、XMLを相互に変換する
 *  XMLの属性は"@"、Text Nodeは"#"(※)というKEYの値になる(※オプションあり)
 *  XMLからJSONまたはArrayに変換する場合、root要素名は捨てられる
 *
 *  @version   0.1.1
 *  @see       http://0-oo.net/sbox/php-tool-box/jax
 *  @copyright 2011-2013 dgbadmin@gmail.com
 *  @license   http://0-oo.net/pryn/MIT_license.txt (The MIT license)
 *
 *  See also
 *  @see http://www.php.net/manual/ja/book.simplexml.php
 *  @see http://www.php.net/manual/ja/ref.json.php
 */
class JAX {
	private $_options;
 
	/**
	 *	コンストラクタ
	 *	@param	array	$options	(Optional)
	 */
	public function __construct(array $options = array()) {
	 	// $opionts['xml_text_#']: XMLのText NodeのKEYを常に"#"にするか
		$this->_options = array_merge(array('xml_text_#' => false), $options);
	}
	/**
	 *	XMLを連想配列にする
	 *	@param	string	$xmlStr
	 *	@param	string	$nameSpace	(Optional) XMLのNameSpace
	 *	@return	array
	 */
	public function xml2array($xmlStr, $nameSpace = '') {
		return $this->_eachXml($this->_str2xml($xmlStr, $nameSpace));
	}
	/**
	 *	XMLをJSONにする
	 *	@param	string	$xmlStr
	 *	@param	string	$nameSpace	(Optional) XMLのNameSpace
	 *	@return	string
	 */
	public function xml2json($xmlStr, $nameSpace = '') {
		return $this->array2json($this->xml2array($xmlStr, $nameSpace));
	}
	/**
	 *	配列(連想配列)をXMLにする
	 *	@param	string	$rootName	root要素名
	 *	@param	array	$arr
	 *	@return	string
	 */
	public function array2xml($rootName, array $arr) {
		return $this->_str2xml($this->_toXmlString($rootName, $arr))->asXML();
	}
	/**
	 *  配列(連想配列)をJSONにする
	 *  @param	array	$arr
	 *  @return	string
	 */
	public function array2json(array $arr) {
		$json = json_encode(
			$arr,
			JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
		);
 
		$this->_validateJson();
		return $json;
	}
	/**
	 *  JSONをXMLにする
	 *	@param	string	$rootName	root要素名
	 *  @param	string	$jsonStr
	 *  @return	string
	 *  @throws	Exception	JSONデコードエラー
	 */
	public function json2xml($rootName, $jsonStr) {
		return $this->array2xml($rootName, $this->json2array($jsonStr));
	}
	/**
	 *  JSONを連想配列にする
	 *  @param	string	$jsonStr
	 *  @return	array
	 *  @throws	Exception	JSONデコードエラー
	 */
	public function json2array($jsonStr) {
		$arr = json_decode($jsonStr, true);
		$this->_validateJson();
		return $arr;
	}
	/**
	 *	文字列からSimpleXMLElementオブジェクトを生成する
	 *	@param	string	$xmlStr
	 *	@param	string	$nameSpace	(Optional) XMLのNameSpace
	 *	@return	SimpleXMLElement
	 */
	private function _str2xml($xmlStr, $nameSpace = '') {
		return new SimpleXMLElement(
			$xmlStr,
			LIBXML_COMPACT | LIBXML_NOERROR,
			false,
			$nameSpace
		);
	}
	/**
	 *	再帰的にXMLを配列にする
	 *	@param	SimpleXMLElement	$xml
	 *	@return	array
	 */
	private function _eachXml(SimpleXMLElement $xml) {
		$arr = array();
		$attrs = (array)$xml->attributes();
 
		if ($attrs) {
			$arr['@'] = $attrs['@attributes'];
		}
 
		if (!$xml->count()) {	// 子要素が無い場合
			$text = (string)$xml;
 
			if ($this->_options['xml_text_#'] || $arr['@']) {
				$arr['#'] = $text;
				return $arr;
			} else {
				return $text;
			}
		}
 
		foreach ($xml->children() as $child) {
			$name = $child->getName();
			$grandchild = $this->_eachXml($child);
 
			if ($arr[$name]) {	// 同名要素が並んでいる場合
				if (is_array($arr[$name]) && $arr[$name][0]) {	// 配列化済みの場合
					$arr[$name][] = $grandchild;
				} else {
					$arr[$name] = array($arr[$name], $grandchild);	// 配列化
				}
			} else {
				$arr[$name] = $grandchild;
			}
		}
 
		return $arr;
	}
	/**
	 *  再帰的に連想配列をXMLにする
	 *  @param	string	$name
	 *  @param	mixed	$data
	 *  @return	string
	 */
	private function _toXmlString($name, $data) {
		$s = '';
		$attr = '';
 
		if (is_array($data)) {
			foreach ($data as $k => $v) {
				if ($k === '@') {
					foreach ($v as $attrName => $attrValue) {
						$attr .= " $attrName=" . '"' . $this->_xmlEscape($attrValue) . '"';
					}
				} else if ($k === '#') {
					$s = $this->_xmlEscape($v);
				} else if (is_numeric($k)) {
					$s .= $this->_toXmlString($name, $v);
				} else {
					$s .= $this->_toXmlString($k, $v);
				}
			}
		} else {
			$s = $this->_xmlEscape($data);
		}
 
		return "<$name$attr>$s</$name>";
	}
	/**
	 *  XMLの文字列エスケープ
	 *  @param	string	$value
	 *  @return	string
	 */
	private function _xmlEscape($value) {
		return htmlSpecialChars($value, ENT_QUOTES, 'UTF-8');
	}
	/**
	 *  JSON変換エラーが発生している場合は例外を投げる
	 *  @throws	Exception
	 */
	private function _validateJson() {
		$error = json_last_error();
 
		if ($error === JSON_ERROR_NONE) {
			return;
		}
 
		$errors = array(
			'JSON_ERROR_DEPTH',
			'JSON_ERROR_STATE_MISMATCH',
			'JSON_ERROR_CTRL_CHAR',
			'JSON_ERROR_SYNTAX',
			'JSON_ERROR_UTF8',
		);
 
		foreach ($errors as $str) {
			if ($error === constant($str)) {
				throw new Exception($str);
			}
		}
 
		throw new Exception("Unknown JSON error ($error)");
	}
}
php-tool-box/jax.txt · 最終更新: 2013/03/13 11:43 by dgbadmin