「逆引きリファレンス」カテゴリーアーカイブ

JSONフォーマットの利用

JSON(JavaScript Object Notation)とは、Javascriptの言語仕様がベースになっている軽量のデータフォーマットです。JSONフォーマットはCurl6.0から扱えるようになりました。詳細については、以下のホームページをご参照ください。
  http://www.json.org/

JSONフォーマットのパースとフォーマット文字列の出力

まずは以下のJSONフォーマットで記載されたファイル(test.json)を用意します。 

{
  “id”:123,
  “name”:”岡田”,
  “address”:”東京都”,
  “hobbies”:[“snowboard”, “golf”]
}

 CurlはJSONデータタイプのクラスとして以下のものを用意しています。 

 クラス 説明 
 JsonValue  これはany型のシノニムです。
 JsonObject  これは{HashTable-of any}型のシノニムです。JSONフォーマットでは”{“及び”}”で囲まれたデータが連想配列となります。
 JsonArray  これは{Array-of any}型のシノニムです。JSONフォーマットでは”[“及び”]”で囲まれたデータが配列となります。

JSONフォーマットをパースし、JsonValueもしくはJsonObject・JsonArrayオブジェクトを生成するには、JsonValue-parseを使います。以下の例では、test.jsonのJSONフォーマットをパースし、JsonValueを作成しています。 

let obj:JsonObject =
    {JsonValue-parse {url “test.json”}} asa JsonObject
    {dump
        v[“id”],
        v[“name”],
        v[“address”],
        v[“hobbies”][0],
     v[“hobbies”][1]
    }

逆にJsonValueをJSONフォーマット(文字列)に変換するには、JsonValue-to-Stringプロシージャを利用します。

let str:String = {JsonValue-to-String v}{dump str}

サポートバージョン

RTE6.0以上

 

XMLフォーマットの利用(SAXパーサー)

XMLフォーマットを扱う場合、Curlでは標準APIとしてSAX(Simple API for XML)機能が用意されています。SAXはXML文書を先頭から解析し、発生したイベントをアプリケーションでキャッチして処理するAPIです。DOMに比べ、解析速度が速く少量のメモリ消費量というメリットがありますが、ノードの追加、削除等、XML文書を更新する機能はありません。

ここではSAXパーサーを利用したXML解析方法について、下記サンプルXMLを用いて説明します。 

<RecordSet>
  <Record name=”AAA”>
    <Value>”123″</Value>
    <Value>”456″</Value>
  </Record>
  <Record name=”BBB”>
    <Value>”xyz”</Value>
  </Record>
</RecordSet>

 SAXパーサーを使用するためには、まずDefaultHandlerクラスを継承したイベントハンドラを作成します。このクラスのstart-document、end-document、start-element、end-element、charactersメソッドを実装します。下記サンプルは、それぞれのメソッドで取得したXMLの値等をoutputプロシージャにて結果を表示させています。

