プログラミングやる前に先お風呂はいっちゃいなさい

296月/090

2009-06-29 の 教訓

Error #2015: BitmapData が無効です。

BitmapDataの幅か高さが規定範囲外の時に出る。

規定範囲は[0, 2880] pxね。これより大きかったり、小さかったりするとこのエラーが出るらしい。

本日の救世主様リンク
http://www.milk-garden.net/blog/?p=619

--

14月/090

MEXSLTをspark projectにコミットしてみた

明日ってのは、確かに地球の自転周期から言えば、24時間かもしれないがね
日光・鬼怒川の自転周期から言えばもっと長いのだよ。

すみません。
週末、鬼怒川に温泉入りに言っててすっかりブログが更新できませんでした。

で、一週間が過ぎましたが、ついにspark projectに自分の作ったライブラリをコミットしましたよ!

MEXSLT

このライブラリは、Flexユーザー向けのXML解析サポートライブラリとでも言いましょうか。

XSLTっていう、XMLを別のXMLやらHTMLやらに変換するマークアップ言語があるんですが、それを参考にして
FlashでXMLを読み込んで、それを自分で定義したクラスに変換したりできるものを作りました。

サンプルとして、こんなXMLを読み込むとします。
ちなみに、このXMLはなんちゃって個人情報です。

<?xml version="1.0" encoding="UTF-8"?>
<records>
  <record>
    <name>宇多田 信輔</name>
    <ruby>うただ しんすけ</ruby>
    <mail>utada_shinsuke@example.com</mail>
    <sex></sex>
    <age>65</age>
    <birthday>1944/1/1</birthday>
    <married>既婚</married>
    <bloodtype>A型</bloodtype>
    <prefecture>埼玉県</prefecture>
    <curry>ぶっかけ・せき止め派</curry>
  </record>
  <record>
    <name>竹下 未來</name>
    <ruby>たけした みらい</ruby>
    <mail>takeshita_mirai@example.com</mail>
    <sex></sex>
    <age>24</age>
    <birthday>1984/10/12</birthday>
    <married>既婚</married>
    <bloodtype>A型</bloodtype>
    <prefecture>新潟県</prefecture>
    <curry>ぶっかけ・せき止め派</curry>
  </record>
  <record>
    <name>北村 翔太</name>
    <ruby>きたむら しょうた</ruby>
    <mail>kitamura_shouta@example.com</mail>
    <sex></sex>
    <age>37</age>
    <birthday>1972/1/8</birthday>
    <married>既婚</married>
    <bloodtype>O型</bloodtype>
    <prefecture>神奈川県</prefecture>
    <curry>手前ルー・せき止め派</curry>
  </record>
  ...
</records>

このXMLを読み込んで、自分で定義したクラスにそれぞれの値を設定したりする作業って割とよくあると思うんですよ。
そこらへんを、MXMLを使って簡単に書けたらいいなという目論見で作ったライブラリの使用例がこちら。

(例1)

  1. <mex:MEXSLT id="parser" output="array">
  2.   <mex:xslt>
  3.     <mex:Template match="records/record">
  4.       <mex:New method="push" product="{DummyData}">
  5.         <mex:ValueOf select="name" target="name" />
  6.         <mex:ValueOf select="ruby" target="ruby" />
  7.         <mex:ValueOf select="mail" target="mail" />
  8.         <mex:ValueOf select="sex" target="sex" />
  9.         <mex:ValueOf select="age" target="age" />
  10.         <mex:ValueOf select="birthday" target="setBirthday" method="invoke" />
  11.         <mex:ValueOf select="married" target="setMarried" method="invoke" />
  12.         <mex:ValueOf select="bloodtype" target="bloodType" />
  13.         <mex:ValueOf select="prefecture" target="prefecture" />
  14.         <mex:ValueOf select="curry" target="curry" />
  15.       </mex:New>
  16.     </mex:Template>
  17.   </mex:xslt>
  18. </mex:MEXSLT>

mexってのは名前空間です。あらかじめ、xmlns:mex="http://da.rkome.me/2009/mexslt"というようにmexという名前空間を設定しておいてください。

例えば、<Application>の直下でこのXMLパーサの定義を行うなら、<Application xmlns:mex="http://da.rkome.me/2009/mexslt">という感じで。

まず、一番上のMEXSLTタグ。これは、XMLパーサの定義です。
output属性には、このパーサがどのような形式を返すかを指定します。arrayかobjectのどちらかです。
そして、このパーサがどのようにXMLを解析するのかを<mex:xslt>ノード以下に書きます。

ここからは、xsltプロパティの設定の仕方。

まず、Templateという大枠の定義を書きます。
match属性に、渡されたXMLのどのタグを元にインスタンスを生成するかをXPathで指定します。
このサンプルの場合は、recordsタグの子要素のrecordタグ以下の値を一つのクラスにまとめます。

