Script Note

VBやPerlなど、スクリプトに関する学習帖です。個人的に理解したことをまとめておく、倉庫のようなものです(^^)。


■PerlでWSH

この文章は、Active Perlに同梱されているhtmlファイルの「Windows Script Host」の中から、個人的に参考にしようと思った部分を抜き出して日本語でまとめ、さらに加筆修正を加えたものです。

■添付ファイル付きメール送信 (Perl)

すでにサーバーにあるファイルを添付してメールを送信するサンプルは、ちらほらと見つかるのですが、ローカルにあるファイルを添付して送信するコードが、あまり見つかりません。あっても、複数のスクリプトを合わせた巨大なもので、チョットした勉強には苦しいかなぁ(T_T)。まあそもそも、添付ファイルを送信しようとしていることが、チョットしたことじゃないよと言われれば、それまでのこと!

■IEをWSHで操作する (VBscript)

まとめたいことがいろいろありますが、今のところIEの起動、表示とリンクオブジェクトについてサラリと書いております。

■HTMLタグを置換する (Javascript)

タグのかっこにあたる文字「<」「>」をHTMLで表示する場合、「&lt;」「&gt;」という風に置き換えることになります。それを、Javascriptでやってみます。

■フォルダ参照ダイアログを表示する (VBscript)

WSHでの、フォルダ参照ダイアログの表示方法です。


添付ファイル付きメール送信

もともと、メールサイズが6KBに制限されているJフォンのロングメールに、より大きな添付ファイルを送りたいなと考えたのがきっかけです。ヘッダを含めて制限が6KBまでなので、普通のメーラーを使った場合、送りたいと思ったファイルが送信できない(添付ファイルは6KB以下だが、トータルでオーバーしている)ことがありました。

内容的には、まずPOSTメソッドでアドレスやメッセージなどと一緒にファイルを転送しています。ファイルはCGIと同じディレクトリに一旦書き込み、他の項目は配列$FORM{'xxxx'}に保存されます。それをsendmailに投げて送信した後、ファイルを削除するという流れです。サーバーへのデータ送信と、サーバーからのメール配信が切り分けて処理されているので、流れは把握しやすいと思います。

今後の取り組みとして、切り分けされている処理の融合(ファイルを書き込むことなく処理する)なんか、面白いかもしれないですね。
#!/usr/bin/perl

require './jcode.pl';
$mycgi = './filemail.cgi';                # このcgiファイル名
$myads = 'hoge@hoge.com';                 # 送信者のアドレス
$sendmail = '/usr/sbin/sendmail';         # sendmailへのパス

if ($ENV{'REQUEST_METHOD'} eq "POST"){
    &formget;
    
    # 添付ファイルをフォームで増やしたら、$fname[x]の項目も増やせる
    # 今の送信エンジンでは、できないんだけど(^^;
    &sendMF($myads,$FORM{'EMAIL2'},$FORM{'SUBJECT'},$FORM{'MESSAGE'},$fname[0]);
    unlink(@fname);

    $message.= "To : $FORM{'EMAIL2'}<br>\n";
    $message.= "Subject : $FORM{'SUBJECT'}<br>\n";
    $message.= "Message :<br>$FORM{'MESSAGE'}<br>\n";
    $message.= "AttachmentFile : @fname<br><br>\n";
    $message.= "----- completed. -----<br>";
}

# HTMLの出力
print "Content-type: text/html\n\n";
print <<"_HTML_";
<html><head><title>メール送信フォーム</title></head>
<body>$message<br>
<form method="POST" enctype="multipart/form-data" action="$mycgi" >
<BR>送信先メールアドレス:
<BR><INPUT TYPE=text SIZE=50 NAME="EMAIL2">
<BR>
<BR>タイトル:
<BR><INPUT TYPE=text SIZE=50 NAME="SUBJECT">
<BR>
<BR>メッセージ:
<BR><TEXTAREA COLS=70 ROWS=6 NAME="MESSAGE"></TEXTAREA>
<BR>
<BR>添付ファイル:
<BR><input type=file size=100 name="filedat1">
<BR>
<BR><INPUT TYPE=submit VALUE=" 送信 "> <INPUT TYPE=reset VALUE=" クリア ">
</FORM>
_HTML_
exit;

# $FORM{'name'}に文字列を返し、また同一ディレクトリにファイルを取得する。
# 複数ファイルがあるときは、$fname[0]から順番にファイル名が保存される。
#
sub formget{
    $filemode = 0; $flabel = 0; $name = '';
    while(<STDIN>){
        if($filemode == 1){
            if(open( OUT, ">./$fname[$flabel]" )){
                while(<STDIN>){
                    if(/^$bound/){ last; }
                print OUT $_;
                }
            close( OUT );
            }
        $filemode = 0; $flabel++;
        next;
        }
        if(/^--/ && $bound == ''){
            $bound = $_;
            $bound =~s/\r\n//;
        }
        if($_=~/^$bound/){
            chomp($FORM{$name});
            $name = '';
            next;
        }
        if(/^Content-Disposition/){
            $name = $_;
            $name =~s/^Content-Disposition: form-data; name=\"(.*)\"(.*)$/$1/i;
            $name =~s/^(.*)\";(.*)$/$1/i;
            chomp($name);
            if(/filename=/ && $_!~/filename=""/){
                $fname[$flabel] = $_;
                $fname[$flabel] =~s/^Content-Disposition: form-data; name=\"(.+)\"; filename=\"(.*)\"(.*)$/$2/i;
                $fname[$flabel] =~s/ /_/g;      #「 」を「_」に置換
                $fname[$flabel] =~s/(.*)\\(.*)/$2/;
                chomp($fname[$flabel]);
                $filemode = 1;
            }
            $dummy = <STDIN>;             #意味の無い行を読み飛ばす
            next;
        }
        if ($name ne ''){
            $FORM{$name}.= $_;
        }
    }
}

