daily dayflower

2009-10-28

Firefox の Pencil 拡張機能をダメ出しに使う

ダメ出しってなんだ。annotation のことだ。


Firefox に Pencil という拡張機能があるんですが,たいてい(GUI アプリの)デザインプロトタイプに使えるなどと紹介されてます。

たしかに,シェイプとして WindowsLinux などの GUI ウィジェットが多数登録されているので,ドラッグアンドドロップで気軽に画面をつくることができます。んでも実際にそれをやってみるとしんどい。

そもそもなんでこいつを Firefox拡張機能としてリリースしたんだ。そういう用途なら XUL アプリにしとけばよかったじゃないか。

と思っていろいろいじったり調べたりしてたら Firefox の画面キャプチャを Pencil ドキュメントに簡単に貼れることがわかりました。しかも annotation 用のシェイプもいくつか用意されてます*1


ということは。

デザイナーの方が作ったウェブページのデザインモックを「ここをこうしてね」とかフィードバックするのに使えるんじゃないか。だから Firefox拡張機能なのかー。


操作手順は以下のとおり。

  1. Firefox の「ツール」メニュー → 「Pencil Sketching...」で Pencil アプリを立ち上げる
  2. Pencil の「Document」メニュー → 「Save As...」を選択し,ドキュメントを保存する
    • 実ファイルとして保存していないと,次の「Send to Pencil Document...」が使えません。(超重要 !!)
    • 保存したあとに Pencil アプリを閉じないこと。Pencil アプリが閉じていてもやはり「Send to Pencil Document...」が使えません。
    • 保存するときの拡張子.ep.epz です。後者はいかにも gzip 圧縮されていそうですが,(すくなくとも Pencil から保存する場合)両者はまったく同じです。
    • なおこの Pencil ドキュメントは XML ドキュメントになっています。
  3. Firefox の「ツール」メニュー → 「Send to Pencil Document...」で,現在ブラウザに表示されている内容を Pencil ドキュメントに貼り付ける
    • これにより Pencil ドキュメントにキャプチャ画像が表示されます。
    • たいていドキュメントより大きな画像を貼ることになるので,一見『一部しかキャプチャできてないやん〜』てことになりますが,画像自体はきちんと全体が貼りこまれているのでご安心を。次の手順で解消します。
    • さきほどもいったように .ep ファイル自体は単一の XML ドキュメントであり,貼りこんだキャプチャ画像は埋め込みではなく別ファイルとして同じディレクトリに(通常の PNG 画像として)保存されます。
    • キャプチャ画像は,ページ全体になります。画面に表示されている領域だけ……とか凝ったことはできません。
  4. Pencil の「Untitled Page」ページタブで右クリックして「Resize to fit contents...」メニューを選択する
    • この手順を踏むことで,ドキュメントを拡張してキャプチャ画像全体が表示されるようにしています。
    • 「padding」を設定することができます。普通には 0 で構わないと思います。
    • ページタブ右クリックメニューの「Properties...」で手でドキュメントの大きさを調整してもいいです。

あとは左ペインの「Annotation」系シェイプや「Common Shapes」系シェイプをペタペタ貼ってダメ出ししていきます。

最後に「Document」メニュー → 「Save」で保存しつつ,「Export Page as PNG...」で画像としてエクスポートしてデザイナにメールで送りつければよいでしょう。

サンプルとしてこんな感じ(いや別に Google にダメ出ししたいわけではないです;あくまでサンプル)

f:id:dayflower:20091028145805p:image


なお,ひとつの Pencil ドキュメント(ページ)に貼ることのできるキャプチャ画像は 1 枚だけではありません。複数枚貼り付けることもできます。また(もともとの機能として)外部画像ファイルを読み込んで貼り付けることもできます*2

複数枚貼ったらページサイズを大きくしないといけなさそうですが,画像はリサイズすることができるので*3小さくしてやればよろしい。

