CSV4Unity コア設計
最初に知っておくこと
CSV4Unityは、CSVをクラスへ一括変換するライブラリではありません。
CSVの文字列をそのまま保持し、必要になったセルだけを文字列・整数・真偽値などへ変換します。これにより、ADVの Arg1 のように、行によって型が変わる列も扱えます。
利用時に中心となる型は、ひとまず次の3つです。
| 型 | 意味 |
|---|---|
CsvTable<TField> |
Enumで列を指定できるCSV全体 |
CsvRow<TField> |
CSVの1行 |
CsvCell |
1つのセル。必要な型を指定して値を取得する |
CsvTable<ScenarioField> table = CSVLoader.LoadTable<ScenarioField>(csvAsset);
CsvRow<ScenarioField> row = table.Row(0);
string command = row[ScenarioField.Command].GetString();
float seconds = row[ScenarioField.Arg1].Get<float>();
Arg1をロード時に特定の型へ固定しないため、別の行では次のように取得できます。
bool enabled = table.Row(1)[ScenarioField.Arg1].Get<bool>();
int count = table.Row(2)[ScenarioField.Arg1].Get<int>();
データが読み込まれる流れ
Unity TextAsset
↓ CSVLoader
CSV文字列
↓ CsvParser
CsvDocument
├── CsvRow / CsvColumn / CsvCell
└── CsvEnumSchema<TField>
↓
CsvTable<TField>
├── CsvRow<TField>
└── CsvColumn<TField>
CSVLoaderがUnityのTextAssetから文字列を取り出します。CsvParserがCSVの構造を解析します。CsvDocumentが元文字列と各セルの位置を保持します。CsvEnumSchema<TField>がEnumとCSV列を対応付けます。CsvTable<TField>から行・列・セルへアクセスします。
データを誰が所有するか
セルの実データを所有するのは CsvDocument だけです。
CsvRow、CsvColumn、CsvCell、CsvTable<TField> は、すべて CsvDocument を参照するためのビューです。行用・列用に同じ値を複製しません。
CsvDocument
├── 元のCSV文字列
├── セルの開始位置と長さの配列
├── ヘッダー配列
└── ヘッダー名から列番号への対応表
CsvRow / CsvColumn / CsvCell
└── CsvDocumentとインデックスだけを参照
現在の CsvDocument は読み取り専用です。将来CSV編集を実装する場合は、直接可変にせず、編集専用の別クラスを用意します。
クラスごとの役割
読み込み
| 型 | 役割 | やらないこと |
|---|---|---|
CSVLoader |
TextAsset とPure C#コアを接続する |
CSV解析、型変換、検証処理 |
CsvParser |
RFC 4180形式のCSV構造を解析する | 値の型推測、Enum対応、Validation |
CsvParseOptions |
ヘッダー、区切り文字、空行などを指定する | 読み込み結果の保持 |
CsvParseException |
CSV構文エラーの位置を伝える | Validationエラーの表現 |
データ保持とアクセス
| 型 | 役割 |
|---|---|
CsvDocument |
読み込んだCSV全体を一度だけ保持する |
CsvCellRange |
元文字列上でのセル開始位置・長さを内部保持する |
CsvCell |
1セルを参照し、文字列化や型付き取得を提供する |
CsvRow |
列番号・ヘッダー名でアクセスできる行ビュー |
CsvColumn |
行番号でアクセスできる列ビュー |
Enumアクセス
| 型 | 役割 |
|---|---|
CsvEnumSchema<TField> |
Enum値とCSV列番号を対応付ける |
CsvSchemaAttribute |
EnumをUnity Inspectorのスキーマ候補として登録する |
CsvHeaderAttribute |
Enum名と異なるヘッダー名を指定する |
CsvHeaderPatternAttribute |
複数表記を正規表現で一意に対応付ける |
CsvTable<TField> |
CsvDocument とEnumスキーマを組み合わせる |
CsvRow<TField> |
Enumを使って1行のセルへアクセスする |
CsvColumn<TField> |
Enumで選択した列へアクセスする |
CsvSchemaException |
必須ヘッダー不足などのスキーマ不一致を表す |
属性を指定しない場合、Enum名とCSVヘッダー名は大文字小文字まで一致している必要があります。
public enum ScenarioField
{
Command,
Arg1,
Arg2,
Text
}
Command,Arg1,Arg2,Text
Text,,,こんにちは
CSV側の命名を変更できない場合は CsvHeader で別名を指定できます。
public enum ItemField
{
[CsvHeader("Item ID")]
Id,
[CsvHeader("DISPLAY NAME", IgnoreCase = true)]
DisplayName
}
複数の表記を許可する必要がある場合だけ CsvHeaderPattern を使用します。パターンは部分一致ではなくヘッダー名全体へ適用されます。
using System.Text.RegularExpressions;
public enum ItemField
{
[CsvHeaderPattern(@"item[_\s-]?id", RegexOptions.IgnoreCase)]
Id
}
対応候補が0件または複数件の場合や、複数のEnumフィールドが同じCSV列へ対応した場合は、曖昧なスキーマとして CsvSchemaException を送出します。
Unity Editor
| 型 | 役割 |
|---|---|
CsvInspectorEditor |
Unity標準TextAsset InspectorへCSV専用の文字コード変換・Viewer・Validationを追加する |
CsvEncodingUtility |
元バイト列の文字コードを検査し、明示された文字コードからUTF-8へ変換する |
CsvViewerWindow |
CSVアセットの選択、解析、検索条件、再読込を管理する |
CsvViewerTable |
表示範囲の行だけを描画し、75〜200%の拡大率、列幅変更、コピー操作を提供する |
ViewerはEditor専用であり、CsvDocumentを読み取り専用データとして利用します。表示用文字列は最大256行分だけキャッシュし、CSV全体を表示専用の二次元文字列配列へ複製しません。編集や書き出しは別の責務とします。拡大率は描画時の行高・列幅・文字サイズへ適用し、CSVデータやキャッシュ内容は複製しません。
UnityのCustomEditorはファイル拡張子で対象を限定できないため、CsvInspectorEditorはCSV以外ではUnity標準のTextAsset Inspectorへ表示を委譲します。CSVでは本文のテキストプレビューを省略し、表形式のViewerを入口にします。CSV用UIは拡張子が.csvのアセットにだけ追加します。
文字コード検査はUnityが生成したTextAsset.textではなく、プロジェクト内の元CSVファイルをバイト列として読み取ります。BOMを優先し、BOMなしは厳密なUTF-8、次にShift_JIS(CP932)として検査します。誤判定時はInspectorで変換元を指定できます。ファイルの自動書き換えは行わず、確認ダイアログを伴う手動操作だけでUTF-8(BOMなし)へ変換します。
型変換
| 型 | 役割 |
|---|---|
CsvValueConverter |
セル文字列を指定された型へ変換する |
CsvConversionException |
型変換に失敗した値と変換先型を伝える |
ロード時の自動型推測は行いません。001 はロード後も 001 のままです。
CsvCell cell = row[ScenarioField.Arg1];
string raw = cell.GetString(); // "001"
int number = cell.Get<int>(); // 1
検索インデックス
| 型 | 役割 |
|---|---|
CsvIndex<TKey> |
指定した列の値から行番号を検索する |
CsvIndexMatches |
一致した複数の行番号を返す |
インデックスは自動では作りません。必要な列にだけ明示的に作成します。
CsvColumn<ScenarioField> commandColumn = table.Column(ScenarioField.Command);
CsvIndex<string> index = CsvIndex<string>.Create(commandColumn);
if (index.TryFindFirst("Text", out int rowIndex))
{
CsvRow<ScenarioField> textRow = table.Row(rowIndex);
}
Validation
| 型 | 役割 |
|---|---|
CsvValidationSchema<TField> |
Enum属性を一度読み取り、検証規則へ変換する |
CsvConditionEvaluator |
コンパイル済みConditionを行ごとに評価する内部クラス |
CsvValidator |
CsvTable<TField> を規則に従って検証する |
CsvValidationResult |
エラーと警告を保持する |
Validationは次の2種類へ分けます。
| 種類 | 制約 |
|---|---|
| セル・行単位 | NotNull、TypeConstraint、Range、Regex、AllowedValues、文字列長 |
| 列全体 | PrimaryKey、Unique |
ConditionAttributeは対象フィールドに付いたValidation属性の適用行を限定します。スキーマ生成時に条件列をEnumへ解決し、Reflectionは行評価中に実行しません。同じConditionGroupの条件はANDで評価され、Validation属性は同じ番号の条件グループだけを参照します。グループ0にConditionがなければ、従来どおり無条件で適用します。
Validation属性は属性1個につき内部規則1個へ変換します。このため、同じ列へCommand別のTypeConstraintを複数定義できます。条件は制約を実行するかだけを決め、条件不成立自体をValidationエラーにはしません。
Unity Inspectorでのスキーマ発見
CsvSchemaAttributeをEnumへ付けると、Unity EditorはTypeCacheを使ってその型を発見し、CSV InspectorのValidation Schema候補へ表示します。Enumを特定の名前空間へ置く必要はありません。
この属性はEditor上の発見だけを担当します。RuntimeのCsvDocument.WithFields<TField>()やCSVLoader.LoadTable<TField>()は、属性がないEnumも従来どおり利用できます。
v0.xでは互換性のため、CSV4Unity.Fields名前空間にある属性なしEnumも候補へ含めます。この名前空間規約は新規コードでは使用せず、明示的にCsvSchemaAttributeを付けます。
PrimaryKey と Unique は条件に一致する行集合を一度だけ走査します。無条件の場合も、各行のセル検証中に列全体を繰り返し走査しません。
依存関係のルール
下位のクラスは上位の都合を知りません。
Unity連携 → Parser → Document
↑
Schema ────────────────┘
Conversion → Cellが利用
Indexing → Columnを利用
Validation → Table / Schema / Cellを利用
CsvDocumentはUnityを知りません。CsvDocumentはValidationを知りません。CsvParserはEnumや属性を知りません。- ValidationはParser内部へ入りません。
- 新コアは旧
CsvData/LineDataに依存しません。
旧APIについて
再設計前の LoadCSV、CsvData、LineData と旧Validatorは削除しました。
読み込みには LoadDocument または LoadTable<TField> を使用します。
今後の予定
- Validationのエラー表現と日本語開発者向け説明を整える。
- CSV編集用の可変モデルと書き出し処理を別レイヤーとして設計する。
- APIが固まった段階で英語文書とHTML APIリファレンスを整備する。