2012年7月26日木曜日

[Java][SV]SVファイルIO(ソースコード)

package util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * セパレーテッドバリューファイル
 */
public class SvFile {

    /** デリミタ */
    private String delm = null;

    /** SV行データリスト */
    List<List<String>> svLineList = null;

    public List<List<String>> getSvLineList() {
        return svLineList;
    }

    public void setSvLineList(List<List<String>> svLineList) {
        this.svLineList = svLineList;
    }

    /**
     * コンストラクタ
     */
    public SvFile() {
    }

    /**
     * 指定されたファイルのデータを読み込みます。デリミタを指定しないコンストラクタの場合、デリミタはタブとなります。
     *
     * @param fileName
     *            ファイル名
     * @throws IOException
     */
    public void loadFile(String fileName) throws IOException {

        // SVファイルをロードする
        loadFile(fileName, "\t", false);
    }

    /**
     * デリミタを区切り文字として、指定されたファイルのデータを読み込みます。最初の行は読み飛ばしません。
     *
     * @param fileName
     *            ファイル名
     * @param delm
     *            デリミタ
     * @throws IOException
     */
    public void loadFile(String fileName, String delm) throws IOException {

        // SVファイルをロードする
        loadFile(fileName, delm, false);
    }

    /**
     * デリミタを区切り文字として、指定されたファイルのデータを読み込みます。
     *
     * @param fileName
     *            ファイル名
     * @param delm
     *            デリミタ
     * @param skipFirstLine
     *            最初の行を読み飛ばす場合はtrue、そうでなければfalse
     * @throws IOException
     */
    private void loadFile(String fileName, String delm, boolean skipFirstLine)
            throws IOException {

        // デリミタを記憶しておく
        this.delm = delm;

        // SV行データリストを初期化する
        setSvLineList(new ArrayList<List<String>>());

        // ファイルを開く
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(new File(fileName)));

            // 最初の行をスキップする設定の場合は、読み捨てを行う
            String line = "";
            if (skipFirstLine) {
                line = br.readLine();
            }

            // 全ての行を処理するまでループ
            while ((line = br.readLine()) != null) {

                // SV行のデータをロードする
                loadLine(line);
            }

            // 最後に必ずファイルを閉じる
        } finally {
            if (br != null) {
                br.close();
            }
        }
    }

    /**
     * SVファイルの行データをロードします。SV値は改行なし、ダブルクオートによるエスケープもなしと前提します。
     *
     * @param line
     *            行データ
     */
    private void loadLine(String line) {

        // 文字列をスプリッタで分割する
        String[] svValArray = line.split(delm);

        // 行データ内の全てのSV値を処理するまでループ
        List<String> svValList = new ArrayList<String>();
        for (String svVal : svValArray) {

            // 【重要!】null値を検出した場合は空文字列を設定する
            if (svVal.equals("(null)")) {
                svValList.add("");
            }

            // 【重要!】SV値はダブルクオートで囲むルールとする
            else if (svVal.length() == "".length()) {
                svValList.add("");
            } else if (svVal.startsWith("\"") && svVal.endsWith("\"")) {
                svValList.add(svVal.substring(1, (svVal.length() - 1)));
            }
        }

        // リストに行データを追加する
        getSvLineList().add(svValList);
    }

    /**
     * 引数で指定されたSV行データリストで既存のデータを上書きします。
     *
     * @param fileName
     *            ファイル名
     * @param delm
     *            デリミタ
     * @param svLineList
     *            SV行データリスト
     * @throws IOException
     */
    public void save(String fileName, List<List<String>> svLineList)
            throws IOException {

        // セーブする
        save(fileName, "\t", svLineList);
    }

    /**
     * 引数で指定されたSV行データリストで既存のデータを上書きします。
     *
     * @param fileName
     *            ファイル名
     * @param delm
     *            デリミタ
     * @param svLineList
     *            SV行データリスト
     * @throws IOException
     */
    public void save(String fileName, String delm, List<List<String>> svLineList)
            throws IOException {

        BufferedWriter bw = null;
        try {
            // ファイルを書き込み用に開く
            bw = new BufferedWriter(new FileWriter(new File(fileName)));

            // 全ての行データを処理するまでループ
            for (List<String> svLine : svLineList) {

                // SV行データの各カラムをデリミタで接続する
                StringBuffer sb = new StringBuffer("");
                for (String column : svLine) {

                    // 1つ以上のカラムをSV行に設定している場合は、区切り文字を入れる
                    if (sb.length() > 0) {
                        sb.append(delm);
                    }

                    // 【重要!】空文字列はnull値として記録する
                    if (column == null || column.length() == 0) {
                        sb.append("(null)");
                    }

                    // 【重要!】SV値はダブルクオートで囲むルールとする
                    else {
                        sb.append("\"" + column + "\"");
                    }
                }

                // ファイルに書き込みを行い、最後に改行を入れる
                bw.write(sb.toString());
                bw.newLine();
            }

        } finally {

            // ファイルをクローズする
            bw.close();
        }
    }
}

2012年4月1日日曜日

