FIREFOX HACKS' (3)

昨日、医者へ行って、背中に出来た「こぶ」を取ってもらってきた。正確には、「ふんりゅう」とか言うそうな。昔からこぶはあって、ご丁寧にも、ふたこぶ だったのだ。数年前に、一つ取ってもらい、ひとこぶ になっていたけど、今回は残りのやつも取ってもらった。

術後の痛さよりも、当日はアルコール禁止の方が痛かったのは、秘密だ。でも、今日から解禁、よかったね >俺

不満が残るぞ、SQL

前回は、gaucheを使って、sqlite3のDBを暴いた。結果、分かった事は、テーブルが多過ぎて、収集がつかない事。更に、INTEGER と言う、DB上のポインターの多さよ。

リレーショナルDBは、マシン独立に設計され、ポインターとは無縁の世界です、なんて言われているけど、結局、INTEGERに置き換えただけじゃん。どうせなら、INTEGERなんて言う、チンケなものややめて、有理数(実数)に置き換えたらどうだい。面倒な事が無くなるよ、と、Web+DB の、「SQLアタマ アカデミー」に書かれていた。なんでも、カントール集合とか言うそうな。

と、言う事で、SQLはきっぱりと諦めます。そもそも、FIREFOX HACKS(大げさな!)を始めた目的は、いろいろなマシンで使っている、FIREFOX の Bookmarkをマージしたかったからだ。

ある時は、Windowsで、ある時はLinuxでと、気分のままにFirefoxを使っていて、Bookmarkを更新していくと、あれ、あのURL どこに残していたかなあ となってしまい、はなはだ不便だからだ。きっと、こういう不満を解消するアプリは出ているだろうけど、前回の、事件(設定ファイル削除)を機に、調べてみる気になったのでした。

じゃーー、どうする? ブックマークの管理メニューを見たら、どうやら JSON形式でも BackUP出来るみたいだ。そんじゃ、やってみんべ。

JSON って、何ものよ?

女房に聞いたら、ジェイソンって、聞き返されちゃいました。ええ、13日の金曜日に出てくる、恐い人ですね。どうやら、聞く人を間違えた。世界の百科辞典で調べてみます。

JavaScript Object Notation、俺的には、いやだなぁ。

JSONは単純であるので、特にAjaxの分野で利用が広がりつつある。
JavaScriptでJSONをパースして読み込むには、文字列をJavaScriptのコードとして
解釈させるeval()関数を作用させるだけでよい

eval()ですか! こりゃ、ちゃんとしないと恐いわな。

恐いもの見たさにJSONの紹介なんて所まで行ってみました。なるほどねぇ。RFC 4627 になって、世界を席圏中ですか。

JSONデータを読んでみるぞ

さて、そんじゃ、FIREFOXから取り出した、jsonデータを読んでみるか。gaucheを使いたい所だけど、まだ、rfc.4627は準備中との事で(HEADには、あると、風の噂です)すので、素直に ruby で、行きます。

生憎と、JSONのモジュールは提供されていない。lib/rubyの下にある、yaml.rb でも、代用出来るそうだけど、折角なので、正しい道を歩む事にします。

Ruby用 Jsonパーサーを頂いてきました。だって、gemは嫌いだもん。 仰せにしたがって、

require "./simple-json"
str = File.read('Bookmarks_2009-06-08.json')
parser = JsonParser.new
res = parser.parse(str, {:validation => false })

早速、実行してみると

[sakae@fb ~/json]$ ruby simple.rb 
./simple-json.rb:169:in `parse_value': [JsonParser] Syntax error (RuntimeError)
        from ./simple-json.rb:203:in `parse_array'
        from ./simple-json.rb:178:in `parse_value'
        from ./simple-json.rb:189:in `parse_hash'
        from ./simple-json.rb:83:in `parse'
        from simple.rb:4

ありゃりゃ、エラーですよ! 何故? rubyが、1.8.7 だから? そんじゃ、ruby のHeadにしたるわい。(久しぶりに、ruby-1.9.xxをコンパイル)

[sakae@fb ~/json]$ /home/sakae/mine/bin/ruby -v
ruby 1.9.2dev (2009-06-07 trunk 23644) [i386-freebsd6.4]
[sakae@fb ~/json]$ /home/sakae/mine/bin/ruby simple.rb 
/home/sakae/json/simple-json.rb:169:in `parse_value': [JsonParser] Syntax error (RuntimeError)
        from /home/sakae/json/simple-json.rb:203:in `parse_array'
        from /home/sakae/json/simple-json.rb:178:in `parse_value'
        from /home/sakae/json/simple-json.rb:189:in `parse_hash'
        from /home/sakae/json/simple-json.rb:83:in `parse'
        from simple.rb:4:in `<main>'

やはり、同じか。しゃーない、gem版のjsonも入れてみるか。

[sakae@fb ~/json]$ PATH=/home/sakae/mine/bin:$PATH
[sakae@fb ~/json]$ gem list --local

*** LOCAL GEMS ***

json (1.1.6)
 .....
#!/home/sakae/mine/bin/ruby
require "json"
str = File.read('Bookmarks_2009-06-08.json')
res = JSON.parse(str)

[sakae@fb ~/json]$ ./test.rb 
/home/sakae/mine/lib/ruby/gems/1.9.1/gems/json-1.1.6/lib/json/common.rb:122:in `parse': 349: unexpected token at ']}' (JSON::ParserError)
        from /home/sakae/mine/lib/ruby/gems/1.9.1/gems/json-1.1.6/lib/json/common.rb:122:in `parse'
        from ./test.rb:4:in `<main>'

