akihiro kamijo: E4X Archives

March 02, 2006

QName クラスの続き

もう少しだけ QName の話題を続けます。

QName と child() メソッド

child() や attribute() を呼ぶときに修飾名を使う必要があれば QName を引数として渡すことができます。以下のサンプルを使って具体的に見てみましょう。

var myOrder:XML =
<foo:order xmlns:foo="http://www.sample.com/foo/">
  <foo:item bar:id="1" quantity="1" xmlns:bar="http://www.sample.com/bar/">
    <name>fresh burger</name>
    <price>300</price>
  </foo:item>
</foo:order>;

まず foo:item に相当する QName のオブジェクトを作成します。これを引数にすることで child() を使っても foo:item を指定できるようになります。

var fooNS:Namespace = myOrder.namespace("foo");
var fooItemQN:QName = new QName(fooNS, "item");
trace(myOrder.child(fooItemQN).name());
// http://www.sample.com/foo/::item が出力される

ちなみに child() の箇所は以下のように書き換えることもできます。

// 以下の2行は同じオブジェクトを指す
myOrder.child(fooItemQN);
myOrder.fooNS::["item"];

QName と attribute() メソッド

attribute() も child() と同様に QName を使って修飾名を指定することができます。

var barNS:Namespace = myOrder.child(0).namespace("bar");
var barIdQN:QName = new QName(barNS, "id");
trace(myOrder.child(fooItemQN).attribute(barIdQN));
// 1 が出力される

attribute() も代わりになる書き方が幾つかあります。どれがお好みですか?

// 以下の3行は同じ属性を指す
trace(myOrder.child(fooItemQN).attribute(barIdQN));
trace(myOrder.fooNS::["item"].@barNS::["id"]);
trace(myOrder.fooNS::["item"].barNS::["@id"]);

Posted by ackie at 05:13 PM | Comments (0)

March 01, 2006

QName クラス

QName クラス

QName クラス (livedocs@lab) は XML の要素や属性の修飾名を扱うために使われます。QName のインスタンスは uri と localName という属性を持っています。それぞれ uri 属性は名前空間修飾子 localName 属性はローカル名の値を持ちます。

var qn:QName = new QName("www.sample.com/uri/", "myElementName");
trace(qn.uri);
// www.sample.com/uri/ が出力される
trace(qn.localName);
// myElementName が出力される

name() メソッドと localName() メソッド

name() メソッドは XML オブジェクト(またはXMLList オブジェクト)の修飾名を、localName() メソッドはローカル名を取得するのに使用します。対象が XMLList オブジェクトの場合はリスト内に一つだけアイテムを持つオブジェクトである必要があります。

var myOrder:XML =
<foo:order xmlns:foo="http://www.sample.com/foo/">
  <foo:item id="1" quantity="1" xmlns="http://www.sample.com/bar/">
    <name>fresh burger</name>
    <price>300</price>
  </foo:item>
</foo:order>;
// 修飾名とローカル名を取得する
trace(myOrder.child(0).name());
// http://www.sample.com/foo/::item が出力される
trace(myOrder.child(0).localName());
// item が出力される

name() メソッドの返す型は QName のため localName 属性を使用してもローカル名を取得できます。また名前空間の URI は uri 属性から取得できます。一方、プレフィックス(この場合は foo)情報は QName は持っていません。プレフィックスが必要な場合は namespace() メソッドを使用します。

trace(myOrder.child(0).namespace().prefix);
// foo が出力される
trace(myOrder.child(0).namespace().uri);
// http://www.sample.com/foo/ が出力される
trace(myOrder.child(0).name().uri);
// http://www.sample.com/foo/ が出力される
trace(myOrder.child(0).name().localName);
// item が出力される
trace(myOrder.child(0).localName());
// item が出力される

Qname および Namespace の uri 属性はプレフィックスがなくてもデフォルトの名前空間に属するオブジェクトには値が設定されます。例えば foo:item ノードではデフォルトの名前空間が宣言されています。そのため name ノードの QName を調べると以下のようになります。

