MOSA Multi-OS Software Artists

MOSAはソフトウェア開発者を支援します

  • iPhone/iPod touch アプリ紹介
  • MOSA掲示板
  • 活動履歴
  • About MOSA(English)

MOSA Developer News[MOSADeN=モサ伝]第313号

2008-09-16

目次

  • りんご味Ruby         第32回  藤本 尚邦
  • 藤本裕之のプログラミング夜話   #145
  • 高橋真人の「プログラミング指南」  第143回
  • 書籍紹介       Cocoa勉強会会誌 Cocoa Life Vol.4

りんご味Ruby   第32回  藤本 尚邦

第27,28回でMechanizeというライブラリについてちらっと触れました。Mechanizeは、Webクライアントを操作するユーザの振る舞いをプログラムするためにあるようなライブラリです。YahooHonyakuのようなものを作るのにはまさにうってつけですね。ということで今回は、YahooHonyakuを実装しなおしながら、Mechanizeを紹介することにします。

■ Mechanize

MechanizeはWebサイトとのやりとり(相互作用)を自動化するためのライブラリで、

 ・cookieの自動送受信
 ・リダイレクトの自動追跡
 ・リンクの管理・追跡
 ・フォームの編集・送信
 ・訪問履歴


などの機能を持っています。いわば、Webクライアントを操作するユーザの振る舞いをプログラムするためのライブラリみたいなものです。インストールにはターミナルでrubygemsのgemコマンドを使います。

 $ sudo gem install mechanize

インストールが終わったら、ドキュメントのGUIDEやEXAMPLESを見ながら、irbコマンドを使って試してみるとよいでしょう。以降、YahooHonyaku でやるべきことを irb で試していくことにします。

 $ irb --simple-prompt
require 'rubygems'
require 'mechanize'

Mechanizeの核はWWW::Mechanizeクラスです。このクラスのインスタンスがWebクライアントとして振る舞うオブジェクトになります。

 agent = WWW::Mechanize.new                # Webクライアントを生成
 agent.user_agent_alias = 'Mac Safari'     # Safariとして振る舞う
 agent.get("http://honyaku.yahoo.co.jp/")  # HTTP GET
 agent.page                                # サーバが返したページ

WWW::Mechanize#user_agent_alias= メソッドでは、HTTPリクエスト時のヘッダ
のUser-Agentの値を変更することができます。あらかじめ有名どころのブラウ
ザの値が準備されていて、'Mac Safari'と指定すると、User-Agentの値が
Safari と同じものになります。