Uum ... 一歩前進。食わせたデータが間違っていると。。今、使った、Bookmarks_2009-06-08.json は、FreeBSD上の Firefox から、吐き出したやつだ。FreeBSD特有な問題でも 抱えているのだろうか? > Firefox君

ならば、Windows上の Firefox から、jsonを採取。実験してみるよ。# => 同様にParseError が、発生。

見たくないけど、しやーない、見るか!

{"title":"","id":1,  .....    "charset":"EUC-JP"}]},]}

改行が1つも入っていない、一本糞 みたいなファイルです。Windows上のemacsに食わせたら、硬直を起こし、反応しなくなりました。(笑)

最初と最後だけを載せましたが、バランスが崩れていますねぇ。どちらに合わせるのが良いのでしょうか? 取り敢えず、最後の部分を5文字削ってみます。

..... jp/~takumi/","charset":"EUC-JP"} (JSON::ParserError)
'
    from /home/sakae/mine/lib/ruby/gems/1.9.1/gems/json-1.1.6/lib/json/common.rb:122:in `parse'
    from ./test.rb:4:in `<main>'

今度は、データの最後の部分で、エラーになってしまいました。 viで、開いて % を使いながら、括弧のバランスを取ってみましたが、エラーを根治できませんでした。残念!

Firefoxでは、ちゃんと扱えるの?

外部に取り出して、検定するとエラーになる JSON ファイルだけれど、Firefoxではちゃんと扱えるの? やってみたら、エラーを出す事なく、ちゃんと読めちゃったよ。その結果は、正しく Bookmarkに反映されてた。

やっぱり、ジェイソン! 外から覗くのはご法度なのね。触らぬ神に祟りなし。 なお、json形式でバックアップしたのを戻すと、favicon が消えてしまいます。faviconまで、正しく戻したい場合、bookmarks.html で、輸出入しましょう。

結論みたいな事を書いてしまったけど、改めてFirefoxのメニューを見たら、toolの所で、javascriptの実験が出来るのね。知らなかった。eval("/home/sakae/json/bookmarks.json")と、無謀な事をしたら、正規表現と誤認しやがるの。しゃーない、ファイルから一度読み込んでと思って、それらしいのを探してみたら、危険防止の為、そういう関数(read)は、提供されてないのね。当たり前の事よ! この当たり前を、堂々と破ってくれているのが、M$です。便利なのは良い事だで、相変わらず節操が無いなあ。

ふと思って、firefoxに、壊れた(壊した)jsonファイルを与えてみたら、ちゃんとエラーになってくれるのだろうか? 乗りかけた舟なので、やってみた。 結果は、受取を拒否されました。拒否の言い訳を頼りに firefoxのソースを検索してみます。

[sakae@fb ~/mozilla]$ find . -type f | xargs grep 'Unable to process the backup file.'
 .....
./browser/locales/en-US/chrome/browser/places/places.properties:bookmarksRestoreParseError=Unable to process the backup file.  

こやつは、メッセージの定義ファイルでしたよ。再び、検索の旅へと出かけます。

[sakae@fb ~/mozilla]$ find . -type f | xargs grep bookmarksRestoreParseError
./browser/components/places/content/places.js:      this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError"));

places.js って事は、javascript なんだ。へぇー、firefox内部でも使っているのね、と改めて驚いてみる。(漫才で、ここ笑う所ですよ、と言うのと一緒)

    try {
      PlacesUtils.restoreBookmarksFromJSONFile(aFile, [PlacesUIUtils.leftPaneFolderId]);
    }
    catch(ex) {
      this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError")); 
    }

今度は、restoreBookmarksFromJSONFile を探して見てみます。 /home/sakae/mozilla/toolkit/components/places/src/utils.js に、有りました。相変わらず js だなあ。どうやって、ファイルから読むんだよう?

  /**
   * Restores bookmarks/tags from a JSON file.
   * WARNING: This method *removes* any bookmarks in the collection before
   * restoring from the file.
   *
   * @param aFile
   *        nsIFile of bookmarks in JSON format to be restored.
   * @param aExcludeItems
   *        Array of root item ids (ie: children of the places root)
   *        to not delete when restoring.
   */
  restoreBookmarksFromJSONFile:
  function PU_restoreBookmarksFromJSONFile(aFile, aExcludeItems) {
    // open file stream
    var stream = Cc["@mozilla.org/network/file-input-stream;1"].
                 createInstance(Ci.nsIFileInputStream);
    stream.init(aFile, 0x01, 0, 0);
    var converted = Cc["@mozilla.org/intl/converter-input-stream;1"].
                    createInstance(Ci.nsIConverterInputStream);
    converted.init(stream, "UTF-8", 1024,
                   Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

    // read in contents
    var str = {};
    var jsonStr = "";
    while (converted.readString(4096, str) != 0)
      jsonStr += str.value;
    converted.close();

    if (jsonStr.length == 0)
      return; // empty file

    this.restoreBookmarksFromJSONString(jsonStr, true, aExcludeItems);
  },

雰囲気からすると、指定されたファイルを streamにmapして、UTF-8変換を掛けながら 文字列として読み込んでいる。読んだ文字列を引数にして、restoreしてるんだ。 以下、このファイルを解析すれば、DBとのやり取りも判明します。javascriptをちょいと勉強して、1600行ぐらいあるファイルを真面目に読んでみようかな。