2011/10/09

JScriptは言語単体ではSafeArrayを作ることができません。

そこでJScriptでSafeArrayが必要な場合、VBScriptを併用しVBScriptの配列(これはSafeArrayです)をJScriptに取り込む方法や、Scripting.DictionaryのItems()メソッドを使う方法などが使われているようです。

しかしこれらの方法で多次元のSafeArrayを作るサンプルをあまり見かけませんでした。Dictionaryの方法ではそもそも1次元しか無理ですしね。そんな中、この記事を発見しました→JScriptの配列とVBScriptの配列(SafeArray)を相互変換する方法(2次元編) - プログラマとSEのあいだ
この記事の二つ目の例ではExcelを使用しRegionオブジェクトが二次元配列を返す点を利用しています。これはなかなか盲点というかアイデアものではありますが、実行速度にやや難があるかな?と思いました。

注: ただしこの記事の方法は、もともとExcelで二次元配列が必要な場合があったから考案されたもののようで、その用途においてはExcelを起動するコストは考慮しなくてよいのかもしれません。

一つ目の方法ではVBScriptを併用していますが、.wsfファイルを使用してJScriptとVBScriptを混在させる形式をとっています。この方法はWSHでは問題ありませんが、複数のスクリプトエンジンを混在できないホスト環境では問題があります。

注:そんな環境ってあるのか?と聞かれそうですが、たしかにHTML/HTA/Windowsデスクトップ(サイドバー)ガジェット/WSH/classic ASPなどほとんどの環境では大丈夫そうです。ただ私が最近はまっているJScript実行環境であるところのAzureaでは無理ですね。WSHでも.jsファイルにこだわるのであれば。

そこで考えたのが、ScriptControlを使用してJScriptのコードの中でVBScriptのコードを実行させる方法です。以下のような感じになります。

function array2dToSafeArray2d(jsArray2d)
{
	var sc = new ActiveXObject("ScriptControl");
	sc.Language = "VBScript";
	var code =
'Function ConvertArray(jsArray)\n' +
'	ReDim arr(jsArray.length - 1, jsArray.[0].length - 1)\n' +
'	outerCount = 0\n' +
'	For Each outer In jsArray\n' +
'		innerCount = 0\n' +
'		For Each inner In outer\n' +
'			arr(outerCount, innerCount) = inner\n' +
'			innerCount = innerCount + 1\n' +
'		Next\n' +
'		outerCount = outerCount + 1\n' +
'	Next\n' +
'	ConvertArray = arr\n' +
'End Function\n';
	sc.AddCode(code);
	return sc.Run("ConvertArray",jsArray2d);
}

まあやっていることは本当にJScriptの配列をバラしてVBScriptの二次元配列に詰め直しているだけです。

ただいくつかポイントがあって、まずVBScriptからはJScriptのオブジェクトメンバーにドット演算子でアクセスができます。JScriptの配列はオブジェクトと同一であり、配列はオブジェクトに0,1,2...という名前のプロパティが存在することになります。しかしVBScriptで数字のメンバ名はそのままではドット演算子でアクセスできないので、[]でくくる必要があります(これ、予約語なんかもそうですね。あとVB6でもVB.NETでも同じなので覚えておくといいかも)。なので次元数2の配列の長さを調べるのにjsArray.[0]でまず内側の配列オブジェクトを取得しているわけです。

さらにポイントとして、VBScriptでJScriptの配列を含むオブジェクトメンバを列挙するのにコード例のようにFor Each Next構文が使えます。ただしFor Nextを使ってインデックスアクセスはできません。というのもjsArray.[3]とかはあくまでjsArrayオブジェクトの3プロパティの値を参照しているにすぎず、jsArray.[I]という書き方ができないからです(これだと単にIプロパティの値を見てることになる)。Eval関数を併用すれば可能ではありますが、コードの中にコードを含ませさらにその中にまたコードを含ませるのも微妙なのでここでは使ってません。

あとは紹介した記事の関数部分だけ置き換えればJScriptのVBArrayオブジェクトを用いたテストもできるかと思います。注意点はExcelオブジェクトは配列添え字が1から始まるのに対し、VBScriptの配列は0から始まる点です。LBound関数を使えばその差違は吸収できるかな、と思います。