{define-class public MySAXApp {inherits DefaultHandler}
  {constructor public {default }
    {construct-super}
  }

  {method public {start-document}:void
    {output “#start-document”}
  }

  {method public {end-document}:void
    {output “#end-document”}
  }

  {method public {start-element
                     uri:String,
                     name:String,
                     qname:String,
                     atts:Attributes
                 }:void
    {output “#start-element”}
    {output ” タグ = ” & name}
    {output ” 属性名 = ” & {atts.get-local-name 0}}
    {output ” 属性 = ” & {atts.get-value 0}}
  }

  {method public {end-element
                     uri:String,
                     name:String,
                     qname:String
                 }:void
    {output “#end-element”}
    {output ” タグ = ” & name}
  }

  {method public {characters
                     ch:StringBuf,
                     start:int,
                     length:int
                 }:void
    {output “#characters”}
    {output ” 要素 = ” & ch}
  }
}

各XML構文解析イベントを受け取り呼び出されるメソッドの説明は下表の通りです。

start-document ドキュメント開始の通知を受け取ります。
end-document ドキュメント終了の通知を受け取ります。
start-element

要素の開始の通知を受け取ります。
XML ドキュメント内の各要素の開始時にこのメソッドを起動します。

end-element

要素の終了の通知を受け取ります。
XML ドキュメント内の各要素の最後にこのメソッドを起動します。

characters 文字データの通知を受け取ります。

XML構文解析イベントの流れは、まずstart-documentメソッドが実行されます。ノードを読み込むとstart-elementメソッドが実行され、タグ名や属性等を取得できます。次にstart-elementメソッドが終了すると、charactersメソッドが実行され、テキストを取得できます。最後に終了タグを読み込むため、end-elementメソッドの中身が実行されます。 

これらのハンドラの呼び出し方法は、以下のようにSAXパーサーを作成し、set-content-handlerを使用して上記で作成したイベントハンドラを登録します。

{let xr:XMLReader = {SAXParser}}
{let handler:MySAXApp = {MySAXAppt}}
{xr.set-content-handler handler}

上記にて作成したSAXパーサーのparseメソッドを使用してXMLデータを解析します。 

{xr.parse {InputSource system-id = XMLデータのURL}}

実行結果

#start-document
#start-element
 タグ = RecordSet
 属性名 = <null>
 属性 = <null>
#characters
 要素 =
 
#start-element
 タグ = Record
 属性名 = name
 属性 = AAA
#characters
 要素 =
   
#start-element
 タグ = Value
 属性名 = <null>
 属性 = <null>
#characters
 要素 = “123”
#end-element
 タグ = Value
#characters
 要素 =
   
#start-element
 タグ = Value
 属性名 = <null>
 属性 = <null>
#characters
 要素 = “456”
#end-element
 タグ = Value
#characters
 要素 =
 
#end-element
 タグ = Record
#characters
 要素 =
 
#start-element
 タグ = Record
 属性名 = name
 属性 = BBB
#characters
 要素 =
   
#start-element
 タグ = Value
 属性名 = <null>
 属性 = <null>
#characters
 要素 = “xyz”
#end-element
 タグ = Value
#characters
 要素 =
 
#end-element
 タグ = Record
#characters
 要素 =

#end-element
 タグ = RecordSet
#end-document

サポートバージョン

RTE5.0以上

参考ドキュメント

http://developers.curlap.com/curl/docs/rte/6.0/ja/docs/ja/dguide/using-sax-xml.html

 

変数の宣言と値の代入

宣言と代入

Curlでの値の宣言方法は以下のように「let 変数名:データ型 = 初期値」となります。

let str:String = “あいうえお” || もしくは{let str:String = “あいうえお”}

以下のように複数の変数を同時に宣言することもできます。

let (str:String, int:i) = (“あいうえお”, 123) 

値を代入する場合は、下記のようにsetを使います。 

set str = “ABCDE” || もしくは{set str = “ABCDE”}

let同様に、以下のように複数の値を同時にセットできます。 

set (str, i) = (“かきくけこ”, 456) 

この際にJava等の言語と違い、注意しないといけないことはNullを許容しないということです。もしNullを許容する変数を宣言する場合は、「#データ型」というような形式でシャープを付与して宣言する必要があります。 

let str:#String = null

Curl6.0からサポートされた宣言方法で、定数を宣言する場合は、defという宣言の仕方ができます。これは定数としての宣言となります。また、データ型をしているする必要もありません。

def str = “あいうえお” || let constant str:String = “あいうえお”と同様 

 

式の結果を代入

式の結果を代入することができます。例えば、ifやswitch、valueの結果を変数に代入することができます。以下はif文の例です。 

set str
    {if a > 1 then
        “あいうえお”
     else
        “かきくけこ”
    }

 

値の増減

Javaのi++やi–のように値の増減をコーディングするには、incやdecを利用します。 

{inc i} || 増
{dec i} || 減

 

関連ドキュメント

基本構文

 

演算子

Curlで利用できる演算子は以下の通りです。

四則演算子 +, -, *, /, div, mod, rem
リレーショナル演算子 ==, !=, <, >, <=, >=, isa
論理演算子 and, or, not
文字列演算子(文字列の結合) &
強制変換演算子(キャスト) asa
NULL 許容演算子 #

 

{value
    let i:int = 2 * 2 
    let str:String = “AB” & “CD”
}

詳細については、以下のサイトをご覧ください。

演算子

 

圧縮・解凍

Curlで圧縮及び解凍する方法を記載します。Curlでは圧縮フォーマットにgzipとzlibをサポートしております。これを利用するには、CURL.IO.ZSTREAMパッケージをインポートします。

{import * from CURL.IO.ZSTREAM}

この中のDeflateByteOutputStreamにて圧縮ストリームを生成し、InflateByteInputStreamにて解凍ストリームを生成することができます。以下に圧縮ファイルへの書き出しのサンプルコードを記載します。 

{define-proc {compress
                 in-url:Url,
                 out-url:Url,
                 compression-format:CompressionFormat =
                     CompressionFormat.gzip || or zlib
             }:void
   
    {with-open-streams
        bos = {DeflateByteOutputStream
                  {write-open-byte out-url},
                  compression-format = compression-format}
     do
        {with-open-streams
            bis = {read-open-byte in-url}
         do
            {while true do
                let (data:{Array-of byte}, num-read:int) = {bis.read n = 1024}
                {if num-read <= 0 then
                    {break}
                }
                let start:int = 0
                {while num-read > 0 do
                    let written:int =
                        {bos.write
                            data,
                            start = start,
                            n = num-read
                        }
                    {if written <= 0 then
                        {break}
                    }
                    {inc start, written}
                    {dec num-read, written}
                }
            }
        }
    }
}

上記のサンプルプロシージャを用いて、以下のようにテキストファイルを圧縮し、圧縮ファイルに書き出すことができます。

{compress {url “a.txt”}, {url “a.gz”}}

また、上記で圧縮したファイルを以下のサンプルプロシージャで解凍します。

{define-proc {uncompress
                 in-url:Url,
                 out-url:Url,
                 compression-format:CompressionFormat =
                     CompressionFormat.gzip || or zlib
             }:void

    {with-open-streams
        bis = {InflateByteInputStream
                  {read-open-byte in-url},
                  compression-format = compression-format
              }
     do
        {with-open-streams
            bos = {write-open-byte out-url}
         do
            {while true do
                let (data:{Array-of byte}, num-read:int) = {bis.read n = 1024}
                {if num-read <= 0 then
                    {break}
                }
                {if-non-null data then
                    {bos.write {non-null data}, n = num-read}
                }
            }
        }
    }
}

以下のようにサンプルプロシージャを実行し、解凍ファイルを取得します。 

{uncompress {url “a.gz”}, {url “ab.txt”}}

これらにより、HTTP圧縮・解凍の仕組みを実装することも可能です。

サンプルコード

圧縮・解凍サンプルコード

 

条件式

条件式について説明していきます。

if文

基本的なルールは以下のようになります。

{if 式 then
    …
 elseif 式 then
    …
 else
    …
}

サンプルは以下のようになります。 

let x:int = 1
let y:int = 2
{if x > y then
    {output “xが大きい”} || outputは出力コンソールに表示するものです。
 else
    {output “yが大きい”}
}

ちなみに、Curlの特徴的な利用方法で、if文は値を返しますため、以下のように利用できます。 

let x:int = 1
let y:int = 2
{output
    {if x > y then
        “xが大きい” || outputは出力コンソールに表示するものです。
     else
        “yが大きい”
    }
}

unless文

これはif文の逆ですね。基本的にはあまり使わないと思います。

 

switch文

他の言語でもサポートされているswitch文です。値の評価には数値だけでなく、どんなデータ型でも可能です。 

{switch 比較変数 
 case 値1 do
    …
 case 値2 do
    …
 else
    …
}

サンプルは以下の通りです。 

let x:int = 1
{switch x
 case 0 do
    {output “xは0”}
 case 1, 2 do
    {output “xは1か2です”}
 else
    {output “xは3以上の値”}
}

 

type-switch文

データ型別に処理を行いたいときはtype-switch文を利用します。基本的にはswitch文と似ていますのでサンプルのみ下に記載します。

{type-switch x
 case i:int do
    {output “xはint”}
 case f:float do
    {output “xはfloat”}
 else
    {output “xはintでもfloatでもないです。”}
}

関連ドキュメント

条件式の構成

 

ループ式

Curlでのループ式の利用方法です。Curlでは、for、until、whileを利用することができます。また、他言語同様break、continueも装備しております。

for文

for文にはいくつかの記述方法があります。まずは、範囲内でのループの記述方法を記載します。

例えば、1づつ増加させていくようなループ文を記載するには、{for … to … step … do …}もしくは{for … below step … do …}を利用します。この2つのfor文は似ていますが、toは~までで、belowは~以下という場合に利用します。具体的なサンプルは以下の通りです。

|| 1づつ増やしていく
{for i:int = 0 below 8 do
    {output i}
}

これは0から7までの値をコンソールに表示します。またstepは、増分する値の幅を指定するために利用できます。

|| 2づつ増やしていく
{for i:int = 0 below 8 step 2 do
    {output i}
}

また減らしていく場合は、donwtoもしくはaboveを利用します。 

|| 2づつ減らしていく
{for i:int = 8 above 0 step 2 do
    {output i}
}

コンテナループについてはコレクション・クラスを説明の機会に紹介します。

 

while文

次にwhile文の記述方法です。条件がtrueの間、処理を繰り返します。 

{while 条件 do
    ….
}

サンプルは以下のようになります。

{while start < 10 do
    {output start}
    set start = start + 1
}

 

until文

Curlではuntil文についてもサポートしております。これは条件がfalseの間、処理を繰り返します。

{until 条件 do
    ….
}

サンプルは以下のようになります。

{until start == 10 do
    {output start}
    set start = start + 1
}

 

breakとcontinue

Curlは、他言語同様、breakやcontinueについてもサポートしております。 

{continue}

{break} 

 

関連ドキュメント

ループ式の構成

 

キーワード引数と残余引数

Curlにはキーワード引数残余引数という独特な引数を付与できます。

通常の引数

プロシージャやメソッド等の通常の引数では以下のようにします。

|| プロシージャの作成
{define-proc public {my-proc1 i:int}:void
   || 処理
}

{my-proc1 100} || 実行

キーワード引数(Keyword Argument)

Curlの持つキーワード引数とは、「引数 = 引数のデフォルト値」というように、プロシージャやメソッド等にセットします。以下に例を記載します。 

|| プロシージャの作成
{define-proc public {my-proc2 i:int = 100}:void
    || 処理
}

{my-proc2 i = 200}
{my-proc2} || {my-proc2 i = 100}と同等

また、複数キーワード引数を指定することや通常の引数と混合させることができます。 

|| プロシージャの作成
{define-proc public {my-proc3 
                                 str:String,
                                 i:int = 100,
                                 f:float = 1.0
                            }:void
    || 処理
}

{my-proc3 “abc”, i = 200}
{my-proc3 “efg”, f = 2.0}
{my-proc3 “xyz”}
{my-proc3 “abc”, i = 200, f = 2.0}

キーワード引数の利点としては、キーワード引数の値をセットする必要がなければ省略できますし、順番が前後しても特に問題がありません。

残余引数(Rest Argument)

残余引数とは、引数に名前(変数)を持たない引数を、無制限に受け取ることができます。これは3つのピリオド…”として指定します。また、”…:String”のように、型を指定することができます。

{define-proc public {my-proc4 }:void
}

{define-proc public {my-proc5 i:int, …:String}:void
}

プロシージャの中では以下のようにコンテナループでそれぞれの引数を順に取得することができます。 

{define-proc public {my-proc6 }:void
    {for v in do
        {output v}
    }
}

{my-proc6 “abc”, “def”, 123, 1.0, true}

ちなみに残余引数の数を算出するにはcount-argumentsマクロを利用します。例えば、{count-arguments …}のようになります。

詳細については、こちらを参照ください。

 

プロシージャとクロージャ

クロージャ

Javascriptなどで利用されるクロージャ機能はCurlにもあります。これは匿名プロシージャとも呼ばれます。これにはprocマクロを利用します。また、プロシージャとして型定義を指定するために、proc-typeマクロが用意されています。この機能を具体例に沿って説明していきたいとおもいます。

{curl 6.0 applet}

{value
    let p:{proc-type {int, int}:int} =
        {proc {a:int, b:int}:int
            || 足し算の結果を返す
            {return a + b}
        }

    {output {p 10, 20}} || 30
    {output {p 5, 30}}   || 35
}

ここでは、単純な足し算のクロージャを例にとって説明します。まず、クロージャを作成するには、procマクロを利用します。procマクロの引数に、実際に必要な引数(ここで足し算する2つの値aとb)を”{“と”}”で囲み、その後戻り値を”:”の後に記載します。あとは、中身のロジックを記載していきます。(ここでは足し算をし、結果を返すというものです。)また、このクロージャ自体を変数に代入する場合(上記の例では、pという変数に代入)、変数の型を定義するため、上記のようにproc-typeマクロを利用します。

このプロシージャを実行するには、変数名を”{“と”}”で囲み、実行します。例えば、上記のように{p 引数, 引数}のようにいます。また、この変数をさらに他のプロシージャやメソッドの引数として指定できますので、Javascriptでよく利用されるコールバック関数的な使い方もできます。

グローバル・プロシージャ(関数)

Curlでは、グローバルな関数(プロシージャ)も作成することができます。具体例を以下に記載します。

{curl 6.0 applet}

{define-proc public {add a:int, b:int}:int
    || 足し算の結果を返す
    {return a + b}
}

{value
    {output {add 20, 30}}
}

これを作成するには、define-procマクロを利用します。あとは、メソッドを作成するようのと同様に記載し、利用いたします。(もちろんpublic以外のプロシージャも作成できます。)

ラムダ式

C#でもサポートされているラムダ式がCurlでも利用できます。これによりプロシージャを利用する際、コード量を減らすことができます。サンプルは以下のようになります。

|| 通常のプロシージャ
def x = {proc {i1:int, i2:int}:int
              {return i1 * i2}
           }

|| ラムダ式(fnというマクロ。C#と同様=>演算子を利用します。
def x = {fn i1, i2 => i1 * i2}

 

関連ドキュメント

匿名プロシージャと引数

 

クラスの作成

クラス

Curlでクラスを作成するにはdefine-classを使用します。

{define-class public Person
    || ここにコンストラクタ、メンバ、メソッド等をコーディング
}

クラスのインスタンス化は、以下のようにします。 

let p:Person = {Person}
もしくは
let p:Person = {new Person}

コンストラクタ

コンストラクタは、constructorを利用します。また、デフォルトコンストラクタはdefaultという名前で作成します。

{define-class public Person

    field private _name:String

    {constructor public {default
                                   name:String
                               }
         set self._name = name
    }
}

default以外のコンストラクタも複数作成することができます。

{define-class public Person

    field private _name:String

    {constructor public {default
                                   name:String
                               }
         set self._name = name
    }

    {constructor public {TestConstructor
                                   first-name:String,
                                   last-name:String
                               }
         set self._name = first-name & ” ” & last-name
    }
}

{let p1:Person = {Person “amori”}} || デフォルトコンストラクタ
{let p2:Person = {Person.TestConstructor “akira”, “mori”}} || コンストラクタTestConstructor

フィールド、ゲッター(getter)、セッター(setter)

フィールドは上記の例の中の_name:Stringのように記載します。

また、ゲッター、セッターをCurlでは定義できます。Java等ではgetName、setNameのような名前でメソッドとして定義するのが通常ですが、Curlではゲッター、セッターをsettergetterを利用して、定義することができます。

{define-class public Person

    field private _name:String

    {getter public {name}:String
        {return self._name}
    }

    {setter public {name _name:String}:void
        set self._name = _name
    }

    {constructor public {default
                                   name:String
                               }
         set self._name = name
    }
}

{let p:Person = {Person “amori”}}
{output p.name}  || ゲッター
{set p.name = “hokada”} || セッター

また、getter、setterを簡単に記述するために、以下のサンプルのように、フィールドにアクセサとして記述することができます。

field public-get private-set x:String
field public-get package-set y:int
field public-get z:bool

 

メソッド

メソッドの定義には、methodを利用します。

{define-class public Person

    field private _name:String

    {constructor public {default
                                   name:String
                               }
         set self._name = name
    }

    || 戻り値 : Stringのメソッド
    {method public {toString}:String
        {return {format “I’m %s.”, self._name}}
    }
}

クラスの継承

クラスを継承するには、inheritsを利用します。また、construct-superを利用して、親クラスのコンストラクタをコールすることができます。さらに親クラスのメソッド等をコールするには、superを利用します。

{define-class public Member
    {inherits Person} || 親クラス

    field _id:int

    {getter public {id}:int
          {return self._id}
    }

    {constructor public {default
                                   id:int,

                                   name:String
                               }
         {construct-super name}
    }
}

{let m:Member = {Member 1, “amori”}}
{output m.id}
{output m.name} || 親クラスのゲッター

また、Curlでは多重継承もサポートしております。多重継承をするには、inheritsの中に複数のクラスをカンマ区切りでコーディングします。

{define-class public Child
    {inherits Oya1, Oya2, Oya3} || 親クラス(多重継承)

    || …….
}

抽象クラス(abstract)

CurlではJavaのようなインターフェースはサポートされていません。ただし、抽象クラスを作成することはできます。(もちろんabstractなメソッド)これを利用するには、修飾子にabstractを指定します。

{define-class public abstract AbstractPerson

    {method public abstract {toString}:String } 
}

アクセス属性

Curlのアクセス属性は、public、private、package、protectedを指定できます。それぞれの説明は以下のとおりです。

public 全コードがメソッドを呼び出すことができることを示します
package 同じパッケージ内のコードのみがメソッドを呼び出すことができることを示します(デフォルト)
protected 同じパッケージ内のコードまたはサブクラス内のコードのみがメソッドを呼び出すことができることを示します
private 同じクラス内のコードのみがメソッドを呼び出すことができることを示します

アクセス属性「library」

バージョン7からlibrary属性を付与できるようになりました。これは同じマニフェスト内ではpublicのような動きをし、違うマニフェストからはpublicとしてアクセスできないというものです。これにより、ライブラリなどで内部的にはパッケージ間で利用したいが、外部公開したくない場合にやくにたちます。(これはJava7のmoduleのような機能)

 詳細は、こちらを参照ください。

 

ファクトリの作成

Curlではファクトリパターンを“ファクトリ”という機能を利用して、簡単に実装できます。これはfactoryを利用します。 

|| TestFactory
{define-class public abstract TestFactory

  {getter public abstract {name}:String }

  {factory public {default
                      switch:int = 1
                  }:TestFactory
    {return
        {if switch == 1 then
            {Class01} || 子クラスのインスタンス生成
         else
            {Class02} || 子クラスのインスタンス生成
        }
    }
  }
}

|| Class01
{define-class public Class01 {inherits TestFactory}
  {getter public {name}:String
    {return “class 01”}
  }
}

|| Class02
{define-class public Class02 {inherits TestFactory}
  {getter public {name}:String
    {return “class 02”}
  }
}

{do
    def fac = {TestFactory switch = 2}
    {output fac.name} || 実行結果はClass02.nameの”class 02”
}

ファクトリの詳細はこちらをご覧ください。

パッケージの作成

パッケージの作成

Curlでパッケージを作成するには、まず以下のようにヘラルドにpackageを指定します。そのファイル内にpackage式を利用し、パッケージの内容を記述します。

{curl 6.0 package}

{package COM.CURLAP.ABC,
    author = “amori”,
    copyright = “Copyright ….”,
    version = “1.0”
}

|| ここに、クラスやプロシージャ等を定義していく。

ちなみに、Javaのようにディレクトリをパッケージ名と合わせる必要はありません。また、いくつかのファイルから構成される場合も、このpackageを指定しているファイル内で、includeを利用してファイルを読み込むことができます。 

{curl 6.0 package}

{package COM.CURLAP.ABC,
    author = “amori”,
    copyright = “Copyright ….”,
    version = “1.0”
}

|| クラス等を定義した外部ファイルを読み込む
{include “abc1.scurl”}
{include “abc2.scurl”}

|| 直接クラス等を記載できる。
{define-class public A
    || …
}
{define-class public B
    || …
}

パッケージのインポート

パッケージをインポートするにはimportを利用します。 

{import * from COM.CURLAP.ABC,
    location = “load.scurl” || 例えば上記のファイル名がload.scurlの場合
}

スーパーパッケージ

java7で出てきたsuperpackageのようなことも可能です。例えば以下のようにスーパーパッケージ(ここではCOM.CURLAP.SUPERPACK)を作成し、そのパッケージに含めるサブパッケージ(ここではCOM.CURLAP.AとCOM.CURLAP.B)をpublic属性付きでimportします。

{curl 6.0 package}

{package COM.CURLAP.SUPERPACK}

{import public * from COM.CURLAP.A}
{import public * from COM.CURLAP.B}

これをアプリケーションで利用する場合には、{import * from COM.CURLAP.SUPERPACK}と記述しますと、COM.CURLAP.AもCOM.CURLAP.Bもimportできます。

詳細については、こちらを参照ください。

基本的なサーバ連携アプリケーション

Curlはクライアントサイドに特化した言語のため、サーバーサイドの技術に依存しません。そのため、サーバーサイドとのインターフェースをあわせればどんなサーバーサイドの技術とも連携できます。ここではサーバーサイドの技術としてServletを利用し、HTTPでの通信を行う基本的なCurlの実装方法を紹介していきます。

 

1. 環境構築

サンプルで使用する環境について説明します。
※サンプルでは以下の環境を前提としていますが、CurlではHTTP通信ができれば問題ないので、独自でその他の環境を構築しても問題ありません。ただし、レスポンスとしてXMLを返す必要がありますので、その点だけ注意が必要になります。

1.1. データベース
サンプルではデータベースとしてAccessを使用するのでデータソースの登録が必要になります。管理ツールより、ODBCデータソースの[システムDSN] or [ユーザDSN]に、サンプルのMDBファイル[sampledb.mdb]を[SampleData]の名前で登録してください

1.2. サーバーサイド
サンプルではサーバーサイドとしてServletを使用しますのでServletが動作する環境が必要になります。以下のものが動作するようにインストールおよび初期設定を行ってください。
・JDKのインストール
・Tomcatのインストール
インストール後、Tomcatをインストールしたフォルダのwebappsフォルダに/sampleフォルダをコピー後、Tomcatを起動してください。

<動作確認>
ブラウザのアドレス欄に[http://localhost:8080/sample/SampleService] と入力します。
以下のXMLが表示されれば、正常に起動しています。

 1.3. クライアントサイド
任意のフォルダに/sample_curlフォルダをコピーしてください。Curlのアプレットをfile:で実行する場合、特権の設定が必要になるので、以下の設定を行ってください。
・「スタート」-「プログラム」-「CurlRTE」-「Curlコントロールパネル」を選択します。
・「セキュリティ」タブを選択し、「このコンピュータ」で「特権ディレクトリの追加」をクリックし、先ほどコピーしたフォルダのパスを入力します。

<動作確認>
/sample_curlフォルダ内の○○.curl(sample1.curl、sample2.curl)をダブルクリックしてブラウザにCurlのアプレットが表示されれば、正常に起動しています。

 

この状態から検索ボタンを押下することによってDBからデータを取得し、結果が画面に表示されます。

検索条件(name)に値(111)を入力し検索ボタンを押下すると以下のようになります。

2. 通信の流れ

通信をする際の流れは以下の通りになります。
Curlでリクエストデータを生成し、サーバサイドに対してHTTPリクエストを送信します。サーバーサイドでそのリクエストを受信し、ロジックを実行します。その後、クライアントサイドにレスポンス(XML)を返却し、クライアントサイドでそのレスポンスを解析およびデータオブジェクト(Curlのデータオブジェクト)を生成します。
通信の流れを以下の図で示します。

 

 3. リクエスト処理

3.1. HttpForm.submit-open
HttpForm内に配置されたコントロール(TextFieldやDropdownリストなど)のnameオプションにリクエストのパラメータ名を指定し、HttpForm.submit-openメソッドを使用することによりサーバーサイドにリクエストを送信することができます。このメソッドの戻り値がレスポンスになりますので、レスポンス処理にこの戻り値を渡し、レスポンスの解析を行います。

{let hf:HttpForm =
    {HttpForm
        ||通信先URL
        {url “http://localhost:8080/sample/SampleService“},

        ||HTTPリクエストメソッド
        ||HttpRequestMethod.post or HttpRequestMethod.get
        method = HttpRequestMethod.post,
       
        ||サーバーに送信される要求のエンコード タイプ
        ||HttpFormData.urlencoded-mime-type or HttpFormData.multipart-mime-type
        encoding = HttpFormData.urlencoded-mime-type,

        ||パラメータに使用する既定の文字エンコード
        default-character-encoding = “utf8”,
       
        width = {add-stretch},
        {spaced-vbox
            margin = 5pt,
            halign = “right”,
            {HBox spacing = 5pt, width = {add-stretch},
                {TextFlowBox “name”},
                {TextField name = “name”, width = 3cm},
                {TextFlowBox “address”},
                {TextField name = “address”, width = 3cm},
                {Fill}
            },
            {CommandButton
                label = “検索”,
                {on Action do
                    {with-busy-cursor
                        {try
                            {with-open-streams response:#TextInputStream =
                                ||リクエスト送信
                                {hf.submit-open
                                    character-encoding={get-character-encoding-by-name “utf8”}}
                             do
                                {if-non-null response then
                                    ||レスポンス解析→データオブジェクト生成
                                    let rs:#RecordSet = {parse-response response}
                                    set rg.record-source = rs
                                }
                            }
                         catch e:HttpException do
                            {popup-message “通信エラーが発生しました。:” & e.message}
                         catch e:Exception do
                            {popup-message “エラーが発生しました。:” & e.message}
                        }
                    }
                }
            }
        }
    }
}

 

3.2. HttpFile.http-read-open
HttpFormを使用せずにリクエストを送信する方法もあります。この方法を使用することによってより柔軟な通信が可能になります。HttpFormDataインスタンスを生成し、このインスタンスに送信したいリクエスト情報を追加します。その後、HttpFile.http-read-openメソッドを使用することによりサーバーサイドにリクエストを送信することができます。このメソッドの戻り値がレスポンスになりますので、レスポンス処理にこの戻り値を渡し、レスポンスの解析を行います。 

|| —————————————————————
|| HttpFormData生成プロシージャ
|| —————————————————————
{define-proc public {create-http-form-data
                        mime-type:String = HttpFormData.urlencoded-mime-type,
                        default-character-encoding:CharEncoding = CharEncoding.utf8,
                        request-parameter:{HashTable-of String, String} = {{HashTable-of String, String}}
                    }:HttpFormData
    ||デフォルトのHttpFormData作成
    let form-data:HttpFormData = {HttpFormData
                                     mime-type                  = mime-type,
                                     default-character-encoding = default-character-encoding
                                 }
    {for s:String key k:String in request-parameter do
        {form-data.append {HttpFormStringParam k, s}}
    }
   
    {return form-data}
}

 

|| —————————————————————
|| データ取得プロシージャ
|| —————————————————————
{define-proc public {get-data
                        url-string:String,
                        request-parameter:{HashTable-of String, String}
                    }:RecordSet
    let http-file:HttpFile = {{url url-string}.resolve} asa HttpFile
    let form-data:HttpFormData = {create-http-form-data request-parameter = request-parameter}
    let record-set:#RecordSet

    {with-busy-cursor
        {try
            {with-open-streams response:#TextInputStream =
                ||リクエスト送信
                {http-file.http-read-open
                    character-encoding = “utf8”,
                    request-method = HttpRequestMethod.post,
                    request-data = form-data
                }
             do
                {if-non-null response then
                    set record-set = {parse-response response}
                    {record-set.commit}
                }
            }
         catch he:HttpException do
            {popup-message “通信エラーが発生しました。:” & he.message}
         catch e:Exception do
            {popup-message “エラーが発生しました。:” & e.message}
        }
    }

    {return {non-null record-set}}
}

 

4. レスポンス処理

4.1. DefaultHandlerを継承
SAXParserを使用し、独自のXMLハンドラクラスのインスタンスをセットすることによりXMLの解析を行います。

|| —————————————————————
|| レスポンス解析プロシージャ
|| —————————————————————
{define-proc public {parse-response
                        tis:TextInputStream
                    }:#RecordSet
    ||SAXParserインスタンスを生成
    let parser:SAXParser = {SAXParser}
    ||XMLハンドラインスタンスを生成
    let response-handler:ResponseHandler = {ResponseHandler}
    ||SAXParserにXMLハンドラをセット
    {parser.set-content-handler response-handler}
    ||TextInputStreamをSAXParserのインプット用に変換
    let source:InputSource = {InputSource character-stream = tis}
    ||解析処理
    {parser.parse source}
    {return response-handler.record-set}
}

|| —————————————————————
|| XMLハンドラクラス
|| —————————————————————
{define-class public ResponseHandler {inherits DefaultHandler}
  field private _record-set:#RecordSet
  field private _data:StringBuf
 
  {getter {record-set}:#RecordSet
    {return self._record-set}
  }
 
  {constructor public {default}
    set self._data = {StringBuf}
    {construct-super}
  }
 
  {method public {start-document}:void}
 
  {method public {start-element url:String, name:String, qname:String, atts:Attributes}:void
    {switch name
     case “RecordSet” do
     case “Record” do
        {self.start-element-record atts}
    }
  }
 
  {method public {characters ch:StringBuf, start:int, length:int}:void
    {self._data.clear}
    {self._data.concat {ch.substr start, length}}
  }
 
  {method public {end-element uri:String, name:String, qname:String}:void
    {switch name
     case “RecordSet” do
        {if-non-null self._record-set then
            {self._record-set.commit}
        }
     case “Record” do
    }
  }
 
  {method public {end-document}:void}
 
  {method private {start-element-record atts:Attributes}:void
    {if self._record-set == null then
        let fields-array:{Array-of RecordField} = {{Array-of RecordField}}
        {for i:int = 0 below {atts.get-length} do
            {if-non-null {atts.get-local-name i} then
                {fields-array.append {RecordField {non-null {atts.get-local-name i}}}}
            }
        }
        set self._record-set = {RecordSet {RecordFields {splice fields-array}}}
    }
    let record:Record = {self._record-set.new-record}
    {for i:int = 0 below {atts.get-length} do
        {if-non-null {atts.get-local-name i} then
            {record.set {non-null {atts.get-local-name i}}, {atts.get-value i}}
        }
    }
    {self._record-set.append record}
  }
}

4.2. WSDKのエラボレーション使用
WSDKで提供されているAPIを使用することでXMLを解析することもできます。

|| —————————————————————
|| レスポンス解析プロシージャ
|| —————————————————————
{define-proc public {parse-response
                        tis:TextInputStream
                    }:RecordSet
    let response-xml:XDMElement = {build-xml preserve-whitespace? = true, tis}.root
    let e:XmlElaboration =
        {xml-elaboration
            {“RecordSet” {…:RecordData}:RecordSet
                {RecordSet
                    {RecordFields
                        {RecordField “name”, domain = String},
                        {RecordField “address”, domain = String},
                        {RecordField “phone”, domain = String},
                        {RecordField “number”, domain = String},
                        {RecordField “amount”, domain = String}
                    },
                    {splice …}}
            }
            {“Record” RecordData}
        }
    {return {e.elaborate response-xml} asa RecordSet}
}

5. サンプルソース

・ sample1.curl(HttpForm, DefaultHandler)
・ sample2.curl(HttpFile, WSDKエラボレーション) 

サンプルソースのダウンロード

 

データ型

Curlは、以下のような多くのデータ型をサポートしています。

プリミティブ型(整数、浮動小数点数、ブール値、文字、数量)

整数

小数部を持たない数値です。
int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、byte

浮動小数点

小数部を持つ数値です。
float、double

ブール値

trueまたはfalseのいずれかを格納します。デフォルトはfalseです。
bool

文字

文字(Curl言語の文字はUnicode標準に準拠しています。)
char

数量

数量は、値とそれに関する測定単位が一体となったものです。
Acceleration、Angle、Area、Distance、EmDistance、Fraction、Frequency、Intensity、Mass、Percent、PixelDistance、Resolution、Speed、Time

クラス型

これはdefine-classで作成されるクラスです。文字列、コレクションなどの組み込むクラスも含まれます。

プロシージャ

プロシージャをproc-typeマクロを利用し、変数の型として定義できます。詳細はプロシージャとクロージャを参照ください。

列挙型

指定した要素の固定リストから成るデータ型です。これはdefine-enumで定義することができます。

any型

任意のデータ型を格納できます。プリミティブを含むこともできます。デフォルトはnullです。変数に格納される値のデータ型がわかっている場合は、anyは利用しないでください。

 

各データ型の詳細は、こちらをご覧ください。

 

 

例外の作成とハンドリンク

CurlにもJava等同様に以下のように例外をtry~catchできます。

{try
    || 処理
 catch 例外1 do
    || 例外1発生時の処理
 catch 例外2 do
    || 例外2発生時の処理
 finally
    || finally処理

また例外クラスを作成するには、以下のサンプルのように、Exceptionクラスを継承して作成します。

{define-class public MyException {inherits Exception}
    {constructor public {default message:String}
         {construct-super message}
    }

このような例外を発生させるには、throwマクロを利用します。

{throw {MyException “My exception has occurred.”}}

以下はサンプルです。

{try
    let i:int = 0
    {if i < 0 then
        {throw {MyException “exception!!”}
    }
 catch e:MyException do
    {popup-message e.message}
}     

 

 

CurlとRails2.0で作るRestfulアプリケーション

Ruby on Railsではバージョン1.2からRESTがサポートされました。これはPUT・GET・DELETE・PUTによるHTTPリクエストによりRailsにアクセスすることができます。これを利用し、CurlとRailsとの連携サンプルを紹介します。

環境セットアップ

まずはMySQLをインストールし、以下のSQL文を実行することでテーブルとデータを作成します。

CREATE DATABASE dev;
USE dev;

DROP TABLE employees;
CREATE TABLE employees (
  id INT auto_increment NOT NULL,
  login CHAR(16),
  firstname CHAR(32),
  lastname CHAR(32),
  phone CHAR(15),
  status INT,
  PRIMARY KEY (id)
);
INSERT INTO employees VALUES(default,’hmino’,’Hiroki’,’Mino’,’0123-45-6789′,1);
INSERT INTO employees VALUES(default,’mishikawa’,’Masaki’,’Ishikawa’,’03-1234-1234′,0);
INSERT INTO employees VALUES(default,’hokada’,’Hitoshi’,’Okada’,’00-0000-0000′,1);
INSERT INTO employees VALUES(default,’tyamamoto’,’Takeshi’,’Yamamoto’,’090-0909-0909′,1);
INSERT INTO employees VALUES(default,’amori’,’Akira’,’Mori’,’111-111-111′,0);

次にRubyとRails2.0をインストールします。Rubyはワンクリックインストーラで簡単にインストールできます。(http://www.rubyonrails.org/)その後、以下のコマンドを実行し、Railsをインストールします。

> gem install rails -y 

 インストールに成功しましたら、”rails demo”コマンドによりdemoディレクトリを作成します。

> rails demo 

demoディレクトリが作成されましたら、その配下のディレクトリにあるconfig/database.ymlファイルを開き、データベースの設定を行います。例えば以下のような設定にします。

development:
  adapter: mysql
  database: dev
  username: user1
  password: pass1
  host: localhost

さらに以下のコマンドを実行しますと簡単なRailsアプリケーションが出来上がります。

> ruby script/generate scaffold Employee 

最後にWebサーバ(WEBrick)を起動します。これは3000ポートで立ち上がります。 

> ruby script/server 

ブラウザで以下のURLを開き、稼動確認ができます。
http://localhost:3000/employees/

 また、RESTで稼動確認するためには以下のURLを開きます。
http://localhost:3000/employees.xml

CurlからRuby on Railsにアクセス

Railsでは以下のようなHTTPアクセスでCRUD(Create/Read/Update/Delete)を実現できるようです。

一覧表示  GET  /employees.xml 
1件表示 GET /employees/1.xml or 2.xml ….
新規作成 POST /employees
更新 PUT  /employees/1 
削除 DELETE /employees/1

※Curl6.0では、PUT、DELETEメソッドがサポートされていませんので、サンプルプログラム内ではGETやPOSTにて代用しています。(将来的にはサポートされる予定です。)

そこで、簡単にアクセスできるレコードセットRailsRecordSetをサンプルとして作成してみましたので、それを用いて連携させてみることとします。まずはRailsRecordSetクラスの入っているパッケージCOM.CURLAP.EXAMPLES.RAILSをimportします。次にRailsRecordSetを引数にRailsサーバのパス・エンティティ名・RecordFieldsを指定して生成します。

{import * from COM.CURLAP.EXAMPLES.RAILS,
    location = “rails/load.scurl”
}

{value
    let record-set:RecordSet =
        {RailsRecordSet
            “http://localhost:3000“,  || rails server
            “employee”,               || entity name
            {RecordFields
                {RecordField “id”, domain = int},
                {RecordField “login”, domain = String},
                {RecordField “firstname”, domain = String},
                {RecordField “lastname”, domain = String},
                {RecordField “phone”, domain = String},
                {RecordField “status”, domain = String}
            }
        } 

この生成したRailsRecordSetをRecordGridコントロールに紐付けることにより、簡単に画面表示させることができます。

        {RecordGrid
            record-source = record-set,
            alternate-row-background = “#ccffff”
        },

また、RecordGrid内でのレコード追加・修正後、commitボタンを押下することにより、データベースへの更新を行うこともできます。

コードサンプルのダウンロード

Ruby on Rails連携サンプル

 

バックグラウンドでアプレットを実行

表示されている画面(アプレット)とは別にバックグラウンドで動くアプレットの作成方法をここでは紹介していきます。これにはCurlのサブアプレット機能を利用します。ここでは、バックグラウンドでタイマーを使い定期的に何か処理をするプログラムの起動・停止例を紹介します。

まずは、呼び出し側(ここではparent.curl)とバックグラウンド実行する側(ここではchild.curl)との2つの.curlファイルを用意します。child.curlの中には、以下のように通常のクラスを用意します。

{define-class public Child

  field displayed-string:String
  field private timer:Timer

  {constructor public {default}
    set self.displayed-string = “child”
    set self.timer =
        {Timer
            enabled? = false,
            interval = 1s,
            {on TimerEvent do
                {output self.displayed-string}
            }
        }
  }

  {method public {start}:void
    {self.timer.enable}
  }

  {method public {change-displayed-string str:String}:void
    set self.displayed-string = str
  }

  {method public {stop}:void
    {self.timer.disable}
  }
}

このファイルの中で、上記クラスのオブジェクト(Child)を以下のようにregister-applet-invoke-handlerを登録することで、親側から子供側のメソッドを呼び出すことができます。

{{get-the-applet}.register-applet-invoke-handler
    {Applet.applet-invoke-handler-for-object {Child}},
    verifier = {proc {}:bool {return true}}
}

次にparent.curlでは、AppletDataを継承したクラスを作成し、child.curlを引数に指定して、そのクラスのオブジェクトを生成します。このオブジェクト(child)に対して、child.curl内のChildクラスのメソッドを実行します。メソッドを実行する際には、applet-invokeもしくはapplet-invoke-asyncを利用します。ここではバックグラウンドで実行させておきたいので、applet-invoke-asyncを利用します。 

{child.applet-invoke-async
    finish-proc = {proc {ex:#Exception, result:any}:void},
    “start” || startメソッドで、タイマー開始
}

実行結果

child
child
child

サポートバージョン

RTE 6.0以上

サンプルコード

画面なしサブアプレットのサンプル

 

コマンド実行

Curlでコマンドを実行する方法を説明します。Curlでコマンドを実行するには、spawn-host-processもしくはspawn-host-shellプロシージャを利用します。ここではpingコマンドを実行する例を紹介します。

{curl 6.0 applet}

{import * from CURL.RUNTIME.HOST-PROCESS}

{value
    def host-process =
        {spawn-host-process 
            read-stdout? = true,
            read-stderr? = true,
            “ping”,
            {StringArray “www.curlap.com“}
        }
    {with-open-streams stream = {host-process.read-open-stdout} do
        {while not stream.end-of-stream? do
            {output {stream.read-line}}
        }
    }
}

spawn-host-processプロシージャの引数にコマンド名(もしくはコマンドのURL)をセットし、引数をStringArrayで渡します。結果はHostProcessクラスのread-open-stdoutメソッドにて標準出力をTextInputStreamにて受け取ることができます。

実行結果

Reply from xxx.xxx.xxx.xxx: bytes=32 time=9ms TTL=48

Reply from xxx.xxx.xxx.xxx: bytes=32 time=9ms TTL=48

Reply from xxx.xxx.xxx.xxx: bytes=32 time=9ms TTL=48

Reply from xxx.xxx.xxx.xxx: bytes=32 time=9ms TTL=48

Ping statistics for xxx.xxx.xxx.xxx:

    Packets: Sent = 4, Received = 3, Lost = 1 (25% loss),

Approximate round trip times in milli-seconds:

    Minimum = 9ms, Maximum = 9ms, Average = 9m