f8g

SilverlightでJavaScriptのライブラリを書く

メモ。

クラスにScriptableTypeAttributeを設定する

ScriptableTypeAttribute Class (System.Windows.Browser) | Microsoft Docs

<Browser.ScriptableType()> _
Public Class Library1
    Dim _aaa As Integer = 1

    Public Property aaa() As Integer
        Get
            Return Me._aaa
        End Get
        Set(ByVal value As Integer)
            _aaa = value
        End Set
    End Property

    Public Function bytesFromBase64(ByVal s As String) As Byte()
        Return Convert.FromBase64String(s)
    End Function

    Public Function fromBase64(ByVal s As String) As String
        Dim bytes() As Byte = Me.bytesFromBase64(s)
        Return Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length)
    End Function

    Public Function toBase64(ByVal s As String)
        Return Convert.ToBase64String(Text.Encoding.UTF8.GetBytes(s))
    End Function
End Class
Dim lib1 As New Library1
window.SetProperty("Lib1", lib1)

Firebugからはフィールドが見えない。

ScriptObjectに追加していく

ScriptObjectにメソッドをそのまま追加するのは無理そうだけど、プロパティにラムダ式で書けばできそう。

Public Class Library2
    Public bytesFromBase64 As Func(Of String, Byte()) = Function(s As String) Convert.FromBase64String(s)
    Public toBase64 As Func(Of String, String) = Function(s As String) Convert.ToBase64String(Text.Encoding.UTF8.GetBytes(s))
    Public fromBase64 As Func(Of String, String) = Function(s As String) Me._fromBase64(s)

    Public Function _fromBase64(ByVal s As String) As String
        Dim bytes() As Byte = Me.bytesFromBase64(s)
        Return Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length)
    End Function
End Class
Dim lib2 As New Library2
Dim instance As Browser.ScriptObject = window.CreateInstance("Object")
Dim fields() As Reflection.FieldInfo = lib2.GetType.GetFields

For Each field As Reflection.FieldInfo In fields
    instance.SetProperty(field.Name, field.GetValue(lib2))
Next

window.SetProperty("Lib2", instance)

Firebugからも見える。
http://gyazo.com/5f32c9a920d43f9ceb9281d71bc74268.png

Canvasでバーコードを読む

JANの仕様

ここが分かりやすい。→ バーコードの作り方

プログラミングでの話

まずは、

  1. バーコードの部分だけ写ってる写真を用意する
  2. 高さ1px分どこかを切り取る
  3. コントラスト調整
  4. 2値化
  5. ビットの文字列にする
  6. モジュールの幅を調節
    • 左右のガードバーからスケールを推測
  7. ガードバー部分を削除

みたいな準備をします。結構写真写りに左右されるかも。
13桁の数字のうち、画像として表現されているのは2-13文字目までの12文字で、最初の1文字目は2-7文字目によって判別します。なので、とりあえず2文字目以降を判別します。対応表は下みたいな感じ。

{
	left: {
		odd: {
			"0001101": 0, "0011001": 1, "0010011": 2, "0111101": 3, "0100011": 4,
			"0110001": 5, "0101111": 6, "0111011": 7, "0110111": 8, "0001011": 9
		},
		even: {
			"0100111": 0, "0110011": 1, "0011011": 2, "0100001": 3, "0011101": 4,
			"0111001": 5, "0000101": 6, "0010001": 7, "0001001": 8, "0010111": 9
		}
	},

	right: {
		"1110010": 0, "1100110": 1, "1101100": 2, "1000010": 3, "1011100": 4,
		"1001110": 5, "1010000": 6, "1000100": 7, "1001000": 8, "1110100": 9
	}
}

leftは2-7文字目を, rightは8-13文字目に対応しています。left部分のodd, evenの組み合わせで1文字目が決まってきます。対応表は下みたいなの。

{
	"oooooo": 0, "ooeoee": 1, "ooeeoe": 2, "ooeeeo": 3, "oeooee": 4,
	"oeeooe": 5, "oeeeoo": 6, "oeoeoe": 7, "oeoeeo": 8, "oeeoeo": 9
}

これで全部の数字が分かるようになりました。不安なときは13文字目でチェックできます。多分こんな感じで。

function checkDigit(nums){
	var n = 0;

	for(var i = 0; i < 12; ++i)
		n += nums[i] * ((i % 2 == 0)? 1 : 3);

	return 10 - (n % 10) == nums[12];
}

デモなど

http://arikui.github.com/ip/barcode.html
Firefox3でのみ確認。何回かボタン押せば出る。
ImageProcessing.jsのモジュールはここに。
GitHub - arikui/image_processing.js: JavaScript library for canvas

Shellなプロトコル

