RhinoでjQuery

Htmlパーサーとか、Javaでゴリゴリ書くよりも明らかにJavaScript(+jQuery)で書いたスクリプトファイルをRhinoで実行したほうが楽そうなので、Rhino上でjQueryを実行する方法を調べてみました。

前提条件

JVM組み込みのRhinoではなく、MozillaのRhino実装(現行の最新版 V1.7R3)を使用します。これはJavaのバージョンによってスクリプトの動作が変わるような危険性を潰すためです。取り敢えずjarファイルをダウンロードしてクラスパスへ配置。

また、Rhino上でjQuery単体の実行はできません。Rhino環境に欠けているdom操作、httpリクエスト、イベント処理などを追加するため、env.jsというライブラリが別途必要となります。
env.jsのスクリプトファイルとして、env.jsページに公開されている最新版(現行ではv1.2)のRhino環境用jsファイルを使用します。(というかgithubから取ってきたコードをantでビルドするとコケる・・・)

env.jsのreadme.txtによると、env.jsが動作を確認しているjQueryの最新版はv1.3.2とのことなので、こちらからjQueryのjsファイルを入手します。

最後に、env.jsのjsファイル(env.rhino.1.2.js)とjQueryのjsファイル(jquery-1.3.2.min.js)を"src/main/resources"フォルダへ配置します。

ソース

sample.js

function doParse(){
	print("Hello");
	//Twitterのトップ画面
	window.location = 'http://twitter.com/';
	//jQuery初期化
	jQuery.ready();
	//ドキュメントのタイトル取得
	print("Title: " + document.title);
	//js-language-linkクラスの要素数を戻す
	return $('.js-language-link').length;
}

Twitterのトップ画面から、特定クラスの要素数を取得します・・・・処理内容には特に意味はありません。セレクタの動作が見れて、JavaScriptからJavaへ値の受け渡しができることを確認できればそれで良しとします。

ちなみに、googleの検索件数を取得しようとすると何故かHttpステータス403エラーとなります。今のところ原因不明。

で、Java側からJavaScript(sample.jsのdoParse()関数)を呼び出すソースはこちらです。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.tools.shell.Global;


public class Parser {
	
	//実行対象となるJavaScript関数名
	private static final String JS_FUNCTION_NAME = "doParse();";
	
	public static void main(String[] args){

		Reader envjsReader = null;
		Reader jqueryReader = null;
		Reader scriptReader = null;
		try {
			envjsReader = new FileReader(new File("src/main/resources/env.rhino.1.2.js"));
			jqueryReader = new FileReader(new File("src/main/resources/jquery-1.3.2.min.js"));
			scriptReader = new FileReader(new File("src/main/resources/sample.js"));
		} catch (FileNotFoundException e) {
			//サンプルなのでエラー処理は考慮せず
			e.printStackTrace();
		}

		//env.jsはshellでしか定義されていないfunction(print等)を必要とするため、Global Contextを取得
		Global global = new Global();
		Context cx = ContextFactory.getGlobal().enterContext();
		global.init(cx); 
		cx.setOptimizationLevel(-1);
		cx.setLanguageVersion(Context.VERSION_1_5);

		Scriptable scope = cx.initStandardObjects(global);
		try {
			
			//env.jsのコンパイル
			Script envjs = cx.compileReader(envjsReader, 
					"env.rhino.1.2.js", 
					1, null);
			envjsReader.close();
			//読みこみ。
			//execすることで複数のjsファイルをContextに読みこみ可能
			envjs.exec(cx, scope);
			
			//jqueryのコンパイルと読みこみ
			Script jquery = cx.compileReader(jqueryReader, 
					"jquery-1.3.2.min.js", 
					1, null);
			jqueryReader.close();
			jquery.exec(cx, scope);
			
			//スクリプトのコンパイルと読みこみ
			Script script  = cx.compileReader(scriptReader, 
					"script.js",
					1, null);
			scriptReader.close();
			script.exec(cx, scope);
			
			//JavaScript function doParse() 実行と結果の取得
			Object result = cx.evaluateString(scope, JS_FUNCTION_NAME, "<cmd>", 1, null);
			
			System.out.println("rtn: " + Context.toString(result));
		} catch (IOException e) {
			// サンプルなので例外処理は考慮せず
			e.printStackTrace();
		}
	}
}

Mozillaのチュートリアルに従うと、env.js読み込み時にエラー(org.mozilla.javascript.EcmaError: ReferenceError: "print" is not defined.)が発生するので注意。

ちなみに実行結果の標準出力はこのような感じです。

[ Envjs/1.6 (Rhino; U; Windows 7 amd64 6.1; en-US; rv:1.7.0.rc2) Resig/20070309 PilotFish/1.2.13 ]
Hello
error loading script [object HTMLScriptElement] ReferenceError: "twttr" is not defined.
error loading script [object HTMLScriptElement] ReferenceError: "WATCH" is not defined.
error loading script [object HTMLScriptElement] ReferenceError: "WATCH" is not defined.
error loading script [object HTMLScriptElement] ReferenceError: "WATCH" is not defined.
Title: Twitter
rtn: 27

スクリプトエラーは出ているものの、ちゃんとセレクタで要素数を取得できている事が確認できます。