# 添付ファイル付きメール送信処理
# 
sub sendMF{

    #   from  to   件名      本文      添付file
    my($from, $to, $subject, $comment, $file1) = @_;
    my($baseType, $fdata1, $buf, $head, $msg0, $msg1, $part);

    # 件名のエンコード
    if($subject !~/^[\r\n]$/){
        &jcode'convert(\$subject, 'jis');
        $str = &encodeBase64($subject);   # 自力でBase64Encode
        $str =~ s/\n//g;
        $subject = "=\?ISO-2022-JP\?B\?$str\?=";
    }

    if($comment !~/^[\r\n]$/){
        &jcode'convert(\$comment, 'jis'); # 本文をJISコードに変換
        # my 宣言された変数の型グロブはないので、*abcのような記載ができない
        # \$abcのように書くと、間違いが起きにくいらしい

        $part++;
    }

    if($file1 ne ''){                     # 添付ファイル処理
        # ファイル情報取得
        my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
        $atime, $mtime, $ctime, $blksize, $blocks) = stat($file1);

        # 作業バッファに読込
        open(IN, $file1);
        binmode(IN);
        read(IN, $buf, $size);
        close(IN);

        $fdata1 = &encodeBase64($buf);    # 自力でBase64Encode

        $part++;
        $part++;
    }

    # 状態に応じてバウンダリ設定
    if($part <= 1){                       # 添付ファイルが無い場合
        $baseType = &planeHeader;
        $msg0 = $comment;
    }

    $buf = &attachHeader($file1);

    if($part == 2){                       # 添付ファイルのみの場合
        $baseType = $buf;
        $msg1 = $fdata1;
    }

    if($part == 3){                       # マルチパートの場合
        $boundary = &makeBound($comment);
        $baseType = &multiHeader($boundary);
        $dummy = &planeHeader;

        $msg0 =<<"__EOF__";
--$boundary
$dummy
$comment
__EOF__

        $msg1 =<<"__EOF__";
--$boundary
$buf
$fdata1
--$boundary--
__EOF__

    }
    
    # ヘッダ
    my($head) = <<"__EOF__";
To: $to
From: $from
Subject: $subject
MIME-Version: 1.0
$baseType
__EOF__
    
    # メール送信
    open(MAIL, "| $sendmail -t -i -f $from");
    print MAIL "$head$msg0$msg1";
    close(MAIL);
}

# base64エンコード
sub encodeBase64{
    # Base64 エンコード/デコード用キャラクタセット
    $base64Char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    my($len, $idx, $num, $ch, $ord, $bit24, @xx, $result, $rest, $cnt, $str4);
    my($temp) = "$_[0]\0\0";
    $result ="";
    if($_[0] eq "") {return $result;}
    $len = length($temp);
    $idx = 0;
    $cnt = 0;

    while($idx < $len){
        $ord = ord(substr($temp, $idx, 1));
        $idx++;
        $num = $idx % 3;
        $xx[$num] = $ord;

        if($num == 0){
            $rest = $len - $idx;
            $bit24 = ($xx[1] << 16) + ($xx[2] << 8) + $xx[0];

            if ($rest < 2){ $ch = "="; }
            else { $ch = substr($base64Char, ($bit24 & 63), 1); }

            $str4 = $ch;
            $bit24 = ($bit24 >> 6);

            if ($rest < 1){ $ch = "="; }
            else { $ch = substr($base64Char, ($bit24 & 63), 1); }

            $str4 = $ch . $str4;
            $bit24 = ($bit24 >> 6);
            $ch = substr($base64Char, ($bit24 & 63), 1);
            $str4 = $ch . $str4;
            $bit24 = ($bit24 >> 6);
            $ch = substr($base64Char, ($bit24 & 63), 1);
            $str4 = $ch . $str4;
            $result .= $str4;

            if($cnt == 15){
                $result .= "\n";
                $cnt = 0;
            }else{
                $cnt++;
            }

            if($rest < 3){ last; }
        }
    }
    return "$result\n";
}

# バウンダリの作成
sub makeBound{
    my($i, $a, $b, $c);
    srand;
    $a = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    while(1){
        $b = '';
        for($i=0; $i<32; $i++){
            $c = substr($a,int(rand(length($a))),1);
            $b.= $c;
        }
        # バウンダリがデータの中にないか
        if($_[0] !~/$b/){ last; }
    }
    return "$b";
}

sub attachHeader{
    my($file) = $_[0];
    my($result);

    $result =<<"__EOF__";
Content-Type: application/octet-stream; name="$file"
Content-Disposition: attachment; filename="$file"
Content-Transfer-Encoding: base64
__EOF__

    return "$result";
}

sub planeHeader{
    my($result);

    $result =<<"__EOF__";
Content-Type: text/plain; charset=iso-2022-jp
Content-Transfer-Encoding: 7bit
__EOF__

    return "$result";
}

sub multiHeader{
    my($head) = $_[0];
    my($result);

    $result =<<"__EOF__";
Content-Type: multipart/mixed; boundary=$head
Content-Transfer-Encoding: 7bit
__EOF__

    return "$result";
}


IEをWSHで操作する

まずは、基本的なことから。
' 最初に、IEのオートメーションオブジェクトを取得。IEを操作するための構えですね。
Set objIE = CreateObject("InternetExplorer.Application")

' IEを起動し、URLの内容を読み込む。
objIE.navigate2("http://www.yahoo.co.jp")

' IEを表示する。
objIE.visible = true
表示させるだけなら、結構簡単にできます。特に難しいこともないですね。 続けて、以下のコードをつなげてみましょう。

' 指定したURLが読み込み中かを判定。完了するまで待機する。
Do While objIE.busy
    Wscript.sleep 100
Loop

' 表示された内容から、<a href= 〜 ></a> でいくつリンクが張られているかを取得、表示する。
a = objIE.document.links.length
msgbox("Yahoo! からリンクされている、URL" & a & "個を表示します。")
str = "<html><head><title>Yahoo! からリンクされている URL</title></head><body>" & vbCrLf
for i = 0 to a - 1
    str = str & objIE.document.links(i).href & "<br>" & vbCrLf
next
str = str & "</body></html>"
objIE.document.write(str)
Yahoo! からリンクされているURLの数と内容が表示されたかと思います。読み込み完了を待って、リンクオブジェクトを用いて実現しています。リンクオブジェクトには、以下のようなものがあります。

・objIE.document.links.length


リンクオブジェクト(<a href= 〜 ></a>)の個数を示します。

・objIE.document.links(n)


それぞれのオブジェクトは、links(n) で参照します。

・objIE.document.links(n).href


<a href= "★">●</a> の ★ の部分を示します。

・objIE.document.links(n).innerText


<a href= "★">●</a> の ● の部分を示します。


HTMLタグを置換する

通常であれば、Perlの強力な正規表現を用いるところですが、ローカルの環境で手軽に行いたい場合は、VBscriptやJavascriptなどで正規表現オブジェクトを使うことになると思います。この正規表現オブジェクト、Perlに慣れているとかなり回りくどい記述をしなければならず、ちょっとメンドウです。
しかし、Javascriptであれば、オブジェクトでなくとも正規表現が利用できます。今回は、その方法を使ってコードを書いてみます。
<html>
<head><title>HTMLタグを置換する</title>
<script language="javascript">
<!--
function repTag(str){
    str = str.replace(/&/g , "&amp;");
    str = str.replace(/</g , "&lt;" );
    str = str.replace(/>/g , "&gt;" );
    str = str.replace(/\t/g, "    " );
    rep.code.value = str;
}
// -->
</script>
</head>
<body>
<form name="rep">
<textarea cols=70 rows=10 name="code"></textarea>
<br><br>
<input type="button" value=" Replace " onClick=repTag(rep.code.value)>
</form>
</body>
</html>
stringオブジェクトのreplaceメソッドで、第一引数に検索に使用する正規表現のパターンを設定し、第二引数に置換する文字列を設定します。置換された文字列は、返り値として戻ります。一致するパターンが無い場合、元の文字列がそのまま返ります。

以下は、サンプルです。上のコードをペーストして、試してみてください。


なお、stringオブジェクトにはmatchメソッドがあり、正規表現を使った普通の検索も当然に行うことができます。利用方法は、stringオブジェクトのmatchメソッドの引数に、検索に使用する正規表現のパターンを設定します。検索の結果、該当するものがあればその文字列を、なければ「null」を返します。WSHでのサンプルです。
str = new Array("Javascript", "21th");
for(i = 0; i < str.length; i++){
    if(str[i].match(/^[0-9]/)){
        WScript.Echo(str[i] + "は、数字から始まっています!");
    }else{
        WScript.Echo(str[i] + "は、数字以外の文字からです。");
    }
}

フォルダ参照ダイアログを表示する

VisualBasicなんかだと、あれこれ記述してやっと使えるフォルダ参照ダイアログ。VBscriptでは、かなり簡単に使うことができます。まさに、Tipsですね。
Set Shell = CreateObject("Shell.Application")
Set objFolder = Shell.BrowseForFolder(0, "フォルダを選択してね!", 1, "c:\\")
if objFolder is Nothing then 
    Msgbox("フォルダを選択してください")
else
    Msgbox(objFolder.Items.Item.Path)
end if


戻る