四隅系のハンドルをドラッグするとおおまかなアスペクト比を保ったまま拡大縮小が可能ですが,Pencil は基本的にグリッド単位で位置調整されるので結構狂ったりします。そんなときは,画像の右クリックメニューから「Actions」メニュー →「Correct Ratio by Width」や「Correct Ratio by Height」を選択すれば,正しいアスペクト比になります。

複数枚の画像の幅などを揃えたい場合は,手作業で揃えるか(先ほど述べたように基本的にグリッド単位で動くのでそんなに難しくない),複数枚選択して「Shape」メニューから「Make Same Width」や「Make Same Height」を選択すればいいです。

そんなふうにして作ったのがこれ。

f:id:dayflower:20091028145806p:image

ちょっとした操作手順書や画面説明書ならこれで作れそう。


細かいところに手が届かない部分はありますが基本的な機能はそこそこしっかりしているので,お手軽にダメ出し文書や操作手順書を作るにはこれでいいんじゃないかと思います。

*1:といっても,「吹き出し」と「矢印」くらいですが。あと番号用丸形シェイプ。

*2:「Common Shapes」から「Bitmap Image」シェイプを貼り付けて,右クリックメニュー「Actions」から「Load Linked Image...」を選択すると画像ファイルを選択することができます。PNG 画像だけではなく,JPEG など Firefox のサポートする画像形式の画像を読み込むこともできます。「Load Embedded Image...」の役割はわかりません。

*3:ただし,WYSIWYG でリサイズすることしかできない(サイズを数値で指定してリサイズすることができない)。また,拡大縮小にともなうスムージングはしてくれない。

2009-01-19

JavaScript でかんたん XUL アプリに挑戦

こんにちは!

みなさんガジェットつくってますか!

なんだか最近、色々な種類があるみたいですね!

(以下略!)

そしたら意外と簡単だった…!

これならぼくにも作れそう!!

ってことで、ちょっとメモしておきますね!

どれにしようかな…!

  • おもしろいこと
  • Ubuntu で動けばいいや*1
  • 自分とこで書きやすいのがいい

こんな感じで考えていくと…

最終的に XUL アプリケーション,ってことになりました!

XUL で作られたツールなら,いつも使っているよ!

Firefox とか Thunderbird とかね)

だから XUL アプリに決定!

つくるのむつかしそう?

XULアプリって C から libxul をゴリゴリ触らないとダメなんじゃないの?

なんて思っていた時期がぼくにもありました…!

大丈夫!XULRunner を使えば(HTML に似た)XULJavaScript だけでできるよ!

だからホームページ作るのと同じくらい簡単に感じる人もいるかもしれないですね!

ブラウザ上のページだと、JavaScriptとかで他のサイトの情報を取得できなかったり

ファイルの読み書きできなかったりと、色々な制約があるんだけど、

XULRunner なら XPCOM もあるから

そんな制約なしで、ウイルスでも何でも好きなものがバンバンつくれます!

やったね!

つくるための準備

準備は Ubuntu Linux 8.10 でのやりかたです!

Windows とか Mac とか一般的な OS 使ってる人はどうせニヤニヤしながら眺めているだけだろうから,ほんとにやりたい人は Getting started with XULRunner | MDN を参考に自分で適当にやっちゃってください!

Ubuntu だととくになにもしなくても*2 XULRunner が入っていたから省略するよ!)

ちゃんと設定できてるかな?

端末を開いて,xulrunner って入れてみよう!

なんかでてきたら XULRunner はインストールされてるよ!

よし!つくろう!

ぼくはいつも ~/tmp/ にガラクタファイルを溜め込んでいってるので,

~/tmp/myapp ってフォルダを作ってみました!

はい!今回つくるやつは「myapp」っていう XUL アプリです!

作業用フォルダに必要なフォルダを用意する

シンプルな XUL アプリの場合,だいたい下記のようなフォルダ&ファイル構成になるよ!

/myapp
  /chrome
    /content
      main.xul
    chrome.manifest
  /defaults
    /preferences
      prefs.js
  application.ini

だから,まずディレクトリをきっておこう!

