140 lines
5.0 KiB
Markdown
140 lines
5.0 KiB
Markdown
# Architecture
|
|
|
|
`dataxl` は、ファイル形式ごとの差を小さくするために、内部表現を2つに分けています。
|
|
|
|
- structured value: JSON/YAML/TOMLから読める `map[string]any`, `[]any`, scalar
|
|
- table: Excel/CSV/TSVに近い `Header []string` と `Rows [][]string`
|
|
|
|
変換は原則として次のどれかです。
|
|
|
|
- structured -> structured
|
|
- structured -> table
|
|
- table -> structured
|
|
- table -> table
|
|
|
|
## Source Layout
|
|
|
|
実装は `cmd/dataxl` package内で責務別に分割しています。
|
|
|
|
- `main.go`: CLI option、stdin/stdout、ファイル入出力
|
|
- `conversion.go`: 変換経路の選択、形式名の正規化・推定
|
|
- `structured.go`: JSON/YAML/TOML adapter
|
|
- `table.go`: CSV/TSV/XLSX adapter、table model、flatten
|
|
- `path.go`: セル値の型推定、列パスのparse、unflatten
|
|
|
|
`convert` はファイル入出力から独立しているため、CLIを経由せず変換matrixをテストできます。
|
|
形式固有処理は `parseStructured` / `encodeStructured` または
|
|
`parseTable` / `encodeTable` に閉じ込めます。
|
|
|
|
現状は単一commandだけが利用するため同じpackageに置いています。別commandやlibrary APIから
|
|
再利用する段階になったら、安定させたい境界を見極めたうえで `internal` packageへ移します。
|
|
|
|
## Table Model
|
|
|
|
表形式は必ず1行目をヘッダーとして扱います。
|
|
|
|
```go
|
|
type table struct {
|
|
Header []string
|
|
Rows [][]string
|
|
}
|
|
```
|
|
|
|
CSV/TSV/XLSXの読み込みでは、短い行を空文字で埋めて列数を揃えます。ヘッダーより
|
|
長い行は、名前のない値を破棄しないようエラーにします。
|
|
XLSXの書き出しではヘッダーを太字にし、1行目を固定します。
|
|
|
|
## Flattening
|
|
|
|
structured -> table では、入れ子のmap/arrayを列パスへ展開します。
|
|
|
|
- map: `user.name`
|
|
- array: `items[0].sku`
|
|
- delimiterを含むmap key: `settings["build.target"]`
|
|
- top-level scalar: `value`
|
|
|
|
列順は安定性を優先してソートしています。Excel上で列の位置が変わっても、
|
|
ヘッダー名を見て復元するため、列順には依存しません。
|
|
|
|
## Unflattening
|
|
|
|
table -> structured では、ヘッダーのパス表現からmap/arrayを復元します。
|
|
|
|
例:
|
|
|
|
```text
|
|
items[0].sku
|
|
```
|
|
|
|
は次の構造になります。
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"sku": "..."
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
セル値は `parseCell` で軽く型推定します。
|
|
|
|
- 空文字: `""`
|
|
- `true` / `false`: boolean
|
|
- 整数: int64
|
|
- 小数または指数表記: float64
|
|
- その他: string
|
|
|
|
ゼロ埋め整数と前後に空白があるセルは、IDや文字列を壊さないためstringとして
|
|
保持します。複数列が同じパスで異なる中間型を要求する場合はエラーにし、入力列を
|
|
黙って捨てません。
|
|
|
|
### Path grammar
|
|
|
|
現在の列パスは次の要素を扱います。
|
|
|
|
```text
|
|
path = first-key, { map-child | quoted-key | array-index };
|
|
first-key = bare-key | quoted-key;
|
|
map-child = ".", bare-key;
|
|
quoted-key = "[", JSON-string, "]";
|
|
array-index = "[", digit, { digit }, "]";
|
|
```
|
|
|
|
実例は `user.name`、`items[0].sku`、`orders[0].items[1].qty` です。map keyに
|
|
`.`、`[`、`]` が含まれる場合や空文字の場合は、`["build.target"]` や `[""]` の
|
|
ようなJSON quoted keyを使います。連続したarray indexも扱うため、
|
|
`matrix[0][1]` を復元できます。入力サイズに対して過大なmemory allocationを
|
|
起こさないよう、pathは最大256要素、array indexは最大10000です。
|
|
|
|
## Structured input integrity
|
|
|
|
- JSON inputはUTF-8として検証します。decoderは最初の値の後まで読み、空白以外の
|
|
後続データを拒否します。object keyもtoken単位で読み、重複を拒否します。
|
|
- YAML decoderはstream終端まで読み、複数文書を順序付きsliceとして保持します。
|
|
- `rows`、`records`、`items` wrapperはオブジェクト唯一のキーである場合だけ
|
|
table rowsとして展開します。
|
|
- recordが存在するのにscalar fieldが1つもないstructured valueは、表へ変換すると
|
|
record数を失うため拒否します。空のrecord listは空の表として扱います。
|
|
|
|
## TOML Output
|
|
|
|
TOMLはトップレベル配列を直接表せないため、表からTOMLへ出力する場合など、
|
|
トップレベルがmapでない値は `rows` キーに包んで出力します。
|
|
|
|
## Dependencies
|
|
|
|
- `github.com/xuri/excelize/v2`: XLSX読み書き
|
|
- `gopkg.in/yaml.v3`: YAML読み書き
|
|
- `github.com/BurntSushi/toml`: TOML読み書き
|
|
|
|
Go 1.25以上を前提にしています。
|
|
|
|
## Error handling
|
|
|
|
- 未対応形式、decode失敗、workbook/sheet操作失敗は呼び出し元へerrorを返します。
|
|
- CSV/TSVのinvalid UTF-8、headerより長い行、値を持つ空header列を拒否します。
|
|
- path復元中の重複header、構文エラー、型競合は変換全体をerrorにします。
|
|
- XLSXのstyle・pane設定も通常の変換errorとして扱い、不完全なworkbookを成功扱いしません。
|