ここでは、プログラミングに関するちょっとした覚え書きをメモとして残します。
従来の「プログラミング雑記帳」のボリュームが大きくなったので、別のページにしました。
このところ必要があって、VBScriptで簡単なスクリプトを書いていました。
その中で、ファイル選択ダイアログを出す方法を探したところ、下のサイトをみつけました。
OpenFileDialog.VBSの詳細情報 : Vector ソフトを探す!
上のサイトから OpenFileDialog.ZIP をダウンロードして解凍すると、ファイル選択ダイアログを実現するためのVBScriptが出てきます。htaを利用するもの、Excelを利用するもの、IEを利用するものなどいろいろあります。
その中で私が使いやすいと思ったのは、Windowsに標準で備わっている mshta.exe を用いる HtaOpenFileEx.vbs です。htaの仕組みを利用しています。
このvbsをrubyで書くとどうなるかということで書いてみました。
HtaOpenFileEx.rbを掲げると下のとおり。
file_selectというメソッドを定義したものです。戻り値は、選択したファイルの名前(フルパス)です。
ruby.exe HtaOpenFileEx.rb [enter]
と入力すれば実行されます。
1# HtaOpenFileEx.rb (encoding: Windows-31J) 2 # WindowsのHTAを利用してファイル選択ダイアログを出す. 3 # filename = file_select(path, filter, title) -- filenameはFullPath 4 # キャンセルボタンを押すと "" を返す. 5require "win32ole" 6 7def file_select(path, filter, title) 8 hta_str = <<-EOS 9 <object id=HtmlDlgHelper classid=CLSID:3050f4e1-98b5-\ 10 11cf-bb82-00aa00bdce0b></object> 11 <script language=vbscript> 12 resizeTo 0,0 13 Sub window_onload() 14 CreateObject(\"Scripting.FileSystemObject\").GetStandardStream(1).\ 15 Write HtmlDlgHelper.object.openfiledlg(\ 16 \"#{path}\",,\"#{filter}\",\"#{title}\") 17 close 18 End Sub 19 </script> 20 <hta:application caption=no showintaskbar=no /> 21 EOS 22 hta_str = hta_str.gsub(/ /, "") 23 execOBJ = WIN32OLE.new("WScript.Shell").Exec("MSHTA.EXE " + 24 "\"javascript:new ActiveXObject('Scripting.FileSystemObject')" + 25 ".GetStandardStream(0).ReadAll()\"") 26 execOBJ.StdIn.WriteLine hta_str 27 execOBJ.StdIn.Close 28 res = execOBJ.StdOut.ReadAll() 29 if (i = res.index("\0")) 30 res = res[0..(i-1)] 31 end 32 return res 33end 34 35path = "C:\\Users\\*.*" 36filter = "すべてのファイル (*.*)|*.*" 37title = "ファイルの選択" 38res = file_select(path, filter, title) 39p res
もう一つ、Excelの機能を利用するものも掲げておきます。
GetOpenFileNameA.rbは下のとおり。
1# GetOpenFileNameA.rb (encoding: Windows-31J) 2 # WindowsのExcelを利用してファイル選択ダイアログを出す. 3 # filename = file_select(path, filter, title) -- filenameはFullPath 4 # キャンセルボタンを押すと "" を返す. 5require "win32ole" 6 7def file_select(folder, filter) 8 shellOBJ = WIN32OLE.new("WScript.Shell") 9 app = WIN32OLE.new("Excel.Application") 10 app.Visible = true 11 app.ExecuteExcel4Macro("CALL(\"kernel32\"," + 12 "\"SetCurrentDirectoryA\",\"JC\",\"#{folder}\")") 13 sleep(0.5) while !shellOBJ.AppActivate(app.Caption) 14 res = app.GetOpenFileName(filter) 15 res = "" if !res 16 app.Quit 17 return res 18end 19 20folder = "C:\\Users" 21filter = "すべてのファイル(*.*),*.*" 22res = file_select(folder, filter) 23p res
ruby には、OpenStructというクラスがあります。弾力的に要素を設定できる構造体類似のクラスです。
xxがOpenStructクラスのオブジェクトである場合、初期設定の段階で要素を定める他に、あとから自由に設けることもできます。たとえば、次のように書くことができます。
require "ostruct"
xx = OpenStruct.new
xx.name = "Tom"
xx.age = 4
xx.type = "cat"
OpenStructオブジェクトを生成する時に、次のように初期値としてHashを与えることができます。
xx = OpenStruct.new({:name => "Tom", :age => 4})
p xx.name
p xx.age
HashをOpenStructオブジェクトに変換すると、構造体のようにして扱えるようになります。括弧を多用するHashに比べて、簡単に記述できるようになります。
ただし、初期値のHashが入れ子になっている場合、つまり HashのvalueがHashになっているとき、’.’ でつなぐ記述ができません。xx.statistic.W のように ‘.’ が2回以上出てくる記述は採れません。
また、要素名の中にスペースあるいはプラス・マイナスなどの演算記号を含めることもできません。Hashに比べてそうした一定の制約があります。
こうした制約からいくらか逃れるため、OpenStruct_rというクラスをつくりました。OpenStructクラスの継承クラスです。
rubyには統計コマンドRを利用するためのrsrubyというライブラリがあります。このrsrubyが返す統計解析結果は、たいていHashです。それも入れ子のHashが多いです。
そこで、OpenStruct_rクラスを考えました。詳細は次のサイトに書いたので、よかったら覗いてみて下さい。
rubyにおける統計Rの利用とOpenStruct_rクラス
Word, PowerPointの表をすべてExcelに書き出すrubyスクリプトを掲げます。
カレントディレクトリの *.doc, *.docx, *.docm, *.ppt, *.pptx などを読み込んで、その表の部分をExcelファイルに出力。
test.doc の表は test.doc.xls に書き出します。もし、既に test.doc.xls が存在すれば、それを削除した上で書き出すので注意。
Wordの表は、ClipBoardを利用し、表をまるごとExcelに貼り付けます。
PowerPointの表は、各セルを1つづつ読み込んで、それをExcelのセルに書き出します。表全体をまとめて扱う方法もあるような気がしますが、今のところ分かりません。
xdoc2txtというコマンドを使うと、Word, PowerPointなどのファイルをテキストファイルに変換できます。変換処理が迅速で便利です。
ただ、表のレイアウトが崩れます。各セルが1行づつになるので、表の縦・横の関係を確認しづらくなります。
そこで、表の部分をExcelに書き出すことを考えました。
カレントディレクトリに Word や PowerPoint のファイルがある場合、
ruby wrd_ppt.rb [enter]
と実行すれば、該当のExcelファイルが出力されます。
以下、wrd_ppt.rbを掲げます。
−−−−−−−− ここから
# wrd_ppt.rb: ruby script (encoding: Windows-31J)
require "exlap"
require "wrdap"
xl = Exlap.new # Excelの起動
unless xl
STDERR.puts "Excelを起動できません."
exit
end
xl.CutCopyMode = false # クリップボードのcopy,pasteを無効化(念のため)
sleep(0.5)
wrd_list = Dir.glob("./*.doc\0./*.doc?") # Wordファイルのリスト
ppt_list = Dir.glob("./*.ppt\0./*.ppt?") # PowerPointファイルのリスト
# Wordの処理
wrd = nil
if wrd_list.size > 0
wrd = Wrdap.new # Wordの起動
if !wrd
STDERR.puts "Wordを起動できません."
end
end
if wrd
msg_str = ''
wrd_list.each do |wrd_file| # ワード文書を1つづつ処理
xl_file = wrd_file + ".xls"
if test(?e, xl_file) # xl_fileが既に存在する時は削除
File.unlink(xl_file)
end
wb = xl.book_open(xl_file) # ワークブックを開く
doc = wrd.doc_open(wrd_file) # ワードファイルを開く
count = 0 # 表の数をカウント
doc.Content.Tables.each do |tbl| # 表を1つづつ確認
count += 1
ss = wb.fes # 空のワークシートを選択
ss.Activate
tbl.Range.Copy # ワードの表をクリップボードにコピー
ss.Range("A1").Select # ワークシートでの書き出しの始点を選択
ss.Paste # クリップボードから貼り付け
xl.CutCopyMode = false # クリップボードのcopy,pasteを無効化
ss.range_autofit
end
doc.close
if count >= 1 # 表の書き込みを1回以上 行った
wb.empty_sheet_names.each {|sn| wb.delete_sheet(sn)}
wb.ss(1).Activate # 第1ワークシートに焦点を当てる
wb.save # ワークブックの保存
msg_str += sprintf("** %s\n", File.basename(wrd_file))
msg_str += sprintf("%d個の表を出力しました.\n\n", count)
else
msg_str += sprintf("** %s\n\n", File.basename(wrd_file))
end
wb.close
end
wrd.quit # ワードの終了
print msg_str
end
# PowerPointの処理
ppt = nil
if ppt_list.size > 0
begin
ppt = WIN32OLE.new("PowerPoint.Application")
rescue
ppt = nil
end
if !ppt
STDERR.puts "PowerPointを起動できません."
end
end
if ppt
ppt.Visible = true
msg_str = ''
ppt_list.each do |ppt_file| # パワポファイルを1つづつ処理
ppt_file = Exl::fullpath(ppt_file)
xl_file = ppt_file + ".xls"
if test(?e, xl_file)
File.unlink(xl_file)
end
wb = xl.book_open(xl_file) # ワークブックを開く
presen = ppt.Presentations.Open(ppt_file) # パワポファイルを開く
slide_count = presen.Slides.Count
msg_str += sprintf("** %s\n", File.basename(ppt_file))
msg_str += sprintf("スライド総数: %d\n", slide_count)
# 各スライドを調べる
xl_tbl = 0 # Excelに書き出す表の個数
sld_nums = []
sld_count = 0
presen.Slides.each do |sld|
sld_count += 1 # スライド番号を更新
sld.Shapes.each do |shp| # 各々のShapeを調べる
next if shp.HasTable == 0 # 表がない
tbl = shp.Table
ss = wb.fes # 空のワークシートを選択
xl_row = 1 # Excelワークシートの行番号
title = tbl.Title.to_s # パワポの表のタイトル
if title != ''
ss.cell(xl_row,1).Value = title
xl_row += 1
end
for i in 1..tbl.Rows.Count # パワポの表の各行を調べる
for j in 1..tbl.Columns.Count # パワポの表の各列を調べる
ss.cell(xl_row,j).Value = tbl.Cell(i,j).Shape.TextFrame.TextRange
end
xl_row += 1
end
unless ss.empty? # Excelワークシートが空ではない
ss.range_autofit # セルの幅・高さを自動調整
xl_tbl += 1
sld_nums << sld_count
end
end
end
if sld_nums.size > 0
nums_str = sld_nums.join(", ")
msg_str += sprintf("スライド %s に表がありました.\n", nums_str)
end
presen.Close # パワポを閉じる
if xl_tbl > 0 # Excelに出力した表が1つ以上ある
wb.empty_sheet_names.each {|sn| wb.delete_sheet(sn)}
wb.ss(1).Activate # 第1ワークシートに焦点を当てる
wb.save # ワークブックの保存
msg_str += "\n"
end
wb.close # ワークブックを閉じる
end
ppt.Quit # PowerPoint アプリケーションの終了
print msg_str
end
xl.quit # Excelの終了
−−−−−−−− ここまで
上のスクリプトでは、拙作 exlap および wrdap を使っています。
カレントディレクトリの下にサブディレクトリがある場合、そのサブディレクトリの下のファイルは対象にしません。もし、そうしたければ、rubyのfindライブラリを使う形に書き換えることによって簡単に行えると思います。
Yahooのテレビ番組表は、以前は ruby の open-uri などで取得できたのですが、何年前になるでしょうか?できなくなりました。該当のWebページにスクリプトが組み込まれて、それを解釈・実行できるブラウザでないと、番組表を取得できなくなったものと思います。
そこで、Internet Exploreで該当のWebページを取得し、番組表のtable部分だけをExcelに引き渡して、xlsファイルとして書き出すことを考えます。
ExcelのQueryTable機能を利用します。
今日のテレビ番組(東京)を取得する例です。他の地域を得る場合は、urlの「/23/」の数値を変更して下さい。
番組表が3番目のtableにあるという前提でスクリプトを書きました。YahooのWebページが変更されて、その前提が通用しなくなれば正しく動きません。
また、予め、IE起動時に「通常使用するブラウザにするか否か」という確認メッセージが出ないようにしておくのが望ましいです(確認メッセージが出る状態でも大丈夫かもしれませんが念のため)。そうする方法は次の2つです。どちらか一方を実行すれば大丈夫です。
スクリプトを実行する場合は下のようにします。
ruby tv_yahoo.rb [enter]
以下、tv_yahoo.rbを掲げます。
−−−−−−−− ここから
# tv_yahoo.rb: ruby script (encoding: Windows-31J)
require "exlap"
url = "http://tv.yahoo.co.jp/listings/23/" # 23は東京地区
ie = WIN32OLE.new("InternetExplorer.Application")
ie.visible = true
ie.navigate(url)
sleep 0.5 while ie.busy == true
tc = ie.Document.Body.All.Tags("TABLE") # tableコレクション
tbl3 = tc.Item(2) # 3番目の表を抽出
tbl3_str = tbl3.outerHtml # 「<table ……>・・・</table>」の箇所を抽出
ie.Quit # IEの終了
html_file = "tv_temp.htm" # 第3テーブルのhtmlソースを書き出すファイル名
File.open(html_file, "w") {|ff| ff.write tbl3_str}
filename = "tv_yahoo.xls" # Excelファイルの名前
Exlap.new(filename) do |wb|
ss = wb.fes # 空白のワークシートを選択
ss.Name = "テレビ番組表" # ワークシート名
qt = ss.QueryTables.Add({
'Connection'=>"URL;#{Exl::fullpath(html_file)}",
'Destination'=>ss.Range("A1")})
qt.Name = "テレビ番組表" # クエリテーブルの名前
qt.WebSelectionType = XlAllTables
qt.WebFormatting = XlWebFormattingAll
qt.SaveData = false # 「クエリテーブルをワークブックと一緒に保存」をしない
qt.RefreshPeriod = 0 # 「定期的更新」の時間(0で無効)
qt.BackgroundQuery = false
qt.Refresh
wb.save
end
File.unlink(html_file)
−−−−−−−− ここまで
Excelに対してhtmlソース全体を引き渡して、その中の3番目の表を抽出・処理することも可能ですが、IEの方で3番目のtableを簡単に抽出できるので、そこだけを引き渡す形にしてみました。
*参考: Excel自動操縦用ライブラリexlap