% cd myapp

% mkdir -p chrome/content defaults/preferences

作業用フォルダに必要なファイルを用意する

4つのファイルを用意しよう!

ひとつが,XUL アプリの情報を設定する application.ini ファイル。

もうひとつが,使用するリソース(画像とか JavaScript ファイルとか)の場所のレイアウトを指定する chrome.manifest ファイル。

さらにもうひとつが,設定を書くための prefs.js ファイル*3

最後が,メインになる main.xul ファイル!

XUL アプリの情報を設定する application.ini ファイル

作業用フォルダの直下に新しく「application.ini」ってファイルを作ってね!

% vi application.ini

中身は…

[App]
Vendor=dayflower
Name=My App
Version=1.0
BuildID=20090119
ID=xulapp@example.org

[Gecko]
MinVersion=1.9
MaxVersion=1.9.0.*

これをコピペでok!

(ほんとは Vendor とか ID を適宜書き換えてほしいけど…)

リソースレイアウトを設定する chrome.manifest ファイル

作業用フォルダの chrome/ フォルダ以下に chrome.manifest ファイルを作ろう!

% vi chrome/chrome.manifest

中身は…

content myapp file:content/

たったこれだけ!これもコピペでok!

念のためにちょっと解説?すると,XUL アプリケーションFirefox 拡張機能では,使用するファイル(画像とか CSS とか html とか xul とか js とか)を chrome/ フォルダ以下につっこむんだけど,一般的にはこの chrome/ フォルダ以下を JAR ファイル(ZIP 形式)で圧縮して配布することが多いんだ。だけど上記のように書くと,圧縮はしてなくて,chrome/content/ フォルダ以下にそのままおいてあるよ,という意味になるよ!

設定ファイル prefs.js ファイル

作業用フォルダの defaults/preferences/ フォルダ以下に prefs.js ファイルを作ろう!

% vi defaults/preferences/prefs.js

中身は…

pref("toolkit.defaultChromeURI", "chrome://myapp/content/main.xul");

pref("browser.dom.window.dump.enabled", true);

これもまたコピペでok!

念のために説明すると,上の行は XULRunner にメインウィンドウとして使われる XUL ファイルの名前を教えてあげるための設定だよ!「chrome://」という形式からもわかるように,これは chrome.manifest にさっき設定したマッピングと関連している。さきほど chromemyappcontentfile:content/*4 って設定したので,chrome://myapp/content/main.xul という表記は,(chrome/)content/main.xul というファイルを指定していることになるんだ。ここはまあわかんなくてもok!

下の行は,のちほど dump() という関数デバッグ用出力を可能にするための設定!そのた JavaScript コンソールにデバッグ情報を出力する方法もあるけど,その場合の設定は Debugging a XULRunner Application | MDN をみてね!

XUL ファイル

いよいよ chrome/content/main.xul ファイルを作る番だね!

% vi chrome/content/main.xul

中身は…

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window id="main" title="Konnichiha Konnichiha" width="400" height="300"
 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script>
<![CDATA[
function start() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && xhr.status == 200) {
            view(xhr);
        }
    };

    xhr.open('get', 'http://b.hatena.ne.jp/hotentry');
    xhr.send(null);
}

function view(xhr) {
    dump(xhr.getAllResponseHeaders());
    document.getElementById('message').value = xhr.responseText;
}
]]>
  </script>
  <textbox id="message" multiline="true" rows="10" readonly="true"
           value="こんにちはこんにちは!"/>
  <button id="pushme" label="ひみつボタン" oncommand="start()"/>
</window>

これもコピペでオッケー!

ただし,文字コードUTF-8 で保存してね!

これは textbox の id をかえたくらいで,ほとんどはまちちゃんのコードのコピペだよ。JavaScript + DOM ってすばらしいね!

実行しよう!

端末を起動して,作業フォルダに移動…

% cd ~/tmp/myapp

さっきつくったやつを実行…!

% xulrunner application.ini

f:id:dayflower:20090119143617p:image