最初は多次元配列というかn次元配列に拡張した関数を書いてやろうと企んでましたが挫折しました。ネストしたループではなく再帰呼び出しである必要がありますし、ReDimは次元数を動的に指定することができないので実行するVBScript自体を動的生成しなければいけません。興味がある方はチャレンジしてみてください。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/10/09/204198.aspx

2008/12/07

レガシASPでサイトを作ってると、Shift-JISなサイトを作るのが基本になると思います。なんでかというと、FileSystemObjectが基本的にShift-JISの読み書きにしか対応しておらず(UTF-16もいけますが)、いまどきのUTF-8を使うのはちょっと面倒です(FSOの代わりにADODB.Streamを使えば行けますけどどうでしょうねー?私はあんまり好きじゃないです)。

ただ、UTF-8な他のWebサイト/サービスと連携する場合はどうしても避けて通れません。そこでレガシASPでShift-JISなページを作る際、UTF-8文字列を扱う上で知っておくべきこと。

1. escape関数を使うとShift-JISでURLエンコードがされる

ASPはだいたいVBScriptで書くと思うんですが、隠し関数であるescape関数を使うとURLエンコードができます。ですが、escape関数は呼び出し元のページコードの文字コードでエンコードします。なのでShift-JISなページで呼び出すとShift-JISのエンコードURLを出力します。(ちなみにWSHで使うとUTF-16のものになる)

JScriptのencodeURIComponent関数はどんな場合でもUTF-8文字列を出力するので、これを使うといいでしょう。使い方はこうです。

Set sc = CreateObject("ScriptControl")
sc.Language = "JScript"
Set js = sc.CodeObject
Response.Write js.encodeURIComponent("文字列") 

逆にShift-JISなページでShift-JISなエンコードURL文字列を取得したい場合は単にescape関数を呼び出せばいいです。
さらに別なケースですがUTF-8なページでShift-JISなエンコードURLを取得したい場合は、こんな関数を使うといいんじゃないでしょうか

2. XMLHTTPでPostメソッドでSendする際は必ずUTF-8でURLエンコードがされる

Set xh = CreateObject("MSXML2.XMLHTTP")
xh.Open "POST", "http://hogehoge/hoge.aspx", False
xh.Send "文字列"

このように何も考えずに書いても、勝手にUTF-8でURLエンコードされてPostされるので大丈夫です。

3. UTF-8なページのHTMLを読み込む際

標準機能だけでやろうと思うとADODB.Streamを使うしかないと思います。
ちなみに読み込むページの文字コードが不明の場合は判定した上で変換する必要がありますが、これはかなり面倒なので、BASP21を使うといいんじゃないでしょうか。

Function GetPageString(strUrl)
 Set bobj = CreateObject("basp21")
 Set oHTTP = CreateObject("Msxml2.XMLHTTP")
 oHTTP.Open "GET", strUrl, False
 oHTTP.Send
 GetPageString = bobj.Kconv (oHTTP.responseBody,4)
End Function

これは引数にURLを与えるとそのHTMLを文字列として取得します。対象の文字コードが何であってもOKなのがミソ。

4. UTF-8のURLエンコードされたクエリ、あるいはPOSTされたデータを受ける際

これのやり方が分からない!具体的にはトラックバックpingなんかを受け取る際に困ります(さすがにShift-JISでトラックバックpingを送れ!というのはゴーマンだと思います)。私はここだけASP.NETを使って逃げました。どなたかやり方わかります?

追記。Request.BinaryReadしたやつをADODB.Streamにかけたあと&でsplitして=でsplitしてDictionaryに入れてdecodeURIComponentすればいけるかな?

ただし、ここだけASP.NETを使う際にも注意が必要です。まずweb.configの<system.web>セクションに

<globalization
requestEncoding="Shift-JIS" responseEncoding="Shift-JIS" fileEncoding="Shift-JIS"/>

というのを埋め込んで、まずレスポンスエンコーディングをShift-JISにしておきます。IISの設定でもいいですが。

続いてコーディング。Request.QueryStringやRequest.Formは使えないので、Request.InputStreamを使ってごりごり読まないと駄目じゃないかな・・・。なぜかVB.NETですがUTF-8なトラックバックpingをShift-JISなページで受けるサンプルコードを。