一応、クラス定義はこんな感じ。

  1. package me.rkome.da.sample.model
  2. {
  3.   public class DummyData
  4.   {
  5.     public function DummyData()
  6.     {
  7.     }
  8.  
  9.     public var name:String;
  10.     public var ruby:String;
  11.     public var mail:String;
  12.     public var sex:String;
  13.     public var age:Number;
  14.     public var birthday:Date;
  15.     public var married:Boolean;
  16.     public var bloodType:String;
  17.     public var prefecture:String;
  18.     public var curry:String;
  19.    
  20.     public function setBirthday(dateString:String):void
  21.     {
  22.       var array:Array = dateString.split("/");
  23.       birthday = new Date(parseInt(array[0]), parseInt(array[1]), parseInt(array[2]));
  24.     }
  25.    
  26.     public function setMarried(marriedString:String):void
  27.     {
  28.       married = (marriedString == "既婚");
  29.     }
  30.    
  31.     public function toString():String
  32.     {
  33.       return "名前: " + this.name + "n" +
  34.           "よみ: " + this.ruby + "n" +
  35.           "メール: " + this.mail + "n" +
  36.           "性別: " + this.sex + "n" +
  37.           "歳: " + (this.sex == "女" ? this.age - 2 : this.age) + "n" +
  38.           "生年月日: " + this.birthday.toDateString() + "n" +
  39.           "結婚: " + (this.sex == "女" && !this.married && 30 <= this.age ? "募集中" : this.married) + "n" +
  40.           "血液型: " + this.bloodType + "n" +
  41.           "出身: " + this.prefecture + "n" +
  42.           "カレーの食べ方: " + this.curry;
  43.     }
  44.   }
  45. }

このクラスへと変えるために、まずNewタグというのをTemplateタグの直下に書きました。
これは、match属性でマッチしたXMLからクラスを生成する時に使用するタグです。

method属性には生成したインスタンスを出力オブジェクトに対してどうするかを指定します。
サンプルの場合、出力オブジェクトはarrayなのでここではpushを指定しています。
例えば、出力オブジェクトがobjectだった場合、Newタグのtarget属性に設定先プロパティ名、method属性にassignを指定するとtarget属性で指定したプロパティに生成したインスタンスを代入します。

product属性には、生成するインスタンスのクラス名を設定します。{}で囲んでいるのは何故かというと、product属性の値はClass型だからです。

で、Newタグの直下にずらりと書いてあるのがValueOfタグで、これはNewタグで生成したクラスに値を設定するためのタグです。

特別なクラスを生成しないで、単にString型の配列を作りたいときは、Templateタグの直下にValueOfオブジェクトを書けばOKです。

例えば、このなんちゃって個人情報の名前の配列を受け取りたい場合はこんな感じ。

(例2)

  1. <mex:MEXSLT id="parser" output="array">
  2.   <mex:xslt>
  3.     <mex:Template name="nameList" match="records/record">
  4.       <mex:ValueOf select="name" method="push" />
  5.     </mex:Template>
  6.   </mex:xslt>
  7. </mex:MEXSLT>

さて、ValueOf タグの属性の説明。
select属性にTemplateタグで指定したXMLに対して、XPathでタグを指定します。
この例2の場合は、Templateタグのmatch属性にrecords/recordがあり、ValueOfタグのselect属性にnameがあるため、recordsタグの子要素のrecordタグの子要素のnameタグの値を指定していることになります。

method属性にはassign、push、invokeを指定できます。assignは代入、pushは配列に追加、invokeはtarget属性で指定した名前の関数をselect属性で指定した値を引数として呼び出します。
例1でinvokeを指定しているValueOfタグのtarget属性にはsetBirthdayとかsetMarriedといった関数の名前が書いてあります。
この二つはどちらもString型を引数に取る関数で、内部ではそれぞれ値をDate型やBoolean型に変換しています。
method属性のデフォルト値はassignなので、プロパティにselect属性で指定した値を代入する場合はmethod属性をいちいち設定しなくてもassignが設定されています。

この例には出てきませんが、ValueOfタグにはもう二つ重要な属性があります。
一つは、defaultValue属性です。select属性で指定した値がXML文書の中に記述されてなかった場合に設定する値を書くと、この属性で指定した値が設定されます。
もう一つは、required属性です。これもselect属性で指定した値がなかった場合の動作で、required属性をtrueに設定するとMEXSLTErrorをスローするようになります。デフォルトはfalseになっているので、必須のものにだけrequired属性をtrueに設定してください。

最後に、このように定義したMEXSLTクラスを実際に使う時です。

  1. var result:Array = parser.parse(new XML(xml));
  2. for each (var data :D ummyData in result)
  3. {
  4.   println(data.toString());
  5.   println("================================");
  6. }

parse関数でXMLを指定すれば、解析して戻り値として結果を返します。

