Xpath使えるんだからGM_も使いたいよね

ということでGM系関数を実装してみたが、その前に普通の言語やサイト上での利用と異なり、SleipnirScriptでは*1直接的なライブラリ呼び出し手段が無い。さらに、GM_xmlhttpRequestを非同期で使うには毎回@type SleipnirScriptで起動して_windowにオブジェクト渡して、といった処理がいる。ライブラリ呼び出しのための定型文はコピペでいいものの、ライブラリの内容よりライブラリ呼び出しが複雑になっては本末転倒なので、橋渡し的なスクリプトにそのあたりの処理とライブラリのロードをまとめて行なわせることにした。
ラボの製作中:ライブラリブリッジがそのためのスクリプトで、Sleipnir\plugins\seahorse\bridge.jsに保存しておき、

var include = [
 "GMWrapper", //GM_系を使えるようにするラッパー
 ""//ダミー。無くてもいい
];
RunScriptEx(ScriptFullName.replace(ScriptName, '').replace(/(seahorse|scripts)\\(|\.js)/, '') + "seahorse\\bridge.js", "loadlib", [todo, include, ScriptName]);
 
function todo()
{ 
  //todo
}

のようにして使う。
ラボの製作中:GMラッパーは、このライブラリブリッジから呼び出すことを想定している。Sleipnir\plugins\seahorse\lib\GMWrapper.js*2に保存してあれば、上記のように"GMWapper"を指定するだけで読み込める。GM_registerMenuCommandはキー関連が未実装。右下隅にウサギのアイコンが出るので左クリックしてメニューを表示する。メインメニューへの追加は無理な話なので諦めてくれ。
GM_xmlhttpRequestはonerror, onreadystatechangeが未実装。postメソッドやshift_jisでの取得は大丈夫だと思う。ライブラリブリッジから呼び出す分には非同期だしクロスドメインも大丈夫なはず。まだ完成度は低く本家との互換性も怪しいものの、GM_logとかGM_getValue, GM_setValueなんかは移植用途でなくてもそれなりに便利に使えると思う。
GM系ではないが、Tridentではdataスキームが使えないので、base64のコードを受け取ってデコード結果をファイルに保存するbase64toFile関数を用意した*3

ライブラリブリッジでGMラッパーとJavaScript-Xpathを利用するサンプル

アイコンクリックででてくるメニューにいくつかのコマンドを追加する。UIがわかりづらいが検索バーのスクリプトを実行*4はISleipnirやAPIGM_、Xpathもつかえるブックマークレットのように使える。

var include = [
	"GMWrapper", //GM_系を使えるようにするラッパー
	"javascript-xpath-latest*", //Xpath使えるようにするライブラリ
	""//ダミー。無くてもいいけど
];
RunScriptEx(ScriptFullName.replace(ScriptName, '').replace(/(seahorse|scripts)\\(|\.js)/, '') + "seahorse\\bridge.js", "loadlib", [todo, include, ScriptName]);

function todo()
{
	GM_registerMenuCommand("検索バーのスクリプトを実行", evalScript, "", "", "");
	GM_registerMenuCommand("検索バーのスクリプトを一時登録", registerScript, "", "", "");
	GM_registerMenuCommand("リンクの数", countLink, "", "", "");
	GM_registerMenuCommand("ヘッダの取得", showHeader, "", "", "");
	
	function countLink()
	{
		var r = document.evaluate('//a[@href!=""]', document, null, 7, null);
		GM_log("リンクの数は" + r.snapshotLength);
	}
	
	function showHeader()
	{
		GM_xmlhttpRequest({
			method:"get",
			url:location.href,
			//charset:"euc-jp",//省略したらADODBの自動判別を使う
			headers:{
				"User-agent":"Sleipnir"
			},
			onload:function(details) {
				GM_log(details.responseHeaders);
				//GM_log(details.responseText);
			}
		});
	}
	
	function evalScript()
	{
		eval(sleipnir.API.SearchBarString);
	}
	
	function registerScript()
	{
		GM_registerMenuCommand(
			prompt("コマンド名の入力", "一時メニュー"),
			(function() {
				var src = sleipnir.API.SearchBarString;
				return function () {eval(src);}
			})(),
			"", "", ""
		);
	}
}

28日追記