trace(myOrder.child(0).child(0).name());
// http://www.sample.com/bar/::name が出力される

関連する名前空間がない場合 uri 属性は空文字になります。

setName() メソッドとsetLocalName() メソッド

setLocalName() メソッドはローカル名のみを引数の文字列と置き換えます。

trace(myOrder.child(0).name());
// http://www.sample.com/foo/::order が出力される
myOrder.child(0).setLocalName("purchase");
trace(myOrder.child(0).name());
// http://www.sample.com/foo/::purchase が出力される

setName() メソッドは引数が文字列の場合修飾名を置き換えますが引数はローカル名として扱われます。

myOrder.child(0).setName("purchase");
trace(myOrder.child(0).name());
// purchase が出力される

setName() に引数としてQName オブジェクトを渡すと名前空間の宣言付きで置き換えることができます。

var myQN:QName = new QName("http://www.sample.com/baz/", "title");
myOrder.child(0).child(0).setName(myQN);
trace(myOrder.child(0).child(0).toXMLString());
// <aaa:title xmlns:aaa="http://www.sample.com/baz/">fresh burger</aaa:title> が出力の変更分

aaa は自動的に振られたプレフィックスです。

Posted by ackie at 05:39 PM | Comments (0)

February 27, 2006

XML名前空間の使用

E4X では XML の名前空間も使用できます。ここで登場するNamespace クラス (livedocs@lab) は以前にアクセス制御の使用目的で紹介した名前空間の Namespace と同じものです。

今回は、以下のサンプルを使って名前空間で修飾された要素や属性へのアクセス方法を説明します。

var myOrder:XML =
<foo:order xmlns:foo="http://www.sample.com/foo/">
  <foo:item bar:id="1" quantity="1" xmlns:bar="http://www.sample.com/bar/">
    <bar:name>fresh burger</bar:name>
    <price>300</price>
  </foo:item>
  <foo:item bar:id="2" quantity="1" xmlns:bar="http://www.sample.com/bar/">
    <bar:name>french fries</bar:name>
    <price>170</price>
  </foo:item>
</foo:order>;

前置修飾子で修飾された要素を扱うには、修飾子に対応する Namespace のインスタンスが必要です。例えば、上記の例では item に foo という前置修飾子付が付いていますが、これは item が何らかの名前空間に属していることを意味します。そのため修飾子情報なしで myOrder.item と記述しても存在しないノードを指すことになってしまいます。

そこで、まず以下のように foo に対応する名前空間のオブジェクト(ここでは fooNS)を取得します。取得には namespace() メソッドを使用します

var fooNS:Namespace = myOrder.namespace("foo");
trace(fooNS); // http://www.sample.com/foo/ が出力される

foo はルートノードで定義されているため myOrder から直接取得しています。名前空間のスコープ内のノードであればどのノードからも同様に名前空間のオブジェクトが取得できます

取得した名前空間オブジェクトは、後に :: を付けると要素名の修飾に使用できます。今回の例では fooNS::item と記述すると item 以下にアクセスすることができるようになります。

trace(myOrder.fooNS::item[0].@quantity);
// 1 が出力される

次に、bar という名前空間も使用されていますので、これも扱えるようにします。bar は foo:item 要素で定義されていますので次のように Namespace オブジェクトを取得してみます。

var barNS:Namespace = myOrder.fooNS::item[0].namespace("bar");
trace(barNS); // http://www.sample.com/bar/ が出力される

これはちょっと長いので大変、という人は以下の方法でもOKです。この場合は URI がわかっている必要があります。

var barNS:Namespace = new Namespace("bar", "http://www.sample.com/bar/");

これで全ての要素が扱えるようになりました。

trace(myOrder.fooNS::item[0].@barNS::id);
// 1 が出力される
trace(myOrder.fooNS::item[0].barNS::name); // fresh burger が出力される

Posted by ackie at 05:22 PM | Comments (0)

February 24, 2006

