Gemmaの日記 このページをアンテナに追加 RSSフィード

2011-01-04

コード書き初め「一二三」 12:09  コード書き初め「一二三」 - Gemmaの日記 を含むブックマーク  コード書き初め「一二三」 - Gemmaの日記 のブックマークコメント

書き初めに、以下のお題に答えます。 C++の問題ですが、 Javascript で。

1から1000までプリントせよ。ただし、ループ文や条件分岐文を使わずに。

c++ - Printing 1 to 1000 without loop or conditionals - Stack Overflow

コード(Firefox 専用)

var zero = function(f) function(x) x;
function succ(c) function(f) function(x) f(c(f)(x))
function add(n,m) function(f) function(x) m(f)(n(f)(x))
function multiply(n,m) function(f) function(x) n(m(f))(x)
function power(n,m) function(f) function(x) m(n)(f)(x)
function cton(c) c(function(x) {document.write(x + 1 + '\n'); return x + 1;})(0)
var two = succ(succ(zero));
var three = succ(two);
var five = add(two, three);
var thousand = power(multiply(two,five),three);
cton(thousand);

実行例:

http://eva-lu-ator.net/~gemma/hatena/hihumi.html

参考

道 (TAO) から一が生まれ, 一から二が生まれ, 二から三が生まれ, 三から万物が生まれ, 云々

Church numerals and Lambda Calculus

近況報告

新人1年目です。

仕事で Allegro Common Lisp を使っています。

いい処理系です。性能もいいし、ドキュメントもしっかりしてるし、 Emacs インタフェースもよくできてるし、マルチプラットフォームと国際化もバッチリだし。

うちの部長もよく言っていますが、確かに仕事で使うなら Allegro Common Lisp ですね。


うちの会社は ウェブサービス屋さんではないので、「入社したらウェブ技術とはお別れかな」と思ったら、全然そんなことなかったです。

おまけに、仕事で RDF とか セマンティックウェブの勉強もさせてもらいました。


書き初めといいつつ、年末年始もずっとコードを書いてました。

まとまった時間がとれたら片付けようと思っていたコードで、

Firefox 拡張で、 TCP/IP ソケットを開いて、 XMPP を nsISAXXMLReader でパースして、digest-md5 認証するやつができました。


今年もよろしくお願いします。

利用者利用者 2011/02/07 15:13 いつも利用させていただいております
ほとんどの板のURLをいれてもエラーが出てきて変換できなくなっています
ご確認お願いします

板エラー板エラー 2011/02/07 15:50 上記の方とは違いますが
私もhttp://gemmat.s206.xrea.com/matome/matome.cgi を重宝させてもらっています。
よろしければ対応お願いします。

利用者利用者 2011/02/07 17:52 直ったみたいです
お騒がせしました

けんせいけんせい 2011/07/06 17:13 はじめまして、chromeで文字化けするのですが
治す方法はありますでしょうか?

ほげほげほげほげ 2014/05/01 13:41 まとめサイトエディターはもう閉鎖でしょうか?
ずっとここ使ってたんで、できれば残しておいて欲しいです

ほげほげほげほげ 2014/05/01 13:52 あら使えるみたいです
サーバーエラーではないようでしたので閉鎖かと思いました
これからもよろしくお願いします

ほげほげほげほげ 2017/02/24 14:50 久しぶりに来てみました
まとめサイトエディター使わせてもらってます
いつもありがとうございます

 ほげほげほげほげ ほげほげほげほげ 2018/01/11 16:52 新年明けたので覗いてみました
相変わらずまとめサイトエディター使わせてもらってます
ありがとう
今後もよろしくお願いします
私しか使ってないとかないよね?

2009-12-19