ScriptNameが拡張子なしのファイル名を返す環境*5があるようなので呼び出し例を修正した。
ライブラリブリッジをmosaで動作しないようにし、ライブラリから利用しそうな変数を整理した(ver0.30)。
GMラッパーのメニュー関係を強化し、accelKey*6とaccessKeyを使えるようにした。メニュー自体はビューにフォーカスがある状態でctrl+Mで開く(ver0.20)。GM_xmlhttpRequestのonreadystatechangeが未実装なままだけどどうしたものか。

*1:wshでもそうだけどXML形式だと普通にできた(´・ω・`)

*2:libフォルダの作成が必要

*3:ぶっちゃけると自分が使いたかっただけなんだけど

*4:何で検索バーかってうちのSleipnirはアドレスバープラグイン外してるしツールバーを検索バーが占有してるから

*5:条件はよくわからない

*6:修飾キー(shiftとか)は一つだけ

JavaScript-XPathためしたよ

特に何か便利なものできたって訳ではないのだけど、気づいたこととか書いた方が良さそうだから。サイト製作者が普通に利用する分には問題無いようなことだけど、SeaHorseスクリプト作ってる人は気をつけたほうがいいと思った。

var fso = sleipnir.CreateObject('Scripting.FileSystemObject');
var path = sleipnir.ScriptFullName.replace(sleipnir.ScriptName, '');
var file = fso.OpenTextFile(path + "javascript-xpath-latest.js");
var window = _window;
eval(file.ReadAll());

var r = document.evaluate('//div', document, null, 7, null);
alert("div要素の数は" + r.snapshotLength);

FSOでライブラリの中身を読み込んでeval, execScriptってやりやりたいよね。でも、これをJavaScript-Xpathでやるとスクリプト要素の無いページでエラーを吐く。
JavaScript-Xpathでは、

<script src="javascrpt-xpath.js?name1=value1&name2=value2"></script>

みたいな指定で設定を変えて読み込める。多分*1。そのために、"自分自身"のsrcの?以降の文字列を取得しているが、eval, execScriptだとその"自分自身"の取得に失敗する。大抵は問題にならないはずなんだけど、下記のケースでエラーを起こす。

  1. 最後に読み込まれたスクリプト要素が同じようなロジックを持っているとき
  2. スクリプト要素が全く無いページ

1は例えばプヨぷよの日記で試しに最後に読み込まれたスクリプト要素を取得してみると、

http://s.hatena.ne.jp/entries.json?uri=http://d.hatena.ne.jp/Puyo2/20071118/p1&uri=〜

srcはこのようになっていた。これだけならおかしなことにはならないが、ver0.1.8ではさらに

var convigValue = configStringSplited[1];

となっていて、configValueが未定義になっている。そのため、続く

 if (configValue == undefined)

でエラーになる。ただのtypoだからこっちはすぐ修正されると思うけど。
2は、取得した"自分自身"であるスクリプト要素がnullになってしまうため、null.srcへのアクセスでエラーを起こす。本来の利用法ではありえないことなので、利用する側で対処する必要がある。ので、余計な要素を追加することになるが、

var path = sleipnir.ScriptFullName.replace(sleipnir.ScriptName, '');
var jscript = document.createElement("script");
jscript.type = "text/javascript";
jscript.src = path + "javascript-xpath.js";
document.getElementsByTagName("head").item(0).appendChild(jscript);

のようにスクリプト要素として追加してやった方が無難。

*1:ソース見た感じだと

ポップアップ辞書がラボを卒業

辞書データのコンバートが実用的速度になったのを受けて、コンバータ、本体ともに一般向けに微調整したものを公式フォーラムへ投稿した*1。導入が煩雑であることと、これまでスクリプトを積極的に利用したことが無い層の需要があるであろうことから質問がくると踏んで新規トピックにした。GENE95辞書以外のデータコンバータの投稿も是非お願いしたい。
ラボの1.13からの機能的な変更点として、マウスオーバーによるポップアップとテキスト選択によるポップアップを両方動作させることが可能になった。デフォルトで両方動作するようになっているが、その場合、テキスト選択状態では他の単語をマウスオーバーしてもポップアップしないので注意。

*1:ラボのものは一応数日残しておいたあと削除する予定

辞書データコンバータの改善

SQLite の INSERT は遅いのか? - 元祖 サトシのブログ
たまたまここを見たがどうやら

SQLiteでは明示的にトランザクションを開始しない限り、INSERT処理の前後に必ず"BEGIN"、"COMMIT"が実行される

http://journal.mycom.co.jp/special/2004/php5/007.html

のために大量のINSERTを行うと非常に効率が悪いらしい。というわけで万単位のINSERTを実行する辞書データコンバータに

db.Query("BEGIN TRANSACTION;");
  (略)
db.Query("END TRANSACTION;");

の2文を追加したところ所要時間を45分から15秒*1へと短縮できた。なんか変だとは思ってたけど知らないって怖いね。というか恥ずかしくて泣きそうだよ。
一度やればいい処理なので多分ほとんどの利用者にとっては意味が無いと思われるが、ラボを更新しておいた。まだ遅いっちゃ遅いし環境にも因ると思うけど格段に早くなるのは間違いない。一般公開考えてもいいレベルかもしれない。

*1:どちらも時計で見ただけだけ

追記

IEDockExを使う例。ボタンクリックでアクティブなタブのfavicon用URLハッシュを表示する。それだけしかしないのであんまり役にたたない。

index.html(IEDockExに表示するhtmlファイル)

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script><!--
      function init()
      {
        open("adapter.html");
      }
      
      function showUrlHash()
      {
        var link = document.createElement("a");
        link.href = sleipnir.API.URL;
        document.getElementById("view").innerText = sleipnir.API.GetUrlHash(link.hostname, 16);
      }
    --></script>
  </head>
  <body onload="init()" style="margin:0;">
    <form>
      <input type="button" onclick="showUrlHash()">
      <span id="view"></span>
    </form>
  </body>
</html>

SeaHorseスクリプト

// ==UserScript==
// @name        AddISleipnirToIEDockEx
// @include     file*adapter.html
// @description IEDockExへISleipnirを渡す
// @type        SleipnirScript
// ==/UserScript==
_window.opener.sleipnir = sleipnir;
_window.close();

config.ini

Align=top
Size=20
NewWindow=true

index.htmlの読み込み時にadapter.htmlを開くのでindex.htmlと同じ位置*1にadapter.htmlをおいておく。中身は空でOK。開かれたadapter.htmlでSeaHorseスクリプトを動かす。config.iniの想定はこんな感じだけれどさして重要ではない。NewWindowをfalseにするならopen()のとこでターゲット指定した方がいいって程度。

*1:plugins\IEDockEx\〜〜

IEDockやIEPanelへISleipnirを渡す

IEパネルのトラバ企画で書いたような気がしたけど気のせいだったので今更書いてみる。IEPanelだとパネルプラグインが作れるから今一だけど、IEDockの場合オリジナルのDockが作れるし、パネル切り替えなくても読み込むので役に立つこともあるかもしれない。技術的には大したことなくて、SeaHorseを使ってローカルファイル(仮にA.html)で

_window.opener.sleipnir = sleipnir;
_window.close();

が動くようにしておき、IEPanel, IEDock側でそのローカルファイル(A.html)を開いてやればいい。これでIEPanelやIEDockで開いているローカルファイルでグローバル変数sleipnirが追加される。ただ、IEPanelでもIEDockでもだけど、setTimeoutなんかのタイマーイベントと併用すると(?)Sleipnirが終了しなくなるような気がする。
これ使ってExSearchBarをIEDockに移植しようと思ったんだけど、IEDockはonkeyupとonkeydownが使えない*1みたい(´・ω・`)

*1:onkeypressはいけるけどIMEがONだとonkeypressイベントは発生しないから日本語入力でサジェストが働かなくなっちゃう

ポップアップ辞書をバージョンアップ(1.13)

データベース内に単語が存在しない場合、語幹の抜き出しを試みるようにした。精度ばっちりとはいえないが、そのままでデータベースに存在する場合はそちらを表示するので今までまともに訳せてた単語に影響はないはず。他はフォントをスクリプト側で指定したりエラー処理といった細かい修正も。
語幹抜きだし処理の部分でGPLのソースを参考にしてるので今回のバージョンからGPLに。明示的になっただけで今までと大差あるわけじゃないけど。GPLは派生物もGPLになるのが面倒っちい。
一ヶ月ちかく放置してたのはSleipnirならではなスキンのために石に刻んだルーンをモチーフにしたボタンを作ってたからなんてことは全くなく暑かったのとなるかな*1やってたから。今年の夏は悪夢だった。

*1:とはいってもPC自体電源入れたくなかったからこっちもあんまり進んでないんだけど