UWSCで.chmファイルを解析する

UWSCCUIインタープリターを作っていて、ヘルプを載せようかと思ったのだけど、天から
「ばーじょんあっぷ」
という声がして、面倒になってやめた。


しかし、、、バージョンアップはそう頻繁じゃないし、ヘルプを生成する補助モジュールがあっても良いんじゃないか?
と思ったので、作ってみた。



その前にhh.exeについて

hh.exeに.chmファイルを渡すと開いてくれるのですが、その際、開くところも指定可能です。
といってもURLがわかっているケースのみ。

hh.exe uwsc.chm ::/_RESOURCE/function.htm#getactiveoleobj

さらに、デコンパイルはこんな感じ。

hh.exe -decompile (出力先フォルダー) uwsc.chm

補助スクリプト

chm.uws

OPTION EXPLICIT, OPTFINALLY

IFB GET_UWSC_NAME = "chm.uws" THEN
	DIM path = NULL, keys, i
	TRY
		// UWSC.chmを展開
		path = CHM.Decompile(GET_UWSC_DIR + "\uwsc.chm")
		// キーワードを読み込む
		keys = CHM.GetKeyWords(path, TRUE)

		FOR i = 0 TO LENGTH(keys) - 1
			PRINT "key:" + keys[i][0]
			PRINT keys[i][1]
			PRINT "--------------------------------------------------------------"
		NEXT
	FINALLY
		IF path <> NULL THEN CHM.DeleteFolder(path)
	ENDTRY
ENDIF


MODULE CHM

	DIM _fso = NULL, _regexp = NULL

	FUNCTION GetTempFolder(sub=FALSE)
		IF _fso = NULL THEN _fso = CREATEOLEOBJ("Scripting.FileSystemObject")
		RESULT = _fso.GetSpecialFolder(2)
		IFB sub THEN
			DIM subp = RESULT + "\" + _fso.GetTempName()
			WHILE _fso.FolderExists(subp) OR _fso.FileExists(subp)
				subp = RESULT + "\" + _fso.GetTempName()
			WEND
			RESULT = subp
			_fso.CreateFolder(RESULT)
		ENDIF
	FEND

	FUNCTION DeleteFolder(path)
		IF _fso = NULL THEN _fso = CREATEOLEOBJ("Scripting.FileSystemObject")
		RESULT = _fso.DeleteFolder(path, TRUE)
	FEND

	FUNCTION Decompile(chmFile, outPath=NULL)
		IF _fso = NULL THEN _fso = CREATEOLEOBJ("Scripting.FileSystemObject")
		RESULT = outPath
		IF RESULT = NULL THEN RESULT = GetTempFolder(TRUE)
		EXEC("hh.exe -decompile " + RESULT + " " + chmFile, TRUE)	// パスを""で括るとダメ。cmdではOKなのに
		IFB _fso.GetFolder(RESULT).Files.Count = 0 THEN
			IF outPath = NULL THEN DeleteFolder(RESULT)
			RESULT = NULL
		ENDIF
	FEND

	FUNCTION GetKeyWords(path, getc=FALSE, ext=".hhk")
		IF _fso = NULL THEN _fso = CREATEOLEOBJ("Scripting.FileSystemObject")
		DIM i, j, tmp
		HASHTBL res
		FOR i = 0 TO GETOLEITEM(_fso.GetFolder(path).Files) - 1
			tmp = ALL_OLE_ITEM[i].Path
			IFB COPY(tmp, LENGTH(tmp) - LENGTH(ext) + 1) = ext THEN
				tmp = GetKeyWordsFromFile(tmp, getc)
				FOR j = 0 TO LENGTH(tmp) - 1
					res[LENGTH(res)] = tmp[j]
				NEXT
			ENDIF
		NEXT
		RESULT = SAFEARRAY(0, LENGTH(res) - 1)
		FOR i = 0 TO LENGTH(res) - 1
			RESULT[i] = res[i]
		NEXT
	FEND
	FUNCTION GetKeyWordsFromFile(path, getc=FALSE)
		IF _fso = NULL THEN _fso = CREATEOLEOBJ("Scripting.FileSystemObject")
		DIM i, ie, fid = -1, tar, n, r
		HASHTBL res
		TRY
			ie = CREATEOLEOBJ("InternetExplorer.Application")
			ie.Navigate("about:blank")
			fid = FOPEN(path)
			ie.Document.body.innerHTML = FGET(fid, F_ALLTEXT)
			FCLOSE(fid)
			fid = -1
			IFB ie.Document.body.childNodes.length THEN
				tar = ie.Document.body.childNodes.item(0).childNodes
				FOR i = 0 TO tar.length - 1
					n = tar.Item(i)
					WHILE n.childNodes.length = 1
						n = n.childNodes.item(0)
					WEND
					r = SAFEARRAY(0, 1)
					r[0] = n.childNodes.item(0).value
					r[1] = n.childNodes.item(1).value
					IF getc THEN r[1] = GetContent(_fso.GetParentFolderName(path), r[1])
					res[LENGTH(res)] = r
				NEXT
			ENDIF
		FINALLY
			ie.Quit()
			IF fid <> -1 THEN FCLOSE(fid)
		ENDTRY
		RESULT = SAFEARRAY(0, LENGTH(res) - 1)
		FOR i = 0 TO LENGTH(res) - 1
			RESULT[i] = res[i]
		NEXT
	FEND

	FUNCTION GetContent(path, url)
		IF _fso = NULL THEN _fso = CREATEOLEOBJ("Scripting.FileSystemObject")
		IF _regexp = NULL THEN _regexp = CREATEOLEOBJ("VBScript.RegExp")
		DIM fid, fname = url, tar = POS("#", url)
		IFB tar THEN
			fname = COPY(url, 1, tar - 1)
			tar = COPY(url, tar + 1)
		ENDIF
		fid = FOPEN(_fso.BuildPath(path, fname))
		RESULT = FGET(fid, F_ALLTEXT)
		FCLOSE(fid)
		IFB tar <> 0 THEN
			_regexp.IgnoreCase = TRUE
			_regexp.Multiline = FALSE
			_regexp.Pattern = "<a\s[^>]*name=(['<#DBL>]?)" + tar + "\1[^>]*>[^<]*</a>([\s\S]*?)(?:<a\s[^>]*name=|$)"
			fid = _regexp.Execute(RESULT)
			IFB fid.Count THEN
				IF fid.Item(0).SubMatches.Count > 1 THEN RESULT = fid.Item(0).SubMatches.Item(1)
			ENDIF
		ENDIF
		TRY
			fid = CREATEOLEOBJ("InternetExplorer.Application")
			fid.Navigate("about:blank")
			fid.Document.body.innerHTML = RESULT
			RESULT = fid.Document.body.innerText
			IF tar <> 0 THEN RESULT = TRIM(RESULT)
		FINALLY
			fid.Quit()
		ENDTRY
	FEND