required属性をtrueに設定した場合など、エラーがスローされる可能性がある場合はtry~catchでMEXSLTErrorをcatchするようにしてください。

また、複数のXMLパーサの定義を行いたい場合は、Templateをいくつも定義してTemplateタグのname属性に一意の名前をつけてcallTemplate関数でその名前を指定することにより、解析することが出来ます。

  1. var names:Array = parser.callTemplate("nameList", new XML(xml));
  2. for each (var name:String in names)
  3. {
  4.   println(name);
  5.   println("================================");
  6. }

導入方法

導入は、一番簡単なのはMEXSLT.swcをライブラリパスとしてFlexプロジェクトに追加します。

MEXSLT.swcはas3/MEXSLT/MEXSLT/binにあります。

次に、名前空間を書いてください。

Flexプロジェクトを作ると、大抵Applicationタグがルートになるはずなので、mx名前空間の次にでもmex名前空間に以下のURLで設定してください。

xmlns:mex="http://da.rkome.me/2009/mexslt"

これでMEXSLTを使えるようになるはずです。

もし分からなかったら、気軽に僕にメールを下さい。このブログにコメントして頂いても結構です。
ある程度サポート致します。

と言うわけで、鬼怒川から帰ってきたDarkOmemeはウラシマ効果によって、もう眠いわけです。普段なら3時まで平気で起きているのに、今日はまだ2時だというのに半分寝てます。

ではでは、おやすみなさい。

243月/090

先日のリングコマンドの不具合について

どうにも気持ち悪かったんで、先日アップしたリングコマンドの不具合について調べてました。

どうやら、僕がUIComponentにBitmapをaddChildして使う際に行った実装がまずかったみたいです。

UIDecorator.as 

  1. public class UIDecorator extends UIComponent
  2. {
  3.   public function UIDecorator(displayObject:DisplayObject)
  4.   {
  5.     super();
  6.     BindingUtils.bindSetter(bindCenter, this, "centerX");
  7.     BindingUtils.bindSetter(bindCenter, this, "centerY");
  8.     this.displayObject = displayObject;
  9.     super.addChild(this.displayObject);
  10.   }
  11.  
  12.   [Bindable] public var centerX:Number = 0;
  13.   [Bindable] public var centerY:Number = 0;
  14.  
  15.   private function bindCenter(dummy:Number):void
  16.   {
  17.     this.lx = this.lx;
  18.     this.ly = this.ly;
  19.   }
  20.  
  21.   [Bindable]
  22.   public function get lx():Number
  23.   {
  24.     return super.x + centerX;
  25.   }
  26.   public function set lx(value:Number):void
  27.   {
  28.     super.x = value - centerX;
  29.   }
  30.  
  31.   [Bindable]
  32.   public function get ly():Number
  33.   {
  34.     return super.y + centerY;
  35.   }
  36.   public function set ly(value:Number):void
  37.   {
  38.     super.y = value - centerY;
  39.   }
  40.  
  41.   public override function set width(value:Number):void
  42.   {
  43.     super.width = value;
  44.     displayObject.width = value;
  45.   }
  46.   public override function set height(value:Number):void
  47.   {
  48.     super.height = value;
  49.     displayObject.height = value;
  50.   }
  51.  
  52.   public var displayObject:DisplayObject;
  53. }

こんな感じに実装してみた。
これは正解のソースで、本当はコンストラクタの中でdisplayObjectの幅と高さをthisの幅と高さにバインドする処理が入ってました。

で、このバインドを信用していたんだけど、どうもビルドするたびに、このバインドが利いたり利かなかったり・・・。
もう、なんでこんな曖昧なんだ。。バインド。。一気に僕の信用を落としました。

さて、で、これではBitmapをまだaddChildしてないので、ちゃんとBitmapをaddChildするようにコレを継承して、こんなのを作りました。

UIBitmap.as

  1. public class UIBitmap extends UIDecorator
  2. {
  3.   private var _bitmap:Bitmap;
  4.   public function UIBitmap(bitmapData:BitmapData=null)
  5.   {
  6.     super(new Bitmap());
  7.     _bitmap = this.displayObject as Bitmap;
  8.     this.bitmapData = bitmapData;
  9.   }
  10.  
  11.   public function set bitmapData(value:BitmapData):void
  12.   {
  13.     _bitmap.bitmapData = value;
  14.     if (value == null)
  15.       return;
  16.     this.width = value.width;
  17.     this.height = value.height;
  18.   }
  19.   public function get bitmap():Bitmap
  20.   {
  21.     return _bitmap;
  22.   }
  23. }

このソースも正解のソース。
本当はbitmapDataのsetterの中でthisの幅と高さを入れる処理はしてなかった。

まあ、つまり、UIComponentに入れたBitmapのサイズがバインド処理で行うはずなのに行われてなかったというものでした。

おわり。

明日は、MXMLで書くXMLパーサを作った話を書く予定。