中身がまったく別物になってきたので別のGistにしました。ちゃんと動いてる気がする。
http://gist.github.com/69913

普通の実行

cmd:command
例「cmd:ruby -e "puts 1"」

ファイルにコードを保存して実行

cmd:command;code;args
例「cmd:vbs; WSH.Echo(WSH.Arguments(0) + WSH.Arguments(1)); "1" "2"」

JavaScriptのevalを通して実行

cmd:-e command;code;args
command, code, argsをJavaScriptのコードとして評価してから実行します。
例「cmd:-e "echo " + (100 * 100)」

コード

いろんな言語を実行できるプロトコル

昨日の発展したやつです。「eval:言語;コード」で実行します。(たとえば「eval:vbs;WSH.Echo(1)」とか「eval:ruby;puts 1」とか) Language.evaluatorsに色々追加していけば色々実行できると思います。
Tomblooを使わないようにしたら、Tomblooのコードをパクリまくるという結果に至ったのだった。

追記

何でも動くようにしました。「eval:ls」とか「eval:ruby -e "puts 1"」みたいに。ファイルを指定して実行する系は「eval:何か;コード」みたいにする。

Firefox3でWSHプロトコル

Firefox3のXPCOMUtilsを使うと、短いコードで独自プロトコルを登録できるんですね。

XPCOMUtilsを使わない方法との比較

使わない方法は以下のリンクを参考に。

QueryInterfaceをあんまり書かなくてよくなった

XPCOMUtils.generateQIが作ってくれます。

ProtocolFactoryを書かなくてよくなった

勝手に作ってくれます。自分で定義したい場合は、_xpcom_factoryに書きます。

TestModuleを書かなくてよくなった

XPCOMUtils.generateModuleが勝手にやってくれます。

オレオレプロトコルを書く

以下の必須っぽいものを実装していきます。

classDescription, classID, contractID

XPCOMUtils.generateModuleの中で参照されるものです。全部揃ってないと登録されません。contractIDは、"@mozilla.org/network/protocol;1?name=" + scheme みたいなものになります。

QueryInterface

XPCOMUtils.generateQI([Ci.nsIProtocolHandler])

scheme

登録するスキーム。

newChannel

実際の動作の中身です。ちゃんとnsIChannelを返すようにしないと、Firefoxが落ちるようです。

その他

defaultPort, protocolFlags, allowPort, newURIはコピー・アンド・ペーストで。

コード

Oretocolの中身を変えればいいようにしています。WSHを実行するのにTomblooを使用。「wsh:return 1」と書かないといけないのがちょっとダサい。

const Ci = Components.interfaces;
const Cc = Components.classes;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

var Oretocol = {
	scheme     : "wsh",
	description: "WSH Protocol",
	id         : Components.ID("F32C80B3-404B-4ce0-8590-B9DC25BDFA9A"),
	newChannel : function(aURI){
		var T   = Components.classes['@brasil.to/tombloo-service;1'].getService().wrappedJSObject;
		var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
		var que = decodeURIComponent(aURI.spec).substr(Oretocol.scheme.length + 1);

		eval("var fun = function(){" + que + "}");
		T.log(T.executeWSH(fun));

		return ios.newChannel("javascript:alert((" + fun.toSource() + ")())", null, null);
	}
};

function Protocol(){}

Protocol.prototype = {
	classDescription: Oretocol.description,
	contractID      : Oretocol.contractID,
	classID         : Oretocol.id,
	contractID      : "@mozilla.org/network/protocol;1?name=" + Oretocol.scheme,
	QueryInterface  : XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),

	scheme       : Oretocol.scheme,
	defaultPort  : -1,
	protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE | Ci.nsIProtocolHandler.URI_NOAUTH,

	allowPort: function(port, scheme){
		return false;
	},

	newURI: function(spec, charset, baseURI){
		var uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
		uri.spec = spec;
		return uri;
	},

	newChannel: Oretocol.newChannel
}

function NSGetModule(compMgr, fileSpec){
	return XPCOMUtils.generateModule([Protocol]);
}

そういえば、ios.newChannel("javascript:なんとか", null, null)が全然動かなかったような。

インストール

Latest topics > Firefoxで独自プロトコルを定義する方法 - outsider reflex」の一番下の記述のとおり。ただし、Profilesディレクトリ(WindowsならC\Documents and Settings\ユーザ名\Application Data\Mozilla\Firefox\Profiles\なんたらかんたら)にあるcompreg.datというファイルを削除してから再起動しないと読み込まれないかもしれません。

「about:何とか」も簡単に作れる

Firefoxをインストールしたところのcomponentsディレクトリ(C:\Program Files\Mozilla Firefox\components)にある「aboutなんとか.js」を参考に。

