プログラミング雑記帳

ここでは、プログラミングに関するちょっとした覚え書きをメモとして残します。

従来の「プログラミング雑記帳」のボリュームが大きくなったので、別のページにしました。


《目次》


    

MS-Windowsにおけるファイル選択ダイアログをrubyから利用する

2016/04/26

 このところ必要があって、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

    

◇ 目次に戻る


    

OpenStructクラスのちょっとした拡張

2015/06/10

 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に出力する

2014/10/05

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のテレビ番組表をExcelに書き出す

2014/10/05

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

◇ 目次に戻る


トップページへ