『 AS3の標準XMLパーサ ちょっとしたまとめ 』

2009 年 7 月 20 日

外部のxmlを読み込んでFlashに反映させたいという要望はよくあります。
僕はAS2のときには、XPath4AS2という高機能なXML解析ライブラリを使っていましたが、AS3になってからは標準のXMLクラスにそういった機能が付加されたのでそっちを使っています。
とはいうものの、XPath4AS2に慣れすぎてしまったせいか微妙な書式の違いにいつもとまどってしまいます。

なので、AS3でのxmlの基本的な解析方法について、trick7さんがまとめてくださった『xfactorstudioのXPath4AS2の使い方』という、僕も相当お世話になったエントリになぞらえて少しまとめておこうと思います。


なお、ここで紹介するやり方が唯一の正解というわけではもちろん無いので、もっと良い方法や、こんな方法もあるよ、というのをどんどん教えて頂けると助かります。

使用するxmlはtrick7さんのエントリーで使っているのと同じのにします。
ファイル名はfoodData.xml、保存形式はutf-8で。
このxmlファイルはswfファイルが出力される場所に置いておいてください。

<?xml version="1.0" encoding="UTF-8"?>
<foods>
  <food category="フルーツ">
      <name>りんご</name>
      <place>青森</place>
    <price>100円</price>
  </food>
  <food category="フルーツ">
    <name>みかん</name>
    <place>愛媛</place>
    <price>80円</price>
  </food>
  <food category="野菜">
    <name>ゴーヤ</name>
    <place>沖縄
      <city>那覇</city>
    </place>
    <price>70円</price>
  </food>
    <food>
    <name>うどん</name>
    <place>香川</place>
    <price>100円</price>
    <description>ネットで評判のおいしいうどんです。</description>
  </food>
</foods>

foodData.xml (拡張子をxmlに変更してください)

まずはswfにこのxmlを読み込みます。
AS3からはテキスト形式のファイルにはURLLoaderクラスを使うのが一般的なのですが、僕はいちいちaddEventListenerとか書くのが面倒くさい人なので、SimpleXMLLoaderというxml読み込み専用のクラスを作りました。
下記からダウンロードしてflaファイルと同じか、クラスパスが通った場所に置いてください。

» SimpleXMLLoader.as (拡張子をasに変更してください)

そして、タイムラインの1フレーム目か、コンストラクタにでも下記のコードをコピペします。

//インスタンスの生成
var loader:SimpleXMLLoader = new SimpleXMLLoader();

//コールバック関数の設定
loader.onLoadComplete      = onXMLLoadComplete;
loader.onLoadIOError       = onXMLLoadIOError;
loader.onLoadSecurityError = onXMLLoadSecurityError;

//読み込み開始
loader.load("foodData.xml");

//xmlの読み込みに成功したときに実行される関数
function onXMLLoadComplete(xml:XML):void {
  trace(xml);
}

//ファイルが存在しないなどの理由で読み込み失敗したときに実行される関数
function onXMLLoadIOError(e:IOErrorEvent):void {
  trace(e.text);
}

//セキュリティ上の理由で読み込み失敗したときに実行される関数
function onXMLLoadSecurityError(e:SecurityErrorEvent):void {
  trace(e.text);
}

これだけで読み込めます。

エラーを無視する場合は _onXMLLoadIOErrorや_onXMLLoadSecurityError関数も不要です。上記をパブリッシュすれば、出力パネルにxmlファイルの内容が出力されます。エラーが出力される、または何も出力されない場合はどこかの手順を間違えている可能性があるか、パブリッシュ設定で「Traceアクションを省略する」にチェックが入っていないことを確認してください。

無事にxmlファイルが読み込まれると、_onXMLLoadComplete関数が実行されるので、この関数にxmlの解析処理を書いていきます。ここでは、Xpath4AS2との書き方の違いをわかりやすくするために、trick7さんの記事と同じ出力を得るための12個のテストをおこないます。

trace("test1:ノードを辿って、指定の要素にアクセスする。");
var test1:XMLList = xml.food.name;
trace(test1);
//output:<name>りんご</name> <name>みかん</name> <name>ゴーヤ</name> <name>うどん</name>

trace("test2:指定の要素中のテキストにアクセスする。");
var test2:XMLList = xml.food.name.text();
trace(test2);
//output:りんご みかん ゴーヤ うどん

trace("test3:指定の要素の属性にアクセスする。");
var test3:XMLList = xml.food.@category;
trace(test3);
//output:フルーツ フルーツ 野菜

trace("test4:2番目のfood要素にアクセスする。");
var test4:XML = xml.food[1];
trace(test4);
//output:<food category="フルーツ"><name>みかん</name><place>愛媛</place><price>80円</price></food>