[EXCEL][マクロ]マッピングによる転記(ソースコード)

Sub onClickExecute()

    ' 現在のシート内にある特定のセルからパス名、ファイル名を取得する
    fromFilePath = Range("C5")
    fromFileName = Range("C6")
    templateFilePath = Range("C7")
    templateFileName = Range("C8")
    mappingFilePath = Range("C9")
    mappingFileName = Range("C10")
    resultFilePath = Range("C11")
    resultFileName = Range("C12")

    ' 転記元ファイルを開く
    Workbooks.Open fromFilePath + "\" + fromFileName

    ' テンプレートファイルを開く
    Workbooks.Open templateFilePath + "\" + templateFileName

    ' マッピングファイルを開く
    Workbooks.Open mappingFilePath + "\" + mappingFileName

    ' マクロ実行結果ファイルは作成する
    Workbooks.Add.SaveAs fileName:=resultFilePath + "\" + resultFileName

    ' マッピングファイルの最初に書かれているシート名を取得する
    loopCtrlSheet = Workbooks(mappingFileName).Sheets("MAPPING").Cells(2, 2)

    ' 全てのデータを転記するまでループ
    For fromLineNum = 2 To 101

        ' 転記元のシートの2列目が空ならばそれ以上のデータがないと判断し、ループを抜ける
        If Workbooks(fromFileName).Sheets(loopCtrlSheet).Cells(fromLineNum, 2) = "" Then Exit For

        ' テンプレートファイルの原紙シートをコピーして転記用の新しいシートを作成する
        Workbooks(templateFileName).Sheets("原紙").Copy Before:=Workbooks(resultFileName).Sheets("Sheet1")

        ' マッピング設定を全て処理するまでループ
        For mappingLineNum = 2 To 101

            ' マッピングファイルの2列目が空ならばそれ以上データがないと判断し、ループを抜ける
            If Workbooks(mappingFileName).Sheets("MAPPING").Cells(mappingLineNum, 2) = "" Then Exit For

            ' 転記元のシートとカラムを取得する
            fromSheet = Workbooks(mappingFileName).Sheets("MAPPING").Cells(mappingLineNum, 2)
            fromColumn = Workbooks(mappingFileName).Sheets("MAPPING").Cells(mappingLineNum, 3)

            ' 転記先のセルを取得する
            toCell = Workbooks(mappingFileName).Sheets("MAPPING").Cells(mappingLineNum, 4)

            ' マッピング設定に従って転記を行う
            Range(toCell) = Workbooks(fromFileName).Sheets(fromSheet).Cells(fromLineNum, fromColumn)

        Next mappingLineNum
    Next fromLineNum

    ' ファイルを閉じる
    Workbooks(fromFileName).Close SaveChanges:=False
    Workbooks(templateFileName).Close SaveChanges:=False
    Workbooks(mappingFileName).Close SaveChanges:=False
    Workbooks(resultFileName).Close SaveChanges:=True

End Sub

[EXCEL][マクロ]マッピングによる転記(準備)

マッピングによって特定のファイルにあるデータの並び順や位置を入れ替えて別のファイルに出力したいということがよくある。

例えば、データベースのテーブル定義書があり、カラム名や型が1行1カラムで記述されているEXCELファイルがあったとする。
このファイルに記述されている各カラムの詳細なデータ説明を、書式が整えられた別のEXCELに転記したいといった場合、VBAが活躍する。

書式が整えられたテンプレートに、データを設定するマクロを組む。
準備として、このマクロを実行する条件を次のように前提する。

(1) 転記元のデータファイルは、1行1データ形式できれいな表形式として用意する。
(2) 転記元のデータファイルの複数のシートからデータを取得できるようにする。
(3) 転記する書式のテンプレートファイルを別EXCELで用意し、「原紙」というシート名にする。
(4) 転記元と転記先のマッピングファイルを別EXCELで作成する。
(5) マクロはテンプレートファイルの「原紙」シートをコピーし、そこにデータの転記を行う。
(6) 転記元の表にある全てのデータを処理したら、作成した処理結果データをセーブする。
(7) マッピングファイルに最初に記述した転記元データのシートにある全データをループ回数制御の基準とする。

転記元データファイル(表形式ファイル)のデータ有無は2列目で判断する。
これは1列目に項番などの一般項目を置いて、データがなくても連番だけは先に振るケースがあるためである。
2列目に記述がある行を有効行と判断する。

マッピングファイルは、次の通り前提する。

(1) マッピングファイルには必ず「MAPPING」という名前のシートを用意する。
(2) このシートの1行目A列からからNo.、転記元のシート、転記元のカラム、転記先と4つのセルを入力し、タイトル行とする。
(3) 2行目以降にデータを記述する。転記元データファイルと同じく、2列目が空白でない行を有効行と判定する。

マクロファイルは、次の通り前提する。

(1) マクロ実行ボタンを押下したら、そのハンドラ関数で転記処理を行う。
(2) 転記元、テンプレート、マッピング、処理結果出力ファイルのパス名とファイル名を可変データで取得する。

[EXCEL][マクロ]Forループ