Mozilla勉強会(#modest)に参加しました 00:49 Mozilla勉強会(#modest)に参加しました - Gemmaの日記 を含むブックマーク Mozilla勉強会(#modest)に参加しました - Gemmaの日記 のブックマークコメント

Mozilla 勉強会 « Mozilla Developer Street (modest)

Firefox3 Hacksにサインをいただきました

http://eva-lu-ator.net/~gemma/geocities/modest/firefox3.png

Firefox 3 Hacks ―Mozillaテクノロジ徹底活用テクニック

Firefox 3 Hacks ―Mozillaテクノロジ徹底活用テクニック

ノベルティをいただきました

  • クリアーファイル! レア物とのこと。

http://eva-lu-ator.net/~gemma/geocities/modest/novelty0.png

  • ノートパソコン用バッグ! Firefoxのアイコン付き! これはすごい

http://eva-lu-ator.net/~gemma/geocities/modest/novelty1.png

  • ステッカー、ボールペン、携帯ストラップ、ネックストラップなどなど!

ありがとうありがとう!

JetpackやMozillaの現状など、とても勉強になりました。次回もぜひお願いします。

2009-11-22

Firefoxと融合するインスタントメッセンジャー「Musubi」をリリースしました 18:43  Firefoxと融合するインスタントメッセンジャー「Musubi」をリリースしました - Gemmaの日記 を含むブックマーク  Firefoxと融合するインスタントメッセンジャー「Musubi」をリリースしました - Gemmaの日記 のブックマークコメント

Google Waveは、電子メールを置き換える可能性を秘めたWebサービスと言われています。

Musubiは、インスタントメッセージ(IM)を置き換える可能性を秘めたFirefoxアドオンです。

このアドオンで目指したのは、ブラウザとIMの融合です。

それは、Webアプリケーションに、HTTPを越えた更なる力、IMによるリアルタイム性を与えることです。

HTTPは、必要なときだけサーバに接続するので、更新があってもこちらからリクエストするまでわかりません。BBSやTwitterを何度もリロードするのはそのためです。

逆に、IMが使うXMPPは、サーバと接続を保って相互にやりとりするので、更新があればリアルタイムにメッセージが飛んできます。もうリロードはいりません。

Musubiでは、Webアプリケーションが自由にメッセージの内容を操作できるので、例えば以下のようなものが作れます。

チャット

  • テキストをメッセージ

おえかきチャット

  • 画像データをメッセージ

http://eva-lu-ator.net/~gemma/geocities/musubi/whiteboard.png

チェス

  • 棋譜データをメッセージ

http://eva-lu-ator.net/~gemma/geocities/musubi/chess.png

IMに似ているTwitterでいうと、例えば、Twitterでみんなとチェスをするというのは可能なわけです。

チェスの盤面は8x8の64文字で、140文字の制限に納まるから、みんなで棋譜データをつぶやけばいい。

だけど、Twitterを何度もリロードしなくちゃいけないし、棋譜データの解読が面倒くさいから何かWebアプリケーションが必要になる。

そこで、ブラウザとIMの融合が必要になってきます。

WWWは、3つの柱からなります。URI、HTTP、HTMLです。そこにIMがどう融合するか。

xmpp: URI

Musubiでは、新しくxmpp:というURIを定義しました。

人にリンクを張る(ブックマークすると、Twitterでいうフォローになるようにしてある)。
xmpp:teruakigemma@gmail.com
その人とチャットをするリンク
xmpp:teruakigemma@gmail.com#http://musubi.im/chat/
ジュリエットがロミオとチャットをするURI
xmpp://juliet@localhost/Musubi/romeo@localhost#http://musubi.im/chat/

HTTPとXMPP

http://eva-lu-ator.net/~gemma/geocities/musubi/archi.png

HTTPと別に、XMPPサーバとの接続を持ちます。

HTTPで静的なデータを、XMPPで動的なメッセージを、と住み分けることができます。

Google Waveは、Comet(HTTP long-polling)という技術で、HTTPの上でリアルタイム性を実現していますが、

スケーラビリティに不安があります。HTTPで無理をせず、専用に設計されたXMPPを使うのがMusubiです。

HTML

さきほど紹介したチェスなどは、誰でもHTMLとJavascriptで簡単に作れます。

musubi.jsを読み込んで、関数を2つ覚えるだけです!

// 初期化(メッセージを処理するrecv関数を渡す)
Musubi.init(recv);
// メッセージを送る。こんにちはこんにちは!
Musubi.send(<message><body>こんにちはこんにちは!</body></message>);

弱点

Firefoxに依存することと、Google Talkがあまり使われていないことです。

2009-09-25

Firefox拡張入門第7回(独自プロトコルの定義 - ttp) 20:27 Firefox拡張入門第7回(独自プロトコルの定義 - ttp) - Gemmaの日記 を含むブックマーク Firefox拡張入門第7回(独自プロトコルの定義 - ttp) - Gemmaの日記 のブックマークコメント

2ちゃんねるなどでリンク避けとして"ttp://..."を使うことがあります。

今回の拡張では、ttpプロトコルを定義して、"http://..."と同様に扱えるようにしましょう。

  1. ttpプロトコルを扱うXPCOMコンポーネントのクラスを登録します。
  2. そのクラスのnewURI関数で、URIの部品を受け取って、nsIURIのインスタンスを返します。(注)
  3. そのクラスのnewChannel関数で、↑で作ったnsIURIのインスタンスを受け取って、ttpプロトコルのためのnsIChannelのインスタンスを返します。ここではhttpプロトコルのnewChannel関数に処理を丸投げするので簡単です。

(注)"ここではhttpプロトコルのnewURI関数に処理を丸投げするので簡単です"・・・だと芸がないですもんね。

拡張のcomponentsディレクトリ以下に、jsファイルを置けば自動で認識してくれます。

//XPCOMコンポーネントを作るための便利ライブラリを使います。
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

//プロトコルを扱うクラス
function TTProtocol() {}

TTProtocol.prototype = {
  //クラスの説明
  classDescription: "TTP Protocol",
  //XPCOMコンポーネントのID。 末尾の"...=ttp"に注目。
  contractID      : "@mozilla.org/network/protocol;1?name=ttp",
  //XPCOMコンポーネントのUUID。UUIDは各自で生成します。
  classID         : Components.ID("0bc360c0-a913-11de-8a39-0800200c9a66"),
  QueryInterface  : XPCOMUtils.generateQI([Components.interfaces.nsIProtocolHandler]),
  //プロコトルのスキーム。
  scheme: "ttp",
  //ポート番号は任意。HTTPと同様。
  defaultPort: -1,
  //プロトコルの設定。HTTPと同様。
  protocolFlags: Components.interfaces.nsIProtocolHandler.URI_STD |
                 Components.interfaces.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
  //ポート番号は任意。
  allowPort: function TTProtocolAllowPort(aPort, aScheme) {
    return false;
  },
  //URIを処理する関数。
  //部品を受け取ってnsIURIのインスタンスを返します。
  newURI: function TTProtocolNewURI(aSpec, aCharset, aBaseURI) {
    var uri = Components.classes["@mozilla.org/network/standard-url;1"]
                .createInstance(Components.interfaces.nsIStandardURL);
    uri.init(Components.interfaces.nsIStandardURL.URLTYPE_STANDARD, -1,
             aSpec, aCharset, aBaseURI);
    uri.QueryInterface(Components.interfaces.nsIURI);
    return uri;
  },
  //通信チャンネルを作る関数。
  //↑で作ったnsIURIのインスタンスを受け取ってnsIChannelのインスタンスを返します。
  newChannel: function TTProtocolNewChannel(aURI) {
    var IOService = Components.classes["@mozilla.org/network/io-service;1"].
                      getService(Components.interfaces.nsIIOService);
    return IOService.newChannel("h" + aURI.spec, null, null);
  }
};

//コンポーネントの生成。
function NSGetModule(compMgr, fileSpec){
	return XPCOMUtils.generateModule([TTProtocol]);
}

特にclassIDには、各コンポーネント固有のUUIDが必要なのでUUID (GUID) Generator on the WEBで生成してください。

newURI関数

この関数は、絶対アドレスと相対アドレスの両方を扱う役目があります。

絶対アドレスのときは、aSpecに文字列"http://.../main.html"、aBaseURIにnullが入っています。

相対アドレスのときは、aSpecに文字列"../images/01.jpg", aBaseURIにnsIURIのインスタンスで、specが"http://.../main.html"なものが入っています。

newURI関数は、これらの部品をもとに、nsIURIのインスタンスを作って返します。

newURI関数のnsIStandardURL

この部品をイチから料理するのは面倒です。

特に"http://..."の処理は複雑で、":80"があったり、"user@domain"があったり・・・。

似たり寄ったりの処理を"ftp://..."や"file://..."でも使うので、このnsIStandardURLがあります。

今回の"ttp://..."はhttpと同じなので、コイツに任せればおしまいです。

さらに柔軟にURIを作りたいときは、nsISimpleURIを使うこともあるようです。

独自プロトコルの使いどころ

ブラウザの窓の中に、"CSSとJavascriptを駆使したブラウザ的なページ"を作りはじめたら、

このテクニックが必要なサインです。ブラウザの中にブラウザを作っていませんか?

例えばGMailの画面は、gmailto:があるとスッキリする気がします。

Webブラウザの未来Web-based protocol handlersに期待しましょう。


参考文献

2009-09-23

Firefox拡張入門第6回(Database.jsmライブラリ) 16:19  Firefox拡張入門第6回(Database.jsmライブラリ) - Gemmaの日記 を含むブックマーク  Firefox拡張入門第6回(Database.jsmライブラリ) - Gemmaの日記 のブックマークコメント

マッチFOXに同梱しているDatabase.jsmライブラリは、SQLite用のORマッパです。tomblooが開発しました。

使用例

// Bookmarkモデルを生成。
var Bookmark = Entity({
    name : 'bookmarks',
    fields : {
        id           : 'INTEGER PRIMARY KEY',
        url          : 'TEXT UNIQUE NOT NULL',
        title        : 'TEXT',
        date         : 'TIMESTAMP NOT NULL',
        last_visited : 'TIMESTAMP',
        comment      : 'TEXT',
    }
})
// データベースのファイル("ProfD/hogehoge/hogehoge.sqlite")を取得。
function dbFile() {
  var pd = DirectoryService.get("ProfD", Ci.nsIFile);
  pd.append("hogehoge");
  if (!pd.exists() || !pd.isDirectory()) {
    pd.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
  }
  pd.append("hogehoge.sqlite");
  return pd;
}
// Databaseインスタンスを生成
var db = new Database(dbFile());
// BookmarkモデルにDatabaseインスタンスをセット
Bookmark.db = db;
// Bookmarkモデルを初期化
Bookmark.initialize();
...
//Bookmark.insert     作成、
//Bookmark.find       検索、
//Bookmark.update     更新、
//Bookmark.deleteById 削除、などなど
...
// データベースを閉じる
db.close();

TIMESTAMP型、LIST型

TIMESTAMP型を指定すると、JavascriptのDateオブジェクトを透過的に読み書きできます。

(SQLiteには日付型がないので実際にはINTEGER型で"Date.getTime()の値"をデータベースに保存しています)

同様に、LIST型を指定すると、JavascriptのArrayオブジェクトを透過的に読み書きできます。

(実際にはTEXT型で"Arrayから作ったCSV的な文字列"を保存しています。)

便利。

findByFoo, findByFooAndBar, countByFoo、countByFooAndBar,

モデルのFooやBarの値で、検索やカウントができます。これは__noSuchMethod__をフックして実現してます。

すごい。

リファレンスマニュアル

Databaseクラス
function: Database aFile
Databaseのインスタンスを生成します。
aFile SQLiteのデータベースファイルの場所。nsIFlieのインスタンス。


getter: version
データベースのバージョンを取得します。
PRAGMAのuser_versionに相当します(schema_versionではない)。


setter: version
データベースのバージョンを設定します。


function: getPragma aName
PRAGMAの値を取得します。
aName PRAGMAの名前。文字列。


function: setPragma aName aVal
PRAGMAの値を設定します。
aName PRAGMAの名前。文字列。
aVal  設定する値。文字列。


function: createStatement aSQL
ステートメントを生成します。mozIStorageStatementWrapperを返します。
aSQL  SQL文。文字列。


function: bindParams aWrapper aParams
ステートメントにパラメーターをバインドします。バインドしたステートメントをmozIStorageStatementWrapperで返します。
aWrapper ステートメント。mozIStorageStatementWrapperのインスタンス。
aParams  パラメーター。
Objectの場合、名前付きパラメーターとみなします。
Arrayの場合、出現順に先頭からバインドします。
単値の場合、先頭のパラメーターにバインドします。
nullの場合、処理を行いません。


function: getParamNames aWrapper
ステートメント内に含まれる名前付きパラメーターのリストを、配列で返します。
aWrapper ステートメント。mozIStorageStatementWrapperのインスタンス。


function: getColumnNames aStatement
ステートメントの列名のリストを、配列で返します。
aStatement ステートメント。mozIStorageStatementかmozIStorageStatementWrapperのインスタンス。


function: getRow aRow aColumnNames
テーブル行をオブジェクトに変換したものを返します。
aRow テーブル行。mozIStorageStatementRowのインスタンス。
aColumnNames 列名のリスト。配列。


function: execute aSQL aParams
SQLを実行します。DDL/DML共に利用できます。
aSQL    SQL文。文字列かmozIStorageStatementWrapper。
aParams SQL文のパラメータ。オブジェクトか配列か文字列。bindParamsと同じです。


function: transaction aProc
トランザクション内で処理を実行します。
パフォーマンスを考慮する必要のある一括追加部分などで用います。
例外が発生した場合は、トランザクションがロールバックします。
それ以外は、自動的にコミットします。
既にトランザクションが始まっていたら新たなトランザクションは開始しません。
aProc   実行する処理。引数なしの関数。


function: beginTransaction
トランザクションを開始します。
トランザクションが既に開始していた場合でも、例外を発生しません。


function: commitTransaction
トランザクションをコミットします。
トランザクションが開始していない場合でも、例外を発生しません。


function: rollbackTransaction
トランザクションをロールバックします。
トランザクションが開始していない場合でも、例外を発生しません。


function: throwException aError
データベースの例外を解釈し再発生します。
aError データベースの例外。


function: close
データベースを閉じます。
閉じないと、ファイルがロックし削除できません。


function: tableExists aName
テーブルが存在するかを確認します。
aName テーブル名。文字列。


function: vacuum
データベースの無駄な領域を除去します。
Modelクラス
function: save
Modelをデータベースに保存します。初回はinsert、次からはupdateで保存します。
特にinsert時には、Modelにidを割り振ります。


function: remove
Modelをデータベースから削除します。内部的にはdeleteByIdです。


property: definitions
ModelのEntityの定義を示したオブジェクトです。


function: initialize
テーブルをCREATEします。内部的には以下のSQLを実行します。
CREATE TABLE IF NOT EXISTS {def.name} (
  {def.fields.join(', ')}
)


function: deinitialize
テーブルをDROPします。内部的には以下のSQLを実行します。
DROP TABLE {def.name}


function: insert
ModelをINSERTします。内部的には以下のSQLを実行します。
INSERT INTO {def.name} (
  {fields.join(', ')}
) VALUES (
  {params.join(', ')}
)


function: update
ModelをUPDATEします。内部的には以下のSQLを実行します。
UPDATE {def.name}
  SET {fields}
  WHERE id = :id


function: deleteById aId
ModelをIDでDELETEします。内部的には以下のSQLを実行します。
aId ModelのID。整数。
DELETE FROM {def.name}
  WHERE id = :id


function: deleteAll
Modelを全てDELETEします。内部的には以下のSQLを実行します。
DELETE FROM {def.name}


function: countAll
Modelの数をカウントします。内部的には以下のSQLを実行します。
SELECT count(*) AS count
  FROM {def.name}


function: findAll
Modelを全て検索します。内部的には以下のSQLを実行します。
SELECT *
  FROM {def.name}


function: findFirst aParams
ModelをパラメータでLIMIT 1で検索します。内部的には以下のSQLを実行します。
aParams パラメータ。Database.executeと同様。
SELECT *
  FROM {def.name}
  ...aParamsの内容...
  LIMIT 1 OFFSET 0


function: find aSQL aParams
Modelをパラメータで検索します。
aSQL    オブジェクトかmozIStorageStatementWrapperのインスタンス。
aParams パラメータ。Database.executeと同様。
aParamsがnullで、aSQLがオブジェクトで、aSQL.whereが文字列のとき、内部的には以下のSQLを実行します。
SELECT *
  FROM {def.name}
  WHERE {sql.where}
aParamsがnullで、aSQLがオブジェクトのとき、内部的には以下のSQLを実行します。
SELECT *
  FROM {def.name}
それ以外のとき、内部的にはDatabase.executeを実行します。
Model.db.execute(sql, params).map(Model.rowToObject);


function: findByFoo aFoo
function: findByFooAndBarAnd... aFoo aBar ...
ModelをFooやBarの値で検索します。__noSuchMethod__をフックして実現しています。
内部的には以下のSQLを実行します。
SELECT *
  FROM {def.name}
  WHERE foo = aFoo and bar = aBar and ...


function: countByFoo aFoo
function: countByFooAndBar... aFoo aBar ...
ModelをFooやBarの値でカウントします。__noSuchMethod__をフックして実現しています。
内部的には以下のSQLを実行します。
SELECT count(id) AS count
  FROM {def.name}
  WHERE foo = aFoo and bar = aBar and ...


function: rowToObject aObject
オブジェクトからModelのインスタンスを生成します。ただし、saveのときupdateが動きます。
aObject Entityの定義に沿った内容のオブジェクト。
Entityクラス
function: Entity aDefinition
エンティティ定義に沿ったModelのインスタンスを生成します。
aDefinition エンティティ定義を示したオブジェクト。
(nameプロパティでテーブル名、fieldsプロパティで、エンティティ名と型の対応。)
例.
var Bookmark = Entity({
    name : 'bookmarks',
    fields : {
        id           : 'INTEGER PRIMARY KEY',
        url          : 'TEXT UNIQUE NOT NULL',
        title        : 'TEXT',
        date         : 'TIMESTAMP NOT NULL',
        last_visited : 'TIMESTAMP',
        comment      : 'TEXT',
    }
})


function: createWhereClause aFields
WHERE節を示した文字列を作ります。
aFields WHEREするパラメータ名をリストにした配列。


function: createInitializeSQL aDefinition
CREATE TABLE IF NOT EXISTS...文を示した文字列を作ります。
aDefinition エンティティ定義を示したオブジェクト。Entityのものと同様。


function: createInsertSQL aDefinition
INSERT INTO...文を示した文字列を作ります。
aDefinition エンティティ定義を示したオブジェクト。Entityのものと同様。


function: createUpdateSQL aDefinition
UPDATE ... 文を示した文字列を作ります。
aDefinition エンティティ定義を示したオブジェクト。Entityのものと同様。


function: compactSQL aSQL
SQL文から不要な空白などを取り除き短く整形したSQL文の文字列を作ります。
表記のぶれを無くし、解析後の文のキャッシュヒットを増やす目的があります。
aSQL SQL文の文字列。

参考文献

2009-09-22

Firefox拡張入門第5回(Prefs.jsmライブラリ) 22:36  Firefox拡張入門第5回(Prefs.jsmライブラリ) - Gemmaの日記 を含むブックマーク  Firefox拡張入門第5回(Prefs.jsmライブラリ) - Gemmaの日記 のブックマークコメント

マッチFOXが生成するPrefs.jsmは、Firefox拡張でユーザ設定(about:configのアレ)を簡単に読み書きできるライブラリです。

ユーザ設定とは

ユーザ設定の項目の値には型があるので気をつけます。
  • boolean (真偽値)
  • integer (整数)
  • string (文字列)
  • など。
項目名が他人様のとカブらないように気をつけます。

"ブランチ"にまとめるように心がけましょう。

例えば拡張で使う項目名には"extensions."ブランチを使いましょう。

例: "javascript.options.strict"オプションを扱う

1. ライブラリを new する。

//ブランチを指定します。ピリオドで終わるのがポイント。
var prefs = new Prefs("javascript.options.");

2. trueにする

prefs.set("strict", true);

3. 取得する

var v = prefs.get("strict");

項目の型はライブラリが自動で判断してくれます(手動で指定もできます)。

リファレンスマニュアル

function: Prefs aBranchName
新しいインスタンスを作ります。
aBranchName ブランチ名。文字列。ブランチ名はピリオドで終わる必要があります。

function: get aPrefName &optional aDefaultValue aType
項目の値を取得します。
aPrefName 項目名。文字列。
aDefaultValue 取得に失敗したとき返す値。
aType 項目の型。文字列。下表1参照。

function: set aPrefName aValue &optional aType aRelFileRelativeToKey
項目の値をセットします。
aPrefName 項目名。文字列。
aValue セットする値。
aType 項目の型。文字列。下表2参照。
aRelFileRelativeToKey 相対ファイルパスの元ディレクトリ。"ProfD"など。文字列。

function: clear aPrefName
項目を削除します。
aPrefName 項目名。文字列。

function: getChildList &optional aStartingAt
ブランチ以下の項目名たちを配列で返します。
aStartingAt ブランチ名。文字列。

表1(getのaType)

aType動作
真偽値"boolean"getBoolPref(aPrefName)
整数"integer"getIntPref(aPrefName)
文字列"string"getComplexValue(aPrefName, Ci.nsISupportsString).data
地域化文字列"localized"getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data
絶対ファイルパス"file"getComplexValue(aPrefName, Ci.nsILocalFile)
相対ファイルパス"relFile"getComplexValue(aPrefName, Ci.nsIRelativeFilePref)

表2(setのaType)

aType動作
真偽値"boolean"setBoolPref(aPrefName, !!aValue)
整数"integer"setIntPref(aPrefName, +aValue)
文字列"string"setComplexValue(aPrefName, Ci.nsISupportsString, nsISupportsStringのインスタンス)
地域化文字列"localized"setComplexValue(aPrefName, Ci.nsIPrefLocalizedString, nsIPrefLocalizedStringのインスタンス
絶対ファイルパス"file"setComplexValue(aPrefName, Ci.nsILocalFile, aValue)
相対ファイルパス"relFile"setComplexValue(aPrefName, Ci.nsIRelativeFilePref, nsIRelativeFilePrefのインスタンス)

相対ファイルパスについてはFile I/O - MDC参照。

2009-09-09

Firefox拡張入門第4回(某占いサイトをスクレイピング) 22:34 Firefox拡張入門第4回(某占いサイトをスクレイピング) - Gemmaの日記 を含むブックマーク Firefox拡張入門第4回(某占いサイトをスクレイピング) - Gemmaの日記 のブックマークコメント

Firefox拡張はスクレイピングの最終兵器です。

今日は、某占いサイトをスクレイピングして、自分の星座のお告げを抜き出します。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard15.png

某占いサイト

スイーツ(笑)テーブルレイアウトです。

http://eva-lu-ator.net/~gemma/geocities/matchfox/c0.jpg

先にFirebugで、抜き出したいところのXPathを調べておきます。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard12.png

個人的にCSSセレクタのほうが好きなので、XPathとCSSセレクタの対応表をあげておきます。

次に、拙作のMatchfox拡張で骨組みを作ります。名前はFortuneにします。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard13.png

XPathを使ったスクレイピング

新しいタブに占いサイトを開いて、onloadイベントでスクレイピングが走ります。

chrome/content/sidebar/00-foobar0.jsに書いてください。

function getMainWindow() {
  return getTopWin();
}

function $x(aDocument, aXPathString) {
  var nodes = aDocument.evaluate(aXPathString,
                aDocument, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var arr = [];
  for (var i = 0, len = nodes.snapshotLength; i < len; i++) {
    arr.push(nodes.snapshotItem(i));
  }
  return arr;
}

function scrapeFortune(aDocument) {
  //contains(@alt, '座'). XPath 1.0 には end-with()がないのでcontainsでお茶を濁す。
  var constes = $x(aDocument, "//img[contains(@alt, '\u5EA7')]").map(function(x) {return x.alt;});
  var texts   = $x(aDocument, "//td[contains(@class,'text')]")  .map(function(x) {return x.innerHTML.replace(/<br>/g, "");});
  var luckys  = $x(aDocument, "//td[contains(@class,'lucky')]") .map(function(x) {return x.innerHTML;});
  var data = {};
  data[constes[0]]  = {rank: 1,  text: texts[0],  advice: luckys[0],  lucky: luckys[1]};
  data[constes[11]] = {rank: 12, text: texts[11], advice: luckys[12], lucky: luckys[13]};
  for (var i = 1; i < 11; i++) {
    data[constes[i]] = {rank: i + 1, text: texts[i], lucky: luckys[i + 1]};
  }
  return data;
}

function sidebarFoobar0() {
  const url = "http://www.fujitv.co.jp/meza/uranai/index.html";
  var mw = getMainWindow();
  var newTab = mw.gBrowser.getBrowserForTab(mw.gBrowser.addTab(url));
  newTab.addEventListener("load", function(e) {
    Application.console.log(scrapeFortune(newTab.contentDocument));
  }, true);
}

scrapeFortuneの結果はこのようなオブジェクトです。

{
 "おうし座": {"rank": 1,
             "text": "全てが順調に進んでHAPPY。悩んでいた課題に打開策を発見。新たな仲間も増え団結できそう。",
             "advice": "お気に入りの服を着る",
             "lucky": "デジタルカメラ"},
 "しし座":   {"rank": 12,
             "text": "自己中心の言動に冷たい視線。努力してきたことまで裏目に。理想ばかり追わず現実を見て。",
             "advice": "レジで一番長い列に並ぶ",
             "lucky": "アクセサリーショップ"},
 "おとめ座":  {"rank": 2,
             "text": "ロマンスがすぐそこに!初対面の人には常に笑顔。",
             "lucky": "アジアン小物"},
 "うお座":    {"rank": 3,
              "text": "実力を発揮するチャンス。攻めの姿勢で一歩成功に。",
              "lucky": "細長いピアス"},
 (以下略)
}

星座の設定画面に挑戦

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard14.png

extensions.fortune.consteに"いて座"などと設定を保存することにします。

以下の変更を加えます。

chrome/content/config.xul (設定画面のXULです)

  <prefpane id="prefpane0" image="chrome://fortune/skin/images/transparent.png" label="&Fortune.config.pane0;" flex="1">
    <preferences>
      <preference id="extensions.fortune.conste" name="extensions.fortune.conste" type="string" />
    </preferences>
    <vbox>
      <groupbox orient="vertical">
        <caption label="&Fortune.config.conste;"/>
        <radiogroup preference="extensions.fortune.conste">
          <radio value="おひつじ座" label="&Fortune.config.radio.aries;" />
          <radio value="おうし座"   label="&Fortune.config.radio.taurus;" />
          <radio value="ふたご座"   label="&Fortune.config.radio.gemini;" />
          <radio value="かに座"     label="&Fortune.config.radio.cancer;" />
          <radio value="しし座"     label="&Fortune.config.radio.leo;" />
          <radio value="おとめ座"   label="&Fortune.config.radio.virgo;" />
          <radio value="てんびん座" label="&Fortune.config.radio.libra;" />
          <radio value="さそり座"   label="&Fortune.config.radio.scorpio;" />
          <radio value="いて座"     label="&Fortune.config.radio.sagittarius;" />
          <radio value="やぎ座"     label="&Fortune.config.radio.capricorn;" />
          <radio value="みずがめ座" label="&Fortune.config.radio.aquarius;" />
          <radio value="うお座"     label="&Fortune.config.radio.pisces;" />
        </radiogroup>
      </groupbox>
    </vbox>
  </prefpane>

chrome/locale/ja/config.dtd (設定画面の日本語ラベルです)

<!ENTITY Fortune.config.conste "あなたの星座">
<!ENTITY Fortune.config.radio.aries "おひつじ座">
<!ENTITY Fortune.config.radio.taurus "おうし座">
<!ENTITY Fortune.config.radio.gemini "ふたご座">
<!ENTITY Fortune.config.radio.cancer "かに座">
<!ENTITY Fortune.config.radio.leo "しし座">
<!ENTITY Fortune.config.radio.virgo "おとめ座">
<!ENTITY Fortune.config.radio.libra "てんびん座">
<!ENTITY Fortune.config.radio.scorpio "さそり座">
<!ENTITY Fortune.config.radio.sagittarius "いて座">
<!ENTITY Fortune.config.radio.capricorn "やぎ座">
<!ENTITY Fortune.config.radio.aquarius "みずがめ座">
<!ENTITY Fortune.config.radio.pisces "うお座">

defaults/preferences/prefs.js (デフォルト設定です。私のいて座です)

pref("extensions.fortune.conste", "いて座");

chrome/content/sidebar/00-foobar0.js (設定を読むコードです)

function getMyConste() {
  var prefs = new Fortune.Prefs("extensions.fortune.");
  return prefs.get("conste");
}

http://eva-lu-ator.net/~gemma/geocities/matchfox/c1.jpg

最終的に00-foobar0.jsはこのようになります。

show関数がスクレイピングしたデータから自分の星座をとりだして表示します。

const EXPORT = ["sidebarFoobar0"];

function getMainWindow() {
  return getTopWin();
}

function $x(aDocument, aXPathString) {
  var nodes = aDocument.evaluate(aXPathString,
                aDocument, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var arr = [];
  for (var i = 0, len = nodes.snapshotLength; i < len; i++) {
    arr.push(nodes.snapshotItem(i));
  }
  return arr;
}

function scrapeFortune(aDocument) {
  //contains(@alt, '座'). XPath 1.0 には end-with()がないのでcontainsでお茶を濁す。
  var constes = $x(aDocument, "//img[contains(@alt, '\u5EA7')]").map(function(x) {return x.alt;});
  var texts   = $x(aDocument, "//td[contains(@class,'text')]")  .map(function(x) {return x.innerHTML.replace(/<br>/g, "");});
  var luckys  = $x(aDocument, "//td[contains(@class,'lucky')]") .map(function(x) {return x.innerHTML;});
  var data = {};
  data[constes[0]]  = {rank: 1,  text: texts[0],  advice: luckys[0],  lucky: luckys[1]};
  data[constes[11]] = {rank: 12, text: texts[11], advice: luckys[12], lucky: luckys[13]};
  for (var i = 1; i < 11; i++) {
    data[constes[i]] = {rank: i + 1, text: texts[i], lucky: luckys[i + 1]};
  }
  return data;
}

function getMyConste() {
  var prefs = new Fortune.Prefs("extensions.fortune.");
  return prefs.get("conste");
}

function show(aData, aMyConste) {
  alert([aMyConste,
         "rank:"  + aData[aMyConste].rank,
         "text:"  + aData[aMyConste].text,
         "lucky:" + aData[aMyConste].lucky,
         aData[aMyConste].advice ? "advice:" + aData[aMyConste].advice : ""].join("\n"));
}

function sidebarFoobar0() {
  const url = "http://www.fujitv.co.jp/meza/uranai/index.html";
  var mw = getMainWindow();
  var newTab = mw.gBrowser.getBrowserForTab(mw.gBrowser.addTab(url));
  newTab.addEventListener("load", function(e) {
    show(scrapeFortune(newTab.contentDocument),
         getMyConste());
  }, true);
}

ここに置いときますね。 http://eva-lu-ator.net/~gemma/geocities/matchfox/fortune.xpi

GOODLUCK!!BABY!!

参考文献

2009-09-08

Firefox拡張入門第3回(全てのタブからはてダのリンクを作る) 02:03 Firefox拡張入門第3回(全てのタブからはてダのリンクを作る) - Gemmaの日記 を含むブックマーク Firefox拡張入門第3回(全てのタブからはてダのリンクを作る) - Gemmaの日記 のブックマークコメント

それでは実際にFirefox拡張を作ってみましょう。

全てのタブの、アドレスとタイトルを取得して、はてなダイアリーのリンクにするサイドバー拡張を作ります。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard11.png

タブの情報は、メインウィンドウのwindow.gBrowser変数に入っています。タブブラウザ用コード - MDC

さて、サイドバーとメインウィンドウとではwindowが違います。

  • <window>要素
    • サイドバーの<browser id="sidebar">要素 <- いまここ
    • <tabbrowser>要素
      • Webページを表示する<browser>要素
        • <html><body>...

サイドバーのwindowからメインウィンドウのwindowを取得するには、3種類の方法があります。

var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIWebNavigation)
                   .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                   .rootTreeItem
                   .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIDOMWindow); 
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                   .getService(Components.interfaces.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");
var mainWindow = getTopWin();

getTopWinは chrome://browser/content/utilityOverlay.js が定義しています。他にも便利な関数が入っているので一読おすすめ。

では実装しましょう。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard10.png

  • まず、拙作のMatchfox拡張で、骨組みを作ります。urllistと名付けましょう。
  • 骨組みのsidebar.xulにこのようにbuttonと、出力先のtextboxを作ります。
これはxmlですうんちゃらなんちゃら...
<page ほげほげ...>
  <button oncommand="urllist.sidebarFoobar0();" label="&urllist.sidebar.test;"/>
  <textbox id="output" multiline="true" rows="20"></textbox>
</page>

buttonを押すとchrome/content/sidebar/00-foobar0.jsのsidebarFoobar0関数を呼ぶようになっているので、

このあたりに以下のコードを書きます。

const EXPORT = ['sidebarFoobar0'];

function sidebarFoobar0() {
  document.getElementById("output").value = getURLs().map(function(x) {
    return "[" + x.url + ":title=" + x.title + "]";
  }).join("\n");
}

function getMainWindow() {
  return getTopWin();
}

function getURLs() {
  var mw = getMainWindow();
  var num = mw.gBrowser.browsers.length;
  var arr = [];
  for (var i = 0; i < num; i++) {
    var b = mw.gBrowser.getBrowserAtIndex(i);
    if (b.currentURI.spec != "about:blank") {
      arr.push({
        url:   b.contentDocument.documentURI,
        title: b.contentDocument.title
      });
    }
  }
  return arr;
}

完成です。

ここに置いときますね。http://eva-lu-ator.net/~gemma/geocities/matchfox/urllist.xpi

開発のヒント

  • サイドバーのプログラムはサイドバーをパカパカでリロードできます。
  • エラーコンソールへの出力は Application.console.log 関数です。
  • 先にMozReplで偵察しておくとスムーズです。
    • MozReplでサイドバーのwindowに潜るにはこうします。
 repl.enter(document.getElementById("sidebar").contentWindow)

2009-09-07

Firefox拡張入門第1回(開発の準備) 03:16  Firefox拡張入門第1回(開発の準備) - Gemmaの日記 を含むブックマーク  Firefox拡張入門第1回(開発の準備) - Gemmaの日記 のブックマークコメント

開発者必携ツール

  • DOM Inspector 右クリックメニューを拡張したいんだけどデータどこにあんねん、というとき便利。
  • QuickRestart ブラウザを再起動して拡張プログラムをリロードするとき便利。
  • Console2 Javascriptコンソールの強化版。エラーメッセージをよりわけできて便利。
  • SQLite Manager ブラウザ内蔵のSQLite RDBMSをGUIで操作できて便利。
  • Venkman JavaScript Debugger Javascriptデバッガです。わりと便利。

MozReplも必携です。Firefox内部のJavascriptインタープリタにtelnet接続できます。

私の開発の流れは、

  • サイドバーで動く拡張プログラムを作る
  • サイドバーをパカパカしてプログラムをリロードして動作確認
  • 小さなプログラムの断片は、MozReplでインタープリタに打ち込んで動作確認
  • 安定して動いたらプログラムを現場に移す

です。

プログラムを変更するたびにブラウザを再起動なんて遅くてやってらんないので、できるだけサイドバーパカパカで済ませます。

参考文献

Firefox拡張入門第2回(MozReplでFirefoxを探検しよう) 03:17  Firefox拡張入門第2回(MozReplでFirefoxを探検しよう) - Gemmaの日記 を含むブックマーク  Firefox拡張入門第2回(MozReplでFirefoxを探検しよう) - Gemmaの日記 のブックマークコメント

windowオブジェクトの入れ子について

いつもwindow.alertなどで見る"Webページのwindow"のさらに上には、ブラウザ全体を支配する"XULのwindow"があります。

もちろんWebページ側からは触れませんが、Firefox拡張ならXULのwindowをいじれます。

ブラウザはこのようになっています。

  • <window>要素
    • サイドバーの<browser id="sidebar">要素
    • <tabbrowser>要素
      • Webページを表示する<browser>要素
        • <html><body>...

そして<browser>要素のようにページ(XULやHTML)を表示できる要素がそれぞれに"ページのwindow"を持っています。

これはHTMLでiframe要素がそれぞれにwindowを持っているのと同じです。

詳細はこちらchrome コードでウィンドウを取り扱う - MDC

動画(wmv 3.0MB)

接続

MozReplを起動し、telnet接続(127.0.0.1:4242)します。

  • telnetの注意
    • 文字コードはUTF-8。ブラウザ内部はUTF-8で動いているので。
    • 改行コードはLF。いわゆる\n。
    • TeraTermはローカルエコーを有効にしましょう。
    • MozReplはローカルの接続しか受け付けない設定なので大丈夫です。
    • Emacsユーザには、MozReplのパッケージの中にmoz.elが入っています。

最初に我々がいるのは、ブラウザ全体を支配するXULのwindowです。

(MozReplが、Current working context: chrome://browser/content/browser.xulと表示しています)

例えば、ブラウザ内部でWebページを開くのに使われているopenUILink関数を呼びます。

repl> window.openUILink("http://www.google.co.jp");

Googleが表示されましたか?

repl.enter(ブラウザー)

repl.enterで別のwindowに潜れます。

例えば"Webページのwindow"に潜ってみましょう。window.contentにあります。

repl> repl.enter(window.content);
[object Window] ? {getInterface: function() {…}, repl: {…}, ...}
repl> alert("hello world");

おなじみのwindow.alertが呼べました。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard09.png

repl.home()

repl.home()でXULのwindowに帰れます。

repl> repl.home()
[object ChromeWindow] ? {gProgressMeterPanel: {…}, gFindBar: {…}, DownloadUtils: {…}, repl: {…}, ...}

repl.enter(サイドバー)

今度は"サイドバーのwindow"に潜ってみましょう。履歴サイドバーを表示してから潜ります。

repl> repl.enter(document.getElementById("sidebar").contentWindow);
[object ChromeWindow] ? {repl: {…}, NS_ASSERT: function() {…}, document: {…}, ...}
repl> location.href
"chrome://browser/content/history/history-panel.xul"

おぉ、location.hrefにhttp://じゃなくてchrome://が輝いています。Firefox拡張ならではですね。

repl.inspect()

repl.inspectで他にどんなプロパティやメソッドがあるか調べることができます。

repl> repl.inspect(location)
<object>.href=chrome://browser/content/history/history-panel.xul
<object>.hash=
<object>.host=browser
<object>.hostname=browser
<object>.pathname=/content/history/history-panel.xul
<object>.port=
<object>.protocol=chrome:
<object>.search=
<object>.reload=[function]
<object>.replace=[function]
<object>.assign=[function]

XULの要素などはDOM Inspectorで調べるのも便利です。

最後に

これでブラウザを掌握できました。

タブブラウザ用コード - MDCなどにブラウザを操作する例があります。

これでブロークンなHTMLをその道ウン10年のFirefoxに読んでもらってスクレイピングとかすると面白いでしょう。

2009-09-06

Firefox拡張簡単キット"マッチFOX" 01:37  Firefox拡張簡単キット"マッチFOX" - Gemmaの日記 を含むブックマーク  Firefox拡張簡単キット"マッチFOX" - Gemmaの日記 のブックマークコメント

Firefox拡張を作りたいけど、ややこしくて手がつけられない、そんなあなたに・・・!

簡単キット"マッチFOX"。Ruby on Rails みたいに、scaffold(骨組み)を作ってくれます。

この骨組みには、はてなブックマーク拡張から取り出したエッセンスが入っていて、

その道のプロフェッショナルと同じスタートラインから開発できます。

- 人柱版(RC2)

使い方

インストールしたら、Matchfoxサイドバー(Ctrl+!)を開きます。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard04.png

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard06.png

骨組みの、"インストール先のディレクトリ"を"参照..."します。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard07.png

新しいフォルダの作成(M)で、"sample@mozdev.org"というちょっと変な名前のフォルダを作ります。

OKをクリックします。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard08.png

最後に、一番下の"骨組みを作る"を押します。

Firefoxを再起動してみてください。

キットの中身

http://eva-lu-ator.net/~gemma/geocities/matchfox/mat.png

なぜサイドバーがキットに入っているのかというと、サイドバーはパカパカするだけでリロードできて

プログラムの確認が楽なんで、はじめのうちはサイドバーで動くものを作るのがおすすめだからです。

おまけで、アドオンの"設定"から"開発者におすすめの設定"ができます。

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard05.png

最後に

人柱版は安定したらFirefox拡張の公式サイトで公開するつもりです。

これからFirefox拡張の記事をいくつか連載するつもりなのでお楽しみに。

"sample@mozdev.org"というちょっと変な名前のフォルダを作るくだりは、

慣れてきたらポインタファイルを使うのがいいですね、おいおい解説します。

はてブ拡張とTomblooのコードが大変参考になりました。感謝。

開発後記

IntelのSSDを買って、気合が入った勢いで開発しました。

入力したテンプレから自動で骨組みを作るために、E4Xをテンプレートエンジンとして使うアイディアをえた後は、

E4Xの特殊文字 {}と<>と& をひたすらエスケープするだけの簡単なお仕事。体力勝負でした。土日がつぶれました。

残念なことに、mozIJSSubScriptLoaderで文字化けするのでDatabase.jsの日本語コメントは消しました。ごめんなさい。

ついでにマッチ箱をイメージしたロゴも作りました。

http://eva-lu-ator.net/~gemma/geocities/matchfox/gen.png