trace("test5:最後のfood要素のnameに含まれるテキストにアクセスする。");
var test5:XMLList = xml.food[xml.food.length() - 1].name.text();
trace(test5);
//output:うどん

trace("test6:XML中の全てのprice要素中のテキストにアクセスする。");
var test6:XMLList = xml..price.text();
trace(test6);
//output:100円 80円 70円 100円

trace("test7:parent()を使って、ノードをさかのぼっていくこともできます。このテスト自体は無意味ですが・・・");
var test7:XMLList = xml.food[3].name.parent().parent().food[1].name.text();
trace(test7);
//output:みかん

trace("test8:food要素の中で、「フルーツ」属性を持つfoodの、name中のテキストにアクセスする。");
var test8:XMLList = xml..food.(hasOwnProperty("@category") && @category == "フルーツ").name.text();
trace(test8);
//output:りんご みかん

trace("test9:food要素の中で、name要素が「うどん」の値を持つfoodの、price中のテキストにアクセスする。");
var test9:XMLList = xml..food.(name.text() == "うどん").price.text();
trace(test9);
//output:100円

trace("test10:属性が「フルーツ」のものの2番目の要素の名前にアクセスする。");
var test10:XMLList = xml..food.(hasOwnProperty("@category") && @category == "フルーツ")[1].name.text();
trace(test10);
//output:みかん

trace("test11:description要素を持つfood要素のnameの値にアクセスする。");
var test11:XMLList = xml..food.description.parent().name.text();
trace(test11);
//output:うどん

trace("test12:text()はテキストだけ、children()は内包する全てにアクセスできる。");
var test12_1:XMLList = xml..food[2].place.text();
var test12_2:XMLList = xml..food[2].place.children();
trace(test12_1);
trace(test12_2);
//output test12_1:沖縄
//output test12_2:沖縄 <city>那覇</city>

まずはじめに、ほとんどのテストで見慣れぬXMLListクラスというものが出てきます。これはXMLが格納された配列のような物だと思ってください。XNKListの中には複数のxmlインスタンスが入っていて、配列と同じく[0]や[1]のようにして中身にアクセスします。こんな感じで。

var subXML:XML = xmlList[10];

大抵の場合xmlを解析すると結果がXMLListで返ってくるので、XMLクラスで受け取りたいときには[0]などで中身を取り出してあげれば良いです。

それでは解説していきます。

test1 : ノードを辿って、指定の要素にアクセスする

trace("test1:ノードを辿って、指定の要素にアクセスする。");
var test1:XMLList = xml.food.name;
trace(test1);
//output:<name>りんご</name> <name>みかん</name> <name>ゴーヤ</name> <name>うどん</name>

現在位置のXMLクラスやXMLListクラスから、子供の要素へとアクセスするには、ドットを使います。
この例では、全てのfood要素の中にある、全てのname要素を取得しています。なお、xmlが既にfoods要素となっているので、xml.foods.food.nameとするとアクセスエラーとなります。

test2 : 指定の要素中のテキストにアクセスする

trace("test2:指定の要素中のテキストにアクセスする。");
var test2:XMLList = xml.food.name.text();
trace(test2);
//output:りんご みかん ゴーヤ うどん

test1ではnameタグごと出力されてしまいましたが、テキストのみが必要な場合がほとんどだと思います。そういうときはtext()関数を使えば要素に含まれるテキストを取り出せます。
この例では、name要素の内容が全て列挙されていますが、何番目の要素だけにアクセスする、特定の属性をもった要素だけにアクセスする方法は後述します。

test3 : 指定の要素の属性にアクセスする

trace("test3:指定の要素の属性にアクセスする。");
var test3:XMLList = xml.food.@category;
trace(test3);
//output:フルーツ フルーツ 野菜

指定の要素の属性値を取り出したい場合には、@の後に取り出したい属性値をつけてアクセスします。
この例では、全てのfood要素のcategory属性を取得します。

test4 : 複数ある要素の、指定番目にアクセスする

trace("test4:2番目のfood要素にアクセスする。");
var test4:XML = xml.food[1];
trace(test4);
//output:<food category="フルーツ"><name>みかん</name><place>愛媛</place><price>80</price></food>

複数の要素を格納したXMLListの中の、指定番目の要素にアクセスするときには配列と同様に[]指定子でアクセスします。
この例では、2番目のfood要素を取り出します。ちなみにXMLListへのアクセスは配列と同様に0から数えます。

test5 : 複数ある要素の、最後の要素にアクセスする

trace("test5:最後のfood要素のnameに含まれるテキストにアクセスする。");
var test5:XMLList = xml.food[xml.food.length() - 1].name.text();
trace(test5);
//output:うどん