できた!やった!なんかうごいた!

ひみつボタンを押したら,はてなブックマークのソースコードが表示されたよー。

改造しよう!

XUL ファイルとか JavaScript は,Mozilla Firefox と同じやつが使われてるらしいですよ!

てことは,普段ホームページ作りの時に「あぁこれ IE だと使えないからなぁ」

なんて諦めていたやつが色々つかえるかもしれないですね!

XPath とか Canvas とか,なんかそういうの!

あと prototype.js とか jQuery みたいな便利ライブラリも普通に使えるよ!(たぶん)

やった!すごい!べんり!

だけど XUL アプリケーションのちょっとまずいところ

  • HTML のかわりに XUL とかいう謎なファイルを書かなくちゃいけない

謎なファイルって書いたけど,みんなも HTML を勉強するとき色々覚えたよね?

<input type="checkbox"> って書くとチェックボックスをだせるとか。

XUL の場合,その代わりにたとえば <checkbox> と書けばいいんだ。

つまり,新しいタグが増えたと思えばいいんだよ!

それに <colorpicker> とか素敵なウィジェットも揃っているよ!

くわしくは XUL controls | MDN をみてね!

今回の Ubuntu Linux の場合,たいていは xulrunner はもともとインストールされている。

けど,Windows とかの場合は,ユーザに XULRunner をダウンロード*5させなきゃいけない。

(まぁ Windows の場合解凍するだけで大丈夫らしいし,Mac OS X の場合はインストーラ形式になってるよ)

2009-01-20 追記:

teramako

id:teramako XUL, xulrunner ネタを取られた...orz // 悔しいので一つネタを。xulrunnerじゃなくてもFx3からは firefox -app application.ini で起動できたりするよ

http://b.hatena.ne.jp/teramako/20090119#bookmark-11728256

ごめんなさいごめんなさい!

それはともかく有益な情報をありがとう!

Firefox 3 だと,わざわざ xulrunner をインストールしなくても,

Firefox のバイナリから XUL アプリケーションを動かせるんだね。

これでアプリケーション配布の間口がちょっと広くなるね!

2009-01-20 追記おわり

AIR でもランタイムインストールさせる必要はあるけど,

アプリケーションの配布と同時にインストールも行うようにできるよ。

そもそも AIR の場合,アプリケーションの標準的な配布方法が定まっているのが大きいね!

参考になるページ

公式のページに参考になることが色々書いてあるよ!

みんな作って、どんどこ公開しちゃえばいいんじゃないかな!

(できれば、ぼくが見て勉強できるようにソースコード付きで…!)

おわりに

いわずとしれてるけど,これは下記のすばらしい記事へのオマージュだよ!

あとは私感だよ!

*1:原典をみればわかるけど,WindowsMac OS X で動かすのも,たいして難しくはないよ!

*2Firefox 3 が入っていれば,だけど

*3:これはいかにも必須じゃなさそうだけど,XULRunner にメインウィンドウとなる XUL ファイルの名前をわたすために使います。

*4:これは chrome/ フォルダからの相対パス指定だよ。

*5http://releases.mozilla.org/pub/mozilla.org/xulrunner/releases/1.9.0.5/runtimes/ とかにあるよ。

*6HTML でいうところの &amp; みたいな書き方のことだよ!XUL だとこの DTD を自分で用意することで,自分オリジナルな実体宣言ができるんだ。あとは,その参照先は英語の場合「foobar」だよ,みたいに DTD に書けば,世界中のみんなから使ってもらえるアプリケーションになるよ!

2008-12-09

はてブコメントの並び順を変えるグリモン書いた

はじめて Greasemonkey 書きましたよ。おかしなところがあったら教えてください。

さいしょ はてなブックマークのコメントを昇順に並べ替えるGreasemonkey を使ってたんですけど,いろいろ不満があったので書き換えてみました。原型はとどめてない。