XML 属性の操作

属性値を取得するためのメソッドとしては attributes() と attribute() があります。

public attributes() : XMLList
public attribute(attributeName:String) : *

attributes() は対象となるノードに含まれる全ての属性値を返します。子孫のノードの属性は含みません(念のため)。戻り値の型は XMLList です。複数のオブジェクトを持つ XMLList に対して呼ばれた場合はリスト内の個々のオブジェクトの属性値をまとめて返します。

attribute() は取得する属性の名前を引数で指定します。その他の動作は基本的に attributes() と同様です。こちらのメソッドも戻り値の型は XMLList (のはず)です。

使い方の例を示します。

var myOrder:XML =
<order>
  <item id="1" quantity="1">
    <name>fresh burger</name>
    <price>300</price>
  </item>
  <item id="2" quantity="1">
    <name>french fries</name>
    <price>170</price>
  </item>
</order>;
 
trace(myOrder.item.attributes());
// 1121
trace(myOrder.item.attribute("id"));
// 12

toString() メソッドは(toXMLString() メソッドも)属性値しか返しません。なので上記の出力だけを見ると attributes() メソッドの方はどんな属性の値なのかという情報が分かりませんね。そんなときは name() メソッドが役に立ちます。XMLList 内のオブジェクト一つ一つに呼び出すことで名前を知ることができます。

trace(myOrder.item.attributes()[0].name());
// id
trace(myOrder.item.attributes()[0]);
// 1
trace(myOrder.item.attributes()[1].name());
// quantity
trace(myOrder.item.attributes()[1]);
// 1

@ と attribute()

@と変数を組み合わせても attribute() メソッドと同じように属性値を得ることができます。以下の3つの記述はそれぞれ同じ値になります。一番下のは前の記事で要素を取り出す方法として紹介したものと同じ形式です。

trace(myOrder.item.attribute("quantity"));
trace(myOrder.item.@["quantity"]);
trace(myOrder.item["@quantity"]);
// どの場合も 11 が出力される

Posted by ackie at 05:39 PM | Comments (0)

February 22, 2006

XML 要素の操作

今回は要素を扱うためのメソッドをいくつか紹介します。

child() メソッド

1つ目は、child() メソッドです。child() メソッドは . (ドット)オペレータと組み合わせて使用します。引数には要素名を指定できます。次の例では myOrder.item.child("name") という使い方がされていますが、これは myOrder.item.name と同じ結果になります。

var myOrder:XML =
<order>
  <item  id="1" quantity="1">
    <name>fresh burger</name>
    <price>300</price>
  </item>
  <item  id="2" quantity="1">
    <name>french fries</name>
    <price>170</price>
  </item>
</order>;
trace(myOrder.item.child("name"));
// 以下が出力される
// <name>fresh burger</name>
// <name>french fries</name>

XMLList に対して child() メソッドを呼ぶとリスト内のオブジェクトそれぞれに対して child() が呼ばれます。上の例では myOrder.item は2つのオブジェクトを持つ XMLList です。そのため2つの name ノードが出力されるわけです。(ちなみに myOrder.item.child("name") と myOrder.item["name"] も同じ結果になります)

さて、child() メソッドの引数にはインデックス値を指定することもできます。この場合は子ノードの順番を指定することになります。例えば下の例のように 0 を引数にすると item ノードの最初の子ノードが対象になります。

trace(myOrder.item.child(0));
// 以下が出力される
// <name>fresh burger</name>
// <name>french fries</name>

そのほかに、引数に "*" (アスタリスク)を渡すと child() は全ての子ノードを返します。これは children() メソッドを呼んだ場合と同じ結果になるようです。

trace(myOrder.item.child("*"));
// 以下が出力される
// <name>fresh burger</name>
// <price>300</price>
// <name>french fries</name>
// <price>170</price>

parent() メソッド

次は parent() です。このメソッドを使用すると親ノードを取得することができます。parent() は複数の親ノードが存在するような XMLList には使用することができません。