XMLListに含まれる要素の数はlength()関数で取得できます。複数の要素を格納したXMLListの中の、最後のの要素にアクセスするときには配列と同様に[要素数 - 1]で取り出せます。

test6 : 階層構造に関係なく、xml中の全ての指定要素にアクセスする

trace("test6:xml中の全てのprice要素中のテキストにアクセスする。");
var test6:XMLList = xml..price.text();
trace(test6);
//output:100円 80円 70円 100円

現在の階層以下の全階層の、指定した要素にアクセスするには..(ドット2つ)を使います。
この例では、xml..priceによって、xml要素以下の全てのprice要素にアクセスしています。
xml..priceの代わりにxml.descendants(“price”)でもOKです。

test7 : 親要素へと、さかのぼってアクセスする

trace("test7:parent()を使って、ノードをさかのぼっていくこともできます。このテスト自体は無意味ですが・・・");
var test7:XMLList = xml.food[3].name.parent().parent().food[1].name.text();
trace(test7);
//output:みかん

親要素へアクセスする場合にはparent()関数を使います。

test8 : 指定した属性値で絞り込みをかける

trace("test8:food要素の中で、「フルーツ」属性を持つfoodの、name中のテキストにアクセスする。");
var test8:XMLList = xml..food.(hasOwnProperty("@category") && @category == "フルーツ").name.text();
trace(test8);
//output:りんご みかん

属性値で絞り込みをかける場合には、
(hasOwnProperty(“@category”) && @category == “フルーツ”)
のように()の中に条件を書きます。この条件指定はXpath4AS2と比べると結構複雑となってしまいます。&&で繋いだ2つの条件が共に満たされる要素へアクセスをするわけですが、左側の条件式は「指定した属性がそもそも要素の中に存在するかどうか」を判定しており、右側の条件式は「その属性が指定された値であるかどうか」を判定しています。
hasOwnProperty()メソッドを使えば、指定した属性が存在するかどうかを判定できます。存在しない属性にアクセスしようとすると例外を吐いてしまうので、2重の判定が必要となります。

test9 : 要素のテキストで絞り込みをかける

trace("test9:food要素の中で、name要素が「うどん」の値を持つfoodの、price中のテキストにアクセスする。");
var test9:XMLList = xml..food.(name.text() == "うどん").price.text();
trace(test9);
//output:100円

要素の内容で絞り込みをする場合も、()の中に条件式を書いていきます。この例では、
(name.text() == “うどん”)
によってname要素の内容で絞り込みをかけています。

test10 : 属性値での絞り込み後に、指定番目の要素にアクセスする

trace("test10:属性が「フルーツ」のものの2番目の要素の名前にアクセスする。");
var test10:XMLList = xml..food.(hasOwnProperty("@category") && @category == "フルーツ")[1].name.text();
trace(test10);
//output:みかん

test8とほとんど同じですが、条件で絞り込み後にさらに[1]で2番目の要素にのみアクセスしている点が異なります。

test11 : 子要素に関する条件を満たす親要素にアクセスする

trace("test11:description要素を持つfood要素のnameの値にアクセスする。");
var test11:XMLList = xml..food.description.parent().name.text();
trace(test11);
//output:うどん

まず、全てのfood要素の中のdescription要素へアクセスします。続けてparent()関数を使うことで、description要素を子供に持つ要素へ絞り込むことができます。

test12 : text()はテキストだけに、children()は内包する全てにアクセスする

trace("test12:text()はテキストだけ、children()は内包する全てにアクセスできる。");
var test12_1:XMLList = xml..food[2].place.text();
var test12_2:XMLList = xml..food[2].place.children();
trace(test12_1);
trace(test12_2);
//output test12_1:沖縄
//output test12_2:沖縄 <city>那覇</city>

test2で示したように、text()関数を使えば要素のテキストを取り出せますが、子要素は含まれません。一方、children()関数を使えばテキストと子要素が格納されたXMLListを取り出すことができます。

以上となります。
僕はこれ以上の機能は使っていないのであとはわかりません(笑)

XMLクラス、XMLListクラスには他にも多様なメソッドがあり、それら関してはAdobeのリファレンスに詳しく載っています。
» XMLクラス
» XMLListクラス

また、今回使ったソースファイル一式を置いておきますので、色々遊んでみてください。
» Download

追記 09.07.20
descendants()メソッドの代わりにドット2つを使う方法をniumさんに教えていただいたので、全面的にドット2つに修正しました。ありがとうございます。

追記 09.07.30 最新版のSimpleXMLLoaderクラスではUTF-8以外で書かれたxmlの読み込みに対応しました。

« 
» 

Leave a Reply