[unity]NPOI を使って ScriptableObject の中身をエクセルに出力

あまり需要なさそうですが、地味にハマったので備忘録も兼ねて。
なお、実行時ではなく Editor 機能として使います。(実行時にエクセル作成する必要はないと思いますが…)

解説

ScriptableObject の型

こんな感じの ScriptableObject。メンバは次のように定義しています。

public class MapTable : ScriptableObject
{
    ///<summary>
    /// Table Row
    ///</summary>
    [System.Serializable]
    public class Row
    {
        public int            ID;
        public eMapObjectType Type;
        public int            StageNo;
        public float          X;
        public float          Z;
        public float          Arg0;
    };
}

リフレクションを使ってクラスメンバの型や名前を取ったり…と完全自動化も出来なくはありませんが、今回はそこまでしませんでした。

代わりにヘッダだけ書いてあるエクセルファイルを読み込むことに。

ヘッダの書いてあるエクセルを読み込む

ファイルの場所は必要に応じて書き換えます(サンプルでは Assets/Header.xlsx。以後同様)

IWorkbook book;

const string excellPath = "Assets/Header.xlsx";

using (FileStream stream = new FileStream(excellPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    book = new XSSFWorkbook(stream);
    stream.Close();
}

xlsx ではなく、xls の場合は XSSFWorkbook HSSFWorkbook に変えてください。

ScriptableObject を読み込む

存在しなければ空テーブルを作成するようにしていますが、絶対に存在する前提であれば data == null の部分は不要。

const string sobjectPath = "Assets/MapTable.asset";

var data = (MapTable)AssetDatabase.LoadAssetAtPath(sobjectPath, typeof(MapTable));
if (data == null)
{
    data = ScriptableObject.CreateInstance<MapTable>();
    AssetDatabase.CreateAsset((ScriptableObject)data, sobjectPath);
}

NPOI でデータを書き込む

行を取得、その行に属する列を1つずつ書いていくイメージです。
ExcelRow は NPOI の設定を楽にするためのシンタックスシュガー。セルを取得できなければこっそり作ってくれるかしこい子。

var sheet = book.GetSheet("MapTable");

// ヘッダの次の行から
int lastrow  = sheet.LastRowNum+1;
int rowIndex = lastrow;

for (int i = 0; i < data.Count; i++)
{
    var erow    = new ExcelRow(sheet, rowIndex);
    var dataRow = data.GetRow(i);
    erow.CellValue(0, dataRow.ID);
    erow.CellValue(1, dataRow.Type.ToString());
    erow.CellValue(2, 0);
    erow.CellValue(3, dataRow.X);
    erow.CellValue(4, dataRow.Z);

    rowIndex++;
}

ExcelRow

public class ExcelRow
{
    IRow row;

    public ExcelRow(ISheet sheet, int rowIndex)
    {
        row = sheet.GetRow(rowIndex) ?? sheet.CreateRow(rowIndex);
    }

    public void CellValue(int columnIndex, bool value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, System.DateTime value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, int value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, float value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, string value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    ICell getCell(int columnIndex)
    {
        return row.GetCell(columnIndex) ?? row.CreateCell(columnIndex);
    }
}

エクセルを書き出す

編集した NPOI データをエクセルに書き出します。
なお、なぜか既存のエクセルファイルに上書きするとファイルが破損したりするので、ファイルを一旦消してから作成するようにします。

const string excellPath2 = "Assets/Data.xlsx";

File.Delete(excellPath2);

using (FileStream stream = new FileStream(excellPath2, FileMode.OpenOrCreate, FileAccess.Write))
{
    book.Write(stream);
    stream.Close();
}

完成品

以上を全てまとめると、次のようなクラスになります。
メニューから Tools > SaveXLSX を選択するとエクセルを作成します。

using System.IO;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using UnityEditor;
using UnityEngine;

public class SaveXlsx : EditorWindow
{
    static readonly string excellPath = "Assets/Header.xlsx";
    static readonly string excellPath2 = "Assets/Data.xlsx";
    static readonly string sobjectPath = "Assets/Data_MapTable.asset";

    [MenuItem ("Tools/SaveXLSX")]
    public static void Exec()
    {
        IWorkbook book;

        using (FileStream stream = new FileStream(excellPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        {
            book = new XSSFWorkbook(stream);
            stream.Close();
        }

        var data = (MapTable)AssetDatabase.LoadAssetAtPath(sobjectPath, typeof(MapTable));
        if (data == null)
        {
            data = ScriptableObject.CreateInstance<MapTable>();
            AssetDatabase.CreateAsset((ScriptableObject)data, sobjectPath);
        }

        var sheet = book.GetSheet("MapTable");

        int lastrow  = sheet.LastRowNum+1;
        int rowIndex = lastrow;

        for (int i = 0; i < data.Count; i++)
        {
            var erow    = new ExcelRow(sheet, rowIndex);
            var dataRow = data.GetRow(i);
            erow.CellValue(0, dataRow.ID);
            erow.CellValue(1, dataRow.Type.ToString());
            erow.CellValue(2, 0);
            erow.CellValue(3, dataRow.X);
            erow.CellValue(4, dataRow.Z);

            rowIndex++;
        }

        File.Delete(excellPath2);

        using (FileStream stream = new FileStream(excellPath2, FileMode.OpenOrCreate, FileAccess.Write))
        {
            book.Write(stream);
            stream.Close();
        }

    }
}

public class ExcelRow
{
    IRow row;

    public ExcelRow(ISheet sheet, int rowIndex)
    {
        row = sheet.GetRow(rowIndex) ?? sheet.CreateRow(rowIndex);
    }

    public void CellValue(int columnIndex, bool value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, System.DateTime value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, int value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, float value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    public void CellValue(int columnIndex, string value)
    {
        getCell(columnIndex).SetCellValue(value);
    }

    ICell getCell(int columnIndex)
    {
        return row.GetCell(columnIndex) ?? row.CreateCell(columnIndex);
    }
}

作成されたエクセル

こんな感じ。Arg0 を忘れてた(笑)

json とかの方が出力は簡単ですが、json を手で編集したいとなると結構大変なんですよね。
Mac 開発の方はこのへん、どうやって折り合いをつけてるのかなーと思います。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA