3.30.2012

Get the length of multibyte string in VBScript

VBScript: 多バイト文字列の長さの取得

VBScript には、文字列の長さを調べる関数として「Len」「LenB」が用意されている。
しかし、「LenB」を行うと文字列はUnicodeとして扱われるため、半角文字も全角文字も2バイトとしてカウントされてしまう。

従ってそれらが混在している場合は正しい長さ(≒SJISのバイト数)を得ることはできない。

半角文字を1バイト、全角文字を2バイトとしてカウントするためには、このような関数を自前で用意する必要がある。

・Function LenMB()

Function LenMB(ByVal expression)
  If IsNull(expression) Then LenMB = Null: Exit Function
  Dim i, c: c = 0
  For i = 1 To Len(expression)
    If Asc(Mid(expression, i, 1)) And &Hff00 Then c = c + 1
  Next
  LenMB = i + c - 1
End Function

・テストコード

Function Check(s): WScript.Echo Len(s), LenB(s), LenMB(s): End Function

Check ""                      ' 0
Check "abcde"                 ' 5
Check "あいうえお"            ' 10 (=2*5)
Check "abcえお"               ' 7 (=3+2*2)
Check " "                     ' 1: 半角スペース
Check " "                    ' 2: 全角スペース
Check String(1000000, vbTab)  ' 1000000
Check String(1000000, "あ")   ' 2000000
Check Null                    ' null

・出力結果

0 0 0
5 10 5
5 10 10
5 10 7
1 2 1
1 2 2
1000000 2000000 1000000
1000000 2000000 2000000
null null null

 

2012/04/04 追記

Windows XP / Windows Server 2003 であれば、同様の処理は Microsoft.CmdLib オブジェクトを利用することで
より簡単に実現することができる。(ただし Null の扱いは少し変わる)

具体的には、CreateObject(“Microsoft.CmdLib”).LengthinBytes() を行なえばよい。

詳細はファイルの実体 %SystemRoot%\system32\cmdlib.wsc を参照。

3.29.2012

Perl: Is Net::FTP working in passive mode?

Perl: Net::FTP の通信モードの確認

Perl で FTP クライアントを実装する際には、パッシブモード/アクティブモードのどちらで動作しているのかを意識する必要がある。
というのも、libnet のバージョンによってデフォルトの動作が異なっているからだ。

デフォルトの動作を知るには、Net/Config.pm の「ftp_int_passive」を見ればよい。

libnet-1.20 (02 Feb 2007)ではデフォルトは 0、つまりアクティブモードになっているが、
http://cpansearch.perl.org/src/GBARR/libnet-1.20/Net/Config.pm

libnet-1.21(19 May 2007)では 1、パッシブモードになっている。
http://cpansearch.perl.org/src/GBARR/libnet-1.21/Net/Config.pm

・Config.pm の例(抜粋)
http://cpansearch.perl.org/src/GBARR/libnet-1.22/Net/Config.pm

%NetConfig = (
  nntp_hosts      => [],
  snpp_hosts      => [],
  pop3_hosts      => [],
  smtp_hosts      => [],
  ph_hosts        => [],
  daytime_hosts   => [],
  time_hosts      => [],
  inet_domain     => undef,
  ftp_firewall    => undef,
  ftp_ext_passive => 1,
  ftp_int_passive => 1,
  test_hosts      => 1,
  test_exist      => 1,
);

また、通信状況のログから確認を行うためには、Net::FTP のコンストラクタで「Debug => 1」と指定すれば簡単だ。

参考:
・ftpのpassiveモード(PASVモード)って何ですか?
http://www.rtpro.yamaha.co.jp/RT/FAQ/TCPIP/ftp-passive-mode.html

3.25.2012

HTTP Friendly error messages in Internet Explorer

Internet Explorer: HTTP エラー メッセージの簡易表示機能

Internet Explorer では、デフォルトで「HTTP エラー メッセージの簡易表示」機能が有効になっている。

その場合、サーバ側でカスタムエラーページを作成しても、そのサイズが閾値に満たない場合、
Internet Explorer 内部の画面が表示されてしまう。

閾値はコードごとに設定されており、例えば HTTP 500 応答の実際のテキストを表示するには
Content-Length を 512 バイトよりも大きくする必要がある。

参考:
http://support.microsoft.com/kb/294807

Parsing XML file with VBScript

VBScript: XMLファイルの読み取り

VBScript による XML ファイルの読み取り方法の一例を記載する。

1. 使用するXMLファイル

 ・shogi.xml

<?xml version="1.0" encoding="Shift_JIS"?>
<shogi version="0.1">
  <turn>+</turn>

  <board place="51"><piece turn="-" type="OU" /></board>
  <board place="59"><piece turn="+" type="OU" /></board>
  <board place="77"><piece turn="+" type="FU" /></board>

  <hand>
    <piece turn="+" type="FU">3</piece>
    <piece turn="-" type="KY">2</piece>
  </hand>
</shogi>

 将棋のある局面における手番、盤上の駒、持ち駒を表現。
 構造を図で表すとこのようになる。

image

2. 実装手順

 (1) XMLファイルのロード

   まず、CreateObject で Msxml2.DOMDocument オブジェクトを作成。
   async プロパティを False に設定して非同期モードとする。
   そして、XMLファイルのパスを指定して load すればよい。

Set xmlDoc = CreateObject("Msxml2.DOMDocument")
xmlDoc.async = False
xmlDoc.load "path_to_the_xml_file"

   XML解析の成否判定は、load メソッドの戻り値または parseError.errorCode プロパティの参照により行う。
   例外は送出されない。

 (2) 単一要素の取得

   要素が1つだけであることがわかっている場合は、selectSingleNode メソッドでそのノードの参照を取得できる。
   パラメータに指定するのは、XPath 式の文字列。
   ノードが末端である場合、text プロパティを利用することでその値を取得できる。
   例えば、「turn」の値はこのように取得できる。

xmlDoc.selectSingleNode("/shogi/turn").text

 (3) 属性の取得

   XPath式で属性名の前に「@」を付ければ、要素と同じ方法で値を取得できる。
   「version」の取得はこのような形だ。

xmlDoc.selectSingleNode("/shogi/@version").text

   子ノードの参照を別の変数に保存すれば、このように相対パスで値を取得することもできる。

Set xmlShogi = xmlDoc.selectSingleNode("/shogi")
xmlShogi.selectSingleNode("@version").text

   ただし、ノードに指定されている属性を取得する方法として、getAttribute メソッドが用意されている。
   これが一番簡単だ。

xmlShogi.getAttribute("version")

 (4) 複数要素の取得

   同じ名前の要素が複数存在する場合は、selectNodes メソッドで要素のコレクションを取得できる。
   For Each … In … 構文を利用すれば、全ての要素に対して処理を行うことが可能だ。

For Each xmlBoard In xmlShogi.selectNodes("board")
  (各 xmlBoard に対する処理)
Next

3. コード

XMLファイルの内容を表示するスクリプト。cscript での実行を前提とする。

・xml_sample.vbs

' Must run in CScript.
If LCase(Right(WScript.FullName, 12)) <> "\cscript.exe" Then WScript.Quit 1

' Load XML file.
Const kXMLPath = "shogi.xml"

Set xmlDoc = CreateObject("Msxml2.DOMDocument")
xmlDoc.async = False
xmlDoc.load kXMLPath
If xmlDoc.parseError.errorCode <> 0 Then Err.Raise 1, , _
    "Failed to load XML file [" & kXMLPath & "]: " & xmlDoc.parseError.reason

' Get single node.
WScript.Echo "turn: " & xmlDoc.selectSingleNode("/shogi/turn").text

' Get attribute.
WScript.Echo "version: " & xmlDoc.selectSingleNode("/shogi/@version").text

Set xmlShogi = xmlDoc.selectSingleNode("/shogi")
WScript.Echo "version: " & xmlShogi.selectSingleNode("@version").text

WScript.Echo "version: " & xmlShogi.getAttribute("version")

' Get all nodes.
For Each xmlBoard In xmlShogi.selectNodes("board")
  Set xmlPiece = xmlBoard.selectSingleNode("piece")
  WScript.Echo "board " & xmlBoard.getAttribute("place") & ": " & _
      xmlPiece.getAttribute("turn") & _
      xmlPiece.getAttribute("type")
Next

For Each xmlHand In xmlShogi.selectNodes("hand")
  For Each xmlPiece In xmlHand.selectNodes("piece")
    WScript.StdOut.Write "hand: " & xmlPiece.getAttribute("turn")

    For i = 1 To CInt(xmlPiece.text)
      WScript.StdOut.Write xmlPiece.getAttribute("type")
    Next
    WScript.Echo
  Next
Next

4. 出力結果

turn: +
version: 0.1
version: 0.1
version: 0.1
board 51: -OU
board 59: +OU
board 77: +FU
hand: +FUFUFU
hand: -KYKY

参考:
http://msdn.microsoft.com/en-us/library/aa468547.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms762722%28v=vs.85%29.aspx

IXMLDOMNode インターフェイス
http://msdn.microsoft.com/ja-jp/library/aa948719%28v=office.11%29.aspx

3.23.2012

Pseudo folder with VBScript

VBScript: 擬似フォルダの作成

機能要件

Windows のフォルダ(へのリンク)と同じような動作をするスクリプトを作成する。
引数によって実行時の挙動を変える。

引数なし(スクリプトをダブルクリックした時):
  コード中に定義したフォルダをエクスプローラで開く。

引数あり(ファイル・フォルダをドラッグ&ドロップした時):
  引数で指定されたパスのファイル・フォルダ全てを、コード中に定義したフォルダ配下へ移動する。
  同名のファイル・フォルダが既に存在している場合は、エラーを通知し処理を中止する。

対象フォルダが存在しない場合はエラーを通知する。

コード

'// PseudoFolder.vbs
kPath = "C:\Temp"  ' Path to the folder
kPathNotFound = &H004C

Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FolderExists(kPath) Then Err.Raise kPathNotFound

For Each arg In WScript.Arguments
  If fso.FileExists(arg) Then
    fso.MoveFile arg, kPath & "\"
  ElseIf fso.FolderExists(arg) Then
    If fso.GetDriveName(arg) = fso.GetDriveName(kPath) Then
      fso.MoveFolder arg, kPath & "\"
    Else
      fso.CopyFolder arg, kPath & "\", False
      fso.DeleteFolder arg, True
    End If
  Else
    Err.Raise kPathNotFound
  End If
Next

If 0 = WScript.Arguments.Count Then CreateObject("Shell.Application").Open kPath

概要

このスクリプトに対するショートカットを作り、フォルダのイメージをアイコンに設定すれば、まさに擬似的なフォルダとして機能するだろう。

普通のショートカットと比べると以下のような利点がある。

・フォルダのパスを動的に定義できる
・ドライブの違いを意識せず、ドラッグ&ドロップで確実に移動処理を行える
 (エクスプローラでは同一ドライブなら移動、異なるドライブならコピーが行われる)

留意事項

・異なるドライブ間で Scripting.FileSystemObject.MoveFolder を行うと失敗する。
 (内部的にはリネームと同じ処理らしい?)
 面倒ではあるが、Scripting.FileSystemObject.GetDriveName でチェックしてドライブが異なっていたら
 コピーと削除を分けて行わないといけない。

・フォルダの開き方は、WScript.Shell.Run よりも Shell.Application.Open のほうが安全かと思う。

・各メソッドのおさらい。

object.MoveFile ( source, destination );
 ⇒ destination をバックスラッシュで終わらせることで、明示的にフォルダと認識させる。
   同名ファイルが存在する場合はエラーとなる。

object.MoveFolder ( source, destination );
 ⇒ destination をバックスラッシュで終わらせることで、明示的に既存フォルダと認識させる。
   同名のファイルやフォルダが存在する場合はエラーとなる。

object.CopyFolder ( source, destination[, overwrite] );
 ⇒ destination をバックスラッシュで終わらせることで、明示的に既存フォルダと認識させる。
   上書きを抑止するため、第3引数に False を与える。

object.DeleteFolder ( folderspec[, force] );
 ⇒ 読み取り専用フォルダも削除するため、第2引数に True を与える。

http://msdn.microsoft.com/ja-jp/library/cc428039.aspx
http://msdn.microsoft.com/ja-jp/library/cc428108.aspx
http://msdn.microsoft.com/ja-jp/library/cc427986.aspx
http://msdn.microsoft.com/ja-jp/library/cc427996.aspx

・フォルダ存在チェックで Raise させているのは 0x4c (10進数では76) Path not found。

エラーコードの一覧は以下を参照。
http://www.csidata.com/custserv/onlinehelp/vbsdocs/vbs241.htm

3.22.2012

UNIX: Reverse all lines

UNIX: 全ての行の逆順表示

例えばファイルリストの逆順を手っ取り早く求めたい時、ファイルの内容を逆から読みたい時、
以下のコマンドを利用すればよい。

Linux (GNU): tac
Solaris        : tail -r

3.15.2012

Configuring timeout value in Internet Explorer

Internet Explorer のタイムアウト時間の設定

IE5以降のデフォルトは60分。
変更する場合は、以下のレジストリを設定すればよい。

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings

名前:ReceiveTimeout
種類:DWORD
データ:タイムアウト値(ミリ秒)

反映にはOSの再起動が必要。

参考:
http://support.microsoft.com/kb/181050/ja

3.08.2012

Hide blank data in Excel chart

Microsoft Excel: 空のデータをグラフへプロットさせない方法

Excelで折れ線グラフなどを作るとき、データ参照元のセルに
値が何もない状態だと、データマーカーはプロットされない。

しかし、数式が存在する場合(e.g., 「=""」)、そのデータは「0」としてプロットされてしまう。

グラフへ表示させないためには、空文字列などではなく、NA()関数を使う。

参考:
http://takashixxx.blog88.fc2.com/blog-entry-75.html

3.04.2012

Comparison of running times

Python: 実行時間の比較

Introduction to Algorithms の 1章の章末問題を Python で計算してみる。

・問題

各関数 f(n) と時間 t に対して、アルゴリズムが問題を解くのに f(n) マイクロ秒かかるとき、
t 時間で解くことができる最大の問題のサイズ n を求めよ。

 関数 f(n): lg n  √n  n  n lg n  n2  n3  2n  n!
 時間 t  : 1秒  1分  1時間  1日  1月  1年  1世紀

・コード

二分検索で解くコード。ただし、lg n に関しては n が大きくなりすぎるので直接 2t を求める。

import math

def binary_search(predicate, lower_bound, upper_bound, not_found=0):
  """
  Returns the least x for which predicate P(x) is true.
   
  Predicate P must be assumed:
    for all x in ordered set S, P(x) implies P(y) for all y > x
   
  @param predicate: unary function which returns true or false
  @param lower_bound: lower bound on the search
  @param upper_bound: upper bound on the search
  @param not_found: return value when all the predicates are false
  """
  ret = not_found

  while lower_bound < upper_bound:
    mid = lower_bound + (upper_bound - lower_bound) / 2
    if predicate(mid):
      ret = upper_bound = mid
    else:
      lower_bound = mid + 1

  return ret

def main():
  # periods
  periods = [None] * 7
  periods[0] = 1e6                    # 1 second = 1,000,000 microsecond
  periods[1] = periods[0] * 60        # 1 minute = 60 seconds
  periods[2] = periods[1] * 60        # 1 hour = 60 minutes
  periods[3] = periods[2] * 24        # 1 day = 24 hours
  periods[5] = periods[3] * 365.2425  # 1 year = 365.2425 days
  periods[4] = periods[5] / 12.0      # 1 month = 1/12 year
  periods[6] = periods[5] * 100       # 1 century = 100 years

  # algorithms
  def find(func, t, max_n):
    return binary_search(lambda n: func(n) > t, 1, max_n) - 1
  algos = [
      ['lg n'  , None],
      ['sqrt n', lambda t: find((lambda n: math.sqrt(n))      , t, 10 ** 32) ],
      ['n'     , lambda t: find((lambda n: n)                 , t, 10 ** 16) ],
      ['n lg n', lambda t: find((lambda n: n * math.log(n, 2)), t, 10 ** 16) ],
      ['n ^ 2' , lambda t: find((lambda n: pow(n, 2))         , t, 10 ** 16) ],
      ['n ^ 3' , lambda t: find((lambda n: pow(n, 3))         , t, 10 ** 16) ],
      ['2 ^ n' , lambda t: find((lambda n: pow(2, n))         , t, 100) ],
      ['n!'    , lambda t: find((lambda n: math.factorial(n)) , t, 100) ],
      ]

  # print results
  print '%-12s' % 'f(n)',
  for i in ('second', 'minute', 'hour', 'day', 'month', 'year', 'century'):
    print '%12s' % i,
  print
  print '-' * (13 * 8 - 1)
  for i, algo in enumerate(algos):
    print '%-12s' % algo[0],

    for t in periods:
      if i == 0:
        print '%12s' % ('1e+(%.1e)' % (math.log(10, 2) * t)),
      else:
        val = algo[1](t)
        if (val < 1e12):
          print '%12d' % val,
        else:
          print '%12.1e' % val,
    print

if __name__ == '__main__':
  main()

 

・出力結果

f(n)               second       minute         hour          day        month         year      century
-------------------------------------------------------------------------------------------------------
lg n         1e+(3.3e+06) 1e+(2.0e+08) 1e+(1.2e+10) 1e+(2.9e+11) 1e+(8.7e+12) 1e+(1.0e+14) 1e+(1.0e+16)
sqrt n            1.0e+12      3.6e+15      1.3e+19      7.5e+21      6.9e+24      1.0e+27      1.0e+31
n                 1000000     60000000   3600000000  86400000000      2.6e+12      3.2e+13      3.2e+15
n lg n              62746      2801417    133378058   2755147513  72876948666 798145165979      6.9e+13
n ^ 2                1000         7745        60000       293938      1621649      5617557     56175574
n ^ 3                 100          391         1532         4420        13802        31600       146678
2 ^ n                  19           25           31           36           41           44           51
n!                      9           11           12           13           15           16           17

参考:
Introduction to Algorithms, Third Edition Supplemental Content
http://mitpress.mit.edu/algorithms/

Video Lectures – MIT Open Courceware
http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-046j-introduction-to-algorithms-sma-5503-fall-2005/video-lectures/

Binary search with predicate in Python

Python: predicate の二分検索

predicate P がある値を境に真偽が分かれる場合、その境界を調べるのに二分検索を行うことができる。

image

整数 x について、P(x) が true となる最小のx(上の図ではa)を検索する処理のPythonによる実装例を以下に示す。

def binary_search(predicate, lower_bound, upper_bound, not_found=0):
  """
  Returns the least x for which predicate P(x) is true.
  
  Predicate P must be assumed:
    for all x in ordered set S, P(x) implies P(y) for all y > x
  
  @param predicate: unary function which returns true or false
  @param lower_bound: lower bound on the search
  @param upper_bound: upper bound on the search
  @param not_found: return value when all the predicates are false
  """
  ret = not_found

  while lower_bound < upper_bound:
    mid = lower_bound + (upper_bound - lower_bound) / 2
    if predicate(mid):
      ret = upper_bound = mid
    else:
      lower_bound = mid + 1

  return ret

参考:
http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binarySearch

3.01.2012

How to get a line count of a file in Windows

WIndows で行数のカウント
UNIXの「wc -l」コマンドと同様の処理は、以下のDOSコマンドで実現できる。
find /c /v "" filename
※ファイルの末尾が改行文字である場合、最後の空行はカウントされない。
 つまり、UNIXの「wc -l」コマンドの結果よりも1少なくなる。
もちろん、VBScriptで実現することも可能。
参考:
http://scripting.cocolog-nifty.com/blog/2008/06/post_2998.html