Ruby AWAKENING for Beginner

AWAKENINGを実現するためのスクリプトの書き方を少しずつ紹介していこう。スクリプト初心者、Windowsユーザーを対象に書く。


  1. HTTPプロトコルでURLから指定のHTMLファイルを読み出そう
    require 'net/http'
    host = 'homepage1.nifty.com'
    directory = '/kazuf'
    h = Net::HTTP.new(host, 80)
    while htmlfile = DATA.gets
        htmlfile = htmlfile.chomp
        resp, data = h.get(directory + "/" + htmlfile, nil)
        data.each { |i| puts i }
    end
    __END__
    renewal_2001_03.html
    renewal_2001_04.html
    renewal_2001_05.html
    renewal.html
    

    簡単なプログラムです。コピーしてエディタに貼り付けて、例えばgeturl.rbという名前で保存しましょう。次にDOSプロンプト等を起動して、保存したフォルダに移動しましょう。PCをインターネットに繋いで、コマンドラインで次のように実行してみてください。

    C:\Ruby> ruby -Ks geturl.rb[enter]

    このホームページの更新日記がコンソール上に次々と表示されたはずです。無論、HTMLのままで。

    さて、うまくいきましたか。うまくいかない場合、あなたはRubyを使ったことはありますか。エディタやDOSプロンプト、コマンドラインという言葉の意味がわかっていますか。わからない場合はすぐに会議室で質問してください。[6/18/2001]

  2. HTMLファイルの特定の部分を取り出す

    さて、いよいよ正規表現によるパターンマッチングを使ってHTMLファイルの特定の部分を取り出してみましょう。

    require 'net/http'
    host = 'homepage1.nifty.com'
    directory = '/kazuf'
    h = Net::HTTP.new(host, 80)
    while htmlfile = DATA.gets
        htmlfile = htmlfile.chomp
        resp, data = h.get(directory + "/" + htmlfile, nil)
        data.each do |i|
            if /<A NAME="[^"]+">([^<]+)<\/A>/ =~ i
                puts $1
            end
        end
    end
    __END__
    renewal_2001_03.html
    renewal_2001_04.html
    renewal_2001_05.html
    renewal.html
    

    そろそろこのスクリプトの目的を説明しましょう。最初のスクリプトから変更した緑色の部分はHTMLファイルの、

    <A NAME="anchor001">なんたらかんたら</A>

    という部分にマッチしてアンカーテキストの「なんたらかんたら」を抜き出して出力します。このアンカーのNAME属性を仕掛けとして「更新日記」のインデックスを別ページとして自動生成しようというのが最終目的です。

    あなたはRubyの参考書を紐解いて、正規表現の章を読む必要があるかもしれません。わからなければ、会議室で質問をどうぞ。[6/23/2001]

  3. スクリプトの中身を解説する

    Rubyをまったくご存知でない方のために少しスクリプトを解説しておきましょう。Perl等のスクリプト言語を知っていれば大体の推測は付くと思いますが、Ruby独特の文法もありますし、大昔(失礼!私もその部類です^^;)、BasicやFortranは齧ったことはあるが、スクリプト言語は初めてという方もいらっしゃるかもしれません。

    require 'net/http'
    host = 'homepage1.nifty.com'
    directory = '/kazuf'
    h = Net::HTTP.new(host, 80)
    while htmlfile = DATA.gets
        htmlfile = htmlfile.chomp
        resp, data = h.get(directory + "/" + htmlfile, nil)
        data.each do |i|
            if /<A NAME="[^"]+">([^<]+)<\/A>/ =~ i
                puts $1
            end
        end
    end
    __END__
    renewal_2001_03.html
    renewal_2001_04.html
    renewal_2001_05.html
    renewal.html
    

    このスクリプトは大きく分けて三つの部分に分かれています。その部分を色分けしてみました。

    1. 赤い部分は、HTTPプロトコルでHTMLファイルを取得します。

      簡単にHTTPプロトコルによるインターネットへのアクセスを提供するNet::HTTPクラスを使用します。require 'net/http'で使えるようになります。Net::HTTPのオブジェクトをまず作ります。引数として、URLのホスト名とポート名を入力します。この部分が、

      h = Net::HTTP.new(host, 80)

      です。実際にgetメソッドを使って、サーバーからの応答とデータを受け取るためには、ディレクトリ名とファイル名を指定する必要があります。この部分が、

      resp, data = h.get(directory + "/" + htmlfile, nil)

      たったこれだけで、サーバーから戻されるヘッダーはrespに、HTMLデータはdataに配列として格納されます。

    2. 青い部分は、読み出したいファイル名を指定します。

      while〜endの制御文で、DATAオブジェクト(Perlと同様に__END__以下に記述しているデータを一行ずつ読み出せる)を使って、読み出したいHTMLファイル名を変数のhtmlfileに一つずつ取得していく。chompはPerlと同様に改行コード取り除くメソッドです。

    3. 緑色の部分は、HTMLファイルのテキストからパターンマッチした部分を出力します。

      data配列から一つずつデータを読み出すのにはイテレータを使います。変数iにデータが順次読み出されます。if〜end文では変数i(HTMLファイルが一行ずつ取り出される)が正規表現によるパターンにマッチすれば、括弧内に対応する正規表現にマッチする部分だけが出力されます。

    これだけの仕事をするプログラムをCで書こうとすれば、おそらく数百行も書くことになるでしょう。あなたもそろそろ腕がむずがゆくなってきたのではないでしょうか。是非AWKINGプロジェクトに参加されて、インターネットを自由自在に使いこなしましょう(^^)v[6/24/2001]

  4. スクリプトの仕上げ:複雑なテキスト処理によるHTMLファイルの生成、FTP自動転送

    更新日記インデックスを作成するスクリプトを簡単にするために更新日記のファイル名の付け方、カテゴリー別のアンカー名の付け方を工夫しました。自分のホームページを対象にする場合にはこういうズルをすることができます。

    require 'net/http'
    require 'net/ftp'
    
    # Setup your configuration.
    ftpsite = ''
    ftpaccount = ''
    ftppassword = ''
    yourdir = ''
    genfile = 'renewal_index.html'
    
    category = { "rn" => "更新履歴",
    	"cw" => "Cygwin",
    	"ps" => "PS2 Linux",
    	"com" => "コミュニケーション",
    	"ai" => "A.I.",
    	"book" => "蔵書目録",
    	"awking" => "AWKING Project",
    	"awk" => "AWK",
    	"net" => ".net",
    	"os" => "Operating System",
    	"tt" => "立花 隆",
    	"perl" => "Perl",
    	"ruby" => "Ruby",
    	"tcl" => "Tcl/Tk",
    	"python" => "Python"
    }
    
    host = 'homepage1.nifty.com'
    directory = '/kazuf'
    anchor = Hash.new()
    h = Net::HTTP.new(host, 80)
    h.set_pipe($stderr) if $VERBOSE
    while htmlfile = DATA.gets
        htmlfile = htmlfile.chomp
        puts htmlfile
        begin
            resp, data = h.get(directory + "/" + htmlfile, nil)
        rescue Errno::EINVAL
        end
        data.each do |i|
            if /<A NAME="([^_]+)_(\d+)">([^<]*)(?:<A[^>]+>)*([^<]*)(?:<\/A>)*([^<]+)<\/A>/i =~ i
                anchor[$1 + ":" + htmlfile + "\#" + $1 + "_" + $2] = "#$3#$4#$5"
                puts "#$3#$4#$5"
            end
        end
    end
    
    f = File.new(genfile, "w")
    f.print <<HERE1
    <HTML>
    <HEAD>
      <TITLE>更新日記インデックス</TITLE>
    	<META HTTP-EQUIV="Content-Type" content="text/html; charset=SHIFT_JIS">
    <style type="text/css">
    <!--
    BODY{background:#FFFFF0}
    .item{font-size:12pt;color:blue;font-weight:bold;text-decoration:underline}
    //-->
    </style>
    
    </HEAD>
    <BODY>
    <IMG SRC="http://homepage1.nifty.com/kazuf/png/tsnetworkc3.png" ALIGN=LEFT HSPACE=20 ALT="tsnetworkc3.png">更新日記インデックス
    <HR>
    HERE1
    cca = ""
    count = 0
    anchor.keys.sort.each do |k|
        ca, anc = k.split(/:/)
        if cca != ca
            if count > 0
                f.print "</UL><BR>\n"
            end
            f.print "<div class=\"item\">", category[ca], "</div>\n<UL>\n"
            cca = ca
            count = count + 1
        end
        f.print "<LI><A HREF=\"", anc, "\">", anchor[k], "</A>\n"
    end
    
    f.print <<HERE2
    </UL><BR>
    <HR>
     <I><A HREF="http://homepage1.nifty.com/kazuf/index.html">TS Network ☆ミ</A></I> <FONT COLOR="BLUERED">>></FONT>
    <ADDRESS><A HREF="mailto:kazuf@mars.dti.ne.jp"><kazuf@mars.dti.ne.jp></A></ADDRESS><BR>
    </BODY>
    </HTML>
    HERE2
    f.close
    
    ftp = Net::FTP.new(ftpsite, ftpaccount, ftppassword)
    print "#{ftpsite} に接続しました。\n"
    print "loginしました。\n"
    ftp.chdir(yourdir)
    print "#{yourdir} ディレクトリに移動しました。\n"
    ftp.puttextfile(genfile, genfile)
    print "#{genfile} を転送しました。\n"
    ftp.close
    print "FTPを終了しました。\n"
    
    __END__
    renewal_2001_03.html
    renewal_2001_04.html
    renewal_2001_05.html
    renewal.html
    
  5. スクリプトの改造

    更新日記インデックスからカテゴリー別専用ページにリンクするように改造。カテゴリー別アンカー名からカテゴリー別専用ページを取得するハッシュを作成しておいて、専用ページがあるかどうかを判定して、ハイパーリンクをカテゴリー項目に付加する。新しく付加したスクリプト部分は赤く表示した。[10/14/2001]

    カテゴリー別アンカータグが一行に二つ以上あると抽出できていなかったので、scanを使うように改造した。わたなべさん、中田さん、サジェスチョンありがとうございました。[10/21/2001]

    カテゴリー項目をページの最初にまとめて表示するように改造。[10/21/2001]

    require 'net/http'
    require 'net/ftp'
    
    # Setup your configuration.
    ftpsite = ''
    ftpaccount = ''
    ftppassword = ''
    yourdir = ''
    genfile = 'renewal_index.html'
    
    category = { "rn" => "更新履歴",
    	"cw" => "Cygwin",
    	"ps" => "PS2 Linux",
    	"com" => "コミュニケーション",
    	"ai" => "A.I.",
    	"book" => "蔵書目録",
    	"awking" => "AWKING Project",
    	"awk" => "AWK",
    	"net" => ".net",
    	"os" => "Operating System",
    	"tt" => "立花 隆",
    	"perl" => "Perl",
    	"ruby" => "Ruby",
    	"tcl" => "Tcl/Tk",
    	"python" => "Python",
    	"prolog" => "Prolog",
    	"linux" => "Linux",
    	"printing" => "Printing",
    	"hiroshima" => "ひろしま",
    	"an" => "ArrangeNote",
    	"qnx" => "QNX",
    	"web" => "Web Development",
    	"diary" => "日記",
    	"scheme" => "Scheme",
    }
    
    page = { "cw" => "cygwin.html",
    	"awk" => "awk.html",
    	"awking" => "awking.html",
    	"perl" => "perl.html",
    	"python" => "python.html",
    	"tcl" => "tcltk.html",
    	"ruby" => "ruby.html",
    	"scheme" => "scheme.html",
    	"ps" => "ps2linux.html",
    }
    
    
    host = 'homepage1.nifty.com'
    directory = '/kazuf'
    anchor = Hash.new()
    h = Net::HTTP.new(host, 80)
    h.set_pipe($stderr) if $VERBOSE
    while htmlfile = DATA.gets
        htmlfile = htmlfile.chomp
        puts htmlfile
        begin
            resp, data = h.get(directory + "/" + htmlfile, nil)
        rescue Errno::EINVAL
        end
        data.each do |i|
    
            i.scan(/<A NAME="([^_]+)_(\d+)">([^<]*)(?:<A[^>]+>)*([^<]*)(?:<\/A>)*([^<]*)<\/A>/i){|category_name,num,cont_a,cont_b,cont_c| anchor[category_name + ":" + htmlfile + "\#" + category_name + "_" + num] = cont_a + cont_b + cont_c;puts cont_a + cont_b + cont_c}
    
        end
    end
    
    f = File.new(genfile, "w")
    #print "Content-type: text/html\n\n"
    f.print <<HERE1
    <HTML>
    <HEAD>
      <TITLE>更新日記インデックス</TITLE>
    	<META HTTP-EQUIV="Content-Type" content="text/html; charset=SHIFT_JIS">
    <style type="text/css">
    <!--
    BODY{background:#FFFFF0}
    .item{font-size:12pt;color:blue;font-weight:bold;text-decoration:underline}
    //-->
    </style>
    </HEAD>
    <BODY>
    <IMG SRC="http://homepage1.nifty.com/kazuf/png/tsnetworkc3.png" ALIGN=LEFT HSPACE=20 ALT="tsnetworkc3.png">更新日記インデックス
    <HR>
    HERE1
    
    
    f.print "<UL>\n"
    category.keys.sort.each do |j|
        f.print "<LI><A HREF=\"renewal_index.html\#", j, "\">", category[j], "</A>\n"
    end
    f.print "</UL>\n<HR>\n"
    
    
    cca = ""
    count = 0
    anchor.keys.sort.each do |k|
        ca, anc = k.split(/:/)
        if cca != ca
            if count > 0
                f.print "</UL><BR>\n"
            end
    
            if page[ca]
                f.print "<div class=\"item\"><A NAME=\"", ca, "\"><A HREF=\"", page[ca], "\">", category[ca], "</A></A></div>\n<UL>\n"
            else
                f.print "<div class=\"item\"><A NAME=\"", ca, "\">", category[ca], "</A></div>\n<UL>\n"
            end
    
            cca = ca
            count = count + 1
        end
        f.print "<LI><A HREF=\"", anc, "\">", anchor[k], "</A>\n"
    end
    
    f.print <<HERE2
    </UL><BR>
    <HR>
     <I><A HREF="http://homepage1.nifty.com/kazuf/index.html">TS Network ☆ミ</A></I> <FONT COLOR="BLUERED">>></FONT>
    <ADDRESS><A HREF="mailto:kazuf@mars.dti.ne.jp"><kazuf@mars.dti.ne.jp></A></ADDRESS><BR>
    </BODY>
    </HTML>
    HERE2
    f.close
    
    ftp = Net::FTP.new(ftpsite, ftpaccount, ftppassword)
    print "#{ftpsite} に接続しました。\n"
    print "loginしました。\n"
    ftp.chdir(yourdir)
    print "#{yourdir} ディレクトリに移動しました。\n"
    ftp.puttextfile(genfile, genfile)
    print "#{genfile} を転送しました。\n"
    ftp.close
    print "FTPを終了しました。\n"
    
    __END__
    renewal.html
    renewal_2001_09.html
    renewal_2001_08.html
    renewal_2001_07.html
    renewal_2001_06.html
    renewal_2001_05.html
    renewal_2001_04.html
    renewal_2001_03.html
    

Automated Web Knowledge, Information and News Gathering Project
(C)jscripter