(1) phpでの再帰処理
最初はphpからです。下記のようなxmlファイル(ファイル名:sample.xml)の処理をします。
<?xml version="1.0" encoding="utf-8"?>
<root>
<question>
<answer>answer1.1</answer>
<question>
<answer>answer1.2</answer>
</question>
</question>
<question>
<answer>answer2</answer>
</question>
</root>
questionタグのひとつが入れ子になっています。このxmlの中のAnswerタグの内容を配列に入れてみます。phpのコードは下記。
<?php
$tmptext = file_get_contents('./sample.xml');
$xml = simplexml_load_string($tmptext);
$q_ary = array();
foreach ($xml->question as $q) {
$tmpary = array();
$a_obj = new find_question();
$tmpary = $a_obj->action($q,$tmpary);
array_push($q_ary,$tmpary);
}
var_dump($q_ary);
class find_question {
function __constructor() {
}
public function action($q,$tmpary) {
array_push($tmpary, (string)$q->Answer);
if ($q->question) {
$newq = $q->question;
$a_obj = new find_question();
$tmpary = $a_obj->action($newq,$tmpary);
}
return $tmpary;
}
} // end of find_question
?>
9行目の foreach は一番浅い階層の question タグのみをループします。そのため、2回のループです。入れ子になった question タグを取り扱うために、 find_question クラスを用意しました。questionタグに、さらに questionが含まれていたら、子のquestionタグを切り出します。切り出したquestionタグに対して再帰処理をします。phpだとxmlの取扱いが簡単です。javascriptだと、こんなに簡単にはいかないですね。
この様な再帰処理は関数でもかけますが、クラスを用いて、再帰処理をしたほうが何か安心感があります(気持ちの問題だけなのですが)。
再帰処理は何か不思議な感じがします。実際のコードでは、ほぼ始めて書いてみたのですが、いざ書いてみると、これまで書いてきたコードの中にも、再帰で書くべきものがたくさんあったような気がしてきました。
21行目の var_dump($q_ary)の出力内容です。
array(2) {
[0]=>
array(2) {
[0]=>
string(9) "answer1.1"
[1]=>
string(9) "answer1.2"
}
[1]=>
array(1) {
[0]=>
string(7) "answer2"
}
}
(2) JavaScriptでの再帰処理
次に、JavaScriptでも、同じような再帰処理を書いて見ました。下記のxmlファイル(sample.xml)を読み込んで、その中のAnswerタグの要素を配列に格納します。先のxmlよりも多少深く入れ子になっています。questionタグは入れ子になっていますが,配列に格納するときには入れ子にせずに,2次元配列として取り扱っています。
<?xml version="1.0" encoding="utf-8"?>
<root>
<question>
<answer>answer1.1</answer>
<question>
<answer>answer1.2</answer>
<question>
<answer>answer1.3</answer>
</question>
</question>
</question>
<question>
<answer>answer2.1</answer>
<question>
<answer>answer2.2</answer>
</question>
</question>
<question>
<answer>answer3</answer>
</question>
</root>
questionタグが入れ子になっていて、そのところが再帰処理となります。先にphpでの再帰処理を書いたのですが、phpがサーバー側で、JavaScriptがクライアント側です。サーバーとクライアントでxmlデータのやり取りをすることを想定しています。その場合、同じようなxmlをphpとJavaScriptで扱うことになります。
下記が、全体のHTMLです。Answer タグの内容を格納する配列は、a_ary (80行)です。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<meta http-equiv="imagetoolbar" content="false" />
<meta charset="UTF-8">
<meta content="chrome=1" http-equiv="X-UA-Compatible">
<meta content="tool for simple question" name="application-name">
<meta content="you can use expression" name="description">
<meta content="HTML5,javaScript,Canvas" name="keywords">
<meta content="somebody" name="Author">
<title>test for simple question</title>
<link rel="stylesheet" href="jquery/jquery-ui.css" type="text/css">
<script src="jquery/jquery.js" type="text/javascript"></script>
<script src="jquery/jquery-ui.js" type="text/javascript"></script>
<script type="text/javascript">
//<![CDTA[
var sample_xml;
function read_xml() {
var tmp = new Date().getTime();
$.ajax({
url:'./sample.xml?' + tmp,
type:'get',
dataType:'text',
timeout:5000,
async:false,
success:function(xmltext,status){
if(status!='success')return;
//alert(xmltext);
sample_xml = text2xml(xmltext);
// firefoxのみ。xmlを表示する。
/*
if (typeof XMLSerializer != "undefined") {
var tmpserial = new XMLSerializer();
alert(tmpserial.serializeToString(sample_xml));
}
*/
find_node(sample_xml);
//alert(a_ary);
var divbox = document.getElementById("divbox");
var ary2text = 'array (<br /><br />';
for (i = 0; i < a_ary.length; i++) {
ary2text = ary2text + ' [ ' + a_ary[i] + ' ]';
if (i == a_ary.length - 1) {
ary2text = ary2text + '<br />';
} else {
ary2text = ary2text + ',<br /><br />'
}
}
ary2text = ary2text + '<br />)';
divbox.innerHTML = ary2text;
}
});
}
var a_ary = new Array();
function find_node(xml) {
// 直接の子要素である question のみを選択したい。
$(xml).find('root').find('> question').each(function(){
var tmpary = new Array();
var answer = $("> Answer",this).text();
tmpary.push(answer);
// 入れ子のquestion の処理
if ($(this).find('> question').length) {
var newq = $(this).find('> question')[0];
tmpary = find_q(newq,tmpary);
}
a_ary.push(tmpary);
});
}
function find_q(q,tmpary) {
var answer = $("> Answer",q).text();
//alert(answer);
tmpary.push(answer);
if ($(q).find('> question').length) {
var newq = $(q).find('> question')[0];
tmpary = find_q(newq,tmpary);
}
return tmpary;
}
// テキストをxmlに変換。もともと xml ならばそのまま。
function text2xml(xmltext) {
if (!($.isXMLDoc(xmltext))) {
if (find_msie.use == 'no') {
var dbObj = new DOMParser();
var xml = dbObj.parseFromString(xmltext,"text/xml");
} else {
xml = new ActiveXObject("Microsoft.XMLDOM");
xml.async = "false";
xml.loadXML(xmltext);
}
} else {
var xml = xmltext;
}
return xml;
}
// IEかどうか判定
find_msie.use = "no";
function find_msie() {
find_msie.use = 'yes';
}
function startup() {
read_xml();
}
//]]>
</script>
</head>
<body onload="startup();">
<!--[if IE]>
<script language="JavaScript" type="text/javascript">
find_msie();
alert('you use IE!');
</script>
<![endif]-->
<div id='divbox' style="color:#000000; position: absolute; left:10px; top: 10px; background-color: #f0f0f0; height:400px; width:400px; border:solid 1px; padding: 10px;"></div>
</body></html>
166行目に、IEであるかどうか判断するところがあります。xmlを取り扱う時に、IEの場合は書き分けないといけません。124行目にtextデータをxmlオブジェクトに変換する関数があります。この時に、IEであるかないかで場合分けをしています。
82行目の関数 find_node が Answer タグを探すものです。JavaScriptでxmlを扱う場合には、find().each でタグを処理していくときに、入れ子になったタグをすべて対象として、処理をしてしまいます。直下のタグのみを取り扱う場合には find(‘> question’) のように、不等号を入れて指示をします。105行目の find_q が再帰的な処理をするための関数です。 97行にあるように、インデックスを指定して子要素をとりだすなど、多少込み入ったやり方になっています。q としたり $(q) としたりして、JavaScript と jQueryを行き来しています。もっとスマートな方法があるのかもしれません。
57行以降が、配列 a_ary の内容を画面に書き出すところです。何か配列をそれらしく表示する関数が欲しい所です。
最終の画面表示は下記のようになります。入れ子になったquestionタグですが,配列では入れ子にしていません。横に並べています。