例えば、myOrder.item には2つのオブジェクトが含まれますがどちらの親も myOrder のため myOrder.item.parent() は myOrder になります。一方、myOrder.item.name.parent() は2つの name ノードの親がそれぞれ違う item ノードのため結果は undefined になります。

trace(myOrder.item.parent());
// <order>
// ... 中略
// </order>
trace(myOrder.item.name.parent());
// undefind が出力される

子ノードの追加

子ノードの追加には目的に応じていくつかのメソッドがあります。

最初の2つはリストの最初か最後にノードを追加します。引数は XML オブジェクトまたは XML オブジェクトの初期値となる文字列です。

public appendChild(child:Object) : XML
public prependChild(child:Object) : XML

具体的な例を示します。

var x1:XML =
<family>
  <kid>child1</kid>
  <kid>child2</kid>
</family>
x1.appendChild(<kid>child3</kid>);
x1.prependChild(<kid>child0</kid>);
trace(x1);
// <family>
//  <kid>child0</kid>
//  <kid>child1</kid>
//  <kid>child2</kid>
//  <kid>child3</kid>
//</family>

残りの2つは第1引数で指定された XML オブジェクトの前もしくは後に第2引数の XML オブジェクトを挿入します。

public insertChildBefore(child1:Object, child2:Object):XML
public insertChildAfter(child1:Object, child2:Object):XML

こちらもサンプルを。

var x2:XML =
<family>
  <kid>child1</kid>
  <kid>child2</kid>
</family>
x2.insertChildBefore(x2.kid[0], <kid>child0</kid>);
x2.insertChildAfter(x2.kid[2], <kid>child3</kid>);
trace(x2);
// <family>
//  <kid>child0</kid>
//  <kid>child1</kid>
//  <kid>child2</kid>
//  <kid>child3</kid>
//</family>

Posted by ackie at 05:53 PM | Comments (0)

February 21, 2006

XML から文字列への変換

XML オブジェクトを文字列に変換するには toString() メソッドか toXMLString() メソッドを使います。どちらもほぼ同じ出力になりますが、テキストノードを出力しようとしたときだけ違う動きをします。toXMLString() ではタグ付きで出力されますが toString() の場合はコンテンツのみが出力されます。

var myOrder:XML =
<order>
  <item  id="1">
    <name>fresh burger</name>
  </item>
</order>;
 
trace(myOrder.item[0].name.toXMLString());
// <name>fresh burger</name> が出力される
trace(myOrder.item[0].name.toString());
// fresh burger が出力される

文字列変換の際、prettyIndent 属性の値を変えると字下げする文字数を変えることができます。デフォルトは2です。また、 prettyPrinting 属性によりタグ間のスペースの処理の有無を指定できます。

これらの属性の指定には setSettings() メソッドが使用できます。setSettings() は static メソッドですのでインスタンスではなくクラスオブジェクトに対して呼び出します。クラスオブジェクトの属性を直接変更することもできます。

// どちらも効果は同じ
XML.setSettings({prettyIndent:0});
XML.prettyIndent = 0;

defaultSettings() メソッドを使用すると setSettings() メソッドで変更した属性をデフォルト値に戻すことができます。setSettings() を引数なしで呼び出してもデフォルト値が設定されます。

コメントと PI

デフォルトではコメントと PI は無視されますが、ignoreComments と ignoreProcessingInstructions の値を変更するとそれぞれパースの対象にできます。これらの属性はパース時に使用されるので、インスタンスの初期化後に値を変更しても効果はありません。

XML.ignoreComments = false;
XML.ignoreProcessingInstructions = false;
 
var myOrder:XML =
<order>
  <!-- comment -->
  <?PI processingInstructions ?>
  <item id="1" quantity="1"/>
</order>;
trace(myOrder);
// <order>
//   <!-- comment -->
//   <?PI processingInstructions ?>
//   <item id="1" quantity="1"/>
// </order>

パースされたコメントと PI は comments() と processingInstructions() メソッドで取得できます。返り値の型は XMLList です。