Dim str As System.IO.Stream
Dim counter, strLen, strRead As Integer
str = Request.InputStream
strLen = CInt(str.Length)
Dim strArr(strLen) As Byte
strRead = str.Read(strArr, 0, strLen)

Dim Forms As New Dictionary(Of String, String)

For Each item As String In Split(Encoding.UTF8.GetString(strArr),"&")
	If InStr(item, "=") Then
		Dim s As String() = Split(item, "=")
		If s.Length = 2 And Not Forms.ContainsKey(s(0)) Then
			Forms.Add(s(0), HttpUtility.HtmlEncode(HttpUtility.UrlDecode(s(1), Encoding.UTF8)).Trim().Replace(vbNullChar, ""))
		End If
	End If
Next

↑自分でも謎なコードを書いてたのでちょっとマシなのに修正。コンパイル通るかどうかわかりませんが・・・さらにゴミコードが残ってたのでバッサリ切りました。

ただし!これの問題は改行コードが消えることなんです。対処法は見つけていません(勘違いでした)。もっといい方法があったら教えてください。そもそもInputStreamを使わないでRequest.Formとか使いたいんですが、Shift-JISのところにUTF-8が来るとうまくいかないですねぇー。

というわけで長々と書きましたが、Shift-JISにこだわらなければこんなに苦労することはないです。FileSystemObjectがUTF-8を読み書きできないので私はSJISにこだわってるだけです。FSOはWSHからも使いますので・・・

元記事:http://blogs.wankuma.com/mutaguchi/archive/2008/12/07/162931.aspx

2007/11/08

.NETアプリをFramework付属のコンパイラを使ってコンパイルし、できあがった実行ファイルexeを実行するというものです。これがまた便利でw *.vbか*.csか*.jsファイルを開いた状態で、コメント行に記述したコンパイラに渡すオプション(VB.NETなら'/r:System.Windows.Forms.Dll /t:winexeという行の/r:System.Windows.Forms.Dll /t:winexeという部分)を選択すると、そのコンパイラオプションをつけてコンパイルします。デフォルトだと/t:exeが渡されます。

'*.cs, *.vb, *.jsをコンパイルして実行
Set regEX = New RegExp
regEx.Global = True
regEX.IgnoreCase = True
Set Fs = CreateObject("Scripting.FileSystemObject")
Set WshShell = CreateObject("WScript.Shell")

sCompilerDir = "C:\windows\Microsoft.NET\Framework\v2.0.50727\"
sDefaultArguments = "/t:exe" 'winexe
sSourcePath = Document.FullName
sEXEPath = Fs.BuildPath(Fs.GetParentFolderName(sSourcePath),Fs.GetBaseName(sSourcePath) & ".exe") 
sExt = LCase(Fs.GetExtensionName(sSourcePath))

Document.Save sSourcePath 'ソース保存

Select Case sExt
	Case "vb" : sCompilerPath = sCompilerDir & "vbc.exe"
	Case "cs" : sCompilerPath = sCompilerDir & "csc.exe"
	Case "js" : sCompilerPath = sCompilerDir & "jsc.exe"
	Case Else : sCompilerPath = ""
End Select

Set sel = Document.Selection

If sel.IsEmpty Then
	sArguments = sDefaultArguments
Else
	regEx.Pattern = "\s?(\/[^\:]+\:\S+)\s?"
	If regEx.Test(sel.Text) Then
		For Each oMatch In regEx.Execute(sel.Text)
			sArguments = sArguments & oMatch.SubMatches(0) & " "
		Next
	Else
		sArguments = ""
	End If
End If

If sCompilerPath="" Then
	Alert sExt & "ファイルに対応するコンパイラがありません。"
Else
	sCommandLine = "cmd.exe /k " & sCompilerPath & " " & _
	"/out:" & """" & sEXEPath & """" & " " & sArguments & " " & _
	"""" & sSourcePath & """"
	WshShell.Run sCommandLine ,,True 'コンパイル実行
	If Fs.FileExists(sEXEPath) Then
		WshShell.Run sEXEPath,,True 'コンパイルしたファイルを実行
	Else
		Alert "コンパイルに失敗したようです。"
	End If
End If
元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/11/08/106978.aspx


Copyright © 2005-2018 Daisuke Mutaguchi All rights reserved
mailto: mutaguchi at roy.hi-ho.ne.jp
プライバシーポリシー

Books

Twitter