# Tableau

> Expert on modern configuration converter — converts Excel/CSV/XML/YAML into Protobuf-backed JSON/Text/Bin configs via protogen + confgen pipeline. Trigger when user mentions: tableauc, protoconf, @TABLEAU metasheet, protogen, confgen, config.yaml for tableau, type syntax (map, list, struct, enum, union, keyed list), field properties (range, refer, unique, optional, patch, fixed, size), well-known types (datetime, duration, fraction, comparator, version), or converting game data / config spreadsheets to structured protobuf configs. Do NOT trigger for Tableau BI/Desktop/Server, general protobuf (protoc), or generic Excel/CSV libraries (pandas, openpyxl).

- Skill: `leoyeai-openclaw-master-skills/tableau` (Agent Skill, multi-file: 22 files)
- Install (CLI): `npx skillmds@latest add leoyeai-openclaw-master-skills/tableau`
- Raw SKILL.md: https://api.skillmd.com/api/skills/leoyeai-openclaw-master-skills/tableau/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: LeoYeAI (https://skillmd.com/u/leoyeai-openclaw-master-skills)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/leoyeai-openclaw-master-skills/tableau

---


# Tableau Expert

You are an expert in **tableau** — the modern configuration converter. Tableau transforms Excel/CSV/XML/YAML spreadsheets into structured config files (JSON/Text/Bin) using Protocol Buffers as the schema layer.

## Learning Resources

When you encounter questions beyond what's documented here, consult these primary sources rather than guessing:

- **Official Documentation**: Git repo `https://github.com/tableauio/tableauio.github.io`, docs path `content/en`
- **Test Cases** (primary learning source): Git repo `https://github.com/tableauio/tableau`, path `test/functest/` — real-world inputs and expected outputs. **DO NOT learn Go APIs — learn the test cases instead.**

## The Two-Parser Pipeline

```
Excel / CSV / XML / YAML
         |
         v  protogen (-m proto)
   .proto files (Protoconf)
         |
         v  confgen (-m conf)
   JSON / .txtpb / .binpb
```

- **protogen**: reads spreadsheet headers (namerow/typerow/noterow) -> `.proto` schema files
- **confgen**: reads `.proto` + spreadsheet data -> JSON/Text/Bin config files

Both configured through `config.yaml`. Run individually or together via `tableauc` CLI.

## Always Use `tableauc` for Real Output

> **IMPORTANT**: When a user asks "what proto/JSON will this generate?", do NOT write proto or JSON by hand. Instead, **create the input files and run `tableauc`** to produce the real output. Always tell the user: "Let me create the input and run `tableauc` to show you the actual output." The tool's output is the source of truth — hand-crafted output gets field numbers, option syntax, and naming conventions wrong.

> **MUST: Whenever you create or modify any input file** (Excel/CSV/XML/YAML), **always run both protogen and confgen immediately after** — even if the user only asked to create/edit the file. This ensures the generated `.proto` and config output are always in sync with the input.

### Workflow