ENDMODULE

.chmファイルのルールを知らないので、適当な部分が多いですが、UWSCのヘルプはこれで解析可能。

Decompile関数

第一引数は展開したい.chmファイル。(フルパス可。ただしスペース含むパスは不可)
第二引数を省略すると、テンポラリーフォルダーに適当なフォルダーを作って、そこに展開します。
テンポラリーフォルダーがスペースを含むパスだとおかしなことになるので、その場合は第二引数を指定してやってください。
リターンは、成功でパス。失敗はNULL

GetKeyWords関数

第一引数は、.chmファイルの展開先フォルダー
第二引数は、GetKeyWordsFromFile関数にそのまま渡す。
第一引数のフォルダー内の末尾が第三引数のファイルを探して、GetKeyWordsFromFile関数に渡します。
リターンは、GetKeyWordsFromFile関数の結果集合。

GetKeyWordsFromFile関数

第一引数は、解析対象(.hhkファイル想定)
第二引数により、リターンが変わります。
基本的には、.hhkファイル(キーワードファイル)を解析し、そのキーワードとURL?を取得します。
第二引数がTRUEの場合、URLをGetContent関数に渡した結果に変換します。
リターンは、キーワードとURL?のタプルのSafeArray。
.hhkファイルはHTMLなので、IEさんの力で解析しています。
ここに手を入れると、同じURLを二度取得しないとか可能です。

GetContent関数

第一引数は、.chmファイルの展開先フォルダー
第二引数は、取得URL
第二引数で名前が指定されていない場合、URLをIEさんに渡して、そのbodyのinnerTextを取得。
名前が指定されている場合、そのAタグから次の名前付AタグまでをIEさんに渡して、そのbodyのinnerTextを取得。
呼ばれる度にIEさんを起動・終了するから遅い。
ここに手を入れると、色とかも取得できそう。

備考

GetContentまではしないで、GetKeyWordsで取得したURLに「::/」を付与して「hh.exe」呼んだ方が良い気もする、、、。