(補足 agent.user_agent_alias = 'Mac Safari' の `=' は代入構文ではなく、
インスタンスメソッド WWW::Mechanize#user_agent_alias= の呼出です)


リンクやフォームには以下のようにアクセスします:

 page = agent.page
 page.links                             # 全てのリンク
 page.links.with.text("使い方")         # 「使い方」へのリンク全て

 page.forms                             # 全てのフォーム
 page.forms.with.name('textFormEntry')  # textFormEntryフォーム全て


コメントのところにわざわざ「全て」と書いているのは、これらがみなWWW::Mechanize::List というコンテナクラス(Arrayのサブクラス)のインスタンスを返すことを強調するためです。with.name や with.text といったメソッドにより条件で絞り込まれたWWW::Mechanize::List が返されます。

フォームに関しては、条件にマッチした最初のフォームを返す form_with というメソッドが用意されています:

 form = agent.page.form_with(:name=>'textFormEntry')

次はフォーム内のフィールドへのアクセス方法です。

 form.fields                           # 全てのフィールド
 form.fields.with.name('text')         # name属性がtextの全フィールド
 form.radiobuttons                     # 全てのラジオボタン
 form.radiobuttons.with.name('eid')    #name属性がeidの全ラジオボタン

フォーム内のフィールド(inputやtextareaなど)の値には、タグのname属性の値
と同じ名前のメソッドでアクセスすることもできます(name属性の値がRubyのメ
ソッド名として有効な場合のみ)。

 form.text            # "text"フィールドの値
 form.text = "hello"  # "text"フィールドの値を入力


次に、ラジオボタンを操作して翻訳モードを設定しましょう。

 form.radiobuttons.with.name('eid').with.value('CR-EJ')[0].check


with.name と with.value を続けて呼び出すことにより、AND検索のような効果でラジオボタンの絞り込みが出来ます。ここでは、英日モードのボタンを絞り込んでチェックしました。

以上で、必要な値をフィールドにセットしました。最後に、翻訳を実行して返されたページのフォームから翻訳結果を取り出します。

 form.submit                                   # 翻訳実行
 page = agent.page                             # 返されたページ
 form = page.form_with(:name=>'textFormEntry') # 返されたフォーム
 form.trn_text   # => "こんにちは"               翻訳結果


正しい翻訳結果は得られたでしょうか? ラジオボタンまわりなど、多少フィールドの操作方法に慣れる必要があるかもしれませんが、そこをクリアできれば、とても手軽にWebクライアントの自動化をプログラムすることができそうです。

■ [参考] リンク

Mechanize (リファレンスマニュアル)
http://mechanize.rubyforge.org/

GUIDE
http://mechanize.rubyforge.org/mechanize/files/GUIDE_txt.html

EXAMPLES
http://mechanize.rubyforge.org/mechanize/files/EXAMPLES_txt.html

■ [参考] Mechanizeで実装した yahoo-honyaku.rb のソース

class YahooHonyaku
 require 'rubygems'
 require 'mechanize'

 def self.translate(mode, text)
   new.translate(mode, text)
 end

 def translate(mode, text)
   open_top_page unless top_page_opened?
   html = post_text(mode, text)
   parse_result(html)
 end

 private

 def open_top_page
   @agent = WWW::Mechanize.new
   @agent.user_agent_alias = 'Mac Safari'
   @agent.get "http://honyaku.yahoo.co.jp/"
 end

 def top_page_opened?
   @agent ? true : false
 end

 def _eid_(mode)
   mode = mode.to_s.upcase
   case mode
   when "JC" then "CR-JC-CN"
   else           "CR-#{mode}"
   end
 end

 def post_text(mode, text)
   eid = _eid_(mode)
   form = @agent.page.form_with(:name=>"textFormEntry")
   form.radiobuttons.name('eid').value(eid)[0].check
   form.text = text
   form.submit
 end

 def parse_result(page)
   page.form_with(:name=>"textFormEntry").trn_text
 end
end

藤本裕之のプログラミング夜話 #145

 さて前回までながながゆるゆると半ば意図的に結論を先送りにしているような調子で「アプリケーションプログラマに未来はあるか」(あれ? 「アプリケーションプログラマは絶滅危惧種か」だったっけ?)というテーマに沿ってそれを考える上で参考になるであろう背景めいたものについて書いてきたわけだが、そろそろ皆さんもお気付きのようにワタシの結論は大変悲観的なものである。

 ひとことで言ってしまえばですね、「パーソナルコンピュータにおけるアプリケーションプログラムの市場というものは大方のヒトが持っているイメージより小さく、コンピュータ・プログラマがアプリケーション・ソフトを開発して継続的に口に糊して行くというのはかなり困難なことである。しかもこの情勢は、パーソナルコンピュータの普及、出荷台数の増加などによって好転する
ことは期待できない」ってことである。

 いやオレとて書いててツラい。実を言えばここで「ワタシは自分を客観的に見ることが出来るんです、皆さんとは違うんです」とギャグを飛ばそうかと思ったが、フクダは首相を辞めても国会議員だし国会議員辞めても食うに困るこたぁないだろうが、オレの場合この分析が正しいってことはつまり早晩食い詰めるということであり、おマンマの食い上げになったら自分を客観的に見る
ことが出来てもなんの足しにもならないのである。ホンマ、笑い事ぢゃないっすよ。

 でも、まぁどうもそういう結論であります。さてどうしましょうか。

 政治の話題が出たのでマクラをそっちに放り投げるが、ここ数日自民党の総裁選だってんで、アソウ、イシハラ、イシバ、コイケ、ヨサノ(あいうえお順です、為念)、5人の候補者が雁首並べてピーチクパーチク自らの抱負というか政見というかワタシが総裁に当選したあかつきにはあーしてこーしてめでたしめでたしですみたいなことを喋っているではないか。おれ思うんだがあのヒト達って「具体的」って言葉の意味が全然具体的に解ってないのね。

 イワク「今後数年で年金問題も解決し、国民生活のさまざまな不安を払拭していい国(美しい国ぢゃなくて)を作る」ちうのはわかったが、どうやってそれをするのかって話になるとどいつもコイツも「きちんと」とか「ちゃんと」とか「しっかり」とか「もっこり」とか(最後のはウソです)、そういう「副詞」ばっかりなのな。いままでそれを「きちんとちゃんとしっかり」やって来
なかったから今このテイタラクなんでしょう? ならばまたぞろ「きちんとちゃんとしっかりやる」なんちうのは全然「具体的」とちゃうでしょうが。

 何を言いたいかと言うと、ワタシは上のような連中とは違うんです、悲観的ながらもそっと具体的に「ほんぢゃどうしましょうか」という議論をしますよ、ということである。すなわち絶滅危惧種たるアプリケーションプログラマが今後の生存競争を生き残っていくための処方せんを考えて行きましょう、と。

 まず第一に考えるのは、前述のごとく小さい市場でも脱落して行く他者を尻目に自分だけは生き残る、その小さい市場の中で生存に必要なエネルギーを(だんだん用語が生物学っぽくなってきたな)確保できる個体はいるというコトである。どのようなアプリケーションなら(これは「どのようなアプリケーションプログラマなら」という問題ではない。解りますよね?)そうなれるの
か、どのようなアプリケーションが今現在そうなっているのか、を次回からちょっと見て行こう。
                       (以下次回 2008_09_12)

高橋真人の「プログラミング指南」第143回

プログラマのためのオブジェクト指向再入門(49)

〜XcodeによるPowerPlant X入門(32)〜

 こんにちは、高橋真人です。早速続きに入ります。
 ウインドウ上で特定のViewを検索するためにFindViewByID()という関数を使うということまでお話ししました。
 この、トップビューを得てそれを基に目的のViewを探すというパターンは、HIViewで行うのと同じです。そうではあるのですが、PPxではHIViewのフレームワークが提供しているHIViewFindByID()を使ってはいません。
 その理由としては、PPxでのViewの識別子がHIViewのものと違うからということも挙げられるでしょう。
 HIViewでは、Viewの識別子としてHIViewIDというものを使います。これは実体的には昔からの技術であるControl Managerでコントロールを識別する際に利用するControlIDと同じものです。定義としては、

struct ControlID {
  OSType signature;
  SInt32 id;
};
typedef struct ControlID ControlID;
typedef ControlID HIViewID;


というような感じになります。OSTypeというのも実体は32bitの整数型ですから、この識別子を使うと理屈的には40億×40億以上という膨大な固有のIDを作れます。
 ただ、HIViewでプログラムを組まれた方であれば恐らく、「Viewの検索がちょっと面倒だ」と思われたことがあるのではないでしょうか。
 というのは、単に1つのViewを見つけるためにも、HIViewID型の変数を用意し、それに然るべき値を設定してからHIViewFindByID()に渡してやる必要があるからです。
 HIViewが、そんな手間をかけてでもあえて識別子にこのような型を使用しているのは、決して膨大な数の識別子が必要だったわけではなく、単にControl Managerとの互換性を取りたかったからなのだと思います。
 ですが、過去のしがらみを引きずらない方針のPPxではその部分は切り捨て、単純に一つの32ビット値のみをViewの識別子とすることにしたのだと思います。そのようにしたことで、今回のプログラムの例のように、直接関数の引数に100など数値を渡すことが可能になっています。
 では、PPxがこのように識別子の“型”を変えたことによって、HIViewとの互換性をどのように取っているのでしょう。互換性とは何のことを言っているのかといいますと、Interface Builderでの設定の話です。
 以前お話ししたことがあったと思いますが、PPxでは当初はInterface Builderを使ったGUIの編集ツールを使うつもりではなかったのです。恐らく、独自のGUI編集ツールの計画はあったのだと思いますが、Mac OS Xの新技術の導入ペースに追随するためには、独自のGUIツールを使うよりも純正のInterface Builderを使った方がいいと判断し、途中で方針を切り替えました。
 Interface Builderでは、CarbonサイドのViewはHIViewの系列となりますから、Interface Builder上で作成したViewのIDはHIViewIDの型となります。
 PPxでは、HIViewIDに指定された値のうち、idの値(上記、構造体の定義を参照)だけを利用するようにし、それをPPx::Viewの識別子としています。signatureの値は無視しますから、PPxのみで使う場合には、ここの値は入れなくても構いません。
 ところで、この識別子ですが、PPx::Viewのクラス定義の中には定義がありません。どこにあるかというと、PPx::Viewが継承している親クラスのうちの一つ、PPx::Identifiableというクラスの中です。ここに、ObjectIDT型としてmObjectIDというプライベートな変数があります。
 このObjectIDTという型がどういう定義なのかを探っていくと、PPxTypes.hというヘッダファイルに以下のような定義があるのが見つかります。

struct ObjectIDStruct { };
typedef IntegerType             ObjectIDT;


 今までの説明によればObjectIDTは単なる整数値であるはずなのに、何やらそんな単純なものではなさそうだと思われるかもしれません。実は、ここにはC++ならではワザが使われているため、ちょっとした“事情”があるのです。
 上記の引用部分は、PPxTypes.hファイルにおいては付け足しのようなもので、その前の部分にあるIntegerTypeという名のクラス定義が重要になります。ただ、クラス定義を見ただけでは、これが一体何をしようとしているのかは簡単に分からないと思いますので、少し説明します。
 まず、このクラスが何のためにあるのかということですが、それは「新たな整数型を定義できるようにするため」です。
 ヘッダファイルの冒頭のコメント部分に、この仕掛けの解説があります。英語ですがそんなに難しいものでもないと思うので「読んでいただければ、分かります」と言いたいところですが、それではあんまりなので説明します。
 C++にはオーバーロードという仕組みがあります。簡単に言うと、引数の構成が異なれば、同じ名前の関数を複数定義できるということです。(Cではできません)
 例として以下のようなプログラムを作ってみます。(実際に試される場合には、XcodeでCarbonアプリケーションとして作成してください)

#include 

void Foo(int a);
void Foo(long a);

int main()
{
     Foo(100);
}

void Foo(int a)
{
     std::cout << "a is int." << std::endl;
}

void Fool(long a)
{
     std::cout << "a is long." << std::endl;
}


 警告が出るかもしれませんが、ビルドはできるはずです。走らせてみると、"a is int."と表示されます。ご存知のようにリテラル値の100の型はintですから、intを引数にしたFoo()が呼ばれるわけです。これが関数のオーバーロードです。
 で、次にプログラムをちょっとだけ変更して以下のようにしてみます。

#include 

void Foo(UInt32 a);
void Foo(OSType a);

int main()
{
     Foo('TEXT');
}

void Foo(UInt32 a)
{
     std::cout << "a is UInt32." << std::endl;
}

void Foo(OSType a)
{
     std::cout << "a is OSType." << std::endl;
}


 こんどはどちらの関数が呼ばれると思いますか?
 ところが、残念なことにこのプログラムはビルドできません。2番目のFoo()のところで「定義が重複している」旨のエラーが出るはずです。
 どういうことかと言いますと、UInt32もOSTypeも、単にtypedefによって別名を与えられたものであり、実体はunsigned longそのものだからです。
 このような問題をどうやって回避するかが、ここで説明する仕掛けです。(次回に続きます)

◇MOSAからのお知らせと編集後記は割愛します◇

 

 MOSA Developer News   略称[MOSADeN=モサ伝]
        配信停止 mailto:mosaden-ml@mosa.gr.jp
 記事内容に関するご意見 mailto:mosaden-toukou@mosa.gr.jp
      記事投稿受付 http://www.mosa.gr.jp/?page_id=850
Apple、Mac OSは米国アップル社の登録商標です。またそのほかの各製品名等
はそれぞれ各社の商標ならびに登録商標です。
このメールの再配信、および掲載された記事の無断転載を禁じます。
特定非営利活動法人MOSA  http://www.mosa.gr.jp/
Copyright (C)2007 MOSA. All rights reserved.