追記

このままだと、どこからでもオレオレプロトコルを実行できてしまい、外部からのヤバイコードも実行してしまいますね。以下のように変更すれば多分大丈夫です。

	protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE
	             | Ci.nsIProtocolHandler.URI_NOAUTH
	             | Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,

nsIProtocolHandler.URI_DANGEROUS_TO_LOAD を設定すると特権的な場所からのみ実行できるようになります。実行制限は、
URI_DANGEROUS_TO_LOAD < URI_IS_UI_RESOURCE < URI_LOADABLE_BY_ANYONE
って感じだそうで。
参考: nsIProtocolHandler - Mozilla | MDN

まだ聴いてない曲を意識したい

友達や家族からCDを借りたり違法ダウンロードしたりしてると、あっという間にプレイリストの中の曲が膨らんできて、一体どの曲を聴いてないか困る、なんて事例がよくあります。自分もそうです。(違法ダウンロードしているという意味ではない) なので、
http://gyazo.com/f9e1c06683e7d6f73f633132be107d22.png
みたいなスマートプレイリストを作って聴いてるんですが、聴いてない曲が減ってる気がしない。いい加減ムカついてきたので、Twitterで知れるようにします。

var user = "username";
var pass = "password";

// iTunesが起動してるかチェック
var WMI       = GetObject("winmgmts:\\\\.\\root\\cimv2");
var processes = WMI.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'iTunes.exe'");

if(processes.Count != 1)
	WSH.Quit();

var iTunes    = WSH.CreateObject("iTunes.Application");
// ライブラリのdont_listenが聴いてない曲のプレイリスト
var playlists = iTunes.Sources.ItemByName("\u30E9\u30A4\u30D6\u30E9\u30EA").Playlists;
var playlist  = playlists.ItemByName("dont_listen");
var count     = playlist.Tracks.Count;

post(count);
//WSH.Echo(count);

function post(text){
	var XmlHttp = WScript.CreateObject('MSXML2.XMLHTTP');
	XmlHttp.open('POST', "http://twitter.com/statuses/update.json", false, user, pass);
	XmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	XmlHttp.send('status=' + encodeURIComponent(text));
}

音楽聴いてないのにクソ重いiTunesがいちいち起動するのも腹立たしいので、起動してるかチェックしてます。これをタスクスケジューラで毎日実行するようにします。拡張子jsをcscriptなどに関連付けておく必要があるかもしれないので、それが嫌な人はwsfで書いた方がいいかも。
専用のアカウントでも作ってXPathGraphなどでグラフにすれば音楽を聴く意欲が増しますね。

vb.jsの日記 : For Next

人気のプログラミング言語VisualBasicっぽく書けるJavaScriptライブラリを開発する日記です。
最初はFor Nextステートメントを実装したいと思います。

function For(start){
	For.isExit = false;

	return {
		To: function(end){
			var step = 1;

			var loop = function(f){
				return {
					Next: function(){
						for(var i = start; i <= end; i += step){
							if(For.isExit) break;
							f(i);
						}
					}
				};
			};

			loop.Step = function(n){
				step = n;
				return loop;
			};

			return loop;
		}
	};
};

function ExitFor(){
	For.isExit = true;
}
For(5).To(10).Step(3)(function(i){
	console.log(i);
}).Next();
// 5, 8

For(5).To(10)(function(i){
	if(i == 8) ExitFor();
	console.log(i);
}).Next();
// 5, 6, 7, 8

ExitForがダメです。ここは妥協してreturn ExitForとします。ヤケクソです。

function For(start){
	return {
		To: function(end){
			var step = 1;

			var loop = function(f){
				return {
					Next: function(){
						for(var i = start; i <= end; i += step){
							if(f(i) == ExitFor) break;
						}
					}
				};
			};

			loop.Step = function(n){
				step = n;
				return loop;
			};

			return loop;
		}
	};
};
For(5).To(10)(function(i){
	if(i == 8) return ExitFor;
	console.log(i);
}).Next();

これで多少はJavaScriptにもVBのよさが出たでしょうか。Nextのような飾りが可愛らしい。
vb.jsの日記の1回目から妥協してしまったので多分続かない。

追記

monjudohさんのコメントを参考にして。

function ExitFor(){
	throw arguments.callee;
}
Next: function(){
	for(var i = start; i <= end; i += step){
		try{
			f(i);
		}
		catch(e){
			if(e == ExitFor) break;
		}
	}
}

これでreturnしなくてもループが抜けられるんですね。

For(5).To(10)(function(i){
	try{
		if(i == 8) ExitFor();
	}
	catch(e){}

	console.log(i);
}).Next();

みたいな使い方をしなければ大丈夫。