trace(myOrder.comments()[0]);
// <!-- comment -->
trace(myOrder.processingInstructions()[0]);
// <?PI processingInstructions ?>

Posted by ackie at 05:25 PM | Comments (0)

February 15, 2006

XMLList のイテレーション

前の記事で書いたように . や .. オペレータの結果は XMLList オブジェクトになります。この XMLList 内の XML オブジェクトを for 文を使って処理する方法を紹介します。for ... in と for each ... in の2種類のやり方があります。

まず、以下のような XML オブジェクト myOrder を考えます。myOrder.item は2つの item ノードを含む XMLList オブジェクトになります。

var myOrder:XML =
<order>
  <item  id="1" quantity="1">
    <name>fresh burger</name>
    <price>300</price>
  </item>
  <item  id="2" quantity="1">
    <name>french fries</name>
    <price>170</price>
  </item>
</order&item>;

for ... in のループを使うと、プロパティ名を使用してイテレーションを行うことができます。プロパティ名といっても XMLList 内の名前で要素名等とは関係ありません。for 文内ではプロパティ名を利用して XMLList から目的の XML オブジェクトにアクセスします。

var total:Number = 0;
for (var pname:String in myOrder.item) {
  total += myOrder.item[pname].@quantity 
         * myOrder.item[pname].price;
}

for each ... in 文では XMLList 内の XML オブジェクトを直接使用できます。上の例と同じ処理を for each 文を使って書き直してみます。

var total:Number = 0;
for each (var prop:XML in myOrder.item) {
  total += prop.@quantity 
         * prop.price;
}

こちらの方がすっきりした感じですね。これに with 文を使用してさらに単純化することもできます。

var total:Number = 0;
for each (var prop:XML in myOrder.item) {
  with (prop) {
    total += @quantity 
           * price;
  }
}

Posted by ackie at 08:41 PM | Comments (0)

February 14, 2006

XML 要素の操作

. と ..

AS3 では XML データの階層構造をたどって目的の要素にアクセスするのに . オペレータと .. オペレータを使用することができます。

どちらを使用した場合も、返される値の型は XMLList (livedocs@lab) です。 XML クラスはルートノードを一つしか持ちませんが XMLList クラスは複数持つことができます。メソッドは両者共通です。

では、下のサンプルを使って具体的な使い方をみてみましょう。

var eList:XML =
<employeeList>
  <employee id="1234">
    <lastName>taro</lastName>
    <firstName>yamada</firstName>
  </employee>
  <employee  id="2345">
    <lastName>hanako</lastName>
    <firstName>yamada</firstName>
    <title>as3 programmer</title>
  </employee>
</employeeList>

上記のコードで宣言された XML オブジェクト eList に対し、eList.employee は最上位のノード (employeeList) の子ノードで名前が employee のものを指します。上記の例ではそのようなノードが2つ存在するため eList.employee の値は2つの要素を持つ XMLList オブジェクトになります。

. (ドット) を繰り返すことで階層構造のさらに下を指定することができます。例えば eList.employee.lastName は、lastName という名前の要素が2つの employee それぞれに存在するため、やはり二つの要素を持つ XMLList オブジェクトになります。

.. アクセサを使用すると、階層の深さに関係なく関連する要素を取り出すことができます。例えば eList..lastName は eList オブジェクト内の lastName という名前の要素全てです。今回の例では eList.employee.lastName と同じ値になりますね。

さて、複数のノードが存在する場合、インデックス値を使って特定のノードを指定することができます。

eList.employee[0];  // 最初の employee ノード
eList.employee[1];  // 2つ目の employee ノード

今回の場合、下の2つはどちらも hanako を指します。

eList.employee[1].lastName
eList.employee.lastName[1]

なお、該当する要素が一つの場合インデックスは指定してもしなくても構いません。例えば eList.employee.title と eList.employee.title[0] は同じ要素を指します。

フィルターを用いた要素の指定