light じゃない版あったのカー→Sort HB Comment for Greasemonkey。じゃあこれいらないや。

  • ページを読み込んだ段階では,ソートボタンを付与するだけで何もしない
    • 問答無用でソートするとコメント数が多いページで待ちが多くなるので
  • 何種類かのボタンを指定できる
  • ソート順を指定する関数を指定できる
  • ソート中は「ソート中」って出る

まぁ「指定できる」ってのは,コード(setup() 部分)書き換えてくださいねっていうレベルなんですけれど。

ほんとは silog - script/SBMCommentsViewer とか使えばいいんでしょうけど,結構ブクマの entry ページとかよく見るんですよ。

// ==UserScript==
// @name           Sort HB Comment 2
// @namespace      http://d.hatena.ne.jp/dayflower/
// @description    Sort Hatena Bookmark Entry Page
// @include        http://b.hatena.ne.jp/entry/*
// @include        http://b.hatena.ne.jp/entry/?mode=more&url=*
// ==/UserScript==

(function () {

setup([
    {   // 逆順 (コメント優先)
        label: '\u25bd\u9006\u9806',
        criteria: function (a, b) {
            // first criteria: comment
            var ac = a.getAttribute('class') || '';
            var bc = b.getAttribute('class') || '';

            if (bc.match(/nocomment/)) {
                if (! ac.match(/nocomment/))
                    return -1;
            }
            else {
                if (ac.match(/nocomment/))
                    return 1;
            }

            // second criteria: original order (reverse)
            return b.xHatebuOrder - a.xHatebuOrder;
        }
    },
    {   // 元の順序
        label: '\u25bd\u6b63\u9806',
        criteria: function (a, b) {
            return a.xHatebuOrder - b.xHatebuOrder;
        }
    }
]);

var items;      // GLOBAL

function setup(confs) {
    var marks = document.getElementById('bookmarked_user');
    if (! marks)
        return;
    if (marks.childNodes[1].textContent
                     // 非表示に設定
             .match(/\u975e\u8868\u793a\u306b\u8a2d\u5b9a/))
        return;

    var res = document.evaluate(
        '//h2[@class="comment bookmark-list"]/span/span[@class="count"]',
        document.body,  null, 7, null
    );
    if (res.snapshotLength <= 0)
        return;
    var area = res.snapshotItem(0);
    for (var i = 0, n = confs.length; i < n; i ++)
        add_button(area, confs[i]);
}

function init_items() {
    var marks = document.getElementById('bookmarked_user');
    var items = [];

    if (1) {
        var t = marks.getElementsByTagName('li');

        for (var i = 0, n = t.length; i < n; i ++) {
            items[i] = t[i];
            items[i].xHatebuOrder = i;
        }
    }
    else {
        var res = document.evaluate('li', marks,  null, 7, null);

        for (var i = 0, n = res.snapshotLength; i < n; i ++) {
            items[i] = res.snapshotItem(i);
            items[i].xHatebuOrder = i;
        }
    }

    return items;
}

function do_sort_comments(criteria) {
    if (items == null)
        items = init_items();

    items.sort(criteria);

    var marks = document.getElementById('bookmarked_user');

    var newlist = document.createElement('ul');
    newlist.setAttribute('class', marks.getAttribute('class'));

    for (var i = 0, n = items.length; i < n; i ++)
        newlist.appendChild(items[i]);

    newlist.setAttribute('id', marks.getAttribute('id'));
    marks.parentNode.replaceChild(newlist, marks);
}

function add_button(area, conf) {
    var link = document.createElement('a');
    link.style.cursor = 'pointer';
    link.style.color  = '#fff';

    link.innerHTML = conf.label || '\u25bd';

    if (conf.title)
        link.title = conf.title;

    link.addEventListener('click', function (e) {
        var me = e.target;

        //              TEXTNODE
        var backup = me.firstChild.nodeValue;
        //                         ソート中…
        me.firstChild.nodeValue = '\u30bd\u30fc\u30c8\u4e2d\u2026';

        window.setTimeout(
            function () {
                do_sort_comments(conf.criteria);
                me.firstChild.nodeValue = backup;
                //me.parentNode.style.visibility = 'hidden';
            },
            0
        );

        e.preventDefault();
    }, true);

    var button = document.createElement('span');
    button.appendChild(document.createTextNode('\u3000['));
    button.appendChild(link);
    button.appendChild(document.createTextNode(']'));

    area.appendChild(button);
}

はじめて Greasemonkey 書いて感じたこと。

  • デバッグがめんどくさい
    • エラー時に firebug console に出力される内容がちょっと変だったり。環境依存?
  • でも DOM 操作とか非常に勉強になる
  • Firefox でしか動かない JavaScript を書くくせがつきそう
  • 自作スクリプトインストールがめんどくさい
    • ドラッグドロップで登録できないんですけど…… Linux だから?
  • HTMLCollection の sort() ができなかったんで,Array にコピーしてます
    • Array.prototype.sort 使ってみたけどうまくいかなかったです

Firefox の places.sqlite からブックマークをとりだす

新規インストールしたマシンに旧マシンの Firefox のブックマークを移行しようと思いました。

で,どうせ bookmarks.html でしょと思ってみてみたら,どうも内容が乏しい。

調べてみたら Firefox 3 では places.sqlite というファイルにブックマークはじめアクセスした URL を記録しているみたいですね。

さいわいプロファイルフォルダをまるごととってあったので places.sqlite を上書きしようかと思ったんですが,そうすると新マシンでとったブックマークがなくなってしまうのでちょっと困ります。

なので places.sqlite の構造をみてみました。

% sqlite3 places.sqlite
SQLite version 3.5.9
Enter ".help" for instructions

sqlite> .tables
moz_anno_attributes  moz_favicons         moz_keywords       
moz_annos            moz_historyvisits    moz_places         
moz_bookmarks        moz_inputhistory   
moz_bookmarks_roots  moz_items_annos    

moz_bookmarks というテーブルがあやしい。

sqlite> .headers on

sqlite> SELECT * FROM moz_bookmarks;

id|type|fk|parent|position|title|keyword_id|folder_type|dateAdded|lastModified
1|2||0|0||||1209528568635986|1219119864784935
2|2||1|0|ブックマークメニュー|||1209528568636177|1228180489548243
3|2||1|1|ブックマークツールバー|||1209528568636276|1225854151400802
4|2||1|2|タグ|||1209528568636372|1209528568636992
5|2||1|3|未整理のブックマーク|||1209528568636468|1224748978301927

...... snip snip snip ......

60|1|44|3|2|Google|0||1209530922956848|1212462725554602

...... snip snip snip ......

ブックマークのタイトルはとれてますが,URL がみあたりません。

moz_bookmarks_roots テーブルをみてみると,

sqlite> SELECT * FROM moz_bookmarks_roots;

root_name|folder_id
places|1
menu|2
toolbar|3
tags|4
unfiled|5

こちらではなかったみたい。これは名前のとおり,ブックマークツリーのルートみたいですね。

では,general な名前の moz_places をみてみると,

sqlite> SELECT * FROM moz_places LIMIT 5;

id|url|title|rev_host|visit_count|hidden|typed|favicon_id|frecency
1|place:queryType=0&sort=8&maxResults=10|queryType=0&sort=8
  &maxResults=10||0|1|0||0
2|place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS
  &folder=TOOLBAR&queryType=1&sort=12
  &excludeItemIfParentHasAnnotation=livemark%2FfeedURI&maxResults=10
  &excludeQueries=1|folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS
  &folder=TOOLBAR&queryType=1&sort=12
  &excludeItemIfParentHasAnnotation=livemark%2FfeedURI&maxResults=10
  &excludeQueries=1||0|1|0||0
3|place:type=6&sort=14&maxResults=10|type=6&sort=14&maxResults=10||0|1|0||0
4|https://en-us.add-ons.mozilla.com/en-US/firefox/bookmarks/
 |/en-US/firefox/bookmarks/|moc.allizom.sno-dda.su-ne.|0|0|0|1|140
5|http://en-us.www.mozilla.com/en-US/firefox/central/
 |/en-US/firefox/central/|moc.allizom.www.su-ne.|0|0|0|2|140

こっちに URL が格納されているみたい。こちらにも title がありますが,こちらはもともとの title なのかな。

で,当て推量してると moz_bookmarksfk フィールドと moz_placesid フィールドが対応しているみたい*1

なので,結合して表示してみましょう。

sqlite> SELECT moz_places.url, moz_bookmarks.title
   ...> FROM moz_bookmarks, moz_places
   ...> WHERE moz_bookmarks.fk IS NOT NULL
   ...>   AND moz_bookmarks.fk = moz_places.id
   ...>   AND moz_places.url NOT LIKE 'place:%'
   ...> ORDER BY moz_bookmarks.id;

url|title
https://en-us.add-ons.mozilla.com/en-US/firefox/bookmarks/|Get Bookmark Add-ons
http://en-us.www.mozilla.com/en-US/firefox/central/|Getting Started
http://en-us.www.mozilla.com/en-US/firefox/help/|Help and Tutorials
http://en-us.www.mozilla.com/en-US/firefox/customize/|Customize Firefox
http://en-us.www.mozilla.com/en-US/firefox/community/|Get Involved
http://en-us.www.mozilla.com/en-US/firefox/about/|About Us
http://www.google.co.jp/|Google

おー。いい感じです。


じゃあこっからエクスポートするスクリプトを書いてみる。

#!/usr/bin/perl

use strict;
use warnings;
use DBI;

my $file = shift;
if (! defined $file) {
    die "database places.sqlite not specified";
}

my $dbh = DBI->connect("dbi:SQLite:dbname=${file}")
    or die $DBI::errstr;

my $sth = $dbh->prepare(<<'END_SQL')
    SELECT moz_places.url, moz_bookmarks.title
      FROM moz_bookmarks, moz_places
     WHERE moz_bookmarks.fk IS NOT NULL
       AND moz_bookmarks.fk = moz_places.id
       AND moz_places.url NOT LIKE ?
     ORDER BY moz_bookmarks.id;
END_SQL
    or die $dbh->errstr;

$sth->execute('place:%')
    or die $dbh->errstr;

my @items;
while (my $rec = $sth->fetchrow_hashref()) {
    #use YAML;
    #print Dump($rec);

    my ($url, $title) = map { $rec->{$_} } qw( url title );

    for ($url) {
        s'"'&quot;'go;
    }
    for ($title) {
        s'&'&amp;'go;
        s'<'&lt;'go;   s'>'&gt'go;
    }

    push @items, <<"END_ITEM";
    <DT><A HREF="${url}">${title}</A>
END_ITEM
}

if (@items) {
    my $items = join q{}, @items;

    print <<"END_HTML";
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>

<DL><p>
${items}
</DL><p>
END_HTML
}

% perl bookmark.pl $(BOOKMARKPROFILE)/places.sqlite

実行すると,

<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>

<DL><p>
    <DT><A HREF="https://en-us.add-ons.mozilla.com/en-US/firefox/bookmarks/">Get
 Bookmark Add-ons</A>
    
</DL><p>

うまくできました。

あとはこのファイルから不要な項目を削って「ブックマークの管理」から「HTML からインポート」すればいいはず。


ほんとうはブックマークフォルダの階層構造もとれるんでしょうけど,面倒だしそこまでもとめていない*2のでこれでいいやと思いました。


あと,favicon もとろうと思えばとれますが,bookmarks.html の場合,内部にアイコンがうめこまれているのに対し,places.sqlite の場合,moz_favicons テーブルからアイコンの URL を取得してブラウザキャッシュのアイコンファイルを利用しているのではないかなー。

*1.schema コマンドでスキーマをみてみましたけど,トリガ等で明示的にリレーションが設定されてはなかったです。

*2:おおかたのブックマークは Gmarks にまかせているので,メインのブックマークはそんなにないのです。