1. **⚠️ MUST: Use `temp/` as the working directory** — all generated files (spreadsheets, `config.yaml`, python scripts) go here. Always use `temp/` as the default directory unless the user specifies otherwise. **If `temp/` does not exist, create it automatically**.
2. **Write the input files** — choose one approach:
   - **Excel**: Use the `xlsx` skill to create a `.xlsx` file with proper sheets (`@TABLEAU` metasheet + data sheets). Excel is the native format and supports multiple sheets in one file.
   - **CSV**: Write `BookName#SheetName.csv` + `BookName#@TABLEAU.csv` files. Use this when the xlsx skill is not available.
   - **XML**: Write XML files following tableau's XML input schema.
   - **YAML**: Write YAML files following tableau's YAML input schema.

   > **⚠️ MUST: Always create the `@TABLEAU` metasheet** — Without it, `tableauc` silently skips the entire workbook and produces no output.
   > For **Excel**: create the `@TABLEAU` sheet as the **first** sheet in the workbook. If it already exists, modify it directly — do NOT recreate it.
   > For **CSV**: always create `BookName#@TABLEAU.csv`.

   > **⚠️ MUST: Apply styling when creating or modifying Excel files** — Always apply the standard tableau Excel style (see [Excel Styling](#excel-styling)) to every `.xlsx` file you create or modify. This includes header coloring, field cell coloring, and auto-fit column widths and row heights.

3. **⚠️ MUST: Ensure `config.yaml` exists** — Before running any `tableauc` command:
   - If the user has provided a `config.yaml` path, use it with `-c <path>`.
   - Otherwise, check whether `config.yaml` exists in the working directory.
     - If it **does not exist**: **read `references/config.md` first**, then copy the **"Minimal default config"** template verbatim into `config.yaml`. **Never use `tableauc -s`** to generate config — that produces a bloated sample with wrong paths that breaks confgen.
     - If it **already exists**: use it as-is.
4. **Run protogen + confgen** (both steps, always):
   ```bash
   tableauc -c config.yaml -m proto   # Step 1: generate .proto files
   tableauc -c config.yaml -m conf    # Step 2: generate JSON/conf files
   ```
5. **Show the user** the actual files produced

### CLI Quick Reference

```bash
tableauc -m proto                                           # protogen only: scan CWD for input files, write .proto to CWD
tableauc -m conf                                            # confgen only: scan CWD for input files, write JSON to CWD
tableauc                                                    # both: scan CWD for input files, write .proto + JSON to CWD
tableauc HelloWorld.xlsx                                    # quick convert single file

tableauc -s                                                 # dump sample config.yaml
tableauc -c config.yaml                                     # both via config
tableauc -c config.yaml -m proto  HelloWorld.xlsx           # protogen a specified file via config
tableauc -c config.yaml -m conf   HelloWorld.xlsx           # confgen a specified file via config
tableauc -c config.yaml -m proto  Hello.xlsx World.xlsx     # protogen multiple specified files via config
tableauc -c config.yaml -m conf   Hello.xlsx World.xlsx     # confgen multiple specified files via config
```

### Locating `tableauc`

1. Try `tableauc --version` first
2. If not found: `go install github.com/tableauio/tableau/cmd/tableauc@latest`

### Common config.yaml Keys

```yaml
locationName: "Asia/Shanghai" # timezone
proto.input.protoFiles: ["common.proto"] # predefined type imports
proto.input.protoPaths: ["."] # proto search paths
conf.output.formats: ["json"] # output: json, txtpb, binpb
conf.output.pretty: true # pretty-print JSON
```

See `references/config.md` for the full reference.

## Header Layout

| Row | Purpose                                 | Default |
| --- | --------------------------------------- | ------- |
| 1   | **Namerow** — column names (PascalCase) | 1       |
| 2   | **Typerow** — protobuf type annotations | 2       |
| 3   | **Noterow** — human-readable comments   | 3       |
| 4+  | **Datarow** — actual data               | 4       |

Column names use **PascalCase** — protogen auto-converts to `snake_case` for proto fields (e.g., `ItemName` -> `item_name`). Configure custom acronyms in config.yaml (`acronyms: {K8s: k8s}`).

> **⚠️ MUST: Noterow content rules (in priority order):**
>
> 1. **Prompt provides parentheses** — When a field is described as `FieldName Type (description, ...)`, use the text before the first comma verbatim as the noterow. For example:
>    - `ID uint32 (赛季ID, 垂直 map key)` → noterow: `赛季ID`
>    - `Name string (名称)` → noterow: `名称`
>    - `Item1ID uint32 (道具1ID, 水平列表)` → noterow: `道具1ID`
> 2. **No parentheses provided** — Infer a concise, human-readable note from the field name and type. Use the same language as the surrounding prompt (Chinese if the prompt is in Chinese). For example:
>    - `ID uint32` → noterow: `ID`
>    - `Name string` → noterow: `名称`
>    - `Level int32` → noterow: `等级`
>    - `CreateTime datetime` → noterow: `创建时间`
>    - `ItemList [Item]uint32` → noterow: `道具列表`
>
> **Never** leave noterow cells blank — always fill them with either the prompt-provided description or an inferred one.

Multi-line headers: set `nameline`/`typeline`/`noteline` > 0 to pack name and type into separate lines within one cell.

## Type Syntax Cheat Sheet

| Typerow Cell                              | Meaning                                          |
| ----------------------------------------- | ------------------------------------------------ |
| `uint32` / `int32` / `string` / `bool`    | Scalar                                           |
| `enum<.FruitType>`                        | Predefined enum                                  |
| `map<uint32, Item>`                       | Vertical map (key col + value fields)            |
| `map<uint32, .Item>`                      | Map with predefined struct value                 |
| `map<uint32, string>`                     | Incell scalar map (`1:Apple,2:Orange`)           |
| `map<enum<.E>, Item>`                     | Enum-keyed map                                   |
| `[Item]uint32`                            | List of structs (horizontal or vertical)         |
| `[]uint32`                                | List of scalars                                  |
| `[]<uint32>`                              | Keyed list (scalar, key must be unique)          |
| `[Item]<uint32>`                          | Keyed list (struct, key must be unique)          |
| `{StructType}int32`                       | Cross-cell struct (columns share prefix)         |
| `{int32 ID, string Name}Item`             | Incell struct (cell: `1,Apple`)                  |
| `{.StructType}`                           | Incell predefined struct                         |
| `{.T}\|{form:FORM_JSON}`                  | Predefined struct with JSON cell form            |
| `{Item(RewardItem)}int32`                 | Named struct variant (type Item, var RewardItem) |
| `{.Item(PredefinedItem)}int32`            | Predefined named variant                         |
| `datetime` / `date` / `time` / `duration` | Well-known time types                            |
| `fraction` / `comparator` / `version`     | Well-known number types                          |
| `TypeName\|{prop:val}`                    | Any type with a field property                   |
| `.TypeName`                               | Reference to external predefined type            |

## Field Properties (`|{...}`)

```
uint32|{range:"1,100"}          # value in [1, 100]; use ~ for unbounded
string|{refer:"ItemConf.Name"}  # foreign-key reference
int32|{refer:"A.ID,B.ID"}      # multi-refer (comma-separated)
int32|{sequence:1}              # values must be 1,2,3...
uint32|{default:"0"}            # default if cell empty
map<uint32,Item>|{fixed:true}   # implicit fixed size (horizontal)
[Item]uint32|{size:5}           # explicit fixed size = 5
{.Item}|{form:FORM_JSON}        # cell contains JSON text
{.Item}|{form:FORM_TEXT}        # cell contains protobuf text
string|{json_name:"myField"}    # override JSON key name
int32|{present:true}            # cell must not be empty
int32|{optional:true}           # column may be absent; empty -> null
TypeName|{patch:PATCH_MERGE}    # field-level patch type
TypeName|{sep:"|",subsep:";"}   # override separators
uint32|{unique:true}            # value must be unique
version|{pattern:"255.255.255"} # version format pattern
```

> `|{optional:true}` means the column may be entirely absent. When set, empty cells produce `null` in JSON (not zero values). Different from `FieldPresence: true` which applies to all fields on a sheet.

## Layout Rules

**Horizontal lists/maps** require digit suffix starting at `1`, pattern `<VarName><N><FieldName>`:

```
| Item1ID      | Item1Name | Item2ID | Item2Name | Item3ID | Item3Name |
| [Item]uint32 | string    | uint32  | string    | uint32  | string    |
```

> ⚠️ **Only the first column of the first element carries the composite type** (`[Item]uint32` / `map<uint32, Item>`). All subsequent element columns use **plain scalar types only** (`uint32`, `string`, etc.) — never repeat the `[Item]` or `map<...>` prefix on element 2, 3, ...

> ⚠️ **Every element must have ALL fields present.** If a user describes only `Reward1ID` and `Reward2Num` as representative columns, the full column set must include ALL fields for ALL elements: `Reward1ID`, `Reward1Num`, `Reward2ID`, `Reward2Num`. Never generate partial columns.

**Column skipping**: columns starting with `#` are ignored (`#InternalNote`).

**Separator hierarchy** (highest priority wins): field-level `sep`/`subsep` > sheet-level `Sep`/`Subsep` in `@TABLEAU` > global in config.yaml > default (`,` / `:`)

## Common Patterns

**Vertical map** (most common):

```
| ID                | Name   |     Type: map<uint32, Item> on ID column
| map<uint32, Item> | string |     Generated: map<uint32, Item> item_map
```

**Nested vertical map** (multi-level map-in-map):

> ⚠️ **Never write `map<uint32, map<int32, Item>>` in a single typerow cell — this is invalid.** Each map level must be declared on its own key column. See `references/types.md` → **Nested Vertical Map** for the full column layout and rules.

**Incell list**: `[]int32` with cell data `1,2,3` -> `repeated int32 param_list`

**Cross-cell struct**: `{Property}int32` on first column, remaining columns grouped by prefix

**Incell struct**: `{int32 ID, string Name}Property` with cell data `1,Apple`

**Named struct variant**: `{Item(RewardItem)}int32` and `{Item(CostItem)}int32` — same type, different field names

**Predefined type**: `.RewardItem` — imported from common.proto

**Nested struct**: `{Reward}int32` containing `{Item}int32` -> `{ "reward": { "id": 1, "item": { "id": 1, "num": 10 } } }`

## Well-Known Types

| Type         | Cell Format               | Proto Backing               |
| ------------ | ------------------------- | --------------------------- |
| `datetime`   | `2023-01-01 12:00:00`     | `google.protobuf.Timestamp` |
| `date`       | `2023-01-01` / `20230101` | `google.protobuf.Timestamp` |
| `time`       | `12:30:00` / `12:30`      | `google.protobuf.Duration`  |
| `duration`   | `72h3m0.5s` (Go format)   | `google.protobuf.Duration`  |
| `fraction`   | `10%`, `3/4`, `0.01`      | `tableau.Fraction`          |
| `comparator` | `>=10%`, `<1/2`           | `tableau.Comparator`        |
| `version`    | `1.0.3`                   | `tableau.Version`           |

- **Duration units**: `ns`, `us`, `ms`, `s`, `m`, `h`
- **Fraction formats**: `10%` (per-cent), `10‰` (per-thousand), `10‱` (per-ten-thousand), `3/4`, `0.01`
- **Comparator signs**: `==`, `!=`, `<`, `<=`, `>`, `>=` combined with fraction
- **Version pattern**: default `255.255.255`; customize with `|{pattern:"99.999.99"}`

## Enum Types

Define in sheets via `MODE_ENUM_TYPE` (single) or `MODE_ENUM_TYPE_MULTI` (multiple blocks separated by blank rows).

> **Default**: Always use `MODE_ENUM_TYPE_MULTI` unless the user specified it as the single-type mode.

See `references/excel/enum.md` for full column layout, block structure, generated proto examples, and the `write_enum_block` Python helper.

## Struct & Union Types

**Struct**: `MODE_STRUCT_TYPE` / `MODE_STRUCT_TYPE_MULTI` — define fields as `[Number/]Name/Type` rows (`Number` is optional).

**Union** (tagged oneof): `MODE_UNION_TYPE` / `MODE_UNION_TYPE_MULTI`

> **Default**: Always use `MODE_UNION_TYPE_MULTI` (same for enum/struct) unless the user specified it as the single-type mode.

See `references/excel/struct.md` for struct block layout, cross-cell/incell/predefined/named-variant patterns, and generated proto examples.

See `references/excel/union.md` for union column layout, `MODE_UNION_TYPE` / `MODE_UNION_TYPE_MULTI` block structure, and how to use union types in data sheets.

## Merge & Scatter

See `references/metasheet.md` → **Merger** and **Scatter** sections for full details and examples.

## Input Formats

All four formats produce identical output. Choose based on your workflow. All formats accepted by default; to restrict, set `proto.input.formats` and `conf.input.formats` in config.yaml (e.g., `formats: [xml]` for XML-only).

| Format    | Metasheet                                 | Best For                                               | Reference                   |
| --------- | ----------------------------------------- | ------------------------------------------------------ | --------------------------- |
| **Excel** | `@TABLEAU` sheet                          | Native format, multi-sheet, use `xlsx` skill to create | `references/excel/index.md` |
| **CSV**   | `BookName#@TABLEAU.csv`                   | Programmatic generation, version control               | `references/csv/index.md`   |
| **XML**   | `<!--<@TABLEAU>...</@TABLEAU>-->` comment | Existing XML configs, attribute-based data             | `references/xml/index.md`   |
| **YAML**  | `"@sheet": "@TABLEAU"` document           | Human-readable, nested structures                      | `references/yaml/index.md`  |

## Empty & Optional Value Handling

| Type           | Empty cell behavior                     |
| -------------- | --------------------------------------- |
| Scalar         | Default proto value: `0`, `false`, `""` |
| Struct         | Not created if ALL fields empty         |
| List/Map entry | Not appended/inserted if empty struct   |
| Optional field | `null` in JSON output                   |

## Nesting

See `references/excel/nesting.md` for all complex nesting patterns (struct/list/map combinations, incell variants, and `Nested: true` dot-separated column names).

### Diagnosing E2016

E2016 fires when a horizontal list has a gap between filled slots. Diagnose intent first:

| Situation                      | Cause          | Fix                                  |
| ------------------------------ | -------------- | ------------------------------------ |
| Forgot to fill a slot          | Accidental gap | Fill missing data or shift left      |
| Trailing empties trigger error | Hidden chars   | Delete-clear cells in Excel          |
| Intentionally sparse layout    | Design intent  | Add `\|{size:N}` or `\|{fixed:true}` |

## Reference Files

### Input Format References (by format)

- `references/excel/index.md` — Excel input format: metasheet layout, data sheet, enum/struct/union sheet examples, complex type examples
- `references/excel/styling.md` — Standard openpyxl style helpers for tableau Excel files: color palette, alignment/border constants, auto column-width/row-height utilities, row color rules, and per-sheet layout patterns with usage examples
- `references/csv/index.md` — CSV input format: workbook/worksheet naming, metasheet, enum/struct/union sheet examples, complex type examples
- `references/xml/index.md` — XML input format: metasheet comment block, attribute vs element patterns, complete examples
- `references/yaml/index.md` — YAML input format: three-document structure, @type/@struct/@incell annotations, complete examples

### General References

- `references/metasheet.md` — All `@TABLEAU` options with examples
- `references/config.md` — Full `config.yaml` reference
- `references/protoconf.md` — Protoconf annotation reference
- `references/types.md` — Deep dive into type syntax