要素名の代わりに括弧内に条件を記述することでも要素を指定できます。以下に使い方の例を示します。要素名や属性の値を使って条件を記述します。

eList.employee.(lastName == "taro")     // 最初の employee ノード
eList.employee.(lastName == "taro").firstName // yamada
eList.employee.(lastName == "taro").@id // 1234
eList.employee.(@id == 2345)            // id が 2345 のノード
eList.employee.(@id == 2345).lastName   // hanako
eList.employee.(@id > 1000)             // 両方の employee ノード

要素の追加

. オペレータを使って要素を追加することができます。まず、次のような XML オブジェクトがあるとします。

var order1:XML =
<order> 
  <book> 
    <title>learning AS3</title> 
  </book> 
</order>

. オペレータで指定された新しい要素や属性に値を代入すると、XML オブジェクトが更新されます。

order1.book.@ISBN = 0123456789;
order1.book.price = 1000;
 
trace(order1);  // 以下の内容が出力される
<order> 
  <book ISBN="0123456789"> 
    <title>learning AS3</title> 
    <price>1000</price> 
  </book> 
</order> 

Posted by ackie at 06:33 PM | Comments (0)

February 13, 2006

E4X

E4X (ECMAScript for XML) は ECMAScript3 の拡張として開発された XML データを扱うための仕様です。2nd Edition が昨年末に公開されています(こちら)。AS3 では新しく E4X のクラスがサポートされ、従来よりもずっと簡単に XML データを扱うことができるようになりました。

AS3 でも AS2 で使われていた XML クラスは XMLDocument と名前を変えて他の関連クラス (XMLNode 等) と共に flash.xml パッケージに含まれています。しかし、これは過去に開発したコードのサポートが主目的で、今後の使用を推奨するものではありません。

さて、まず手始めに E4X の簡単な例を見てみましょう。以下のような XML 文書があるとします。

<order> 
  <book id="1"> 
    <title>learning AS3</title> 
  </book> 
</order> 

E4X では . や @ を使って要素にアクセスできます。上の XML 文が myOrder という XML オブジェクトに保持されているとすると、以下のような記述が可能です。

trace(myOrder.book.title);  // learning AS3 が出力される
trace(myOrder.book.@id);    // 1 が出力される

また、同様にデータの更新を行うこともできます。

myOrder.book.title = "learning Flex";
myOrder.book.@id = 2;
trace(myOrder);  
// 以下の内容が出力される
<order> 
  <book id="2"> 
    <title>learning Flex</title> 
  </book> 
</order> 

その他、Namespace など名前空間をサポートするためのクラスもあります。XML クラスの仕様はこちらをご覧ください(livedocs@lab)。

XML オブジェクトの初期化

E4X では XML 文をそのままオブジェクトにアサインして初期化することができます。配列の初期化に [] を使用したりオブジェクトの初期化に {} を使用するのと同様に XML の初期化に <> が使えます。

myXML:XML =
<order> 
  <book ISBN="0123456789"> 
    <title>learning AS3</title> 
  </book> 
</order> 

このとき、初期化データである XML 文の一部を変数にすることもできます。XML 文中に中括弧で囲んで変数を挿入すると、オブジェクトの初期化時に対応する文字列と置き換えられます。

var attrName:String = "ISBN";
var attrVal:String = "0123456789";
var tagName:String = "title";
var content:String = "learning AS3";
 
myXML:XML =
<order> 
  <book {attrName}={attrVal}> 
    <{tagName}>{content}</{tagName}>
  </book> 
</order> 
 
trace(myXML);
// 以下の内容が出力される
<order> 
  <book ISBN="0123456789"> 
    <title>learning AS3</title> 
  </book> 
</order>

もちろんコンストラクタを使用することもできます。その場合は、引数に文字列を渡します。

var str:String = "<order>"
               + "<book ISBN="0123456789">"
               + "<title>learning AS3</title>"
               + "</book>"
               + "</order>";
var myXML:XML = new XML(str);

Posted by ackie at 06:10 PM | Comments (0)