Forループは、次のように記述する

For lineNum = 2 To 10
    If Workbooks("Sample.xls").Sheets("Sheet1").Cells(lineNum, 2) = "" Then Exit For
    MsgBox Workbooks("Sample.xls").Sheets("Sheet1").Cells(lineNum, 2)
Next lineNum

例えば1行目がタイトル行となっており、2行目から10行目のデータをループで処理したい場合、

For lineNum = 2 To 10
    (処理)
Next lineNum

のように記述する。
また、2カラム目が空白ならデータなしと見なして途中でループを抜けたい場合は、

    If Workbooks("Sample.xls").Sheets("Sheet1").Cells(lineNum, 2) = "" Then Exit For

のように記述する。
Cellsの第2引数はカラムなので各行の2カラム目を見て空白ならループを抜ける処理になる。

[EXCEL][マクロ]ファイルを閉じる

ファイルを閉じる場合は、次のように記述する。

Workbooks([EXCELファイル名]).Close SaveChanges:=False

EXCELファイル名はフルパス名ではなく単純にファイル名を指定する。
また、EXCELファイルが開かれていないとエラーになる。
次のように記述すると、"Sample.xls"というファイルを閉じる。

Workbooks("Sample.xls").Close SaveChanges:=False

SaveChangesはTrueにすれば編集内容を保存し、Falseにすれば編集内容を破棄する。

[EXCEL][マクロ]別のEXCELファイルを開く

マクロを実行しているEXCELファイル以外の別のEXCELファイルをオープンする場合は、次のように記述する。

Workbooks.Open [EXCELファイルのフルパス名]

必ずフルパス名でファイルを指定する必要があるので注意を要する。
例えば"C:\sample\sample.xls"を開く場合は、次のように記述する。

Workbooks.Open "C:\sample\sample.xls"

複数のEXCELファイルが開かれている場合、セルの値は次のように取得する。
なお、ここで指定するファイル名は、フルパス名ではなくファイル名となる。

Workbooks([EXCELファイル名]).Sheets([シート名]).Range([セルの位置])

Workbooksを前置し、どのワークブックのどのシートのどのセルかを指定すればアクセスできる。
また、セルの値は次のように記述して取得することもできる。

Workbooks([ファイル名]).Sheets([シート名]).Cells(line, column)

例えば次のように記述した場合、

Workbooks("Sample.xls").Sheets("Sheet1").Cells(1, 1)

既に開かれている"Sample.xls"というEXCELファイルの"Sheet1"シートの、1行目1列目(A1のこと)を取得することができる。
Rangeでのアクセスは直観的で便利だが、ループ処理でループ変数を使用したサクセスではCellsの方が使いやすい。

[EXCEL][マクロ]セルの値の取得

特定のシートにある値を取得する場合は、次のように記述する。

    Dim valueSheet2 As String
    valueSheet2 = Sheets("Sheet2").Range("A1")
    MsgBox ("valueSheet2 is : " + valueSheet2)

    Dim valueSheet2 As String
    Sheets("Sheet2").Select
    valueSheet2 = Range("A1")
    MsgBox ("valueSheet2 is : " + valueSheet2)

いずれも"Sheet2"のシート、"A1"の位置の値を取得してメッセージボックスで表示するコード。
自シートの場合は、Sheets("[シート名]")の指定は不要。(単にRange("[セル位置]")で取得できる)
別シートにある場合は、Sheets("[シート名]").Range("[セル位置]")と記述する必要がある。

[EXCEL][マクロ]ボタンの追加

EXCELにボタンを追加して、そのボタンがクリックされたときの動作を定義する方法は、次の通り。

(1) [開発]リボンから[挿入]-[フォームコントロール]と選択する。
(2) ボタンを選んでEXCELシート上にボタンを作成する。
(3) 上記(2)の操作時にマクロの登録を行う画面が立ち上がる。
(4) 上記(3)の画面でonClickExecuteなど、VBAの関数名を決める。
(5) 上記(4)の入力が完了したら[新規作成]ボタンをクリックする。

以上の操作でVBAの編集画面が立ち上がる。
上記(4)で入力した関数が、実際にボタンがクリックされたときに実行される関数となる。(下記)

Sub onClickExecute()

    MsgBox ("This is test.")
End Sub

ここではMsgBox関数を呼び出している。
実際にボタンをクリックしてメッセージボックスが表示されれば成功。

まずはVBAの処理を起動する端点をこのように作成し、ここから呼び出される処理を高度なものにしていくことで、インクリメンタルな開発が可能である。
簡単なマクロを組む場合はこの方法が時間的に効率がよい。

[EXCEL][マクロ]Office2007でのVBA起動方法

Office2007以前のバージョンでは、EXCELマクロ開発のためのVBAはメニューから起動できたが、Office2007以降はデフォルトでこのメニューがない。

左上にある丸ボタン(「名前を付けて保存」などのメニューが出てくるもの)で出てくるメニュー上から「EXCELのオプション」を選択し、「[開発]タブをリボンに表示する」にチェックを入れて設定を適用すると、VBAのエディタを起動できる。