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つ。
- json_encode()の第2引数をいろいろ指定することにより、セキュリティ的に強固にした方が良い
- json_encode()/json_decode()でエラーが発生した場合、そのエラーの取得はjson_last_error()で行う。取得したエラーの値はJSON_ERROR_XXXXといった感じのPHPの定数と一致するはず
XMLと配列の変換
PHPにはXMLをパースする機能が標準的にいくつか提供されていて、中でもSimpleXMLが分かりやすい。しかし、XMLと配列/連想配列には下記のように埋め難い溝があるので、どうしても変換は「条件付き」になってしまう。
- XMLは同じ要素名の要素が同じレベルに複数並ぶことができるが、連想配列ではそれはできない。なので配列の場合、要素名をキーとする連想配列の値として配列を持ち、その配列にデータを並列で並べることになる
- XMLは子要素の他に属性(attribute)として値を持つことができる。連想配列でそれを表そうとすると、要素名をキーとする連想配列の値として属性と子要素の2種類を持たなければならない。属性は“@“というキーの下に連想配列で属性名と値を持たせて、子要素は要素名をキーにした連想配列化することにより持ち分けることができる(XMLの要素名として”@“というのはありえないので)が、そうすると子要素がテキストノード(XML要素でない、普通の文字列)だった場合にもわざわざ連想配列で値を持たなければならなくなる。
JAXでは苦肉の策として下記のようにした。- 属性が無く子要素がテキストノードの場合は、連想配列の値にテキストノードをそのまま入れる
- 属性があり子要素がテキストノードの場合は、連想配列の値を連想配列にして、その中の”@“というキーの値に属性をさらに連想配列にして入れる。テキストノードは”@“と同じレベルに”#“というキーを作り、その値としてテキストノードの文字列を入れる
さらにオプション(コンストラクタで指定)として、属性が無い場合でも必ずテキストノードは”#“をキーとする連想配列の値として入れるようにすることもできるようにした。
(なんか書いているうちに何を説明しているか分からなくなってきた データを入れて動かしてみるのが一番わかり易い。)
ライセンス
MITライセンス
ソースコード
<?php /** * JAX.php * * JSON、Array、XMLを相互に変換する * XMLの属性は"@"、Text Nodeは"#"(※)というKEYの値になる(※オプションあり) * XMLからJSONまたはArrayに変換する場合、root要素名は捨てられる * * @version 0.1.0 * @see http://0-oo.net/sbox/php-tool-box/jax * @copyright 2011 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 = $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.1318860057.txt.gz · 最終更新: 2011/10/17 23:00 by dgbadmin