diff --git a/assets/js/index.js b/assets/js/index.js index b99ab905b..2ba63c644 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -94,7 +94,7 @@ Source: // https://discourse.gohugo.io/t/range-length-or-last-element/3803/2 - {{ $list := (where .Site.Pages "Section" "docs") -}} + {{ $list := (where .Site.AllPages "Section" "docs") -}} {{ $len := (len $list) -}} index.add( diff --git a/config/_default/menus/menus.zh.toml b/config/_default/menus/menus.zh.toml index 2182572a3..d287f8649 100644 --- a/config/_default/menus/menus.zh.toml +++ b/config/_default/menus/menus.zh.toml @@ -4,11 +4,11 @@ identifier = "prologue" url = "/docs/prologue/" -[[docs]] - name = "帮助" - weight = 60 - identifier = "help" - url = "/docs/help/" +# [[docs]] +# name = "帮助" +# weight = 60 +# identifier = "help" +# url = "/docs/help/" [[main]] name = "文档" @@ -16,10 +16,30 @@ # url = "/docs/1.0/prologue/introduction/" weight = 10 +[[main]] + name = "教程" + weight = 20 + identifier = "tutorial" + url = "/docs/prologue/introduction/" + +[[main]] + name = "简介" + weight = 310 + identifier = "introduction" + url = "/docs/prologue/introduction/" + parent = "tutorial" + +[[main]] + name = "快速开始" + weight = 320 + identifier = "quick-start" + url = "/docs/prologue/quick-start/" + parent = "tutorial" + [[main]] name = "博客" url = "/blog/" - weight = 20 + weight = 30 [[social]] name = "GitHub" diff --git a/content/en/docs/help/_index.md b/content/en/docs/help/_index.md index 7fec6ce05..d925da3c8 100644 --- a/content/en/docs/help/_index.md +++ b/content/en/docs/help/_index.md @@ -4,7 +4,7 @@ description: "Help Tableau." lead: "" date: 2020-10-06T08:49:15+08:00 lastmod: 2020-10-06T08:49:15+08:00 -draft: false +draft: true images: [] weight: 1000 --- diff --git a/content/zh/docs/api/_index.md b/content/zh/docs/api/_index.md new file mode 100644 index 000000000..036e2a2d0 --- /dev/null +++ b/content/zh/docs/api/_index.md @@ -0,0 +1,9 @@ +--- +title : "API" +description: "API 指南。" +date: 2022-09-04T08:48:45+08:00 +lastmod: 2022-09-04T08:48:45+08:00 +draft: false +images: [] +weight: 3000 +--- \ No newline at end of file diff --git a/content/zh/docs/api/checker/_index.md b/content/zh/docs/api/checker/_index.md new file mode 100644 index 000000000..c46f6a1c2 --- /dev/null +++ b/content/zh/docs/api/checker/_index.md @@ -0,0 +1,10 @@ +--- +title: "检查器" +description: "检查器指南。" +lead: "" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3300 +--- diff --git a/content/zh/docs/api/checker/guide.md b/content/zh/docs/api/checker/guide.md new file mode 100644 index 000000000..8c243570a --- /dev/null +++ b/content/zh/docs/api/checker/guide.md @@ -0,0 +1,47 @@ +--- +title: "指南" +description: "Go 检查器指南。" +lead: "Go 检查器指南。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 300 +toc: true +--- + +## 生成脚手架代码 + +例如,为 Protobuf 消息 `ItemConf` 生成的 `*.check.go` 文件内容如下: + +```go +type ItemConf struct { + tableau.ItemConf +} + +func (x *ItemConf) Check(hub *tableau.Hub) error { + // TODO: implement here. + return nil +} + +func (x *ItemConf) CheckCompatibility(hub, newHub *tableau.Hub) error { + // TODO: implement here. + return nil +} + +func init() { + // NOTE: This func is auto-generated. DO NOT EDIT. + register(func() tableau.Messager { + return new(ItemConf) + }) +} +``` + +## 插件:protoc-gen-go-tableau-checker + +使用此 protoc 插件的示例: +[checker/test/gen.sh](https://github.com/tableauio/checker/blob/master/test/gen.sh)。 + +## 完整示例 + +请查看 [go-tableau-checker](https://github.com/tableauio/checker/blob/master/test)。 diff --git a/content/zh/docs/api/loader/_index.md b/content/zh/docs/api/loader/_index.md new file mode 100644 index 000000000..538f653d8 --- /dev/null +++ b/content/zh/docs/api/loader/_index.md @@ -0,0 +1,10 @@ +--- +title: "加载器" +description: "加载器指南。" +lead: "" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3200 +--- diff --git a/content/zh/docs/api/loader/cpp.md b/content/zh/docs/api/loader/cpp.md new file mode 100644 index 000000000..08969e6f6 --- /dev/null +++ b/content/zh/docs/api/loader/cpp.md @@ -0,0 +1,109 @@ +--- +title: "C++" +description: "C++ 加载器指南。" +lead: "C++ 加载器指南。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3220 +toc: true +--- + + +## API + +### Data + +`const ProtobufMessage& Data()` + +获取内部的 protobuf 消息数据。 + +### Map + +`const MapValueType* Get(k1 KEY1, k2 KEY2...) const` + +获取第 N 级的映射值。请注意,这仅适用于每个级别消息的**第一个映射字段**。 + +### OrderedMap + +> 前提条件:你需要将元表选项 `OrderedMap` 设置为 `true`。 +> +> 请参阅 [元表选项:OrderedMap](../../../excel/metasheet/#option-orderedmap)。 + +`const OrderedMapValueType* GetOrderedMap(k1 KEY1, k2 KEY2...) const` + +获取第 N 级的有序映射值。请注意,这仅适用于每个级别消息的**第一个映射字段**。 + +### Index + +> 前提条件:你需要适当设置元表选项 `Index`。 +> +> 请参阅 [元表选项:Index](../../../excel/metasheet/#option-index)。 + +如果索引名称是 `Chapter`,则访问器如下: + +- `const Index_ChapterMap& FindChapter() const`:获取整个哈希映射。 +- `const vector* FindChapter(k1 KEY1, k2 KEY2...) const`:通过键查找值。一个键可能对应多个值,这些值通过向量返回。 +- `const ParentType* FindFirstChapter(k1 KEY1, k2 KEY2...) const`:通过键查找第一个值。 + +### OrderedIndex + +> 前提条件:你需要适当设置元表选项 `OrderedIndex`。 +> +> 请参阅 [元表选项:OrderedIndex](../../../excel/metasheet/#option-orderedindex)。 + +如果有序索引名称是 `Chapter`,则访问器如下: + +- `const OrderedIndex_ChapterMap& FindChapter() const`:获取整个有序映射。 +- `const vector* FindChapter(k1 KEY1, k2 KEY2...) const`:通过键查找值。一个键可能对应多个值,这些值通过向量返回。 +- `const ParentType* FindFirstChapter(k1 KEY1, k2 KEY2...) const`:通过键查找第一个值。 + +## 自定义消息器 + +如果内置 API 不足以满足你的业务逻辑,你可以添加一个自定义消息器,在其中可以基于加载的配置对象编写预处理逻辑。 + +示例:[cpp-tableau-loader/hub/custom](https://github.com/tableauio/loader/tree/master/test/cpp-tableau-loader/src/hub/custom) + +**custom_xxx_conf.h**: + +```cpp +#pragma once +#include "protoconf/hub.pc.h" +#include "protoconf/xxx_conf.pc.h" +class CustomXXXConf : public tableau::Messager { + public: + static const std::string& Name() { return kCustomName; }; + virtual bool Load(const std::string& dir, tableau::Format fmt, + const tableau::LoadOptions* options = nullptr) override { + return true; + } + virtual bool ProcessAfterLoadAll(const tableau::Hub& hub) override; + + private: + static const std::string kCustomName; + // TODO: 添加自定义数据字段。 +}; +``` + +**custom_xxx_conf.cpp**: + +```cpp +#include "hub/custom/xxx/custom_xxx_conf.h" + +const std::string CustomXXXConf::kCustomName = "CustomXXXConf"; + +bool CustomItemConf::ProcessAfterLoadAll(const tableau::Hub& hub) { + // TODO: 在此处实现。 + return true; +} +``` + +## 插件:protoc-gen-cpp-tableau-loader + +使用此 protoc 插件的示例: +[cpp-tableau-loader/gen.sh](https://github.com/tableauio/loader/blob/master/test/cpp-tableau-loader/gen.sh)。 + +## 完整示例 + +请查看 [cpp-tableau-loader](https://github.com/tableauio/loader/tree/master/test/cpp-tableau-loader)。 diff --git a/content/zh/docs/api/loader/csharp.md b/content/zh/docs/api/loader/csharp.md new file mode 100644 index 000000000..c5aad346f --- /dev/null +++ b/content/zh/docs/api/loader/csharp.md @@ -0,0 +1,15 @@ +--- +title: "C#" +description: "C# 加载器指南。" +lead: "C# 加载器指南。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3240 +toc: true +--- + +## 概述 + +TODO: 请参考 [Tableau 加载器](https://github.com/tableauio/loader)。 diff --git a/content/zh/docs/api/loader/go.md b/content/zh/docs/api/loader/go.md new file mode 100644 index 000000000..8f46872eb --- /dev/null +++ b/content/zh/docs/api/loader/go.md @@ -0,0 +1,98 @@ +--- +title: "Go" +description: "Go 加载器指南。" +lead: "Go 加载器指南。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3230 +toc: true +--- + +## API + +### Data + +`func Data() *ProtobufMessage` + +获取内部的 protobuf 消息数据。 + +### Map + +`func GetN(k1 KEY1, k2 KEY2...) (*MapValueType, error)` + +获取第 N 级的映射值。请注意,这仅适用于每个级别消息的**第一个映射字段**。 + +### OrderedMap + +> 前提条件:你需要将元表选项 `OrderedMap` 设置为 `true`。 +> +> 请参阅 [元表选项:OrderedMap](../../../excel/metasheet/#option-orderedmap)。 + +`func GetOrderedMapN(k1 KEY1, k2 KEY2...) (*OrderedMapValueType, error)` + +获取第 N 级的有序映射值。请注意,这仅适用于每个级别消息的**第一个映射字段**。 + +### Index + +> 前提条件:你需要适当设置元表选项 `Index`。 +> +> 请参阅 [元表选项:Index](../../../excel/metasheet/#option-index)。 + +如果索引名称是 `Chapter`,则访问器如下: + +- `func FindChapterMap() *Index_ChapterMap`:获取整个哈希映射。 +- `func FindChapter(k1 KEY1, k2 KEY2...) []*ParentType`:通过键查找值。一个键可能对应多个值,这些值通过切片返回。 +- `func FindFirstChapter(k1 KEY1, k2 KEY2...) *ParentType`:通过键查找第一个值。 + +### OrderedIndex + +> 前提条件:你需要适当设置元表选项 `OrderedIndex`。 +> +> 请参阅 [元表选项:OrderedIndex](../../../excel/metasheet/#option-orderedindex)。 + +如果有序索引名称是 `Chapter`,则访问器如下: + +- `func FindChapterMap() *OrderedIndex_ChapterMap`:获取整个有序映射。 +- `func FindChapter(k1 KEY1, k2 KEY2...) []*ParentType`:通过键查找值。一个键可能对应多个值,这些值通过切片返回。 +- `func FindFirstChapter(k1 KEY1, k2 KEY2...) *ParentType`:通过键查找第一个值。 + +## 自定义消息器 + +如果内置 API 不足以满足你的业务逻辑,你可以添加一个自定义消息器,在其中可以基于加载的配置对象编写预处理逻辑。 + +示例:[go-tableau-loader/customconf](https://github.com/tableauio/loader/tree/master/test/go-tableau-loader/customconf) + +**custom_xxx_conf.go**: + +```go +type CustomXXXConf struct { + tableau.UnimplementedMessager + // TODO: 添加自定义数据字段。 +} + +func (x *CustomItemConf) Name() string { + return "CustomXXXConf" +} + +func (x *CustomItemConf) ProcessAfterLoadAll(hub *tableau.Hub) error { + // TODO: 在此处实现。 + return nil +} + +func init() { + tableau.Register(func() tableau.Messager { + return new(CustomXXXConf) + }) +} +``` + +## 插件:protoc-gen-go-tableau-loader + +使用此 protoc 插件的示例: +[go-tableau-loader/gen.sh](https://github.com/tableauio/loader/blob/master/test/go-tableau-loader/gen.sh)。 + +## 完整示例 + +请查看 [go-tableau-loader](https://github.com/tableauio/loader/tree/master/test/go-tableau-loader)。 diff --git a/content/zh/docs/api/loader/lua.md b/content/zh/docs/api/loader/lua.md new file mode 100644 index 000000000..357462504 --- /dev/null +++ b/content/zh/docs/api/loader/lua.md @@ -0,0 +1,15 @@ +--- +title: "Lua" +description: "Lua 加载器指南。" +lead: "Lua 加载器指南。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3260 +toc: true +--- + +## 概述 + +TODO: 请参考 [Tableau 加载器](https://github.com/tableauio/loader)。 diff --git a/content/zh/docs/api/loader/overview.md b/content/zh/docs/api/loader/overview.md new file mode 100644 index 000000000..2a1b0f541 --- /dev/null +++ b/content/zh/docs/api/loader/overview.md @@ -0,0 +1,22 @@ +--- +title: "概述" +description: "Tableau 加载器 API 概述。" +lead: "Tableau 加载器 API 概述。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3210 +toc: true +--- + +## 支持的 API + +| 语言 | 字典 | 有序字典 | 索引 | 有序索引 | +| ------------------- | --- | ---------- | ----- | ------------ | +| [C++](../cpp) | ✔️ | ✔️ | ✔️ | ✔️ | +| [Go](../go) | ✔️ | ✔️ | ✔️ | ✔️ | +| [C#](../csharp) | | | | | +| [TypeScript](../ts) | | | | | +| [Lua](../lua) | | | | | +{.table-striped .table-hover} diff --git a/content/zh/docs/api/loader/ts.md b/content/zh/docs/api/loader/ts.md new file mode 100644 index 000000000..d088ed25e --- /dev/null +++ b/content/zh/docs/api/loader/ts.md @@ -0,0 +1,15 @@ +--- +title: "TypeScript" +description: "TypeScript 加载器指南。" +lead: "TypeScript 加载器指南。" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3250 +toc: true +--- + +## 概述 + +TODO: refer [Tableau loader](https://github.com/tableauio/loader). \ No newline at end of file diff --git a/content/zh/docs/api/tableau/_index.md b/content/zh/docs/api/tableau/_index.md new file mode 100644 index 000000000..c3730a42e --- /dev/null +++ b/content/zh/docs/api/tableau/_index.md @@ -0,0 +1,10 @@ +--- +title: "Tableau" +description: "Tableau 指南。" +lead: "" +date: 2022-03-10T08:00:00+08:00 +lastmod: 2022-03-10T08:00:00+08:00 +draft: false +images: [] +weight: 3100 +--- \ No newline at end of file diff --git a/content/zh/docs/api/tableau/guide.md b/content/zh/docs/api/tableau/guide.md new file mode 100644 index 000000000..853000b2d --- /dev/null +++ b/content/zh/docs/api/tableau/guide.md @@ -0,0 +1,69 @@ +--- +title: "Guide" +description: "本指南通过一个简单的工作示例帮助您开始在 Go 中使用 Tableau。" +lead: "本指南通过一个简单的工作示例帮助您开始在 Go 中使用 Tableau。" +date: 2020-11-16T13:59:39+08:00 +lastmod: 2020-11-16T13:59:39+08:00 +draft: false +images: [] +weight: 4101 +toc: true +--- + +## 前置条件 + +- [Go](https://golang.org/),Go 的 **三个最新主要** [版本](https://golang.org/doc/devel/release.html) 中的任何一个。 + - 有关安装说明,请参阅 Go 的 [入门指南](https://golang.org/doc/install)。 +- [Protocol buffer](https://developers.google.com/protocol-buffers) 编译器 `protoc`,[版本 3](https://developers.google.com/protocol-buffers/docs/proto3)。 + - 有关安装说明,请参阅 [Protocol Buffer 编译器安装](https://grpc.io/docs/protoc-installation/)。 +- **Go 插件** 用于协议编译器: + 1. 使用以下命令安装用于 Go 的协议编译器插件: + + ```bash + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 + ``` + + 2. 更新您的 PATH,以便 protoc 编译器可以找到插件: + + ```bash + export PATH="$PATH:$(go env GOPATH)/bin" + ``` + +## 获取示例代码 + +示例代码是 [tableau/demo](https://github.com/tableauio/demo) 仓库的一部分。 + +1. [将仓库下载为 zip 文件](https://github.com/tableauio/demo/archive/refs/heads/master.zip) 并解压缩,或者克隆仓库: + + ```bash + git clone https://github.com/tableauio/demo + ``` + +2. 切换到快速入门示例目录: + + ```bash + cd demo/examples/helloworld + ``` + +## 运行示例 + +从 `examples/helloworld` 目录: + +1. 切换到 **excel2proto** 目录,编译并执行: + + ```bash + go run main.go + ``` + + 然后 proto 文件将生成到 `examples/helloworld/proto` 目录。 + +2. 切换到 **excel2conf** 目录,生成 `*.pb.go` 文件,然后编译并执行: + + ```bash + bash gen.sh + go run main.go + ``` + + 然后 `*.pb.go` 文件将生成到 `examples/helloworld/protoconf` 目录,JSON 文件将生成到 `examples/helloworld/excel2conf/_out` 目录。 + +恭喜!您刚刚使用 Tableau 运行了一个现代配置转换应用程序。 \ No newline at end of file diff --git a/content/zh/docs/basics/_index.md b/content/zh/docs/basics/_index.md new file mode 100644 index 000000000..152ca4928 --- /dev/null +++ b/content/zh/docs/basics/_index.md @@ -0,0 +1,10 @@ +--- +title : "基础" +description: "Tableau 基础知识。" +lead: "" +date: 2020-10-06T08:48:45+08:00 +lastmod: 2020-10-06T08:48:45+08:00 +draft: false +images: [] +weight: 8000 +--- diff --git a/content/zh/docs/basics/concepts.md b/content/zh/docs/basics/concepts.md new file mode 100644 index 000000000..7f46aab98 --- /dev/null +++ b/content/zh/docs/basics/concepts.md @@ -0,0 +1,136 @@ +--- +title: "概念" +description: "Tableau 核心概念。" +lead: "Tableau 核心概念。" +date: 2022-02-26T13:59:39+08:00 +lastmod: 2022-02-26T13:59:39+08:00 +draft: false +images: [] +weight: 8100 +toc: true +--- + +## 术语 + +### 基础 + +| 术语 | 定义 | +| ------------ | ---------------------------------------------------------------------------------------------------------------------- | +| `Workbook` | 一个 excel 文件。
一组使用相同前缀并用 `#` 分隔的 CSV 文件。
一个 XML 文件。
一个 YAML 文件。 | +| `Worksheet` | excel 文件中的一个工作表。
一个 CSV 文件。
XML 文件的一个根节点。
YAML 文件中的一个文档。 | +| `Metasheet` | 一个名为 `@TABLEAU` 的工作表,用于指定 tableau 解析器选项。 | +| `Row` | 工作表中的一行。 | +| `Column` | 工作表中的一列。 | +| `Cell` | 行和列的交叉点。 | +| `In-cell` | 单元格内部。 | +| `Cross-cell` | 一行或一列的连续单元格。 | +{.table-striped .table-hover} + +### 工作表 + +| 术语 | 定义 | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `Namerow` | 工作表中列名定义的确切行号。
⚠️ 注意:每个列名在工作表中必须唯一!
默认值:`1`。 | +| `Typerow` | 工作表中列类型定义的确切行号。
默认值:`2`。 | +| `Noterow` | 工作表中列注释的确切行号。
默认值:`3`。 | +| `Datarow` | 工作表中数据开始的行号。
默认值:`4`。 | +| `Nameline` | 单元格中列名定义的行号。`0` 表示整个单元格。
默认值:`0`。 | +| `Typeline` | 单元格中列类型定义的行号。`0` 表示整个单元格。
默认值:`0`。 | +| `Sep` | 分隔符,用于:
1. 分隔单元格内列表元素。
2. 分隔单元格内字典项。
默认值:`,`。 | +| `Subsep` | 子分隔符,用于分隔单元格内字典的键值对。
默认值:`:`。 | +| `Nested` | **namerow** 的嵌套命名。
默认值:`false`。 | +| `Layout` | 单元格内、垂直(跨单元格)或水平(跨单元格)。 | +| `Transpose` | 交换给定工作表的行和列。 | +{.table-striped .table-hover} + +## 字典到 Protoconf + +| 术语 | Protoconf | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Workbook` | 一个 protoconf(`.proto`) 文件。 | +| `Worksheet` | protoconf 文件中的一个顶层 [message](https://developers.google.com/protocol-buffers/docs/proto3#simple),除了名为 `@TABLEAU` 的 tableau 元数据表。 | +| `column` | [message](https://developers.google.com/protocol-buffers/docs/proto3#simple) 中的一个字段 | + +## 一个简单的字典示例 + +### 输入:一个 excel 文件 + +一个工作簿(*HelloWorld.xlsx*),包含两个数据工作表(`ItemConf` 和 `ActivityConf`)和一个空的 tableau 元数据表(`@TABLEAU`)。 + +{{< spreadsheet "HelloWorld.xlsx" ItemConf ActivityConf "@TABLEAU" >}} + +{{< sheet colored>}} + +| ID | Name | Type | +| ----------------- | ------------ | ------------ | +| map | string | int32 | +| Item's ID. | Item's name. | Item's type. | +| 1 | item1 | 100 | +| 2 | item2 | 200 | +| 3 | item3 | 300 | + +{{< /sheet >}} + +{{< sheet colored>}} + +| ID | Name | Open | +| --------------------- | ---------------- | ----------------- | +| map | string | bool | +| Activity's ID. | Activity's name. | Activity is open? | +| 1 | activity1 | true | +| 2 | activity2 | false | +| 3 | activity3 | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +### 输出:一个 protoconf 文件 + +一个 protoconf 文件(`hello_world.proto`),包含两个顶层 message(`ItemConf` 和 `ActivityConf`)。 + +{{< details "hello_world.proto" open >}} + +```protobuf +syntax = "proto3"; +package protoconf; +option go_package = "github.com/tableauio/demo/examples/helloworld/protoconf"; + +import "tableau/protobuf/tableau.proto"; + +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + int32 type = 3 [(tableau.field) = {name:"Type"}]; + } +} + +message ActivityConf { + option (tableau.worksheet) = {name:"ActivityConf"}; + + map activity_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Activity { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + bool open = 3 [(tableau.field) = {name:"Open"}]; + } +} +``` + +{{< /details >}} diff --git a/content/zh/docs/basics/enum.md b/content/zh/docs/basics/enum.md new file mode 100644 index 000000000..678c84ddc --- /dev/null +++ b/content/zh/docs/basics/enum.md @@ -0,0 +1,48 @@ +--- +title: "枚举" +description: "枚举基础。" +lead: "本指南演示枚举类型的基础知识。" +date: 2022-02-26T13:59:39+08:00 +lastmod: 2022-02-26T13:59:39+08:00 +draft: false +images: [] +weight: 8300 +toc: true +--- + +## 枚举值 + +tableau 解析器接受三种枚举值形式: + + 1. 枚举值**名称**。 + 2. 枚举值**数字**。 + 3. 枚举值**别名**。它是英语、中文或任何其他语言的另一个名称,可以通过扩展 [google.protobuf.EnumValueOptions](https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto#L669) 来指定 [tableau.evalue](https://github.com/tableauio/tableau/blob/master/proto/tableau/protobuf/tableau.proto#L26)。 + +例如,`common.proto` 中的枚举类型 `FruitType` 定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 2 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 3 [(tableau.evalue).name = "Banana"]; +} +``` + +然后这三种形式的枚举值都可以被接受: + +| 枚举值数字 | 枚举值名称 | 枚举值别名 | +| ----------------- | ------------------ | ---------------- | +| 0 | FRUIT_TYPE_UNKNOWN | Unknown | +| 1 | FRUIT_TYPE_APPLE | Apple | +| 2 | FRUIT_TYPE_ORANGE | Orange | +| 3 | FRUIT_TYPE_BANANA | Banana | +{.table-striped} + +> 注意:枚举类型必须预先定义。 + +前往阅读关于预定义**枚举**类型的详细信息:[预定义类型 →]({{< relref "predefined-types" >}})。 + +## 验证 + +由于枚举类型是预先定义的,因此 tableau 解析器会自动验证枚举值。 diff --git a/content/zh/docs/basics/grammar-and-types.md b/content/zh/docs/basics/grammar-and-types.md new file mode 100644 index 000000000..52565c24e --- /dev/null +++ b/content/zh/docs/basics/grammar-and-types.md @@ -0,0 +1,87 @@ +--- +title: "语法和类型" +description: "语法和类型。" +lead: "本指南讨论 Tableau 的基本语法、变量声明和数据类型。" +date: 2022-02-26T13:59:39+08:00 +lastmod: 2022-02-26T13:59:39+08:00 +draft: false +images: [] +weight: 8200 +toc: true +--- + +## 概述 + +Tableau 借用了 [Protocol Buffers (proto3)](https://developers.google.com/protocol-buffers/docs/proto3) 和 [Golang](https://go.dev/) 的大部分语法和类型。 + +## 标量值 + +> 详细信息请参阅 [Protocol Buffers Proto3 标量值](https://developers.google.com/protocol-buffers/docs/proto3#scalar)。 + +| 类型 | 类型 | 默认值 | +| -------- | ----------------------------------------------------------- | ------------------- | +| 数字 | `int32`, `uint32`
`int64`, `uint64`
`float`, `double` | `0`
`0`
`0.0` | +| 布尔值 | `bool` | `false` | +| 字符串 | `string` | `""` | +| 字节 | `bytes` | `""` | +{.table-striped} + +## 复合类型 + +| 类型 | 描述 | +| -------- | -------------------------------------------------- | +| `struct` | 结构体映射到 protobuf **message**。 | +| `list` | 列表映射到 protobuf **repeated** 字段。 | +| `map` | 字典映射到 protobuf **map** 字段。 | +{.table-striped} + +### struct + +| 特性 | 描述 | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 水平布局 | 每个标量字段位于一个单元格中。 | +| 简单单元格内结构 | 每个字段必须是**标量值**类型。
它是字段的逗号分隔列表。例如:`1,test,3.0`。
如果数据列表的大小与结构体的字段不同,则字段将按顺序填充。未配置的字段将由于其标量值类型而被填充为默认值。 | +{.table-striped} + +### list + +| 特性 | 描述 | +| -------------------- | --------------------------------------------------------------------------------------------- | +| 水平布局 | 这是列表的默认布局。
元素类型可以是**结构体**或**标量**。 | +| 垂直布局 | 列表的元素类型应该是**结构体**。 | +| 简单单元格内列表 | 元素类型必须是**标量**。
它是元素的逗号分隔列表。例如:`1,2,3`。 | +| 可扩展 | 可扩展或动态列表大小。 | +| 忽略空元素 | 智能识别任意位置的空元素。 | +{.table-striped} + +### map + +| 特性 | 描述 | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| 水平布局 | | +| 垂直布局 | 这是字典的默认布局。 | +| 哈希字典 | 实现为无序字典或哈希字典。 | +| 有序字典 | 由 [tableauio/loader](https://github.com/tableauio/loader) 支持。
- C++ | +| 简单单元格内字典 | 键和值都必须是**标量**类型。
它是 `key:value` 对的逗号分隔列表。
例如:`1:10,2:20,3:30`。 | +| 可扩展 | 可扩展或动态字典大小。 | +| 忽略空项 | 智能识别任意位置的空项。 | +{.table-striped} + +## 枚举 + +| 特性 | 描述 | +| ------------------------- | ----------------------------------------------------------------------------------------------------------- | +| 三种形式的枚举值 | 1. 枚举值数字。
2. 枚举值名称。
3. 枚举值别名名称(指定了 EnumValueOptions)。 | +| 验证 | 自动检查枚举值的合法性。 | +{.table-striped} + +## 空值 + +| 类型 | 描述 | +| ------- | --------------------------------------------------------------------------------------------------------------------------------- | +| 标量值 | 空标量值将被替换为标量值类型的默认值。 | +| 结构体 | 如果所有字段都为空,则不会生成空结构体。 | +| 列表 | 如果列表大小为 0,则不会生成空列表。
如果列表的元素(结构体类型)为空,则不会追加空结构体。 | +| 字典 | 如果字典大小为 0,则不会生成空字典。如果字典的值(结构体类型)为空,则不会插入空结构体。 | +| 嵌套 | 递归为空。 | +{.table-striped} diff --git a/content/zh/docs/basics/naming-convention.md b/content/zh/docs/basics/naming-convention.md new file mode 100644 index 000000000..967432bb1 --- /dev/null +++ b/content/zh/docs/basics/naming-convention.md @@ -0,0 +1,36 @@ +--- +title: "命名规范" +description: "命名规范。" +date: 2024-06-22T23:40:00+08:00 +lastmod: 2024-06-22T23:40:00+08:00 +draft: false +images: [] +weight: 8110 +toc: true +--- + +**工作簿**、**工作表**、**列**和**结构体**(message)的所有名称都应使用 `PascalCase`(首字母大写)命名规范。因此,tableau 解析器会将**工作表**名称视为 protoconf message 名称,并自动将 `PascalCase` 转换为 `snake_case`,用于 protobuf [message 字段名称](https://protobuf.dev/programming-guides/style/#message-field-names)和文件名,以符合 [Protocol Buffers 样式指南](https://protobuf.dev/programming-guides/style)。 + +## 枚举 + +枚举类型名称使用 `PascalCase`(首字母大写),值名称使用 CAPITALS_WITH_UNDERSCORES: + +```protobuf +enum FooBar { + FOO_BAR_UNSPECIFIED = 0; + FOO_BAR_FIRST_VALUE = 1; + FOO_BAR_SECOND_VALUE = 2; +} +``` + +请参阅 [Protobuf 样式:枚举](https://protobuf.dev/programming-guides/style/#enums)。 + +## 示例 + +| 名称 | 样式 | 示例 | +| ---------------- | ------------ | --------------- | +| 工作簿 | `PascalCase` | HelloWorld.xlsx | +| 工作表 | `PascalCase` | HelloWorld | +| 结构体 (message) | `PascalCase` | HelloWorld | +| 字段 (column) | `PascalCase` | HelloWorld | +{.table-striped} diff --git a/content/zh/docs/basics/predefined-types.md b/content/zh/docs/basics/predefined-types.md new file mode 100644 index 000000000..7bd67b736 --- /dev/null +++ b/content/zh/docs/basics/predefined-types.md @@ -0,0 +1,142 @@ +--- +title: "预定义类型" +description: "预定义类型。" +lead: "Tableau 支持预定义类型的导入,然后您可以在 Excel/CSV/XML/YAML 中使用它们。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 8500 +toc: true +--- + +## 概述 + +您可以预先在 protoconf 文件(如 `common.proto`)中定义 `enum`、`struct` 或 `union` 类型。然后使用它们来指定工作表的列类型或跨单元格类型。 + +## 用法 + +- 语法:在工作表中使用预定义的 `CustomType` 时,在前面加一个点 `.`(即 `.CustomType`)。 +- 导入:指定 **tableauc** 配置的 `protoFiles` 选项以导入公共 proto 文件,其中定义了预定义的 `enum`、`struct`、`union` 类型。请参阅 [Tableauc 配置](../../tutorial/config/#configyaml)。 + +## 枚举 + +例如,*common.proto* 中的枚举类型 `FruitType` 定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 2 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 3 [(tableau.evalue).name = "Banana"]; +} +``` + +以下是一些演示如何使用预定义枚举类型的示例: + +- Excel/CSV:[使用预定义枚举类型](../../excel/enum/#use-predefined-enum-type)。 +- XML:[使用预定义枚举类型](../../xml/enum/#use-predefined-enum-type) +- YAML:[使用预定义枚举类型](../../yaml/enum/#use-predefined-enum-type) + +## 结构体 + +例如,*common.proto* 中的结构体类型 `Prop` 定义如下: + +```protobuf +message Prop { + int32 id = 1 [(tableau.field).name = "ID"]; + int32 value = 2 [(tableau.field).name = "Value"]; +} +``` + +以下是一些演示如何使用预定义结构体类型的示例: + +- Excel/CSV + - `struct`:[预定义结构体](../../excel/struct/#predefined-struct) + - `list`:[垂直预定义结构体列表](../../excel/list/#vertical-predefined-struct-list) + - `map`:[垂直预定义结构体字典](../../excel/map/#vertical-predefined-struct-map) +- XML + - `struct`:[预定义结构体](../../xml/struct/#predefined-struct) + - `list`:[预定义结构体列表](../../xml/list/#predefined-struct-list) + - `map`:待办 +- YAML + - `struct`:[预定义结构体](../../yaml/struct/#predefined-struct) + - `list`:[预定义结构体列表](../../yaml/list/#predefined-struct-list) + - `map`:待办 + +在 `水平字典` 或 `水平列表` 中,您可以使用预定义结构体定义自定义变量名称。 +请参阅 [自定义命名结构体](../../excel/struct/#custom-named-struct)。 + +## 联合类型 + +例如,*common.proto* 中的结构体类型 `Target` 定义如下: + +```protobuf +// 预定义联合类型。 +message Target { + option (tableau.union) = true; + + Type type = 9999 [(tableau.field) = { name: "Type" }]; + oneof value { + option (tableau.oneof) = { + field: "Field" + }; + Pvp pvp = 1; // 绑定到枚举值 1:TYPE_PVP。 + Pve pve = 2; // 绑定到枚举值 2:TYPE_PVP。 + Story story = 3; // 绑定到枚举值 3:TYPE_STORY。 + Skill skill = 4; // 绑定到枚举值 4:TYPE_SKILL。 + } + + enum Type { + TYPE_NIL = 0; + TYPE_PVP = 1 [(tableau.evalue) = { name: "PVP" }]; + TYPE_PVE = 2 [(tableau.evalue) = { name: "PVE" }]; + TYPE_STORY = 3 [(tableau.evalue) = { name: "Story" }]; + TYPE_SKILL = 4 [(tableau.evalue) = { name: "Skill" }]; + } + message Pvp { + int32 type = 1; // 标量 + int64 damage = 2; // 标量 + repeated protoconf.FruitType types = 3; // 单元格内枚举列表 + } + message Pve { + Mission mission = 1; // 单元格内结构体 + repeated int32 heros = 2; // 单元格内列表 + map dungeons = 3; // 单元格内字典 + + message Mission { + int32 id = 1; + uint32 level = 2; + int64 damage = 3; + } + } + message Story { + protoconf.Item cost = 1; // 单元格内预定义结构体 + map fruits = 2; // 单元格内字典,值为枚举类型 + map flavors = 3; // 单元格内字典,键为枚举类型 + message Flavor { + protoconf.FruitFlavor key = 1 [(tableau.field) = { name: "Key" }]; + int32 value = 2 [(tableau.field) = { name: "Value" }]; + } + } + message Skill { + int32 id = 1; // 标量 + int64 damage = 2; // 标量 + // 无字段标签 3 + } +} +``` + +以下是一些演示如何使用预定义联合类型的示例: + +- Excel/CSV + - `list`:[列表中的预定义联合](../../excel/union/#predefined-union-in-list) + - `map`:[字典中的预定义联合](../../excel/union/#predefined-union-in-map) +- XML + - `union`:[预定义联合](../../xml/union/#predefined-union) + - `list`:[预定义联合列表](../../xml/union/#predefined-union-list) + - `map`:待办 +- YAML + - `union`:[预定义联合](../../yaml/union/#predefined-union) + - `list`:[预定义联合列表](../../yaml/union/#predefined-union-list) + - `map`:待办 diff --git a/content/zh/docs/basics/wellknown-types.md b/content/zh/docs/basics/wellknown-types.md new file mode 100644 index 000000000..01f4ece0f --- /dev/null +++ b/content/zh/docs/basics/wellknown-types.md @@ -0,0 +1,133 @@ +--- +title: "知名类型" +description: "知名类型。" +lead: "知名类型包含在整个 Tableau 生态系统中使用的常见类型。" +date: 2024-09-24T15:59:39+08:00 +lastmod: 2024-09-24T15:59:39+08:00 +draft: false +images: [] +weight: 8400 +toc: true +--- + +## 概述 + +为了方便使用,知名类型是 Tableau 中的内置类型。 +这个概念类似于 [Protocol Buffers 知名类型](https://protobuf.dev/reference/protobuf/google.protobuf/)。 + +您应该包含 Tableau 和 Protocol Buffers 提供的 proto 文件: + +- [tableau/protobuf/wellknown.proto](https://github.com/tableauio/tableau/blob/master/proto/tableau/protobuf/wellknown.proto) +- [google/protobuf/timestamp.proto](https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/timestamp.proto) +- [google/protobuf/duration.proto](https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/duration.proto) + +## 日期时间 + +> 用法请参阅 [Excel 知名类型:日期时间 →]({{< relref "../excel/wellknown-types/#datetime" >}}) + +| 类型 | 默认值 | 描述 | +| ---------- | --------------------- | ------------------------------------------------------------------------------------------------------------ | +| `datetime` | `0000-00-00 00:00:00` | 格式:`yyyy-MM-dd HH:mm:ss` 或 RFC3339。
例如:`2020-01-01 05:10:00`
或 `2020-01-01T05:10:00+08:00`。 | +| `date` | `0000-00-00` | 格式:`yyyy-MM-dd` 或 `yyyyMMdd`。
例如:`2020-01-01` 或 `20200101`。 | +| `time` | `00:00:00` | 格式:`HH:mm:ss` 或 `HHmmss`、`HH:mm` 或 `HHmm`。
例如:`05:10:00` 或 `051000`、`05:10` 或 `0510`。 | +{.table-striped} + +**提示:** + +- `datetime` 和 `date` 基于 [**google.protobuf.Timestamp**](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp),请参阅 [JSON 映射](https://developers.google.com/protocol-buffers/docs/proto3#json)。 +- `time` 基于 [**google.protobuf.Duration**](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration),请参阅 [JSON 映射](https://developers.google.com/protocol-buffers/docs/proto3#json)。 +- [RFC 3339:互联网上的日期和时间:时间戳](https://datatracker.ietf.org/doc/html/rfc3339) + +## 持续时间 + +> 用法请参阅 [Excel 知名类型:持续时间 →]({{< relref "../excel/wellknown-types/#duration" >}}) + +| 类型 | 默认值 | 描述 | +| ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `duration` | `0s` | 格式如:`72h3m0.5s`。
持续时间字符串是一个可能带符号的十进制数字序列,每个数字带有可选的小数和单位后缀,例如 `300ms`、`-1.5h` 或 `2h45m`。
有效的时间单位是 `ns`、`us`(或 `µs`)、`ms`、`s`、`m`、`h`。 | + +**提示:** + +- `duration` 基于 [**google.protobuf.Duration**](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration),请参阅 [JSON 映射](https://developers.google.com/protocol-buffers/docs/proto3#json)。 +- [golang 持续时间字符串形式](https://golang.org/pkg/time/#Duration.String)。 +- [golang ParseDuration](https://pkg.go.dev/time#ParseDuration)。 + +## 分数 + +> 用法请参阅 [Excel 知名类型:分数 →]({{< relref "../excel/wellknown-types/#fraction" >}}) + +分数表示整体的一部分,或者更一般地,表示任何数量的相等部分。有关更多详细信息,请参阅 [wiki:分数](https://en.wikipedia.org/wiki/Fraction)。 + +| 类型 | 默认值 | 描述 | +| ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `fraction` | `0` | 格式:
- `N%`:百分比,例如:`10%`
- `N‰`:千分比,例如:`10‰`
- `N‱`:万分比,例如:`10‱`
- `N/D`:简单分数,例如:`3/4`
- `N`:仅分子,例如:`3` 等同于 `3/1`
- `N`:浮点分子,例如:`0.01` 等同于 `1/100` | + +```protobuf +message Fraction { + int32 num = 1; // 分子 + int32 den = 2; // 分母 +} +``` + +## 比较器 + +> 用法请参阅 [Excel 知名类型:比较器 →]({{< relref "../excel/wellknown-types/#comparator" >}}) + +比较器包含一个 `sign` 和一个分数 `value`。任何数字或分数都可以与之比较。 + +| 类型 | 默认值 | 描述 | +| ------------ | ------- | --------------------------------------------------------------------------------------- | +| `comparator` | `==0` | 格式:``。
例如:`==10`、`!=1/2`、`<10%`、`<=10‰`、`>10%`、`>=10‱` | + +```protobuf +message Comparator { + Sign sign = 1; + Fraction value = 2; + + enum Sign { + SIGN_EQUAL = 0; // == + SIGN_NOT_EQUAL = 1; // != + SIGN_LESS = 2; // < + SIGN_LESS_OR_EQUAL = 3; // <= + SIGN_GREATER = 4; // > + SIGN_GREATER_OR_EQUAL = 5; // >= + } +} +``` + +## 版本 + +> 用法请参阅 [Excel 知名类型:版本 →]({{< relref "../excel/wellknown-types/#version" >}}) + +版本表示 [点十进制表示法](https://en.wikipedia.org/wiki/Dot-decimal_notation) 中的版本号。 +版本形式为:`..[.]...`。 + +版本字段包含三种表示形式以便于使用: + +- 字符串版本:`str` +- 整数版本:`val` +- 整数版本部分:`major`、`minor`、`patch`、`others` + +您可以指定版本的 `pattern`(一个字段属性)为 `..[.]...`。 + +- 每个带有后缀 "MAX" 的部分表示 [点十进制表示法](https://en.wikipedia.org/wiki/Dot-decimal_notation) 中每个部分的最大十进制值。 +- 每个 "XXX_MAX+1" 部分表示该部分的值在整数中占用的位数。 +- 通用模式 `..` 的整数版本公式为:`MAJOR*(MINOR_MAX+1)*(PATCH_MAX+1) + MINOR*(PATCH_MAX+1) + PATCH` + +默认 `pattern` 为:`255.255.255`。 + +| 类型 | 默认值 | 描述 | +| -------------------------------------- | ------- | -------------------------------------------------------------- | +| `version` | `""` | 格式:`..`。
例如:`1.0.1` | +| `version\|{pattern:"255.255.255.255"}` | `""` | 格式:`...`。
例如:`1.0.1.1` | + +```protobuf +message Version { + string str = 1; // 字符串形式的版本。 + uint64 val = 2; // 整数形式的版本。 + uint32 major = 3; // 主版本号。 + uint32 minor = 4; // 次版本号。 + uint32 patch = 5; // 补丁版本号。 + repeated uint32 others = 6; // 其他版本号,如构建号、资源版本等。 +} +``` diff --git a/content/zh/docs/csv/_index.md b/content/zh/docs/csv/_index.md new file mode 100644 index 000000000..0e052aa9f --- /dev/null +++ b/content/zh/docs/csv/_index.md @@ -0,0 +1,9 @@ +--- +title : "CSV" +description: "CSV 指南。" +date: 2022-03-08T08:48:45+08:00 +lastmod: 2022-03-08T08:48:45+08:00 +draft: false +images: [] +weight: 6000 +--- diff --git a/content/zh/docs/csv/overview.md b/content/zh/docs/csv/overview.md new file mode 100644 index 000000000..495535f2b --- /dev/null +++ b/content/zh/docs/csv/overview.md @@ -0,0 +1,27 @@ +--- +title: "概述" +description: "CSV 概述。" +lead: "CSV 工作簿和工作表简介。" +date: 2022-02-26T13:59:39+08:00 +lastmod: 2022-02-26T13:59:39+08:00 +draft: false +images: [] +weight: 6100 +toc: true +--- + +## 概念 + +由于 Tableau 识别模式 `#.csv`,因此 CSV 工作簿(Glob 模式)`#*.csv` 由同一目录中的多个 CSV 工作表(文件)组成。 + +**例如**: + +一个 CSV 工作簿 `HelloWorld#*.csv` 由三个 CSV 工作表组成: + +1. 工作表 `Item`:`HelloWorld#Item.csv` +2. 工作表 `Activity`:`HelloWorld#Activity.csv` +3. 工作表 `@TABLEAU`:`HelloWorld#@TABLEAU.csv` + +## 指南 + +由于 CSV 工作表与 Excel 工作表相同,因此只需阅读 [Excel 指南 →]({{< relref "../Excel" >}}) diff --git a/content/zh/docs/design/_index.md b/content/zh/docs/design/_index.md new file mode 100644 index 000000000..cb4bb9dea --- /dev/null +++ b/content/zh/docs/design/_index.md @@ -0,0 +1,9 @@ +--- +title: "设计" +description: "The Doks Blog." +date: 2020-10-06T08:49:55+08:00 +lastmod: 2020-10-06T08:49:55+08:00 +draft: false +images: [] +weight: 2000 +--- diff --git a/content/zh/docs/design/metadata.md b/content/zh/docs/design/metadata.md new file mode 100644 index 000000000..ed214b04e --- /dev/null +++ b/content/zh/docs/design/metadata.md @@ -0,0 +1,199 @@ +--- +title: "元数据" +description: "一种称为 Protoconf 的 IDL,用于描述配置的结构(元数据),基于 Protobuf。" +lead: "一种称为 Protoconf 的 IDL,用于描述配置的结构(元数据),基于 Protobuf。" +date: 2022-01-09T19:39:57+08:00 +lastmod: 2022-01-09T19:39:57+08:00 +draft: false +images: [] +weight: 3002 +toc: true +mermaid: false +--- + +## 表示法 + +语法使用 [扩展巴克斯-诺尔形式 (EBNF)](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) 指定。 + +## 工作簿 -> Protoconf + +### 基础 + +工作簿:`(AliasTest)DemoTest`,工作表:`(AliasActivity)DemoActivity` + +- protoconf 文件名为 `alias_test.proto`。如果没有 `()`,名称将为 `demo_test.proto` +- 配置消息名称为 `AliasActivity`。如果没有 `()`,名称将为 `DemoActivity` +- 列表:`[ELEM-TYPE]COLUMN-TYPE`,COLUMN-TYPE 是列类型,ELEM-TYPE 是消息名称和列表前缀(不得与 protobuf 关键字冲突)。 +- 字典:`map`,KEY-TYPE 必须是标量值类型,VALUE-TYPE 是消息名称和字典前缀(不得与内置标量值类型冲突)。 +- 导入消息类型:`.TYPE`,例如:`.Item` 表示在同一 protobuf 包中已定义的消息 `Item`,不应重新定义它。 +- 知名类型 + - 时间戳:`google.protobuf.Timestamp` + - 持续时间:`google.protobuf.Duration` + +{{< spreadsheet "Activity.xlsx" Activity "@TABLEAU" >}} + +{{< sheet colored >}} + +| ActivityID | ActivityName | ActivityBeginTime | ActivityDuration | ChapterID | ChapterName | SectionID | SectionName | SectionItem1Id | SectionItem1Num | SectionItem2Id | SectionItem2Num | +| -------------------- | ------------ | ------------------- | ---------------- | ------------------- | ----------- | --------------- | ----------- | -------------- | --------------- | -------------- | --------------- | +| map | string | timestamp | duration | map | string | [Section]uint32 | int32 | [.Item]int32 | int32 | int32 | int32 | +| 1 | activity1 | 2020-01-01 05:00:00 | 72h | 1 | chapter1 | 1 | section1 | 1001 | 1 | 1002 | 2 | +| 1 | activity1 | 2020-01-01 05:00:00 | 72h | 1 | chapter1 | 2 | section2 | 1001 | 1 | 1002 | 2 | +| 1 | activity1 | 2020-01-01 05:00:00 | 72h | 2 | chapter2 | 1 | section1 | 1001 | 1 | 1002 | 2 | +| 2 | activity2 | 2020-01-01 05:00:00 | 72h3m0.5s | 1 | chapter1 | 1 | section1 | 1001 | 1 | 1002 | 2 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +|---|---|---| +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +```protobuf +// common.proto +message Item { + int32 id = 1 [(tableau.field).name = "Id"]; + int32 num= 2 [(tableau.field).name = "Num"]; +} +``` + +#### 不带前缀的输出 + +```protobuf +// demo_test.proto +import "common.proto" + +message DemoActivity{ + map activity_map = 1 [(key) = "ActivityID"]; + message Activity { + uint32 id= 1 [(tableau.field).name = "ActivityID"]; + string name = 2 [(tableau.field).name = "ActivityName"]; + map chapter_map = 3 [(tableau.field).key = "ChapterID"]; + } + message Chapter { + uint32 id= 1 [(tableau.field).name = "ChapterID"]; + string name = 2 [(tableau.field).name = "ChapterName"]; + repeated Section section_list = 3 [(tableau.field).layout = LAYOUT_VERTICAL]; + } + message Section { + uint32 id= 1 [(tableau.field).name = "SectionID"]; + string name = 2 [(tableau.field).name = "SectionName"]; + repeated Item item_list = 3 [(tableau.field).name = "SectionItem"]; + } +} +``` + +#### 带前缀的输出 + +```protobuf +// demo_test.proto +message DemoActivity{ + map activity_map = 1 [(key) = "ActivityID"]; + message Activity { + uint32 activity_id= 1 [(tableau.field).name = "ActivityID"]; + string activity_name = 2 [(tableau.field).name = "ActivityName"]; + map chapter_map = 3 [(tableau.field).key = "ChapterID"]; + } + message Chapter { + uint32 chapter_id= 1 [(tableau.field).name = "ChapterID"]; + string chapter_name = 2 [(tableau.field).name = "ChapterName"]; + repeated Section section_list = 3 [(tableau.field).layout = LAYOUT_VERTICAL]; + } + message Section { + uint32 section_id= 1 [(tableau.field).name = "SectionID"]; + string section_name = 2 [(tableau.field).name = "SectionName"]; + repeated Item section_item_list = 3 [(tableau.field).name = "SectionItem"]; + } +} +``` + +### 单元格内 + +工作簿:`(AliasTest)DemoTest`,工作表:`(Env)Environment` + +{{< sheet colored >}} + +| ID | Name | IncellMessage | IncellList | IncellMap | IncellMessageList | IncellMessageMap | +| ------ | ------ | ------------------------------------- | ---------- | ----------------- | ---------------------------- | -------------------------------------- | +| uint32 | string | {int32 id,string desc,int32 value}Msg | []int32 | map | []{int32 id,string desc}Elem | map | +| 1 | Earth | 1,desc,100 | 1,2,3 | 1:hello,2:world | {1,hello},{2,world} | 1:{1,hello},2:{2,world} | + +{{< /sheet >}} + +#### IncellMessage + +语法:*待办事项:EBNF* +类型:消息类型 +值:逗号分隔的字段值,例如:`1,desc,100` +规则: +| 默认类型 | 值 | +| ------------ | -------------------------- | +| int32 | 可以解析为数字 | +| string | 不能解析为数字 | + +#### IncellList + +语法:`[]Type` +类型:任何标量值类型 +值:逗号分隔的列表项,例如:`1,2,3` + +#### IncellMap + +语法:`map` +类型:任何标量类型 +值:逗号分隔的键值对,键值之间用冒号分隔。例如:`1:hello,2:world` + +#### IncellMessageList + +待办事项... + +#### IncellMessageMap + +待办事项... + +#### 输出 + +```protobuf +// demo_test.proto +message Env { + uint32 ID = 1 [(tableau.field).name = "ID"]; + string name = 2 [(tableau.field).name = "Name"]; + Msg incell_message= 3 [(tableau.field).name = "IncellMessage"]; + repeated int32 incell_list= 4 [(tableau.field).name = "IncellList"]; + map incell_map = 5 [(tableau.field).name = "IncellMap"]; + repeated Elem incell_message_list= 6 [(tableau.field).name = "IncellMessageList"]; + map incell_message_map = 7 [(tableau.field).name = "IncellMessageMap"]; + + // 默认名称:field + + message Msg { + int32 id = 1; + string desc= 2; + int32 value= 3; + } + message Elem { + int32 id = 1; + string desc= 2; + } + message Value { + int32 id = 1; + string desc= 2; + } +} +``` + +- 单元格内消息:逗号分隔的序列:`{TYPE [NAME],TYPE [NAME]}`,NAME 是可选的,如果未指定,将自动生成为 `field + `。 +- 单元格内列表:`[]TYPE`,TYPE 必须是标量值类型。 +- 单元格内字典:`map[KEY]VALUE`,KEY 和 VALUE 必须是标量值类型。 +- 单元格内消息列表:`[]TYPE`,TYPE 必须是消息类型。 +- 单元格内消息字典:`map[KEY]VALUE`,KEY 是标量,VALUE 必须是消息类型。 + +## Protoconf -> 工作簿 + +待办事项... diff --git a/content/zh/docs/design/overview.md b/content/zh/docs/design/overview.md new file mode 100644 index 000000000..e2b4d4db7 --- /dev/null +++ b/content/zh/docs/design/overview.md @@ -0,0 +1,294 @@ +--- +title: "概述" +description: "Tableau 是一款功能强大的配置转换器,由 Protobuf (proto3) 驱动。" +lead: "Tableau 是一款功能强大的配置转换器,由 Protobuf (proto3) 驱动。" +date: 2022-01-09T19:39:57+08:00 +lastmod: 2022-01-09T19:39:57+08:00 +draft: false +images: [] +weight: 3001 +toc: true +mermaid: true +--- + +## 特性 + +- 将 **Excel/CSV/XML/YAML** 转换为 **JSON/Text/Bin**。 +- 使用 **Protobuf** 定义 **Excel/CSV/XML/YAML** 的结构。 +- 使用 **Golang** 开发转换引擎。 +- 支持多种编程语言,得益于 **Protobuf (proto3)**。 + +## 概念 + +- 导入器(Importer): + - 将 **Excel/CSV** 文件导入到内存中的 **Table** 工作表簿。 + - 将 **XML/YAML** 文件导入到内存中的 **Document** 工作表簿。 +- 解析器(Parsers): + - protogen:将 **Excel/CSV/XML/YAML** 文件转换为 **Protoconf** 文件。 + - confgen:将 **Excel/CSV/XML/YAML** 与 **Protoconf** 文件转换为 **JSON/Text/Bin** 文件。 +- 导出器(Exporter): + - protogen:将 [tableau.Workbook](https://github.com/tableauio/tableau/blob/master/proto/tableau/protobuf/workbook.proto) 导出为 proto 文件。 + - confgen:将 protobuf message 导出为 **JSON/Text/Bin** 文件。 +- Protoconf:[Protocol Buffers (proto3)](https://developers.google.com/protocol-buffers/docs/proto3) 的方言,扩展了 [tableau 选项](https://github.com/tableauio/tableau/blob/master/proto/tableau/protobuf/tableau.proto),旨在定义 Excel/CSV/XML/YAML 的结构。 + +## 工作流程 + +```mermaid +flowchart TD + + subgraph Input + I1(Excel) + click I1 href "https://github.com/qax-os/excelize" + + I2(CSV) + click I2 href "https://pkg.go.dev/encoding/csv" + + I3(XML) + click I3 href "https://github.com/antchfx/xmlquery" + + I4(YAML) + click I4 href "https://github.com/go-yaml/yaml" + end + + Input --> I[importer] + click I href "https://github.com/tableauio/tableau/tree/master/internal/importer" + + subgraph Protogen + PGP[protogen.Parser] --> E1[protogen.Exporter] + click PGP href "https://github.com/tableauio/tableau/tree/master/internal/protogen/parser.go" + click E1 href "https://github.com/tableauio/tableau/tree/master/internal/protogen/exporter.go" + end + + I --> Protogen:::orangeclass + + subgraph Confgen + CGP[confgen.Parser] --> CE[confgen.Exporter] + click CGP href "https://github.com/tableauio/tableau/tree/master/internal/confgen/parser.go" + click CE href "https://github.com/tableauio/tableau/tree/master/internal/confgen/mexporter" + end + + I --> Confgen:::orangeclass + + Protogen --> B + B{{Protoconf}}:::greenclass --> | protobuf descriptor | Confgen + + subgraph Output + O1("JSON") + click O1 href "https://developers.google.com/protocol-buffers/docs/proto3#json" + + O2("Text") + click O2 href "https://developers.google.com/protocol-buffers/docs/text-format-spec" + + O3("Bin") + click O3 href "https://developers.google.com/protocol-buffers/docs/encoding" + end + + Confgen --> Output + + classDef orangeclass fill:#f96; + classDef greenclass fill:#40E0D0; + +``` + +## 类型 + +- 标量 +- 消息(结构体) +- 列表 +- 字典(无序) +- 时间戳 +- 持续时间 + +## 待办事项 + +### protoc 插件 + +- [x] Golang +- [x] C++ +- [ ] C#/.NET +- [ ] Python +- [ ] Lua +- [ ] Javascript/Typescript/Node +- [ ] Java + +### 元数据 + +- [ ] metatable:用于描述工作表元数据的 message。 +- [ ] metafield:用于描述标题元数据的 message。 +- [x] captrow:标题行,工作表中标题的确切行号。为了更好的可读性,允许在标题中使用换行符,并在转换时进行修剪。 +- [ ] descrow:描述行,工作表中描述的确切行号。 +- [x] datarow:数据行,数据开始的行号。 + +主要操作系统中的 [换行符](https://www.wikiwand.com/en/Newline)(换行): + +| 操作系统 | 缩写 | 转义序列 | +| ---------------------- | ------ | -------- | +| Unix (linux, OS X) | LF | `\n` | +| Microsoft Windows | CRLF | `\r\n` | +| classic Mac OS/OS X | CR | `\r` | + +> **LF**:换行,**CR**:回车。 +> +> [Mac OS X](https://www.oreilly.com/library/view/mac-os-x/0596004605/ch01s06.html) + +### 生成器 + +- [x] 通过 Excel(header)生成 protoconf:**Excel -> protoconf** +- [ ] 通过 protoconf 生成 Excel(header):**protoconf -> Excel** + +### 转换 + +- [x] Excel -> JSON(默认格式和人类可读) +- [x] Excel -> protowire(小尺寸) +- [x] Excel -> prototext(人类调试) +- [ ] JSON -> Excel +- [ ] protowire -> Excel +- [ ] prototext -> Excel + +### 美化打印 + +- [x] 多行:每个文本元素在新行上 +- [x] 缩进:4 个空格字符 +- [x] JSON 支持 +- [x] prototext 支持 + +### EmitUnpopulated + +- [x] JSON:`EmitUnpopulated` 指定是否发出未填充的字段。 + +### 标量值 + +- [x] 整数:int32、uint32、int64 和 uint64 +- [x] 浮点数:float 和 double +- [x] bool +- [x] string +- [x] bytes +- [x] datetime、date、time、duration + +### 枚举 + +- [x] enum:解析器接受三种枚举值形式: + - 枚举值数字 + - 枚举值名称 + - 枚举值别名名称(指定了 EnumValueOptions) +- [x] enum:验证枚举值。 + +### 复合类型 + +- [x] message:水平(行方向)布局,字段位于单元格中。 +- [x] message:简单单元格内消息,每个字段必须是**标量值**类型。它是字段的逗号分隔列表。例如:`1,test,3.0`。列表的大小不必等于字段的大小,因为字段将按顺序填充。未配置的字段将由于其标量值类型而被填充为默认值。 +- [x] list:水平(行方向)布局,这是列表的默认布局,每个项目可以是**消息**或**标量**。 +- [x] list:垂直(列方向)布局,每个项目应该是**消息**。 +- [x] list:简单单元格内列表,元素必须是**标量**类型。它是元素的逗号分隔列表。例如:`1,2,3`。 +- [x] list:可扩展或动态列表大小。 +- [x] list:智能识别任意位置的空元素。 +- [x] list + - [ ] 单元格内结构体列表:无需支持 + - [x] 跨单元格水平标量/枚举列表 + - [x] 跨单元格水平单元格内结构体列表 + - [ ] 跨单元格垂直标量列表:无需支持,使用这个:`[Item]int32` + - [x] 跨单元格垂直单元格内结构体列表 +- [x] list 大小 + - [x] 动态大小:项目应连续出现,如果插入空项目则报告错误。 + - [x] 固定大小 +- [x] map:水平(行方向)布局。 +- [x] map:垂直(列方向)布局,这是字典的默认布局。 +- [x] map:无序字典或哈希字典。 +- [ ] map:由 [tableauio/loader](https://github.com/tableauio/loader) 支持的有序字典。 + - [x] C++ + - [ ] Golang + - [ ] C# +- [x] map:简单单元格内字典,键和值都必须是**标量**类型。它是 `key:value` 对的逗号分隔列表。例如:`1:10,2:20,3:30`。 +- [x] map:可扩展或动态字典大小。 +- [x] map:智能识别任意位置的空值。 +- [x] map + - [ ] 跨单元格水平标量字典:无需支持,使用这个:`map` + - [ ] 跨单元格垂直标量字典::无需支持,使用这个:`map` +- [x] map 大小 + - [x] 动态大小:项目应连续出现,如果插入空项目则报告错误。 + - [x] 固定大小 +- [x] 嵌套:消息、列表和字典的无限制嵌套。 +- [ ] 嵌套:复合类型的第一个元素可以是复合类型。 + +### 默认值 + +每个标量值类型的默认值与 protobuf 相同。 + +- [x] 整数:`0` +- [x] 浮点数:`0.0` +- [x] bool:`false` +- [x] string:`""` +- [x] bytes:`""` +- [x] 单元格内消息:每个字段的默认值与 protobuf 相同 +- [x] 单元格内列表:元素的默认值与 protobuf 相同 +- [x] 单元格内字典:键和值的默认值与 protobuf 相同 +- [x] message:所有字段都有默认值 + +### 空 + +- [x] 标量:默认值与 protobuf 相同。 +- [x] message:如果所有字段都为空,则不会生成空消息。 +- [x] list:如果列表大小为 0,则不会生成空列表。 +- [x] list:如果列表的元素(消息类型)为空,则不会追加空消息。 +- [x] map:如果字典大小为 0,则不会生成空字典。 +- [x] map:如果字典的值(消息类型)为空,则不会插入空消息。 +- [x] 嵌套:递归为空。 + +### 合并 + +- [ ] 合并多个工作簿 +- [ ] 合并多个工作表 + +### 工作簿元数据 + +工作簿元数据表 **@TABLEAU**: + +- 指定要解析的工作表 +- 为每个工作表指定解析器选项 + +| 工作表 | 别名 | Nameline | Typeline | +| ------ | ---------- | -------- | -------- | +| Sheet1 | ExchangeInfo | 2 | 2 | + +### 日期时间 + +> [关于 RFC 3339 在软件工程中用于日期时间和时区格式的理解](https://medium.com/easyread/understanding-about-rfc-3339-for-datetime-formatting-in-software-engineering-940aa5d5f68a) +> +> `2019-10-12T07:20:50.52Z # 这在 ISO 8601 和 RFC 3339 中是可接受的(带 T)` +> `2019-10-12 07:20:50.52Z # 这仅在 RFC 3339 中可接受(不带 T)` +> +> - "Z" 代表**零时区**或**祖鲁时区** `UTC+0`,在 RFC 3339 中等于 `+08:00`。 +> - **RFC 3339** 遵循 **ISO 8601** 日期时间格式。唯一的区别是 RFC 允许我们用空格替换 "T"。 + +使用 [RFC 3339](https://tools.ietf.org/html/rfc3339),它遵循 [ISO 8601](https://www.wikiwand.com/en/ISO_8601)。 + +- [x] 时间戳:基于 `google.protobuf.Timestamp`,请参阅 [JSON 映射](https://developers.google.com/protocol-buffers/docs/proto3#json) +- [x] 时区:请参阅 [ParseInLocation](https://golang.org/pkg/time/#ParseInLocation) +- [ ] DST:夏令时。*没有计划处理这个无聊的东西*。 +- [x] 日期时间:excel 格式:`yyyy-MM-dd HH:mm:ss`,例如:`2020-01-01 05:10:00` +- [x] 日期:excel 格式:`yyyy-MM-dd` 或 `yyMMdd`,例如:`2020-01-01` 或 `20200101` +- [x] 时间:excel 格式:`HH:mm:ss` 或 ``HHmmss``,例如:`05:10:00` 或 `051000` +- [x] 持续时间:基于 `google.protobuf.Duration`,请参阅 [JSON 映射](https://developers.google.com/protocol-buffers/docs/proto3#json) +- [x] 持续时间:excel 格式:`form "72h3m0.5s"`,请参阅 [golang 持续时间字符串形式](https://golang.org/pkg/time/#Duration.String) + +### 转置 + +- [x] 交换工作表的行和列。 + +### 验证 + +- [x] 唯一性:检查字典键的唯一性。 +- [x] 范围:`[left,right]`。 +- [ ] 引用:`XXXConf.ID`。将由 [tableauio/loader](https://github.com/tableauio/loader) 支持。 + +### 错误消息 + +- [ ] 当转换器失败时,报告清晰而精确的错误消息,请参阅编程语言编译器 +- [ ] 使用 golang 模板定义错误消息模板 +- [ ] 多语言支持,专注于英语和简体中文 + +### 性能 + +- [ ] 压力测试 +- [ ] 每个 goroutine 处理一个工作表 +- [ ] 多进程模型 diff --git a/content/zh/docs/excel/_index.md b/content/zh/docs/excel/_index.md new file mode 100644 index 000000000..78b5c535e --- /dev/null +++ b/content/zh/docs/excel/_index.md @@ -0,0 +1,9 @@ +--- +title : "Excel" +description: "Excel 指南。" +date: 2022-03-08T08:48:45+08:00 +lastmod: 2022-03-08T08:48:45+08:00 +draft: false +images: [] +weight: 7000 +--- diff --git a/content/zh/docs/excel/enum.md b/content/zh/docs/excel/enum.md new file mode 100644 index 000000000..c53d0e053 --- /dev/null +++ b/content/zh/docs/excel/enum.md @@ -0,0 +1,388 @@ +--- +title: "枚举" +description: "Excel 枚举指南。" +lead: "本指南演示 Excel 枚举类型的不同特性。" +date: 2026-01-09T14:00:00+08:00 +lastmod: 2026-01-09T14:00:00+08:00 +draft: false +images: [] +weight: 7102 +toc: true +--- + +## 使用预定义枚举类型 + +基本的枚举指南,请前往阅读 [枚举 →]({{< relref "enum" >}}) + +例如,`common.proto` 中的枚举类型 `FruitType` 定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 2 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Type | +| ----------------- | ----------------- | +| map | enum<.FruitType> | +| Item's ID | Fruit's type | +| 1 | 1 | +| 2 | Orange | +| 3 | FRUIT_TYPE_BANANA | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + FruitType type = 2 [(tableau.field) = {name:"Type"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "type": "FRUIT_TYPE_APPLE" + }, + "2": { + "id": 3, + "type": "FRUIT_TYPE_ORANGE" + } + "3": { + "id": 2, + "type": "FRUIT_TYPE_BANANA" + }, + } +} +``` + +{{< /details >}} + +## 在工作表中定义枚举类型 + +在元数据表 `@TABLEAU` 中有两种 `Mode` 可以在工作表中定义枚举类型: + +- `MODE_ENUM_TYPE`:在工作表中定义单个枚举类型。 +- `MODE_ENUM_TYPE_MULTI`:在工作表中定义多个枚举类型。 + +### 在工作表中定义单个枚举类型 + +您应该在元数据表 `@TABLEAU` 中将 `Mode` 选项指定为 `MODE_ENUM_TYPE`。 + +例如,*HelloWorld.xlsx* 中的工作表 `ItemType`: + +{{< spreadsheet "HelloWorld.xlsx" ItemType "@TABLEAU" >}} + +{{< sheet >}} + +| Name | Alias | +| --------------- | ----- | +| ITEM_TYPE_FRUIT | Fruit | +| ITEM_TYPE_EQUIP | Equip | +| ITEM_TYPE_BOX | Box | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| -------- | -------------- | +| ItemType | MODE_ENUM_TYPE | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx"}; + +// 从工作表生成:ItemType。 +enum ItemType { + ITEM_TYPE_INVALID = 0; + ITEM_TYPE_FRUIT = 1 [(tableau.evalue).name = "Fruit"]; + ITEM_TYPE_EQUIP = 2 [(tableau.evalue).name = "Equip"]; + ITEM_TYPE_BOX = 3 [(tableau.evalue).name = "Box"]; +} +``` + +{{< /details >}} + +### 在工作表中定义多个枚举类型 + +> 一个块定义一个枚举类型,它是一系列连续的非空行。 +> 因此不同的块由一个或多个空行分隔。 + +您应该在元数据表 `@TABLEAU` 中将 `Mode` 选项指定为 `MODE_ENUM_TYPE_MULTI`。 + +例如,*HelloWorld.xlsx* 中的工作表 `ItemType`: + +{{< spreadsheet "HelloWorld.xlsx" ItemType "@TABLEAU" >}} + +{{< sheet >}} + +| CatType | CatType note | | +| -------------------- | ------------------------- | ---------- | +| Number | Name | Alias | +| 1 | CAT_TYPE_RAGDOLL | Ragdoll | +| 2 | CAT_TYPE_PERSIAN | Persian | +| 3 | CAT_TYPE_SPHYNX | Sphynx | +| | | | +| DogType | DogType note | | +| Number | Name | Alias | +| 1 | DOG_TYPE_POODLE | Poodle | +| 2 | DOG_TYPE_BULLDOG | Bulldog | +| 3 | DOG_TYPE_DACHSHUND | Dachshund | +| | | | +| BirdType | BirdType note | | +| Number | Name | Alias | +| 1 | CANARY | Canary | +| 2 | WOODPECKER | Woodpecker | +| 3 | OWL | Owl | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| -------- | -------------------- | +| ItemType | MODE_ENUM_TYPE_MULTI | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx"}; + +// CatType note +enum CatType { + option (tableau.etype) = {name:"EnumType" note:"CatType note"}; + + CAT_TYPE_INVALID = 0; + CAT_TYPE_RAGDOLL = 1 [(tableau.evalue).name = "Ragdoll"]; // Ragdoll + CAT_TYPE_PERSIAN = 2 [(tableau.evalue).name = "Persian"]; // Persian + CAT_TYPE_SPHYNX = 3 [(tableau.evalue).name = "Sphynx"]; // Sphynx +} + +// DogType note +enum DogType { + option (tableau.etype) = {name:"EnumType" note:"DogType note"}; + + DOG_TYPE_INVALID = 0; + DOG_TYPE_POODLE = 1 [(tableau.evalue).name = "Poodle"]; // Poodle + DOG_TYPE_BULLDOG = 2 [(tableau.evalue).name = "Bulldog"]; // Bulldog + DOG_TYPE_DACHSHUND = 3 [(tableau.evalue).name = "Dachshund"]; // Dachshund +} + +// BirdType note +enum BirdType { + option (tableau.etype) = {name:"EnumType" note:"BirdType note"}; + + BIRD_TYPE_INVALID = 0; + BIRD_TYPE_CANARY = 1 [(tableau.evalue).name = "Canary"]; // Canary + BIRD_TYPE_WOODPECKER = 2 [(tableau.evalue).name = "Woodpecker"]; // Woodpecker + BIRD_TYPE_OWL = 3 [(tableau.evalue).name = "Owl"]; // Owl +} +``` + +{{< /details >}} + +### 指定 Number 列 + +在 `Number` 列中,您可以指定自定义的唯一枚举值数字。 + +{{< alert icon="ⓘ" context="info" text="如果您未指定默认枚举值 \"0\",它将自动生成。默认枚举值名称模式为:\"{ENUM_TYPE}_INVALID\"。" />}} + +例如,*HelloWorld.xlsx* 中的工作表 `ItemType`: + +{{< spreadsheet "HelloWorld.xlsx" ItemType "@TABLEAU" >}} + +{{< sheet >}} + +| Number | Name | Alias | +| ------ | ----------------- | ------- | +| 0 | ITEM_TYPE_UNKNOWN | Unknown | +| 10 | ITEM_TYPE_FRUIT | Fruit | +| 20 | ITEM_TYPE_EQUIP | Equip | +| 30 | ITEM_TYPE_BOX | Box | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| -------- | -------------- | +| ItemType | MODE_ENUM_TYPE | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx"}; + +// 从工作表生成:ItemType。 +enum ItemType { + ITEM_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + ITEM_TYPE_FRUIT = 10 [(tableau.evalue).name = "Fruit"]; + ITEM_TYPE_EQUIP = 20 [(tableau.evalue).name = "Equip"]; + ITEM_TYPE_BOX = 30 [(tableau.evalue).name = "Box"]; +} +``` + +{{< /details >}} + +## 在工作表中定义和使用枚举类型 + +例如,*HelloWorld.xlsx* 中的两个工作表 `ItemType` 和 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemType ItemConf "@TABLEAU" >}} + +{{< sheet >}} + +| Number | Name | Alias | +| ------ | --------------- | ----- | +| 1 | ITEM_TYPE_FRUIT | Fruit | +| 2 | ITEM_TYPE_EQUIP | Equip | +| 3 | ITEM_TYPE_BOX | Box | + +{{< /sheet >}} + +{{< sheet colored >}} + +| ID | Type | Name | Price | +| ---------------- | --------------- | ----------- | ------------ | +| map | enum<.ItemType> | string | int32 | +| Item's ID | Item's type | Item's name | Item's price | +| 1 | Fruit | Apple | 40 | +| 2 | Fruit | Orange | 20 | +| 3 | Equip | Sword | 10 | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| -------- | -------------- | +| ItemType | MODE_ENUM_TYPE | +| ItemConf | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +// 从工作表生成:ItemType。 +enum ItemType { + ITEM_TYPE_INVALID = 0; + ITEM_TYPE_FRUIT = 1 [(tableau.evalue).name = "Fruit"]; + ITEM_TYPE_EQUIP = 2 [(tableau.evalue).name = "Equip"]; + ITEM_TYPE_BOX = 3 [(tableau.evalue).name = "Box"]; +} + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.ItemType type = 2 [(tableau.field) = {name:"Type"}]; + string name = 3 [(tableau.field) = {name:"Name"}]; + int32 price = 4 [(tableau.field) = {name:"Price"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "type": "ITEM_TYPE_FRUIT", + "name": "Apple", + "price": 40 + }, + "2": { + "id": 2, + "type": "ITEM_TYPE_FRUIT", + "name": "Orange", + "price": 20 + }, + "3": { + "id": 3, + "type": "ITEM_TYPE_EQUIP", + "name": "Sword", + "price": 10 + } + } +} +``` + +{{< /details >}} diff --git a/content/zh/docs/excel/field-property.md b/content/zh/docs/excel/field-property.md new file mode 100644 index 000000000..439963e56 --- /dev/null +++ b/content/zh/docs/excel/field-property.md @@ -0,0 +1,214 @@ +--- +title: "字段属性" +description: "Tableau 字段属性指南。" +lead: "Tableau 字段属性指南。" +date: 2026-01-09T15:21:01+08:00 +lastmod: 2026-01-09T23:21:01+08:00 +draft: false +images: [] +weight: 7901 +toc: true +--- + +## 概述 + +| 选项 | 类型 | 描述 | +| ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `unique` | bool | 检查字段唯一性。
默认值:`false`。特别对于字典(或 KeyedList)键,默认将自动推导。 | +| `range` | string | 格式:`"left,right"`。例如:`"1,10"`、`"1,~"`、`~,10"`。
范围的不同解释:
- 数字:值范围。
- 字符串:utf-8 码点数。 | +| `refer` | string | 格式:`"SheetName(SheetAlias).ColumnName"`。
确保此字段在另一个工作表的列值空间中。多个引用用逗号分隔。 | +| `sequence` | int64 | 确保此字段的值是一个序列并以此值开始。 | +| `default` | string | 如果单元格为空,则使用此默认值。 | +| `fixed` | bool | 自动检测水平列表/字典的固定大小。
默认值:`false`。 | +| `size` | uint32 | 指定水平列表/字典的固定大小。 | +| `form` | Form | 指定单元格内结构体的单元格数据形式。
- `FORM_TEXT`
- `FORM_JSON` | +| `json_name` | string | 指定字段的自定义 JSON 名称,而不是 proto 字段名的 lowerCamelCase 名称。 | +| `present` | bool | 如果 present 为 true,则必须显式填充单元格数据。
默认值:`false`。 | +| `optional` | bool | 此字段是否为可选的(字段名称存在性)。 | +| `patch` | Patch | 字段修补类型。
- `PATCH_REPLACE`
- `PATCH_MERGE` | +| `sep` | string | 字段级别分隔符。 | +| `subsep` | string | 字段级别子分隔符。 | +| `cross` | int32 | 指定具有基数的复合类型(如列表和字典)的跨节点/单元格/字段的数量。 | +| `pattern` | string | 指定标量、列表元素和字典值的模式。 | +{.table-striped .table-hover} + +## 选项 `unique` + +选项 `unique` 可以在字段属性中指定为 `true` 或 `false`。它可以检查列表/字典元素中任何标量字段的唯一性。 + +- 如果您将 `unique` 显式设置为 `true`,则如果出现重复键,tableau 将报告错误。 +- 如果您将 `unique` 显式设置为 `false`,则不会执行检查。 + +### 字典(或 KeyedList)键 + +Tableau 将自动推导字典(或 KeyedList)键的 `unique` 为 true 或 false。 + +**规则是**:如果字典的值类型(或 KeyedList 元素类型)没有相同布局(垂直/水平)的子字典/列表字段,则键必须是唯一的。 + +因此,在大多数情况下,没有必要显式配置它。 + +### 一般标量字段 + +如果您将一般标量字段的属性 `unique` 指定为 true,则 tableau 将检查字典或列表中字段的唯一性。 + +## 选项 `range` + +{{< alert icon="⚠️️" context="warning" text="如果单元格数据为空(不存在),则不会应用此检查选项。因此,如果您仍然想检查即使单元格数据为空,则应该将选项 `present` 设置为 true。" />}} + +选项 `range` 可以指定为格式:`"left,right"`(左右都包含在内)。 + +`range` 的不同解释: + +- [x] **数字**:值范围,例如:`"1,10"`、`"1,~"`、`~,10"`。 +- [ ] **字符串**:utf-8 码点数。 +- [ ] **列表**:列表长度。 +- [ ] **字典**:字典长度。 + +## 选项 `refer` + +选项 `refer` 类似于 SQL 中的**外键**约束,以防止破坏表之间链接的操作。但是,tableau `refer` 可以引用任何工作表的列,即使它不是字典键列,并且**多个引用**(逗号分隔)也受支持。它用于确保此字段至少在其他工作表的列值空间(即消息的字段值空间)中。 + +格式:`"SheetName(SheetAlias).ColumnName[,SheetName(SheetAlias).ColumnName]..."`。 + +例如: + +- `map|{refer:"ItemConf.ID"}`:不带别名的单引用,因此**工作表名称**只是生成的 protobuf 消息名称。 +- `map|{refer:"ItemConf.ID,EquipConf.ID"}`:不带别名的多引用,因此**工作表别名**是生成的 protobuf 消息名称。 +- `map|{refer:"Sheet1(ItemConf).ID"}`:带别名的单引用,因此**工作表别名**是生成的 protobuf 消息名称。 + +## 选项 `sequence` + +选项 `sequence` 用于确保此字段的值是一个序列并以此值开始。 + +它甚至可以用于嵌套列表/字典中的任何字段。 + +例如: + +- `map|{sequence:1}`:此字典键必须遵循以值 `1` 开始的序列规则。 +- `int32|{sequence:1}`:父列表/字典元素必须遵循以值 `1` 开始的序列规则。 + +## 选项 `default` + +如果设置了选项 `default`,则在单元格为空时使用它作为默认值。 + +## 选项 `fixed` + +如果将选项 `fixed` 设置为 `true`,则自动检测水平列表/字典的固定大小。 + +例如: + +- [列表:隐式固定大小 →]({{< relref "list/#implicit-fixed-size" >}}) +- [字典:隐式固定大小 →]({{< relref "map/#implicit-fixed-size" >}}) + +## 选项 `size` + +选项 `size` 用于指定水平列表/字典的固定大小。 + +例如: + +- [列表:显式固定大小 →]({{< relref "list/#explicit-fixed-size" >}}) +- [字典:显式固定大小 →]({{< relref "map/#explicit-fixed-size" >}}) + +## 选项 `form` + +选项 `form` 用于指定单元格内结构体的单元格数据形式。 + +可以指定两种形式: + +- `FORM_TEXT`:protobuf [文本格式](https://developers.google.com/protocol-buffers/docs/text-format-spec)。 +- `FORM_JSON`:protobuf [JSON 格式](https://developers.google.com/protocol-buffers/docs/proto3#json)。 + +有关详细演示,请参阅 [高级预定义单元格内结构体 →]({{< relref "struct/#advanced-predefined-incell-struct" >}})。 + +## 选项 `json_name` +默认情况下,JSON 名称通过将字段的 proto 名称转换为 camelCase 来推导。现在您可以 +通过 `json_name` 属性选项显式指定它。 + +例如,`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Rarity_1 | SpecialEffect_2 | +| ---------------- | ----------------------------- | ------------------------------------ | +| map | int32\|{json_name:"rarity_1"} | int32\|{json_name:"specialEffect_2"} | +| Item's ID | Item's rarity. | Item's special effect. | +| 1 | 10 | 101 | +| 2 | 20 | 102 | +| 3 | 30 | 103 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +## 选项 `present` + +如果选项 `present` 设置为 `true`,则单元格数据不能为空并且必须显式填充。 +否则将报告错误。 + +## 选项 `optional` + +指定此字段是否为可选的(字段名称存在性)。 + +如果设置为 true,则: +- 表格式(Excel/CSV):字段的列可以不存在。 +- 文档格式(XML/YAML):字段的名称可以不存在。 + +## 选项 `patch` + +请参阅字段级别修补 [选项修补 →]({{< relref "metasheet/#option-patch" >}})。 + +## 选项 `sep` + +**字段级别**分隔符,用于分隔: +- 单元格内列表元素(标量或结构体)。 +- 单元格内字典项。 + +如果未设置,它将使用 [元数据表](../metasheet/#option-sep) 中的**工作表级别** seq。 + +## 选项 `subsep` + +**字段级别**子分隔符,用于分隔: +- 每个单元格内字典项的键值对。 +- 每个单元格内结构体列表元素的结构体字段。 + +如果未设置,它将使用 [元数据表](../metasheet/#option-subsep) 中的**工作表级别** subseq。 + +## 选项 `cross` + +指定具有基数的复合类型(如列表和字典)的跨节点/单元格/字段的数量。 + +### 联合列表字段 + +> 待办事项:示例说明。 + +指定联合列表字段将交叉并占用 +(每个列表元素一个字段)。它还将更改此列表 +字段的布局从单元格内到水平。 +- 值 0 表示它是一个单元格内列表。 +- 值 > 0 表示它是一个水平列表,占用 N 个字段。 +- 值 < 0 表示它是一个水平列表,占用所有后续字段。 + +## 选项 `pattern` + +指定标量字段、列表元素和字典值的模式。 + +### 知名版本字段 + +> 用法请参阅 [Excel 知名类型:版本 →]({{< relref "../excel/wellknown-types/#version" >}}) + +指定当前单元格的点十进制模式。每个十进制 +数字范围从 0 到模式的对应部分(MAX)。 + +默认模式:`255.255.255`。 diff --git a/content/zh/docs/excel/infinite-nesting.md b/content/zh/docs/excel/infinite-nesting.md new file mode 100644 index 000000000..aa4631311 --- /dev/null +++ b/content/zh/docs/excel/infinite-nesting.md @@ -0,0 +1,157 @@ +--- +title: "无限嵌套" +description: "Excel 无限嵌套指南。" +lead: "Excel 复合类型的无限嵌套特性。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7500 +toc: true +--- + +## 概述 + +现在,水平/垂直列表元素的第一个字段可以是任何类型,甚至是结构体、列表和字典。 + +- 列表元素的第一个字段是结构体:`[Reward]{Icon}int32` +- 列表元素的第一个字段是预定义结构体:`[Cost]{.Item}uint32` +- 列表元素的第一个字段是单元格内结构体:`[Magic]{int32 Id, int32 Num}Ability` +- 列表元素的第一个字段是列表:`[Reward][Item]uint32` +- 列表元素的第一个字段是元素为预定义结构体的列表:`[Power][.Item]uint32` +- 列表元素的第一个字段是字典:`[Superpower]map` + +> TODO: 一些清晰的示例。 + +## 嵌套命名 + +"common.proto" 中的预定义类型: + +```protobuf +// --snip-- +enum ConfType { + CONF_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + CONF_TYPE_CLOUD = 1 [(tableau.evalue).name = "Cloud"]; + CONF_TYPE_LOCAL = 2 [(tableau.evalue).name = "Local"]; + CONF_TYPE_REMOTE = 3 [(tableau.evalue).name = "Remote"]; +} + +enum ServerType { + SERVER_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + SERVER_TYPE_GAME = 1 [(tableau.evalue).name = "GameServer"]; + SERVER_TYPE_ACTIVITY = 2 [(tableau.evalue).name = "ActivityServer"]; + SERVER_TYPE_MATCH = 3 [(tableau.evalue).name = "MatchServer"]; +} +``` + +{{< spreadsheet "HelloWorld.xlsx" LoaderConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ServerType | ServerConfType | ServerConfConditionType | ServerConfConditionValue | +|--------------------------------|-------------------------|-------------------------|--------------------------| +| map, Server> | [Conf]> | [Condition] | int32 | +| 服务器名称 | 表格名称 | 条件类型 | 条件值 | +| | | | | +| SERVER_TYPE_GAME | CONF_TYPE_CLOUD | 0 | 113 | +| | | 0 | 134 | +| SERVER_TYPE_ACTIVITY | CONF_TYPE_CLOUD | | | +| | 1 | | | +| | CONF_TYPE_LOCAL | 9 | 34 | +| | CONF_TYPE_LOCAL | 9 | 12 | +| | CONF_TYPE_LOCAL | | | +| | Remote | | | +| MatchServer | CONF_TYPE_UNKNOWN | | | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Nested | +|------------|--------| +| LoaderConf | true | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message LoaderConf { + option (tableau.worksheet) = {name:"LoaderConf" nested:true}; + + map server_map = 1 [(tableau.field) = {name:"Server" key:"Type" layout:LAYOUT_VERTICAL}]; + message Server { + ServerType type = 1 [(tableau.field) = {name:"Type"}]; + repeated Conf conf_list = 2 [(tableau.field) = {name:"Conf" key:"Type" layout:LAYOUT_VERTICAL}]; + message Conf { + ConfType type = 1 [(tableau.field) = {name:"Type"}]; + repeated Condition condition_list = 2 [(tableau.field) = {name:"Condition" key:"Type" layout:LAYOUT_VERTICAL}]; + message Condition { + int32 type = 1 [(tableau.field) = {name:"Type"}]; + int32 value = 2 [(tableau.field) = {name:"Value"}]; + } + } + } +} +``` + +{{< /details >}} + +{{< details "loader_conf.json" >}} + +```json +{ + "serverMap": { + "1": { + "type": "SERVER_TYPE_GAME", + "confList": [ + { + "type": "CONF_TYPE_CLOUD", + "conditionList": [ + { + "type": 0, + "value": 113 + } + ] + } + ] + }, + "2": { + "type": "SERVER_TYPE_ACTIVITY", + "confList": [ + { + "type": "CONF_TYPE_CLOUD", + "conditionList": [] + }, + { + "type": "CONF_TYPE_LOCAL", + "conditionList": [ + { + "type": 9, + "value": 34 + } + ] + }, + { + "type": "CONF_TYPE_REMOTE", + "conditionList": [] + } + ] + }, + "3": { + "type": "SERVER_TYPE_MATCH", + "confList": [] + } + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/keyedlist.md b/content/zh/docs/excel/keyedlist.md new file mode 100644 index 000000000..b1ad2f0bc --- /dev/null +++ b/content/zh/docs/excel/keyedlist.md @@ -0,0 +1,386 @@ +--- +title: "键控列表" +description: "Excel 键控列表指南。" +lead: "本指南演示了 Excel 键控列表类型的不同特性。" +date: 2022-02-26T13:59:39+08:00 +lastmod: 2025-04-12T13:59:39+08:00 +draft: false +images: [] +weight: 7107 +toc: true +--- + +## 语法 + +键控列表与普通列表相同,只是 `ColumnType`(第一个字段类型)被尖括号 `<>` 包围,并被视为字典键。 + +**语法**: `[ElemType]` + +## 水平列表 + +> TODO... + +## 垂直键控列表 + +### 垂直标量键控列表 + +> 它的定义与[单元格内标量键控列表](#incell-scalar-keyedlist)相同,但如果提供多个行,则会聚合。 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | +| ------------ | +| []\ | +| ID | +| 1,2,3 | +| 4,5 | +| 6 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated uint32 id_list = 1 [(tableau.field) = {name:"ID" key:"ID" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "idList": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] +} +``` + +{{< /details >}} + +### 垂直枚举键控列表 + +> 它的定义与[单元格内枚举键控列表](#incell-enum-keyedlist)相同,但如果提供多个行,则会聚合。 + +*common.proto* 中预定义的 `FruitType`: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Type | +| ------------------------ | +| []\\> | +| Type | +| Apple,Orange | +| FRUIT_TYPE_BANANA | +| 0 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType type_list = 1 [(tableau.field) = {name:"Type" key:"Type" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "typeList": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA", + "FRUIT_TYPE_UNKNOWN" + ] +} +``` + +{{< /details >}} + +### 垂直结构体键控列表 + +例如,*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | PropID | PropName | +| ---------------- | ---------------- | ----------- | +| [Item]\ | map | string | +| Item 的 ID | Prop 的 ID | Prop 的名称 | +| 1 | 1 | sweet | +| 2 | 1 | sweet | +| 2 | 2 | delicious | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + map prop_map = 2 [(tableau.field) = {key:"PropID" layout:LAYOUT_VERTICAL}]; + message Prop { + int32 prop_id = 1 [(tableau.field) = {name:"PropID"}]; + string prop_name = 2 [(tableau.field) = {name:"PropName"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "propMap": { + "1": { + "propId": 1, + "propName": "sweet" + } + } + }, + { + "id": 2, + "propMap": { + "1": { + "propId": 1, + "propName": "sweet" + }, + "2": { + "propId": 2, + "propName": "delicious" + } + } + } + ] +} +``` + +{{< /details >}} + +## 单元格内键控列表 + +### 单元格内标量键控列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | +| ------------ | +| []\ | +| ID 列表 | +| 1,2,3 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"; + + repeated uint32 id_list = 1 [(tableau.field) = {name:"ID" key:"ID" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "idList": [ + 1, + 2, + 3 + ] +} +``` + +{{< /details >}} + +### 单元格内枚举键控列表 + +*common.proto* 中预定义的 `FruitType`: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Param | +| -------------------------- | +| []enum<.FruitType> | +| Param 列表 | +| 1,FRUIT_TYPE_ORANGE,Banana | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +`Param` 列的类型是单元格内列表 `[]enum<.FruitType>`,因为列表元素是预定义的枚举类型 `FruitType`。 + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType type_list = 1 [(tableau.field) = {name:"Type" key:"Type" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "typeList": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/list-in-list.md b/content/zh/docs/excel/list-in-list.md new file mode 100644 index 000000000..5a3735043 --- /dev/null +++ b/content/zh/docs/excel/list-in-list.md @@ -0,0 +1,716 @@ +--- +title: "列表中的列表" +description: "Excel 列表中列表指南。" +lead: "Excel 列表中列表的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7301 +toc: true +--- + +## 垂直列表中的嵌套 + +### 垂直列表中的水平列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | Prop1ID | Prop1Value | Prop2ID | Prop2Value | +| ------------ | ----------- | ----------- | ------------- | ---------- | ------------- | +| [Item]uint32 | string | [Prop]int32 | int64 | int32 | int64 | +| Item 的 ID | Item 的名称 | Prop1 的 ID | Prop1 的值 | Prop2 的 ID | Prop2 的值 | +| 1 | Apple | 1 | 10 | 2 | 20 | +| 2 | Orange | 3 | 30 | | | +| 3 | Banana | | | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + repeated Prop prop_list = 3 [(tableau.field) = {name:"Prop" layout:LAYOUT_HORIZONTAL}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "name": "Apple", + "propList": [ + { + "id": 1, + "value": "10" + }, + { + "id": 2, + "value": "20" + } + ] + }, + { + "id": 2, + "name": "Orange", + "propList": [ + { + "id": 3, + "value": "30" + } + ] + }, + { + "id": 3, + "name": "Banana", + "propList": [] + } + ] +} +``` + +{{< /details >}} + +### 垂直键控列表中的垂直列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | PropID | PropValue | +| ---------------- | ----------- | ----------- | ------------ | +| [Item]\ | string | [Prop]int32 | int64 | +| Item 的 ID | Item 的名称 | Prop 的 ID | Prop 的值 | +| 1 | Apple | 1 | 10 | +| 2 | Orange | 1 | 20 | +| 2 | Banana | 2 | 30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + repeated Prop prop_list = 3 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Prop { + int32 prop_id = 1 [(tableau.field) = {name:"PropID"}]; + int64 prop_value = 2 [(tableau.field) = {name:"PropValue"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "name": "Apple", + "propList": [ + { + "propId": 1, + "propValue": "10" + } + ] + }, + { + "id": 2, + "name": "Orange", + "propList": [ + { + "propId": 1, + "propValue": "20" + }, + { + "propId": 2, + "propValue": "30" + } + ] + } + ] +} +``` + +{{< /details >}} + +### 垂直键控列表中的垂直键控列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Desc | PropID | PropNum | +| --------------------- | ----------- | ---------------- | ---------- | +| [KeyedItem]\ | string | [Prop]\ | int32 | +| Item 的 ID | Item 的描述 | Prop 的 ID | Prop 的数量 | +| 1 | Apple | 10 | 100 | +| 1 | Banana | 11 | 110 | +| 2 | Orange | 20 | 200 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated KeyedItem keyed_item_list = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message KeyedItem { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + repeated Prop prop_list = 3 [(tableau.field) = {key:"PropID" layout:LAYOUT_VERTICAL}]; + message Prop { + uint32 prop_id = 1 [(tableau.field) = {name:"PropID"}]; + int32 prop_num = 2 [(tableau.field) = {name:"PropNum"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "keyedItemList": [ + { + "id": 1, + "desc": "Apple", + "propList": [ + { + "propId": 10, + "propNum": 100 + }, + { + "propId": 11, + "propNum": 110 + } + ] + }, + { + "id": 2, + "desc": "Orange", + "propList": [ + { + "propId": 20, + "propNum": 200 + } + ] + } + ] +} +``` + +{{< /details >}} + +### 垂直键控列表中的单元格内列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Prop | +| ------------ | ------------ | +| [Item]uint32 | []int32 | +| Item 的 ID | Item 的属性 | +| 1 | 10,20,30 | +| 2 | 10,20 | +| 3 | 10 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + repeated int32 prop_list = 2 [(tableau.field) = {name:"Prop" layout:LAYOUT_INCELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "propList": [ + 10, + 20, + 30 + ] + }, + { + "id": 2, + "propList": [ + 10, + 20 + ] + }, + { + "id": 3, + "propList": [ + 10 + ] + } + ] +} +``` + +{{< /details >}} + +### 垂直键控列表中的单元格内键控列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Desc | Tip | +| ------------------------------------- | ----------- | ------------ | +| [KeyedItem]\\|{unique:false} | string | []\ | +| Item 的 ID | Item 的描述 | Item 的提示 | +| 1 | Apple | 1,2,3 | +| 1 | Banana | 4,5 | +| 2 | Orange | 1,2 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +我们希望父结构键控列表聚合单元格内键控列表,所以需要将字段属性 `unique` 设置为 `false`。 + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated KeyedItem keyed_item_list = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message KeyedItem { + uint32 id = 1 [(tableau.field) = {name:"ID" prop:{unique:false}}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + repeated uint32 tip_list = 3 [(tableau.field) = {name:"Tip" key:"Tip" layout:LAYOUT_INCELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "keyedItemList": [ + { + "id": 1, + "desc": "Apple", + "tipList": [ + 1, + 2, + 3, + 4, + 5 + ] + }, + { + "id": 2, + "desc": "Orange", + "tipList": [ + 1, + 2 + ] + } + ] +} +``` + +{{< /details >}} + +## 水平列表中的嵌套 + +### 水平列表中的水平列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1Item1ID | Reward1Item1Num | Reward1Item2ID | Reward1Item2Num | Reward1Name | Reward2Item1ID | Reward2Item1Num | Reward2Name | +| ------------------- | --------------- | -------------- | --------------- | ------------- | -------------- | --------------- | ------------- | +| [Reward][Item]int32 | int32 | int32 | int32 | string | int32 | int32 | string | +| Item1 的 ID | Item1 的数量 | Item2 的 ID | Item2 的数量 | 奖励的名称 | Item1 的 ID | Item1 的数量 | 奖励的名称 | +| 1 | 10 | 2 | 20 | Lotto | 10 | 100 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Reward reward_list = 1 [(tableau.field) = {name:"Reward" layout:LAYOUT_HORIZONTAL}]; + message Reward { + repeated Item item_list = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_HORIZONTAL}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "itemList": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ], + "name": "Lotto" + }, + { + "itemList": [ + { + "id": 10, + "num": 100 + } + ], + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} + +### 水平列表中的预定义结构体列表 + +*common.proto* 中预定义的 `Item`: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1Item1ID | Reward1Item1Num | Reward1Item2ID | Reward1Item2Num | Reward1Name | Reward2Item1ID | Reward2Item1Num | Reward2Name | +| -------------------- | --------------- | -------------- | --------------- | ------------- | -------------- | --------------- | ------------- | +| [Reward][.Item]int32 | int32 | int32 | int32 | string | int32 | int32 | string | +| Item1 的 ID | Item1 的数量 | Item2 的 ID | Item2 的数量 | 奖励的名称 | Item1 的 ID | Item1 的数量 | 奖励的名称 | +| 1 | 10 | 2 | 20 | Lotto | 10 | 100 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Reward reward_list = 1 [(tableau.field) = {name:"Reward" layout:LAYOUT_HORIZONTAL}]; + message Reward { + repeated protoconf.Item item_list = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_HORIZONTAL}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "itemList": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ], + "name": "Lotto" + }, + { + "itemList": [ + { + "id": 10, + "num": 100 + } + ], + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} + +### 水平列表中的单元格内列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Task1Param | Task2Param | Task3Param | +| ------------- | ---------- | ---------- | +| [Task][]int32 | []int32 | []int32 | +| Task1 | Task2 | Task3 | +| 1,2 | 3,4 | 5,6,7 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Task task_list = 1 [(tableau.field) = {name:"Task" layout:LAYOUT_HORIZONTAL}]; + message Task { + repeated int32 param_list = 1 [(tableau.field) = {name:"Param" layout:LAYOUT_INCELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "taskList": [ + { + "paramList": [ + 1, + 2 + ] + }, + { + "paramList": [ + 3, + 4 + ] + }, + { + "paramList": [ + 5, + 6, + 7 + ] + } + ] +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/list-in-map.md b/content/zh/docs/excel/list-in-map.md new file mode 100644 index 000000000..9f20da9d0 --- /dev/null +++ b/content/zh/docs/excel/list-in-map.md @@ -0,0 +1,554 @@ +--- +title: "字典中的列表" +description: "Excel 字典中列表指南。" +lead: "Excel 字典中列表的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7302 +toc: true +--- + +## 垂直字典中的嵌套 + +### 垂直字典中的水平列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | Prop1ID | Prop1Value | Prop2ID | Prop2Value | +| ----------------- | ----------- | ----------- | ------------- | ---------- | ------------- | +| map | string | [Prop]int32 | int64 | int32 | int64 | +| Item 的 ID | Item 的名称 | Prop1 的 ID | Prop1 的值 | Prop2 的 ID | Prop2 的值 | +| 1 | Apple | 1 | 10 | 2 | 20 | +| 2 | Orange | 3 | 30 | | | +| 3 | Banana | | | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + repeated Prop prop_list = 3 [(tableau.field) = {name:"Prop" layout:LAYOUT_HORIZONTAL}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "name": "Apple", + "propList": [ + { + "id": 1, + "value": "10" + }, + { + "id": 2, + "value": "20" + } + ] + }, + "2": { + "id": 2, + "name": "Orange", + "propList": [ + { + "id": 3, + "value": "30" + } + ] + }, + "3": { + "id": 3, + "name": "Banana", + "propList": [] + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的垂直列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | PropID | PropValue | +| ----------------- | ----------- | ----------- | ------------ | +| map | string | [Prop]int32 | int64 | +| Item 的 ID | Item 的名称 | Prop 的 ID | Prop 的值 | +| 1 | Apple | 1 | 10 | +| 2 | Orange | 1 | 20 | +| 2 | Banana | 2 | 30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + repeated Prop prop_list = 3 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Prop { + int32 prop_id = 1 [(tableau.field) = {name:"PropID"}]; + int64 prop_value = 2 [(tableau.field) = {name:"PropValue"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "name": "Apple", + "propList": [ + { + "propId": 1, + "propValue": "10" + } + ] + }, + "2": { + "id": 2, + "name": "Orange", + "propList": [ + { + "propId": 1, + "propValue": "20" + }, + { + "propId": 2, + "propValue": "30" + } + ] + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的单元格内列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Prop | +| ----------------- | ------------ | +| map | []int32 | +| Item 的 ID | Item 的属性 | +| 1 | 10,20,30 | +| 2 | 10,20 | +| 3 | 10 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + repeated int32 prop_list = 2 [(tableau.field) = {name:"Prop" layout:LAYOUT_INCELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "propList": [ + 10, + 20, + 30 + ] + }, + "2": { + "id": 2, + "propList": [ + 10, + 20 + ] + }, + "3": { + "id": 3, + "propList": [ + 10 + ] + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的单元格内结构体列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Item | +| ------------------- | --------------------------- | +| map | []{uint32 ID,int32 Num}Item | +| 奖励的 ID | 奖励的物品 | +| 1 | 1001:10,1002:20,1003:30 | +| 2 | 2001:10,2002:20 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Reward { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + repeated Item item_list = 2 [(tableau.field) = {name:"Item" layout:LAYOUT_INCELL span:SPAN_INNER_CELL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "itemList": [ + { + "id": 1001, + "num": 10 + }, + { + "id": 1002, + "num": 20 + }, + { + "id": 1003, + "num": 30 + } + ] + }, + "2": { + "id": 2, + "itemList": [ + { + "id": 2001, + "num": 10 + }, + { + "id": 2002, + "num": 20 + } + ] + } + } +} +``` + +{{< /details >}} + +## 水平字典中的嵌套 + +### 水平字典中的水平列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1ID | Reward1Item1ID | Reward1Item1Num | Reward1Item2ID | Reward1Item2Num | Reward2ID | Reward2Item1ID | Reward2Item1Num | +| ------------------- | ---------------- | ----------------- | ---------------- | ----------------- | ---------- | ---------------- | ----------------- | +| map | [Item]uint32 | int32 | uint32 | int32 | uint32 | uint32 | int32 | +| Reward1 的 ID | Reward1 物品1 的 ID | Reward1 物品1 的数量 | Reward1 物品2 的 ID | Reward1 物品2 的数量 | Reward2 的 ID | Reward2 物品1 的 ID | Reward2 物品1 的数量 | +| 1 | 1 | 10 | 2 | 20 | 2 | 3 | 30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {name:"Reward" key:"ID" layout:LAYOUT_HORIZONTAL}]; // Reward + message Reward { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // ID + repeated Item item_list = 2 [(tableau.field) = {name:"Item" layout:LAYOUT_HORIZONTAL}]; // item + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // ID + int32 num = 2 [(tableau.field) = {name:"Num"}]; // num + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "itemList": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ] + }, + "2": { + "id": 2, + "itemList": [ + { + "id": 3, + "num": 30 + } + ] + } + } +} +``` + +{{< /details >}} + +### 水平字典中的单元格内列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1ID | Reward1Item | Reward2ID | Reward2Item | +| ------------------- | --------------------------- | ---------- | ------------- | +| map | []{uint32 ID,int32 Num}Item | uint32 | []Item | +| Reward1 的 ID | Reward1 的物品 | Reward2 的 ID | Reward2 的物品 | +| 1 | 1:10,2:20 | 2 | 3:30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +对于预定义结构体列表,您可以使用 `[]{.Item}` 代替 `[]{uint32 ID,int32 Num}Item`。 + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {name:"Reward" key:"ID" layout:LAYOUT_HORIZONTAL}]; // Reward + message Reward { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // ID + repeated Item item_list = 2 [(tableau.field) = {name:"Item" layout:LAYOUT_INCELL span:SPAN_INNER_CELL}]; // items + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "itemList": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ] + }, + "2": { + "id": 2, + "itemList": [ + { + "id": 3, + "num": 30 + } + ] + } + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/list.md b/content/zh/docs/excel/list.md new file mode 100644 index 000000000..0e8adb07c --- /dev/null +++ b/content/zh/docs/excel/list.md @@ -0,0 +1,307 @@ +--- +title: "列表" +description: "Excel 列表指南。" +lead: "本指南演示 Excel 列表类型的不同特性。" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 7105 +toc: true +--- + +## 水平列表 + +注意:水平列表的列名**必须**有一个从 `1` 开始的数字后缀。 + +水平列表语法概述: + +| 列表元素类型 | 语法示例 | +| --------------------------------------------------------------------- | ------------------------------- | +| [标量](#horizontal-scalar-list) | `[]uint32` | +| [枚举](#horizontal-enum-list) | `[]enum<.FruitType>` | +| [结构体](#horizontal-struct-list) | `[Item]uint32` | +| [预定义结构体](#horizontal-predefined-struct-list) | `[.Item]uint32` | +| [单元格内结构体](#horizontal-incell-struct-list) | `[]{uint32 ID, string Num}Item` | +| [单元格内预定义结构体](#horizontal-incell-predefined-struct-list) | `[]{.Item}` | +{.table-striped .table-hover} + +### 水平标量列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID1 | ID2 | ID3 | +| ----------- | ----------- | ----------- | +| []uint32 | uint32 | uint32 | +| ID1's value | ID2's value | ID3's value | +| 1 | 2 | 3 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated uint32 id_list = 1 [(tableau.field) = {name:"ID" layout:LAYOUT_HORIZONTAL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "idList": [ + 1, + 2, + 3 + ] +} +``` + +{{< /details >}} + +### 水平枚举列表 + +`common.proto` 中的 `FruitType` 预定义为: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Param1 | Param2 | Param3 | +| ------------------ | ----------------- | ---------------- | +| []enum<.FruitType> | enum<.FruitType> | enum<.FruitType> | +| Param1's value | Param2's value | Param3's value | +| 1 | FRUIT_TYPE_ORANGE | Banana | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType param_list = 1 [(tableau.field) = {name:"Param" layout:LAYOUT_HORIZONTAL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "paramList": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] +} +``` + +{{< /details >}} + +### 水平结构体列表 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Item1ID | Item1Name | Item2ID | Item2Name | Item3ID | Item3Name | +| ------------ | ------------ | ---------- | ------------ | ---------- | ------------ | +| [Item]uint32 | string | uint32 | string | uint32 | string | +| Item1's ID | Item1's name | Item2's ID | Item2's name | Item3's ID | Item3's name | +| 1 | Apple | 2 | Orange | 3 | Banana | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_HORIZONTAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id":1, + "name": "Apple" + }, + { + "id": 2, + "name": "Orange" + }, + { + "id": 3, + "name": "Banana" + } + ] +} +``` + +{{< /details >}} + +### 水平预定义结构体列表 + +`common.proto` 中的 `Item` 预定义为: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Item1ID | Item1Num | Item2ID | Item2Num | Item3ID | Item3Num | +| ------------ | ----------- | ---------- | ----------- | ---------- | ----------- | +| [.Item]int32 | int32 | int32 | int32 | int32 | int32 | +| Item1's ID | Item1's num | Item2's ID | Item3's num | Item3's ID | Item3's num | +| 1 | 100 | 2 | 200 | 3 | 300 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.Item item_list = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_HORIZONTAL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "ItemList": [ + { + "id":1, + "num": 100 + }, + { + "id": 2, + "num": 200 + }, + { + "id": 3, + "num": 300 + } + ] +} +``` + +{{< /details >}} diff --git a/content/zh/docs/excel/map-in-list.md b/content/zh/docs/excel/map-in-list.md new file mode 100644 index 000000000..49bd56102 --- /dev/null +++ b/content/zh/docs/excel/map-in-list.md @@ -0,0 +1,463 @@ +--- +title: "列表中的字典" +description: "Excel 列表中字典指南。" +lead: "Excel 列表中字典的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7401 +toc: true +--- + +## 垂直列表中的嵌套 + +### 垂直列表中的水平字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | Prop1ID | Prop1Value | Prop2ID | Prop2Value | +| ------------ | ----------- | ---------------- | ------------- | ---------- | ------------- | +| [Item]uint32 | string | map | int64 | int32 | int64 | +| Item 的 ID | Item 的名称 | Prop1 的 ID | Prop1 的值 | Prop2 的 ID | Prop2 的值 | +| 1 | Apple | 1 | 10 | 2 | 20 | +| 2 | Orange | 3 | 30 | | | +| 3 | Banana | | | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + map prop_map = 3 [(tableau.field) = {name:"Prop" key:"ID" layout:LAYOUT_HORIZONTAL}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "name": "Apple", + "propMap": { + "1": { + "id": 1, + "value": "10" + }, + "2": { + "id": 2, + "value": "20" + } + } + }, + { + "id": 2, + "name": "Orange", + "propMap": { + "3": { + "id": 3, + "value": "30" + } + } + }, + { + "id": 3, + "name": "Banana", + "propMap": {} + } + ] +} +``` + +{{< /details >}} + +### 垂直键控列表中的垂直字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | PropID | PropValue | +| ---------------- | ----------- | ---------------- | ------------ | +| [Item]\ | string | map | int64 | +| Item 的 ID | Item 的名称 | Prop 的 ID | Prop 的值 | +| 1 | Apple | 1 | 10 | +| 2 | Orange | 1 | 20 | +| 2 | Banana | 2 | 30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + map prop_map = 3 [(tableau.field) = {key:"PropID" layout:LAYOUT_VERTICAL}]; + message Prop { + int32 prop_id = 1 [(tableau.field) = {name:"PropID"}]; + int64 prop_value = 2 [(tableau.field) = {name:"PropValue"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "name": "Apple", + "propMap": { + "1": { + "propId": 1, + "propValue": "10" + } + } + }, + { + "id": 2, + "name": "Orange", + "propMap": { + "1": { + "propId": 1, + "propValue": "20" + }, + "2": { + "propId": 2, + "propValue": "30" + } + } + } + ] +} +``` + +{{< /details >}} + +### 垂直列表中的单元格内字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Props | +| ------------ | -------------------------- | +| [Item]uint32 | map\ | +| Item 的 ID | Item 的属性 | +| 1 | 1:sour,2:sweet,3:delicious | +| 2 | 1:sour,2:sweet | +| 3 | 1:sour | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + map props_map = 2 [(tableau.field) = {name:"Props" layout:LAYOUT_INCELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "propsMap": { + "1": "sour", + "2": "sweet", + "3": "delicious" + } + }, + { + "id": 2, + "propsMap": { + "1": "sour", + "2": "sweet" + } + }, + { + "id": 3, + "propsMap": { + "1": "sour" + } + } + ] +} +``` + +{{< /details >}} + +## 水平列表中的首字段 + +### 水平列表中的水平字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1Item1ID | Reward1Item1Num | Reward1Item2ID | Reward1Item2Num | Reward1Name | Reward2Item1ID | Reward2Item1Num | Reward2Name | +| ------------------------ | --------------- | -------------- | --------------- | ------------- | -------------- | --------------- | ------------- | +| [Reward]map | int32 | int32 | int32 | string | int32 | int32 | string | +| Item1 的 ID | Item1 的数量 | Item2 的 ID | Item2 的数量 | 奖励的名称 | Item1 的 ID | Item1 的数量 | 奖励的名称 | +| 1 | 10 | 2 | 20 | Lotto | 10 | 100 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Reward reward_list = 1 [(tableau.field) = {name:"Reward" layout:LAYOUT_HORIZONTAL}]; + message Reward { + map item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "itemMap": { + "1": { + "id": 1, + "num": 10 + }, + "2": { + "id": 2, + "num": 20 + } + }, + "name": "Lotto" + }, + { + "itemMap": { + "10": { + "id": 10, + "num": 100 + } + }, + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} + +### 水平列表中的预定义结构体字典 + +*common.proto* 中预定义的 `Item`: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1Item1ID | Reward1Item1Num | Reward1Item2ID | Reward1Item2Num | Reward1Name | Reward2Item1ID | Reward2Item1Num | Reward2Name | +| ------------------------- | --------------- | -------------- | --------------- | ------------- | -------------- | --------------- | ------------- | +| [Reward]map | int32 | int32 | int32 | string | int32 | int32 | string | +| Item1 的 ID | Item1 的数量 | Item2 的 ID | Item2 的数量 | 奖励的名称 | Item1 的 ID | Item1 的数量 | 奖励的名称 | +| 1 | 10 | 2 | 20 | Lotto | 10 | 100 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Reward reward_list = 1 [(tableau.field) = {name:"Reward" layout:LAYOUT_HORIZONTAL}]; + message Reward { + map item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "itemMap": { + "1": { + "id": 1, + "num": 10 + }, + "2": { + "id": 2, + "num": 20 + } + }, + "name": "Lotto" + }, + { + "itemMap": { + "10": { + "id": 10, + "num": 100 + } + }, + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/map-in-map.md b/content/zh/docs/excel/map-in-map.md new file mode 100644 index 000000000..cded497ef --- /dev/null +++ b/content/zh/docs/excel/map-in-map.md @@ -0,0 +1,444 @@ +--- +title: "字典中的字典" +description: "Excel 字典中字典指南。" +lead: "Excel 字典中字典的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7402 +toc: true +--- + +## 垂直字典中的嵌套 + +### 垂直字典中的水平字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | Prop1ID | Prop1Value | Prop2ID | Prop2Value | +| ----------------- | ----------- | ---------------- | ------------- | ---------- | ------------- | +| map | string | map | int64 | int32 | int64 | +| Item 的 ID | Item 的名称 | Prop1 的 ID | Prop1 的值 | Prop2 的 ID | Prop2 的值 | +| 1 | Apple | 1 | 10 | 2 | 20 | +| 2 | Orange | 3 | 30 | | | +| 3 | Banana | | | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + map prop_map = 3 [(tableau.field) = {name:"Prop" key:"ID" layout:LAYOUT_HORIZONTAL}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "name": "Apple", + "propMap": { + "1": { + "id": 1, + "value": "10" + }, + "2": { + "id": 2, + "value": "20" + } + } + }, + "2": { + "id": 2, + "name": "Orange", + "propMap": { + "3": { + "id": 3, + "value": "30" + } + } + }, + "3": { + "id": 3, + "name": "Banana", + "propMap": {} + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的垂直字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | PropID | PropValue | +| ----------------- | ----------- | ---------------- | ------------ | +| map | string | map | int64 | +| Item 的 ID | Item 的名称 | Prop 的 ID | Prop 的值 | +| 1 | Apple | 1 | 10 | +| 2 | Orange | 1 | 20 | +| 2 | Orange | 2 | 30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + map prop_map = 3 [(tableau.field) = {key:"PropID" layout:LAYOUT_VERTICAL}]; + message Prop { + int32 prop_id = 1 [(tableau.field) = {name:"PropID"}]; + int64 prop_value = 2 [(tableau.field) = {name:"PropValue"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "name": "Apple", + "propMap": { + "1": { + "propId": 1, + "propValue": "10" + } + } + }, + "2": { + "id": 2, + "name": "Orange", + "propMap": { + "1": { + "propId": 1, + "propValue": "20" + }, + "2": { + "propId": 2, + "propValue": "30" + } + } + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的单元格内字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Props | +| ----------------- | -------------------------- | +| map | map | +| Item 的 ID | Item 的属性 | +| 1 | 1:sour,2:sweet,3:delicious | +| 2 | 1:sour,2:sweet | +| 3 | 1:sour | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + map props_map = 2 [(tableau.field) = {name:"Props" layout:LAYOUT_INCELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "propsMap": { + "1": "sour", + "2": "sweet", + "3": "delicious" + } + }, + "2": { + "id": 2, + "propsMap": { + "1": "sour", + "2": "sweet" + } + }, + "3": { + "id": 3, + "propsMap": { + "1": "sour" + } + } + } +} +``` + +{{< /details >}} + +## 水平字典中的嵌套 + +### 水平字典中的水平字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1ID | Reward1Item1ID | Reward1Item1Num | Reward1Item2ID | Reward1Item2Num | Reward2ID | Reward2Item1ID | Reward2Item1Num | +| ------------------- | ----------------- | ----------------- | ---------------- | ----------------- | ---------- | ---------------- | ----------------- | +| map | map | int32 | uint32 | int32 | uint32 | uint32 | int32 | +| Reward1 的 ID | Reward1 物品1 的 ID | Reward1 物品1 的数量 | Reward1 物品2 的 ID | Reward1 物品2 的数量 | Reward2 的 ID | Reward2 物品1 的 ID | Reward2 物品1 的数量 | +| 1 | 1 | 10 | 2 | 20 | 2 | 3 | 30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {name:"Reward" key:"ID" layout:LAYOUT_HORIZONTAL}]; // Reward + message Reward { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // ID + map item_map = 2 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}]; // item + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // ID + int32 num = 2 [(tableau.field) = {name:"Num"}]; // num + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "itemMap": { + "1": { + "id": 1, + "num": 10 + }, + "2": { + "id": 2, + "num": 20 + } + } + }, + "2": { + "id": 2, + "itemMap": { + "3": { + "id": 3, + "num": 30 + } + } + } + } +} +``` + +{{< /details >}} + +### 水平字典中的单元格内字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1ID | Reward1Item | Reward2ID | Reward2Item | +| ------------------- | ------------------ | ---------- | ------------------ | +| map | map | uint32 | map | +| Reward1 的 ID | Reward1 的物品 | Reward2 的 ID | Reward2 的物品 | +| 1 | 1:10,2:20 | 2 | 3:30 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {name:"Reward" key:"ID" layout:LAYOUT_HORIZONTAL}]; // Reward + message Reward { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // ID + map item_map = 2 [(tableau.field) = {name:"Item" layout:LAYOUT_INCELL}]; // Reward1 items + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "itemMap": { + "1": 10, + "2": 20 + } + }, + "2": { + "id": 2, + "itemMap": { + "3": 30 + } + } + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/map.md b/content/zh/docs/excel/map.md new file mode 100644 index 000000000..54ba07fee --- /dev/null +++ b/content/zh/docs/excel/map.md @@ -0,0 +1,150 @@ +--- +title: "字典" +description: "Excel 字典指南。" +lead: "本指南演示 Excel 字典类型的不同特性。" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 7106 +toc: true +--- + +## 水平字典 + +有几种水平字典: + +1. 水平**标量**字典,字典值类型为标量。例如:`map`。 +2. 水平**结构体**字典,字典值类型为结构体。例如:`map`。 +3. 水平**预定义结构体**字典,字典值类型为预定义结构体。例如:`map`。 + +### 水平标量字典 + +无需支持,改用:`map`。 + +### 水平结构体字典 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Item1ID | Item1Name | Item2ID | Item2Name | Item3ID | Item3Name | +| ----------------- | ------------ | ---------- | ------------ | ---------- | ------------ | +| map | string | uint32 | string | uint32 | string | +| Item1's ID | Item1's name | Item2's ID | Item2's name | Item3's ID | Item3's name | +| 1 | Apple | 2 | Orange | 3 | Banana | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id":1, + "name": "Apple" + }, + "2": { + "id": 2, + "name": "Orange" + }, + "3": { + "id": 3, + "name": "Banana" + } + } +} +``` + +{{< /details >}} + +### 水平预定义结构体字典 + +*common.proto* 中的 `Item` 预定义为: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Item1ID | Item1Num | Item2ID | Item2Num | Item3ID | Item3Num | +| ----------------- | ----------- | ---------- | ----------- | ---------- | ----------- | +| map | int32 | int32 | int32 | int32 | int32 | +| Item1's ID | Item1's num | Item2's ID | Item3's num | Item3's ID | Item3's num | +| 1 | 100 | 2 | 200 | 3 | 300 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}]; +} +``` + +{{< /details >}} diff --git a/content/zh/docs/excel/metasheet.md b/content/zh/docs/excel/metasheet.md new file mode 100644 index 000000000..6d5fd4a55 --- /dev/null +++ b/content/zh/docs/excel/metasheet.md @@ -0,0 +1,149 @@ +--- +title: "元数据表" +description: "Excel 元数据表 @TABLEAU 指南。" +lead: "元数据表是一个名为 \"@TABLEAU\" 的工作表,用于指定 tableau 解析器的工作表级别选项。" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 7902 +toc: true +--- + +## 概述 + +以下选项可以在元数据表 `@TABLEAU` 中指定,以影响相应工作表的布局、能力、加载器等。 + +| 选项 | 类型 | 描述 | +| ------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Sheet` | string | 要处理的工作表名称。特别地,`#` 引用工作簿名称,因此您可以设置工作簿的 `Alias`。 | +| `Alias` | string | 对于工作表,别名用作 proto 消息名称。对于工作簿 `#`,别名用作 proto 文件名(不带文件扩展名)。 | +| `Namerow` | int32 | 工作表中列名定义的确切行号。
默认值:`1`。 | +| `Typerow` | int32 | 工作表中列类型定义的确切行号。
默认值:`2`。 | +| `Noterow` | int32 | 工作表中列注释定义的确切行号。
默认值:`3`。 | +| `Datarow` | int32 | 工作表中数据开始的行号。
默认值:`4`。 | +| `Nameline` | int32 | 单元格中列名定义的行号。`0` 表示整个单元格。
默认值:`0`。 | +| `Typeline` | int32 | 单元格中列类型定义的行号。`0` 表示整个单元格。
默认值:`0`。 | +| `Transpose` | bool | 交换给定工作表的行和列。 | +| `Nested` | bool | **namerow** 的嵌套命名。
默认值:`false`。 | +| `Sep` | string | 工作表级别分隔符。 | +| `Subsep` | string | 工作表级别子分隔符。 | +| `Merger` | []string | 将多个工作表(逗号分隔)合并为一个具有相同结构的工作表。
每个元素可以是:
- 只是一个工作簿文件路径或 glob 路径(相对于此工作簿):``,然后工作表名称与此工作表相同。
- 一个工作簿文件路径(相对于此工作簿)和一个工作表名称:`#`。 | +| `AdjacentKey` | bool | 合并具有相同键的相邻行。如果未设置键单元格,则将其视为与同一列中最接近的键相同。
默认值:`false`。 | +| `FieldPresence` | bool | 为了跟踪基本类型(数字、字符串、字节和枚举)的字段存在性,生成的字段将被标记为 `optional`。
默认值:`false`。 | +| `Mode` | Mode | 工作表模式。
可用模式:
- `MODE_ENUM_TYPE`
- `MODE_ENUM_TYPE_MULTI`
- `MODE_STRUCT_TYPE`
- `MODE_STRUCT_TYPE_MULTI`
- `MODE_UNION_TYPE`
- `MODE_UNION_TYPE_MULTI` | +| `Scatter` | []string | 使用相同的架构分别转换多个工作表。
每个元素可以是:
- 一个工作簿名称或相对于此工作簿的 Glob:``,然后工作表名称与此工作表相同。
- 或一个相对于此工作簿的工作簿名称和一个工作表名称:`#`。 | +| `Optional` | bool | 此工作表中的所有字段是否都是可选的(字段名称存在性)。 | +| `Patch` | Patch | 工作表修补类型。
- `PATCH_REPLACE`
- `PATCH_MERGE` | +| `WithParentDir` | bool | confgen:导出 JSON/Bin/Text 文件时创建父目录。 | +| `ScatterWithoutBookName` | bool | confgen(scatter):导出 JSON/Bin/Text 文件名时不带工作簿名称前缀。 | +| `OrderedMap` | bool | 是否生成 OrderedMap 访问器。 | +| `Index` | []string | 生成索引访问器。
- 单列索引格式:`Column@IndexName`。
- 多列索引格式:`(Column1,Column2,...)@IndexName`。 | +| `OrderedIndex` | []string | 生成 OrderedIndex 访问器。
- 单列 OrderedIndex 格式:`Column@IndexName`。
- 多列 OrderedIndex 格式:`(Column1,Column2,...)@IndexName`。 | +| `LangOptions` | map | 指定加载器语言选项。
有效键为:`OrderedMap`、`Index`。
不同的键值对必须用 `,` 分隔,一个键值必须用 `:` 分隔。
如果一个键在字典中不存在,则表示此加载器选项在所有语言中都受支持。
有效值为 `cpp`、`go` 的所有组合,以空格作为分隔符。
示例:
- `OrderedMap:cpp,Index:cpp go` // cpp 中支持有序字典,cpp 和 go 中支持索引
- `OrderedMap:cpp` // cpp 中支持有序字典,所有语言中都支持索引 | +{.table-striped .table-hover} + +## 空的 `@TABLEAU` + +如果元数据表 `@TABLEAU` 为空,则同一工作簿中的所有其他工作表都将被处理。 + +## 一个简单的示例 + +*HelloWorld.xlsx* 中有一个工作表 `Sheet1`,我们想将工作表重命名为 +`ItemConf`,定义自定义分隔符为 `|`,并生成有序字典访问器。 + +因此,*HelloWorld.xlsx* 中的元数据表 `@TABLEAU` 应配置为: + +{{< spreadsheet "HelloWorld.xlsx" Sheet1 "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | +| ----------------- | ----------- | +| map | string | +| Item's ID | Item's Name | +| 1 | Apple | +| 2 | Orange | +| 3 | Banana | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Alias | Sep | OrderedMap | +| ------ | -------- | --- | ---------- | +| Sheet1 | ItemConf | \| | true | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +## 工作簿 `Alias` + +生成的 proto 文件名是输入文件名的 snake case。例如,如果您有一个名为 *HelloWorld.xlsx* 的工作簿,生成的 proto 文件名是 *hello_world.proto*。如果您想为生成的 proto 文件手动指定名称,也可以使用 `Alias` 选项。在这种情况下,`#` 引用工作簿名称。 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" Sheet1 "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | +| ----------------- | ----------- | +| map | string | +| Item's ID | Item's Name | +| 1 | Apple | +| 2 | Orange | +| 3 | Banana | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Alias | +| ------ | ----------- | +| # | custom_conf | +| Sheet1 | ItemConf | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "custom_conf.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +## 选项 `Mode` + +工作表模式定义 tableauc (protogen) 如何解析工作表:数据或类型。 + +可用模式: + +- `MODE_DEFAULT`:默认模式,定义工作表的数据结构。 +- `MODE_ENUM_TYPE`:在工作表中定义单个枚举类型,请参阅 [示例](../enum/#single-enum-type-in-sheet)。 +- `MODE_ENUM_TYPE_MULTI`:在工作表中定义多个枚举类型,请参阅 [示例](../enum/#multiple-enum-types-in-sheet)。 +- `MODE_STRUCT_TYPE`:在工作表中定义单个结构体类型,请参阅 [示例](../struct/#single-struct-type-in-sheet)。 +- `MODE_STRUCT_TYPE_MULTI`:在工作表中定义多个结构体类型,请参阅 [示例](../struct/#multiple-struct-types-in-sheet)。 +- `MODE_UNION_TYPE`:在工作表中定义单个联合类型,请参阅 [示例](../union/#single-union-type-in-sheet)。 +- `MODE_UNION_TYPE_MULTI`:在工作表中定义多个联合类型,请参阅 [示例](../union/#multiple-union-types-in-sheet)。 + +## 选项 `Transpose` + +在线性代数中,矩阵的转置是沿其对角线翻转矩阵的运算符。同样,工作表(2D 矩阵)的转置意味着将其行转换为列,反之亦然。 diff --git a/content/zh/docs/excel/scalar.md b/content/zh/docs/excel/scalar.md new file mode 100644 index 000000000..bcf881587 --- /dev/null +++ b/content/zh/docs/excel/scalar.md @@ -0,0 +1,80 @@ +--- +title: "标量" +description: "Excel 标量指南。" +lead: "本指南演示 Excel 标量类型的不同特性。" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 7101 +toc: true +--- + +## 标量 + +*HelloWorld.xlsx* 中的工作表 `Apple`: + +{{< spreadsheet "HelloWorld.xlsx" Apple "@TABLEAU" >}} + +{{< sheet colored>}} + +| ID | Name | Desc | +| --------- | ----------- | -------------------------- | +| uint32 | string | string | +| Item's ID | Item's Name | Item's Description | +| 1 | Apple | A kind of delicious fruit. | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +在此工作表中,定义了三个标量字段: + +1. ID:`uint32` +2. Name:`string` +3. Desc:`string` + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message Apple { + option (tableau.worksheet) = {name:"Apple"}; + + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; +} +``` + +{{< /details >}} + +{{< details "Apple.json" >}} + +```json +{ + "id": 1, + "name": "Apple", + "desc": "A kind of delicious fruit." +} +``` + +{{< /details >}} + +### 注意 + +标量类型通常用于定义结构体类型的字段。[结构体 →]({{< relref "struct" >}}) diff --git a/content/zh/docs/excel/struct-in-list.md b/content/zh/docs/excel/struct-in-list.md new file mode 100644 index 000000000..1630f1b34 --- /dev/null +++ b/content/zh/docs/excel/struct-in-list.md @@ -0,0 +1,432 @@ +--- +title: "列表中的结构体" +description: "Excel 列表中结构体指南。" +lead: "Excel 列表中结构体的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7202 +toc: true +--- + +## 垂直列表中的嵌套 + +### 垂直列表中的结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | PropID | PropValue | +| ------------ | ----------- | ----------- | ------------ | +| [Item]uint32 | string | {Prop}int32 | int64 | +| Item 的 ID | Item 的名称 | Prop 的 ID | Prop 的值 | +| 1 | Apple | 1 | 10 | +| 2 | Orange | 2 | 20 | +| 3 | Banana | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + Prop prop = 3 [(tableau.field) = {name:"Prop"}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "name": "Apple", + "prop": { + "id": 1, + "value": "10" + } + }, + { + "id": 2, + "name": "Orange", + "prop": { + "id": 2, + "value": "20" + } + }, + { + "id": 3, + "name": "Banana", + "prop": null + } + ] +} +``` + +{{< /details >}} + +### 垂直列表中的单元格内结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Name | PropID | +| ------------ | ----------- | -------------------------- | +| [Item]uint32 | string | {int32 ID,int64 Value}Prop | +| Item 的 ID | Item 的名称 | Prop 的 ID | +| 1 | Apple | 1,100 | +| 2 | Orange | 2,200 | +| 3 | Banana | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + Prop prop_id = 3 [(tableau.field) = {name:"PropID" span:SPAN_INNER_CELL}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "name": "Apple", + "propId": { + "id": 1, + "value": "100" + } + }, + { + "id": 2, + "name": "Orange", + "propId": { + "id": 2, + "value": "200" + } + }, + { + "id": 3, + "name": "Banana", + "propId": null + } + ] +} +``` + +{{< /details >}} + +## 水平列表中的首字段 + +### 水平列表中的结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1ItemID | Reward1ItemNum | Reward1Name | Reward2ItemID | Reward2ItemNum | Reward2Name | +| ------------------- | -------------- | ------------- | ------------- | -------------- | ------------- | +| [Reward]{Item}int32 | int32 | string | int32 | int32 | string | +| Item1 的 ID | Item1 的数量 | 奖励的名称 | Item1 的 ID | Item1 的数量 | 奖励的名称 | +| 1 | 10 | Lotto | 10 | 100 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Reward reward_list = 1 [(tableau.field) = {name:"Reward" layout:LAYOUT_HORIZONTAL}]; + message Reward { + Item item = 1 [(tableau.field) = {name:"Item"}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "item": { + "id": 1, + "num": 10 + }, + "name": "Lotto" + }, + { + "item": { + "id": 10, + "num": 100 + }, + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} + +### 水平列表中的预定义结构体 + +*common.proto* 中预定义的 `Item`: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1ItemID | Reward1ItemNum | Reward1Name | Reward2ItemID | Reward2ItemNum | Reward2Name | +| -------------------- | -------------- | ------------- | ------------- | -------------- | ------------- | +| [Reward]{.Item}int32 | int32 | string | int32 | int32 | string | +| Item1 的 ID | Item1 的数量 | 奖励的名称 | Item1 的 ID | Item1 的数量 | 奖励的名称 | +| 1 | 10 | Lotto | 10 | 100 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Reward reward_list = 1 [(tableau.field) = {name:"Reward" layout:LAYOUT_HORIZONTAL}]; + message Reward { + protoconf.Item item = 1 [(tableau.field) = {name:"Item"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "item": { + "id": 1, + "num": 10 + }, + "name": "Lotto" + }, + { + "item": { + "id": 10, + "num": 100 + }, + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} + +### 水平列表中的单元格内结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Reward1Item | Reward1Name | Reward2Item | Reward2Name | +| --------------------------------- | ------------- | -------------- | ------------- | +| [Reward]{int32 ID, int32 Num}Item | string | Item | string | +| Reward1 的物品 | 奖励的名称 | Reward2 的物品 | 奖励的名称 | +| 1,10 | Lotto | 2,20 | Super Lotto | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + Prop prop_id = 3 [(tableau.field) = {name:"PropID" span:SPAN_INNER_CELL}]; + message Prop { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int64 value = 2 [(tableau.field) = {name:"Value"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardList": [ + { + "item": { + "id": 1, + "num": 10 + }, + "name": "Lotto" + }, + { + "item": { + "id": 2, + "num": 20 + }, + "name": "Super Lotto" + } + ] +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/struct-in-map.md b/content/zh/docs/excel/struct-in-map.md new file mode 100644 index 000000000..1368b6c13 --- /dev/null +++ b/content/zh/docs/excel/struct-in-map.md @@ -0,0 +1,271 @@ +--- +title: "字典中的结构体" +description: "Excel 字典中结构体指南。" +lead: "Excel 字典中结构体的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7203 +toc: true +--- + +## 垂直字典中的嵌套 + +### 垂直字典中的结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | ItemID | ItemNum | +| ------------------ | ----------- | ---------- | +| map | {Item}int32 | int32 | +| 奖励的 ID | 物品的 ID | 物品的数量 | +| 1 | 1 | 10 | +| 2 | 2 | 20 | +| 3 | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Reward { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + Item item = 2 [(tableau.field) = {name:"Item"}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "item": { + "id": 1, + "num": 10 + } + }, + "2": { + "id": 2, + "item": { + "id": 2, + "num": 20 + } + }, + "3": { + "id": 3, + "item": null + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的预定义结构体 + +*common.proto* 中预定义的 `Item`: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | ItemID | ItemNum | +| ------------------ | ------------ | ---------- | +| map | {.Item}int32 | int32 | +| 奖励的 ID | 物品的 ID | 物品的数量 | +| 1 | 1 | 10 | +| 2 | 2 | 20 | +| 3 | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Reward { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.Item item = 2 [(tableau.field) = {name:"Item"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "item": { + "id": 1, + "num": 10 + } + }, + "2": { + "id": 2, + "item": { + "id": 2, + "num": 20 + } + }, + "3": { + "id": 3, + "item": null + } + } +} +``` + +{{< /details >}} + +### 垂直字典中的单元格内结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Item | +| ------------------ | ------------------------- | +| map | {int32 ID, int32 Num}Item | +| 奖励的 ID | 物品的信息 | +| 1 | 1,100 | +| 2 | 2,200 | +| 3 | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map reward_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Reward { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + Item item = 2 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardMap": { + "1": { + "id": 1, + "item": { + "id": 1, + "num": 100 + } + }, + "2": { + "id": 2, + "item": { + "id": 2, + "num": 200 + } + }, + "3": { + "id": 3, + "item": null + } + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/struct-in-struct.md b/content/zh/docs/excel/struct-in-struct.md new file mode 100644 index 000000000..1214ee73a --- /dev/null +++ b/content/zh/docs/excel/struct-in-struct.md @@ -0,0 +1,225 @@ +--- +title: "结构体中的结构体" +description: "Excel 结构体中结构体指南。" +lead: "Excel 结构体中结构体的嵌套规范。" +date: 2022-02-26T08:48:57+08:00 +lastmod: 2022-02-26T08:48:57+08:00 +draft: false +images: [] +weight: 7201 +toc: true +--- + +## 结构体中的结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| RewardID | RewardItemID | RewardItemNum | +| ------------- | ------------ | ------------- | +| {Reward}int32 | {Item}int32 | int32 | +| 奖励的 ID | 物品的 ID | 物品的数量 | +| 1 | 1 | 10 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Reward reward = 1 [(tableau.field) = {name:"Reward"}]; + message Reward { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + Item item = 2 [(tableau.field) = {name:"Item"}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "reward": { + "id": 1, + "item": { + "id": 1, + "num": 10 + } + } +} +``` + +{{< /details >}} + +## 结构体中的预定义结构体 + +*common.proto* 中预定义的 `Item`: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| RewardID | RewardItemID | RewardItemNum | +| ------------- | ------------ | ------------- | +| {Reward}int32 | {.Item}int32 | int32 | +| 奖励的 ID | 物品的 ID | 物品的数量 | +| 1 | 1 | 10 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Reward reward = 1 [(tableau.field) = {name:"Reward"}]; + message Reward { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.Item item = 2 [(tableau.field) = {name:"Item"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "reward": { + "id": 1, + "item": { + "id": 1, + "num": 10 + } + } +} +``` + +{{< /details >}} + +## 结构体中的单元格内结构体 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| RewardID | RewardItem | +| ------------- | ------------------------- | +| {Reward}int32 | {int32 ID, int32 Num}Item | +| 奖励的 ID | 奖励的物品 | +| 1 | 1,100 | +| | 2,200 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成结果: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Reward reward = 1 [(tableau.field) = {name:"Reward"}]; + message Reward { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + Item item = 2 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "reward": { + "id": 1, + "item": { + "id": 2, + "num": 200 + } + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/excel/struct.md b/content/zh/docs/excel/struct.md new file mode 100644 index 000000000..5e7be8ef0 --- /dev/null +++ b/content/zh/docs/excel/struct.md @@ -0,0 +1,765 @@ +--- +title: "结构体" +description: "Excel 结构体指南。" +lead: "本指南演示 Excel 结构体类型的不同特性。" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 7103 +toc: true +--- + +## 跨单元格结构体 + +**语法**:`ColumnType`。 + +每个列名都应该以相同的结构体变量名作为前缀,默认情况下,该变量名与结构体类型名相同。 + +例如,`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| PropertyID | PropertyName | PropertyDesc | +| :-------------- | :-------------- | :--------------------- | +| {Property}int32 | string | string | +| Property's ID | Property's Name | Property's Description | +| 1 | Orange | A kind of sour fruit. | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +注意 `ItemConf` 中的每个列名都以结构体变量名 **Property** 作为前缀,该变量名与结构体类型名相同。 + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Property property = 1 [(tableau.field) = {name:"Property"}]; + message Property { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "property": { + "id": 1, + "name": "Orange", + "desc": "A kind of sour fruit." + } +} +``` + +{{< /details >}} + +### 注意 + +跨单元格结构体通常与以下内容一起使用: + +- 跨单元格水平/垂直字典,作为字典值类型。[字典 →]({{< relref "map" >}}) +- 跨单元格水平/垂直列表,作为列表元素类型。[列表 →]({{< relref "list" >}}) + +## 单元格内结构体 + +结构体的每个字段类型都应该是标量值类型。 + +例如,`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Prop | +| ---------------- | ------------------------------------------ | +| map | {int32 ID,string Name,string Desc}Property | +| Item's ID | Item's property. | +| 1 | 1,Orange,A good fruit. | +| 2 | 2,Apple | +| 3 | 3 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +`Property` 列的类型是单元格内结构体 `{int32 ID,string Name,string Desc}Property`。 + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + Property prop = 2 [(tableau.field) = {name:"Prop" span:SPAN_INNER_CELL}]; + message Property { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "prop": { + "id": 1, + "name": "Apple", + "desc": "A kind of delicious fruit." + } + }, + "2": { + "id": 2, + "prop": { + "id": 2, + "name": "Orange", + "desc": "" + } + }, + "3": { + "id": 3, + "prop": { + "id": 3, + "name": "", + "desc": "" + } + } + } +} +``` + +{{< /details >}} + +## 预定义结构体 + +例如,`common.proto` 中定义的结构体类型 `Prop`: + +```protobuf +message Prop { + int32 id = 1 [(tableau.field).name = "ID"]; + int32 value = 2 [(tableau.field).name = "Value"]; +} +``` + +`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Prop1ID | Prop1Value | Prop2ID | Prop2Value | +| ----------------- | ------------ | ------------- | ---------- | ------------- | +| map | [.Prop]int32 | int32 | int32 | int32 | +| Item's ID | Prop1's ID | Prop1's value | Prop2's ID | Prop2's value | +| 1 | 1 | 100 | 2 | 200 | +| 2 | 3 | 300 | 4 | 400 | +| 3 | 5 | 500 | | | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + repeated Prop prop_list = 2 [(tableau.field) = {name:"Prop" layout:LAYOUT_HORIZONTAL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "propList": [ + { + "id": 1, + "value": 100 + }, + { + "id": 2, + "value": 200 + } + ] + }, + "2": { + "id": 2, + "propList": [ + { + "id": 3, + "value": 300 + }, + { + "id": 4, + "value": 400 + } + ] + }, + "3": { + "id": 3, + "propList": [ + { + "id": 5, + "value": 500 + } + ] + } + } +} +``` + +{{< /details >}} + +## 预定义单元格内结构体 + +预定义结构体的每个字段类型都应该是标量值类型。 + +例如,`common.proto` 中预定义的 `Property`: + +```protobuf +message Property { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; +} +``` + +`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| ID | Prop | +| ----------------- | ---------------------- | +| map | {.Property} | +| Item's ID | Item's property. | +| 1 | 1,Orange,A good fruit. | +| 2 | 2,Apple | +| 3 | 3 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +`Prop` 列的类型是预定义结构体 `Property`。 + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.Property prop = 2 [(tableau.field) = {name:"Prop" span:SPAN_INNER_CELL}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "prop": { + "id": 1, + "name": "Apple", + "desc": "A kind of delicious fruit." + } + }, + "2": { + "id": 2, + "prop": { + "id": 2, + "name": "Orange", + "desc": "" + } + }, + "3": { + "id": 3, + "prop": { + "id": 3, + "name": "", + "desc": "" + } + } + } +} +``` + +{{< /details >}} + +## 自定义命名结构体 + +默认情况下,结构体变量名与结构体类型名相同,但您可以指定不同的结构体变量名。自定义命名结构体主要用于识别名称行中连续单元格的名称前缀,当 tableau (protogen) 无法自动识别变量名时。 + +**语法**:在结构体类型名之后,使用括号 `()` 指定结构体变量名:`VariableType(VariableName)`。 + +例如,预定义了 `Item`: + +```protobuf +message Item { + int32 id = 1 [(tableau.field).name = "ID"]; + int32 num = 2 [(tableau.field).name = "Num"]; +} +``` + +`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| RewardItemID | RewardItemNum | CostItemID | CostItemNum | PredefinedItemID | PredefinedItemNum | +| ----------------------- | ------------- | --------------------- | ----------- | ---------------------------- | -------------------- | +| {Item(RewardItem)}int32 | int32 | {Item(CostItem)}int32 | int32 | {.Item(PredefinedItem)}int32 | int32 | +| Item's ID | Item's ID | Cost's ID | Cost's ID | Predefined item's ID | Predefined item's ID | +| 1 | 100 | 2 | 200 | 10 | 20 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +**详情**: +在类型单元格 `{Item(RewardItem)}int32` 中,`RewardItem` 是新定义结构体 `Item` 的自定义变量名。在类型单元格 `{Item(CostItem)}int32` 中,`CostItem` 是同一作用域中已定义结构体 `Item` 的自定义变量名。最后,在类型单元格 `{.Item(PredefinedItem)}int32` 中,`PredefinedItem` 是全局(在同一 protobuf 包中)预定义结构体 `Item` 的自定义变量名。 + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item reward_item = 1 [(tableau.field) = {name:"RewardItem"}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + Item cost_item = 2 [(tableau.field) = {name:"CostItem"}]; + protoconf.Item predefined_item = 3 [(tableau.field) = {name:"PredefinedItem"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "rewardItem": { + "id": 1, + "num": 100 + }, + "costItem": { + "id": 2, + "num": 200 + }, + "predefinedItem": { + "id": 10, + "num": 20 + } +} +``` + +{{< /details >}} + +## 高级预定义单元格内结构体 + +在某些情况下,您可能希望在单元格中配置任何复杂的结构体,因此 tableau 支持两种 protobuf 序列化格式:[文本格式](https://developers.google.com/protocol-buffers/docs/text-format-spec) 和 [JSON 格式](https://developers.google.com/protocol-buffers/docs/proto3#json)。 + +**语法**:在字段属性中,指定 `form` 选项为 `FORM_TEXT` 或 `FORM_JSON`。 + +例如,预定义了 `Transform`: + +```protobuf +message Transform { + Vector3 position = 1; + Vector3 rotation = 2; + Vector3 scale = 3; +} +message Vector3 { + float x = 1; + float y = 2; + float z = 3; +} +``` + +`HelloWorld.xlsx` 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Transform1 | Transform2 | +| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| {.Transform}\|{form:FORM_TEXT} | {.Transform}\|{form:FORM_JSON} | +| Box's transform1 | Box's transform2 | +| position:{x:1 y:2 z:3} rotation:{x:4 y:5 z:6} scale:{x:7 y:8 z:9} | {"position":{"x":1, "y":2, "z":3}, "rotation":{"x":4, "y":5, "z":6}, "scale":{"x":7, "y":8, "z":9}} | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Transform transform_1 = 1 [(tableau.field) = {name:"Transform1" span:SPAN_INNER_CELL prop{form:FORM_TEXT}}]; + protoconf.Transform transform_2 = 2 [(tableau.field) = {name:"Transform2" span:SPAN_INNER_CELL prop:{form:FORM_JSON}}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "transform1": { + "position": { + "x": 1, + "y": 2, + "z": 3 + }, + "rotation": { + "x": 4, + "y": 5, + "z": 6 + }, + "scale": { + "x": 7, + "y": 8, + "z": 9 + } + }, + "transform2": { + "position": { + "x": 1, + "y": 2, + "z": 3 + }, + "rotation": { + "x": 4, + "y": 5, + "z": 6 + }, + "scale": { + "x": 7, + "y": 8, + "z": 9 + } + } +} +``` + +{{< /details >}} + +## 在工作表中定义结构体类型 + +在元数据表 `@TABLEAU` 中有两种 `Mode` 可以在工作表中定义结构体类型: + +- `MODE_STRUCT_TYPE`:在工作表中定义单个结构体类型。 +- `MODE_STRUCT_TYPE_MULTI`:在工作表中定义多个结构体类型。 + +### 在工作表中定义单个结构体类型 + +您应该在元数据表 `@TABLEAU` 中将 `Mode` 选项指定为 `MODE_STRUCT_TYPE`。 + +例如,`HelloWorld.xlsx` 中的工作表 `Item`: + +{{< spreadsheet "HelloWorld.xlsx" Item "@TABLEAU" >}} + +{{< sheet >}} + +| Name | Type | +| --------- | ------------------------------------------------------ | +| ID | uint32 | +| Num | int32 | +| FruitType | enum<.FruitType> | +| Feature | []int32 | +| Prop | map | +| Detail | {enum<.ItemType> Type, string Name, string Desc}Detail | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| ----- | ---------------- | +| Item | MODE_STRUCT_TYPE | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx"}; + +// 从工作表生成:Item。 +message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + protoconf.FruitType fruit_type = 3 [(tableau.field) = {name:"FruitType"}]; + repeated int32 feature_list = 4 [(tableau.field) = {name:"Feature" layout:LAYOUT_INCELL}]; + map prop_map = 5 [(tableau.field) = {name:"Prop" layout:LAYOUT_INCELL}]; + Detail detail = 6 [(tableau.field) = {name:"Detail" span:SPAN_INNER_CELL}]; + message Detail { + protoconf.ItemType type = 1 [(tableau.field) = {name:"Type"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; + } +} +``` + +{{< /details >}} + +### 在工作表中定义多个结构体类型 + +> 一个块定义一个结构体类型,它是一系列连续的非空行。 +> 因此不同的块由一个或多个空行分隔。 + +您应该在元数据表 `@TABLEAU` 中将 `Mode` 选项指定为 `MODE_STRUCT_TYPE_MULTI`。 + +例如,`HelloWorld.xlsx` 中的工作表 `Item`: + +{{< spreadsheet "HelloWorld.xlsx" Item "@TABLEAU" >}} + +{{< sheet >}} + +| Tree | Tree note | +| --------- | ------------------ | +| Name | Type | +| ID | uint32 | +| Num | int32 | +| | | +| Pet | Pet note | +| Name | Type | +| Kind | int32 | +| Tip | []string | +| | | +| FruitShop | FruitShop note | +| Name | Type | +| FruitType | enum<.FruitType> | +| Prop | map | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| ----- | ---------------------- | +| Item | MODE_STRUCT_TYPE_MULTI | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx"}; + +message Tree { + option (tableau.struct) = {name:"StructType" note:"Tree note"}; + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +message Pet { + option (tableau.struct) = {name:"StructType" note:"Pet note"}; + int32 kind = 1 [(tableau.field) = {name:"Kind"}]; + repeated string tip_list = 2 [(tableau.field) = {name:"Tip" layout:LAYOUT_INCELL}]; +} +message FruitShop { + option (tableau.struct) = {name:"StructType" note:"FruitShop note"}; + protoconf.FruitType fruit_type = 1 [(tableau.field) = {name:"FruitType"}]; + map prop_map = 2 [(tableau.field) = {name:"Prop" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +### 指定 Number 列 + +在 `Number` 列中,您可以指定自定义的唯一字段编号。 + +例如,`HelloWorld.xlsx` 中的工作表 `Item`: + +{{< spreadsheet "HelloWorld.xlsx" Item "@TABLEAU" >}} + +{{< sheet >}} + +| Number | Name | Type | +| ------ | --------- | ---------------- | +| 1 | ID | uint32 | +| 20 | Num | int32 | +| 30 | FruitType | enum<.FruitType> | + +{{< /sheet >}} + +{{< sheet >}} + +| Sheet | Mode | +| ----- | ---------------- | +| Item | MODE_STRUCT_TYPE | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx"}; + +// 从工作表生成:Item。 +message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 20 [(tableau.field) = {name:"Num"}]; + protoconf.FruitType fruit_type = 30 [(tableau.field) = {name:"FruitType"}]; +} +``` + +{{< /details >}} diff --git a/content/zh/docs/excel/union.md b/content/zh/docs/excel/union.md new file mode 100644 index 000000000..3c4d3e662 --- /dev/null +++ b/content/zh/docs/excel/union.md @@ -0,0 +1,124 @@ +--- +title: "联合类型" +description: "Excel 联合类型指南。" +lead: "本指南演示 Excel 联合类型的不同特性。" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 7104 +toc: true +--- + +## 理论 + +在 protoconf 中,`union` 类型意味着**标记联合**:一种用于保存可以采用几种不同但固定类型的值的数据结构。任何时候只能使用其中一种类型,并且一个**标记**字段明确指示正在使用哪一种类型。更多详细信息可以从 wikipedia [标记联合](https://en.wikipedia.org/wiki/Tagged_union) 中学习。 + +不同编程语言中的**标记联合**: + +- C++:[std::variant](https://en.cppreference.com/w/cpp/utility/variant)。 +- Rust:[定义枚举](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html)。 + +Tableau 使用 protobuf `message` 将 `enum` 类型和 [`oneof`](https://protobuf.dev/programming-guides/proto3/#oneof) 类型捆绑在一起来实现**标记联合**。默认情况下,每个枚举值(> 0)都绑定到 [`oneof`](https://protobuf.dev/programming-guides/proto3/#oneof) 类型中具有相同标签号的字段。 + +## 联合类型定义 + +例如,*common.proto* 中预定义的联合类型 `Target`: + +```protobuf +// 预定义联合类型。 +message Target { + option (tableau.union) = true; + + Type type = 9999 [(tableau.field) = { name: "Type" }]; + oneof value { + option (tableau.oneof) = { + field: "Field" + }; + Pvp pvp = 1; // 绑定到枚举值 1:TYPE_PVP。 + Pve pve = 2; // 绑定到枚举值 2:TYPE_PVE。 + Story story = 3; // 绑定到枚举值 3:TYPE_STORY。 + Skill skill = 4; // 绑定到枚举值 4:TYPE_SKILL。 + } + + enum Type { + TYPE_NIL = 0; + TYPE_PVP = 1 [(tableau.evalue) = { name: "PVP" }]; + TYPE_PVE = 2 [(tableau.evalue) = { name: "PVE" }]; + TYPE_STORY = 3 [(tableau.evalue) = { name: "Story" }]; + TYPE_SKILL = 4 [(tableau.evalue) = { name: "Skill" }]; + } + message Pvp { + int32 type = 1; // 标量 + int64 damage = 2; // 标量 + repeated protoconf.FruitType types = 3; // 单元格内枚举列表 + } + message Pve { + Mission mission = 1; // 单元格内结构体 + repeated int32 heros = 2; // 单元格内列表 + map dungeons = 3; // 单元格内字典 + + message Mission { + int32 id = 1; + uint32 level = 2; + int64 damage = 3; + } + } + message Story { + protoconf.Item cost = 1; // 单元格内预定义结构体 + map fruits = 2; // 单元格内字典,值为枚举类型 + map flavors = 3; // 单元格内字典,键为枚举类型 + message Flavor { + protoconf.FruitFlavor key = 1 [(tableau.field) = { name: "Key" }]; + int32 value = 2 [(tableau.field) = { name: "Value" }]; + } + } + message Skill { + int32 id = 1; // 标量 + int64 damage = 2; // 标量 + // 无字段标签 3 + } +} +``` + +## 列表中的预定义联合类型 + +> 基于 [预定义联合类型 `Target`]({{< relref "union/#union-definition" >}})。 + +*HelloWorld.xlsx* 中的工作表 `TaskConf`: + +{{< spreadsheet "HelloWorld.xlsx" Apple "@TABLEAU" >}} + +{{< sheet colored>}} + +| ID | Target1Type | Target1Field1 | Target1Field2 | Target1Field3 | Target2Type | Target2Field1 | Target2Field2 | Target2Field3 | +| ---------------- | --------------------------- | ---------------- | ---------------- | ------------------- | ------------------ | ---------------- | ---------------- | ---------------- | +| map | [.Target]enum<.Target.Type> | union | union | union | enum<.Target.Type> | union | union | union | +| ID | Target1's type | Target1's field1 | Target1's field2 | Target1's field3 | Target2's type | Target2's field1 | Target2's field2 | Target2's field3 | +| 1 | PVP | 1 | 10 | Apple,Orange,Banana | PVE | 1,100,999 | 1,2,3 | 1:10,2:20,3:30 | +| 2 | Story | 1001,10 | 1:Apple,2:Orange | Fragrant:1,Sour:2 | Skill | 1 | 2 | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +## 字典中的预定义联合类型 + +> 基于 [预定义联合类型 `Target`]({{< relref "union/#union-definition" >}})。 + +*HelloWorld.xlsx* 中的工作表 `TaskConf`: + +{{< spreadsheet "HelloWorld.xlsx" Apple "@TABLEAU" >}} + +{{< sheet colored>}} + +| ID | Target1Type | Target1Field1 | Target1Field2 | Target1Field3 | Target2Type | Target2Field1 | Target2Field2 | Target2Field3 | +| ---------------- | --------------------------- | ---------------- | ---------------- | ------------------- | ---------------- | ---------------- | ------------------- | ---------------- | ---------------- | +| map | [.Target]enum<.Target.Type> | union | union | union | enum<.Target.Type> | union | union | union | +| ID | Target1's type | Target1's field1 | Target1's field2 | Target1's field3 | Target2's type | Target2's field1 | Target2's field2 | Target2's field3 | +| 1 | PVP | 1 | 10 | Apple,Orange,Banana | PVE | 1,100,999 | 1,2,3 | 1:10,2:20,3:30 | +| 2 | Story | 1001,10 | 1:Apple,2:Orange | Fragrant:1,Sour:2 | Skill | 1 | 2 | | + +{{< /sheet >}} + +{{< /spreadsheet >}} diff --git a/content/zh/docs/excel/wellknown-types.md b/content/zh/docs/excel/wellknown-types.md new file mode 100644 index 000000000..048be8972 --- /dev/null +++ b/content/zh/docs/excel/wellknown-types.md @@ -0,0 +1,578 @@ +--- +title: "知名类型" +description: "知名类型指南。" +lead: "本指南演示知名类型的不同特性。" +date: 2026-01-09T14:00:00+08:00 +lastmod: 2026-01-09T14:00:00+08:00 +draft: false +images: [] +weight: 7108 +toc: true +--- + +## 日期时间 + +### 日期时间 + +> 请参阅 [基础:日期时间 →]({{< relref "../basics/wellknown-types/#datetime" >}}) + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| BeginDatetime | EndDatetime | Datetime | +| ------------------- | ------------------- | --------------------------------------- | +| datetime | datetime | []datetime | +| Begin datetime | End datetime | Datetime | +| 2020-01-01 10:25:00 | 2022-10-10 05:10:00 | 2020-01-01 10:25:00,2022-10-10 05:10:00 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + google.protobuf.Timestamp begin_datetime = 1 [(tableau.field) = {name:"BeginDatetime"}]; + google.protobuf.Timestamp end_datetime = 2 [(tableau.field) = {name:"EndDatetime"}]; + repeated google.protobuf.Timestamp datetime_list = 3 [(tableau.field) = {name:"Datetime" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "beginDatetime": "2020-01-01T02:25:00Z", + "endDatetime": "2022-10-09T21:10:00Z", + "datetimeList": [ + "2020-01-01T02:25:00Z", + "2022-10-09T21:10:00Z" + ] +} +``` + +{{< /details >}} + +### 日期 + +> 请参阅 [基础:日期时间 →]({{< relref "../basics/wellknown-types/#datetime" >}}) + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| BeginDate | EndDate | Date | +| ---------- | -------- | ------------------- | +| date | date | []date | +| Begin date | End date | Date | +| 2020-01-01 | 20221010 | 2020-01-01,20221010 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + google.protobuf.Timestamp begin_date = 1 [(tableau.field) = {name:"BeginDate"}]; + google.protobuf.Timestamp end_date = 2 [(tableau.field) = {name:"EndDate"}]; + repeated google.protobuf.Timestamp date_list = 3 [(tableau.field) = {name:"Date" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "beginDate": "2019-12-31T16:00:00Z", + "endDate": "2022-10-09T16:00:00Z", + "dateList": [ + "2019-12-31T16:00:00Z", + "2022-10-09T16:00:00Z" + ] +} +``` + +{{< /details >}} + +### 时间 + +> 请参阅 [基础:日期时间 →]({{< relref "../basics/wellknown-types/#datetime" >}}) + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| BeginTime | EndTime | Time | +| ---------- | -------- | ------------- | +| time | time | []time | +| Begin time | End time | Time | +| 10:25:00 | 1125 | 10:25:00,1125 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + google.protobuf.Duration begin_time = 1 [(tableau.field) = {name:"BeginTime"}]; + google.protobuf.Duration end_time = 2 [(tableau.field) = {name:"EndTime"}]; + repeated google.protobuf.Duration time_list = 3 [(tableau.field) = {name:"Time" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "beginTime": "37500s", + "endTime": "41100s", + "timeList": [ + "37500s", + "41100s" + ] +} +``` + +{{< /details >}} + +## 持续时间 + +> 请参阅 [基础:持续时间 →]({{< relref "../basics/wellknown-types/#duration" >}}) + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Duration1 | Duration2 | Duration | +| ---------- | ---------- | ---------------- | +| duration | duration | []duration | +| Duration 1 | Duration 2 | Duration | +| 1h2m3s | 4ms5us6ns | 1h2m3s,4ms5us6ns | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + google.protobuf.Duration duration_1 = 1 [(tableau.field) = {name:"Duration1"}]; + google.protobuf.Duration duration_2 = 2 [(tableau.field) = {name:"Duration2"}]; + repeated google.protobuf.Duration duration_list = 3 [(tableau.field) = {name:"Duration" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "duration1": "3723s", + "duration2": "0.004005006s", + "durationList": [ + "3723s", + "0.004005006s" + ] +} +``` + +{{< /details >}} + +## 分数 + +> 请参阅 [基础:分数 →]({{< relref "../basics/wellknown-types/#fraction" >}}) + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| MinRatio | Ratio1 | Ratio2 | Ratio3 | Ratio4 | Ratio5 | +| --------- | ---------- | -------- | -------- | -------- | -------- | +| fraction | []fraction | fraction | fraction | fraction | fraction | +| min ratio | ratio1 | ratio 2 | ratio 3 | ratio 4 | ratio 5 | +| 1/4 | 10% | 10‰ | 10‱ | 10 | 0.01 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + tableau.Fraction min_ratio = 1 [(tableau.field) = {name:"MinRatio"}]; + repeated tableau.Fraction ratio_list = 2 [(tableau.field) = {name:"Ratio" layout:LAYOUT_HORIZONTAL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "minRatio": { + "num": 1, + "den": 4 + }, + "ratioList": [ + { + "num": 10, + "den": 100 + }, + { + "num": 10, + "den": 1000 + }, + { + "num": 10, + "den": 10000 + }, + { + "num": 10, + "den": 1 + }, + { + "num": 1, + "den": 100 + } + ] +} +``` + +{{< /details >}} + +## 比较器 + +> 请参阅 [基础:比较器 →]({{< relref "../basics/wellknown-types/#comparator" >}}) + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| MinRatio | Ratio1 | Ratio2 | Ratio3 | Ratio4 | Ratio5 | +| ---------- | ------------ | ---------- | ---------- | ---------- | ---------- | +| comparator | []comparator | comparator | comparator | comparator | comparator | +| min ratio | ratio1 | ratio 2 | ratio 3 | ratio 4 | ratio 5 | +| !=1/4 | <10% | <=10‰ | >10‱ | >=10 | ==3/5 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + tableau.Comparator min_ratio = 1 [(tableau.field) = {name:"MinRatio"}]; + repeated tableau.Comparator ratio_list = 2 [(tableau.field) = {name:"Ratio" layout:LAYOUT_HORIZONTAL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "minRatio": { + "sign": "SIGN_NOT_EQUAL", + "value": { + "num": 1, + "den": 4 + } + }, + "ratioList": [ + { + "sign": "SIGN_LESS", + "value": { + "num": 10, + "den": 100 + } + }, + { + "sign": "SIGN_LESS_OR_EQUAL", + "value": { + "num": 10, + "den": 1000 + } + }, + { + "sign": "SIGN_GREATER", + "value": { + "num": 10, + "den": 10000 + } + }, + { + "sign": "SIGN_GREATER_OR_EQUAL", + "value": { + "num": 10, + "den": 1 + } + }, + { + "sign": "SIGN_EQUAL", + "value": { + "num": 3, + "den": 5 + } + } + ] +} +``` + +{{< /details >}} + +## 版本 + +> 请参阅 [基础:版本 →]({{< relref "../basics/wellknown-types/#version" >}}) + +默认 `pattern` 为:`255.255.255`。 + +*HelloWorld.xlsx* 中的工作表 `ItemConf`: + +{{< spreadsheet "HelloWorld.xlsx" ItemConf "@TABLEAU" >}} + +{{< sheet colored >}} + +| Version | CustomVersion | IncellVersion | HorizontalVersion1 | HorizontalVersion2 | HorizontalVersion3 | +| --------------- | ----------------------------------------- | ---------------------------------- | ---------------------------------- | ------------------- | ------------------- | +| version | version\|{pattern:"99.999.99.999.99.999"} | []version\|{pattern:"999.999.999"} | []version\|{pattern:"999.999.999"} | version | version | +| default version | custom version | incell version | horizontal version1 | horizontal version2 | horizontal version3 | +| 1.0.3 | 1.2.3.4.5.6 | 1.2.3,4.5.6 | 1.0.0 | 1.2.3 | 2.0.3 | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + tableau.Version version = 1 [(tableau.field) = {name:"Version"}]; // default version + tableau.Version custom_version = 2 [(tableau.field) = {name:"CustomVersion" prop:{pattern:"99.999.99.999.99.999"}}]; // custom version + repeated tableau.Version incell_version_list = 3 [(tableau.field) = {name:"IncellVersion" layout:LAYOUT_INCELL prop:{pattern:"999.999.999"}}]; // incell version + repeated tableau.Version horizontal_version_list = 4 [(tableau.field) = {name:"HorizontalVersion" layout:LAYOUT_HORIZONTAL prop:{pattern:"999.999.999"}}]; // horizontal version +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "version": { + "str": "1.0.3", + "val": "65539", + "major": 1, + "minor": 0, + "patch": 3, + "others": [] + }, + "customVersion": { + "str": "1.2.3.4.5.6", + "val": "10020300405006", + "major": 1, + "minor": 2, + "patch": 3, + "others": [ + 4, + 5, + 6 + ] + }, + "incellVersionList": [ + { + "str": "1.2.3", + "val": "1002003", + "major": 1, + "minor": 2, + "patch": 3, + "others": [] + }, + { + "str": "4.5.6", + "val": "4005006", + "major": 4, + "minor": 5, + "patch": 6, + "others": [] + } + ], + "horizontalVersionList": [ + { + "str": "1.0.0", + "val": "1000000", + "major": 1, + "minor": 0, + "patch": 0, + "others": [] + }, + { + "str": "1.2.3", + "val": "1002003", + "major": 1, + "minor": 2, + "patch": 3, + "others": [] + }, + { + "str": "2.0.3", + "val": "2000003", + "major": 2, + "minor": 0, + "patch": 3, + "others": [] + } + ] +} +``` + +{{< /details >}} diff --git a/content/zh/docs/help/_index.md b/content/zh/docs/help/_index.md index ecb565001..4a2eeb91d 100644 --- a/content/zh/docs/help/_index.md +++ b/content/zh/docs/help/_index.md @@ -4,6 +4,6 @@ description: "Help Doks." lead: "" date: 2020-10-06T08:49:15+00:00 lastmod: 2020-10-06T08:49:15+00:00 -draft: false +draft: true images: [] --- diff --git a/content/zh/docs/prologue/quick-start.md b/content/zh/docs/prologue/quick-start.md index 2406997c4..ff307b2db 100644 --- a/content/zh/docs/prologue/quick-start.md +++ b/content/zh/docs/prologue/quick-start.md @@ -1,69 +1,163 @@ --- title: "快速开始" -description: "This guide gets you started with Tableau in Go with a simple working example." -lead: "This guide gets you started with Tableau in Go with a simple working example." -date: 2020-11-16T13:59:39+01:00 -lastmod: 2020-11-16T13:59:39+01:00 +description: "快速开始" +lead: "如何通过tableauc将工作簿文件转换为proto和JSON文件的一页总结。" +date: 2020-11-16T13:59:39+08:00 +lastmod: 2020-11-16T13:59:39+08:00 draft: false images: [] -weight: 100 +weight: 9902 toc: true --- -## Prerequisites +## 1. 下载 tableauc + +选择合适的 tableauc(又称 Tableau 编译器)进行下载: + +
+
+

+ Windows +

+

+ + Download Windows x64 + +

+ +
+
+

+ Linux +

+

+ + Download Linux x64 + +

+ +
+ +
+ +> 更多平台可在 [tableau releases →](https://github.com/tableauio/tableau/releases) 获取。 + +## 2. 添加工作簿 + +添加 **HelloWorld.xlsx** 文件,包含两个工作表: + +- `Item`:将以下数据复制到该工作表。 +- `@TABLEAU`:现在可以留空。它是 tableau 的 [元表 →]({{< relref "../excel/metasheet" >}}),用于指定解析器选项。 + +{{< spreadsheet "HelloWorld.xlsx" Item "@TABLEAU" >}} + +{{< sheet colored>}} + +| ID | Name | Desc | +| ---------------- | ----------- | ----------------------------- | +| map | string | string | +| Item 的 ID | Item 的名称 | Item 的描述 | +| 1 | Apple | A kind of delicious fruit. | +| 2 | Orange | A kind of sour fruit. | +| 3 | Banana | A kind of calorie-rich fruit. | + +{{< /sheet >}} + +{{< sheet >}} + +| | | | +| --- | --- | --- | +| | | | +| | | | +| | | | + +{{< /sheet >}} + +{{< /spreadsheet >}} + +## 3. 运行 tableauc + +执行命令:`./tableauc HelloWorld.xlsx` + +然后会生成 *hello_world.proto* 和 *Item.json* 文件: + +{{< details "hello_world.proto" open >}} + +```protobuf +syntax = "proto3"; + +package protoconf; + +import "tableau/protobuf/tableau.proto"; + +option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4}; + +message Item { + option (tableau.worksheet) = {name:"Item"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; + } +} +``` + +{{< /details >}} + +{{< details "Item.json" open >}} + +```json +{ + "itemMap": { + "1": { + "id": 1, + "name": "Apple", + "desc": "A kind of delicious fruit." + }, + "2": { + "id": 2, + "name": "Orange", + "desc": "A kind of sour fruit." + }, + "3": { + "id": 3, + "name": "Banana", + "desc": "A kind of calorie-rich fruit." + } + } +} +``` -- [Go](https://golang.org/), any one of the **three latest major** [releases of Go](https://golang.org/doc/devel/release.html). - - For installation instructions, see Go’s [Getting Started](https://golang.org/doc/install) guide. -- [Protocol buffer](https://developers.google.com/protocol-buffers) compiler, `protoc`, [version 3](https://developers.google.com/protocol-buffers/docs/proto3). - - For installation instructions, see [Protocol Buffer Compiler Installation](https://grpc.io/docs/protoc-installation/). -- **Go plugins** for the protocol compiler: - 1. Install the protocol compiler plugins for Go using the following commands: +{{< /details >}} - ```bash - go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 - ``` - - 2. Update your PATH so that the protoc compiler can find the plugins: - - ```bash - export PATH="$PATH:$(go env GOPATH)/bin" - ``` - -## Get the example code - -The example code is part of the [tableau/demo](https://github.com/tableauio/demo) repo. - -1. [Download the repo as a zip file](https://github.com/tableauio/demo/archive/refs/heads/master.zip) and unzip it, or clone the repo: - - ```bash - git clone https://github.com/tableauio/demo - ``` - -2. Change to the quick start example directory: - - ```bash - cd demo/examples/helloworld - ``` - -## Run the example - -From the `examples/helloworld` directory: - -1. Change dir to **excel2proto**, compile and execute: - - ```bash - go run main.go - ``` - - Then proto files will be generated to `examples/helloworld/proto`. - -2. Change dir to **excel2conf**, generate `*.pb.go` and then compile and execute: - - ```bash - bash gen.sh - go run main.go - ``` - - Then `*.pb.go` files will be generated to `examples/helloworld/protoconf`, and JSON files will be generated to `examples/helloworld/excel2conf/_out`. - -Congratulations! You’ve just run a modern configuration converter application with Tableau. +恭喜!您已经成功使用 `tableauc` 将工作簿转换为 proto 和 JSON 文件。 \ No newline at end of file diff --git a/content/zh/docs/tutorial/_index.md b/content/zh/docs/tutorial/_index.md new file mode 100644 index 000000000..71afa217c --- /dev/null +++ b/content/zh/docs/tutorial/_index.md @@ -0,0 +1,10 @@ +--- +title : "教程" +description: "Tableau 教程" +lead: "" +date: 2026-01-09T08:48:45+08:00 +lastmod: 2026-01-09T08:48:45+08:00 +draft: false +images: [] +weight: 9800 +--- diff --git a/content/zh/docs/tutorial/config.md b/content/zh/docs/tutorial/config.md new file mode 100644 index 000000000..007322efc --- /dev/null +++ b/content/zh/docs/tutorial/config.md @@ -0,0 +1,212 @@ +--- +title: "Tableauc 配置" +description: "Tableauc 配置详细信息" +lead: "" +date: 2026-01-09T13:59:39+08:00 +lastmod: 2026-01-09T13:59:39+08:00 +draft: false +images: [] +weight: 9801 +toc: true +--- + +## config.yaml + +创建一个名为 *config.yaml* 的文件,并将以下配置复制到其中: + +```yaml +# locale BCP 47 语言标签:en, zh。 +lang: en +# location 表示地理区域中使用的时区偏移量集合。 +# - 如果 name 为 "" 或 "UTC",LoadLocation 返回 UTC。 +# - 如果 name 为 "Local",LoadLocation 返回 Local。 +# - 否则,name 被视为对应于 IANA 时区数据库中文件的 +# 位置名称,例如 "America/New_York"、"Asia/Shanghai" 等。 +# +# 参见 https://go.dev/src/time/zoneinfo_abbrs_windows.go。 +locationName: Local +# 配置您的自定义缩写。开箱即用,"ID" -> "id" 已自动配置。 +# 例如,如果您配置 K8s -> k8s,那么 PascalCase 中的字段名 "InK8s" +# 将转换为 snake_case "in_k8s" 但不是 "in_k_8_s"。 +acronyms: {} +# 日志选项。 +log: + # 日志模式:SIMPLE, FULL。 + mode: SIMPLE + # 日志级别:DEBUG, INFO, WARN, ERROR。 + level: INFO + # 日志文件名:如果您想将日志消息写入文件,请设置此项。 + filename: "" + # 日志输出:CONSOLE, FILE 和 MULTI。 + sink: CONSOLE +# 生成 proto 文件的选项。 +proto: + input: + # 工作表和工作簿的标题选项。 + header: + # 工作表中列名定义的确切行号。 + namerow: 1 + # 工作表中列类型定义的确切行号。 + typerow: 2 + # 工作表中列注释的确切行号。 + noterow: 3 + # 工作表中数据开始的行号。 + datarow: 4 + # 单元格中列名定义的行号。 + # 值 0 表示整个单元格。 + nameline: 0 + # 单元格中列类型定义的行号。 + # 值 0 表示整个单元格。 + typeline: 0 + # 单元格中列注释定义的行号。 + # 值 0 表示整个单元格。 + noteline: 0 + # 分隔符,用于分隔: + # - 单元格内列表元素(标量或结构体)。 + # - 单元格内字典项。 + # + # 默认值:"," + sep: "" + # 子分隔符,用于分隔: + # - 每个单元格内字典项的键值对。 + # - 每个单元格内结构体列表元素的结构体字段。 + # + # 默认值::" + subsep: "" + # proto 路径用于搜索在 proto 源文件的 import 语句中引用的依赖项。 + # 如果未提供导入路径,则假定 "."(当前目录)是唯一的导入路径。 + protoPaths: [.] + # protoFiles 中的枚举和消息可以在 Excel/CSV/XML/YAML 中作为 + # 公共类型使用。 + protoFiles: [] + # 指定要解析的输入文件格式。如果未设置,它将识别所有格式。 + # 可用格式:"xlsx"、"csv"、"xml" 和 "yaml"。 + formats: [xlsx] + # 指定仅处理这些子目录(相对于输入目录)。 + subdirs: [] + # 指定重写子目录路径(相对于输入目录)。 + subdirRewrites: {} + # 递归遍历目录时是否遵循符号链接。 + # 警告:请小心使用此选项,它可能导致无限循环。 + followSymlink: false + # 指定元数据表名称。 + metasheetName: "@TABLEAU" + # 指定第一遍模式,在生成指定配置文件时解析预定义类型。 + # 在底层,解析的预定义类型将在第二遍中被识别和使用。 + # + # 第一遍模式可以是: + # + # - "":默认模式,基于指定的配置文件解析。 + # - "normal":基于所有配置文件解析。 + # - "advanced":基于所有先前生成的 proto 文件解析。 + firstPassMode: "" + # 指定消息的名称模式(支持正则表达式)。如果生成的 + # 消息名称不匹配此模式,将报告错误。 + # 示例:"Conf$" + messagerPattern: "" + output: + # 指定生成的 proto 文件的子目录(相对于输出目录)。 + subdir: "" + # 文件名中的目录分隔符 `/` 或 `\` 被替换为 "__"。 + filenameWithSubdirPrefix: false + # 为每个生成的 proto 文件名附加后缀。 + filenameSuffix: "" + # 指定 proto 文件选项。 + # 示例:go_package、csharp_namespace... + fileOptions: {} + # 是否在每个枚举值名称前添加枚举类型的 "UPPER_SNAKE_CASE" 前缀。 + # + # 如果设置,枚举值名称将添加 "ENUM_TYPE_" 前缀。例如: + # 枚举 ItemType 有一个值 "EQUIP",则转换为 "ITEM_TYPE_EQUIP"。 + # 如果枚举值名称已经添加了 "ENUM_TYPE_" 前缀,则不会 + # 再次添加前缀。 + enumValueWithPrefix: false +# 生成配置文件的选项。 +conf: + input: + # proto 路径用于搜索在 proto 源文件的 import 语句中引用的依赖项。 + # 如果未提供导入路径,则假定 "."(当前目录)是唯一的导入路径。 + protoPaths: [.] + # 要解析以生成配置的文件。 + # + # 注意: + # - 如果未设置,则识别 "*.proto" 模式。 + # - 支持通配符模式,可以使用通配符字符指定 + # 文件名集合。 + protoFiles: ["*.proto"] + # 不解析以生成配置的文件。 + # + # 注意:支持通配符模式,可以使用通配符字符指定 + # 文件名集合。 + excludedProtoFiles: [] + # 指定要解析的输入文件格式。如果未设置,它将识别所有格式。 + # 可用格式:"xlsx"、"csv"、"xml" 和 "yaml"。 + formats: [xlsx] + # 指定仅处理这些子目录(相对于 proto 文件中的工作簿名称选项)。 + subdirs: [] + # 指定重写子目录路径(相对于 proto 文件中的工作簿名称选项)。 + subdirRewrites: {} + output: + # 指定生成的配置文件的子目录(相对于输出目录)。 + subdir: "" + # 指定生成的配置文件格式。如果未设置,它将生成所有格式。 + # 参考:https://protobuf.dev/programming-guides/techniques/#suffixes + # 可用格式:"json"、"binpb" 和 "txtpb"。 + formats: [json] + # 是否输出 JSON 的美化格式,包括多行和缩进。 + pretty: true + # EmitUnpopulated 指定是否发出未填充的字段。它不会 + # 发出未填充的 oneof 字段或未填充的扩展字段。 + # 为未填充字段发出的 JSON 值如下: + # ╔═══════╤════════════════════════════╗ + # ║ JSON │ Protobuf 字段 ║ + # ╠═══════╪════════════════════════════╣ + # ║ false │ proto3 布尔字段 ║ + # ║ 0 │ proto3 数值字段 ║ + # ║ "" │ proto3 字符串/字节字段 ║ + # ║ null │ proto2 标量字段 ║ + # ║ null │ 消息字段 ║ + # ║ [] │ 列表字段 ║ + # ║ {} │ 字典字段 ║ + # ╚═══════╧════════════════════════════╝ + # + # 注意:FieldPresence 设置为 true 的工作表忽略此选项。 + # + # 参考:https://github.com/protocolbuffers/protobuf/blob/main/docs/field_presence.md + emitUnpopulated: false + # 是否以带时区的字符串格式发出时间戳(由偏移量指示)。 + emitTimezones: false + # 是否在 JSON 字段名称中使用 proto 字段名而不是 lowerCamelCase 名称。 + useProtoNames: false + # 是否将枚举值作为数字发出。 + useEnumNumbers: false + # 指定试运行模式: + # - "patch":如果指定了工作表选项:修补 (PATCH_MERGE) 和分散 + dryRun: "" +``` + +### proto.input.header.seq + +默认值:`,` + +**全局级别**分隔符,用于分隔: +- 单元格内列表元素(标量或结构体)。 +- 单元格内字典项。 + +还支持**工作表级别**和**字段级别**分隔符选项: +- [元数据表中的工作表级别分隔符](../../excel/metasheet/#option-sep) +- [字段属性中的字段级别分隔符](../../excel/field-property/#option-sep) + +### proto.input.header.subseq + +默认值:`:` + +**全局级别**子分隔符,用于分隔: + +- 每个单元格内字典项的键值对。 +- 每个单元格内结构体列表元素的结构体字段。 + +还支持**工作表级别**和**字段级别**子分隔符选项: + +- [元数据表中的工作表级别子分隔符](../../excel/metasheet/#option-subsep) +- [字段属性中的字段级别子分隔符](../../excel/field-property/#option-subsep) diff --git a/content/zh/docs/xml/_index.md b/content/zh/docs/xml/_index.md new file mode 100644 index 000000000..883dd3878 --- /dev/null +++ b/content/zh/docs/xml/_index.md @@ -0,0 +1,10 @@ +--- +title : "XML" +description: "XML 指南。" +lead: "XML 指南。" +date: 2026-01-09T08:48:45+08:00 +lastmod: 2026-01-09T08:48:45+08:00 +draft: false +images: [] +weight: 5000 +--- diff --git a/content/zh/docs/xml/enum.md b/content/zh/docs/xml/enum.md new file mode 100644 index 000000000..53184ca6f --- /dev/null +++ b/content/zh/docs/xml/enum.md @@ -0,0 +1,77 @@ +--- +title: "枚举" +description: "XML 枚举指南。" +lead: "XML 枚举指南。" +date: 2026-01-09T11:21:01+08:00 +lastmod: 2026-01-09T11:21:01+08:00 +draft: false +images: [] +weight: 5001 +toc: true +--- + +## 使用预定义枚举类型 + +*common.proto* 中的枚举类型 `FruitType` 预定义为: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1 + FRUIT_TYPE_APPLE + A kind of delicious fruit. + +``` + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.FruitType type = 2 [(tableau.field) = {name:"Type"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" open >}} + +```json +{ + "id": 1, + "type": "FRUIT_TYPE_APPLE", + "desc": "A kind of delicious fruit." +} +``` + +{{< /details >}} diff --git a/content/zh/docs/xml/list.md b/content/zh/docs/xml/list.md new file mode 100644 index 000000000..3bfed6363 --- /dev/null +++ b/content/zh/docs/xml/list.md @@ -0,0 +1,551 @@ +--- +title: "列表" +description: "XML 列表指南。" +lead: "XML 列表指南。" +date: 2026-01-09T15:21:01+08:00 +lastmod: 2026-01-09T15:21:01+08:00 +draft: false +images: [] +weight: 5200 +toc: true +--- + +## 标量列表 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1 + 2 + 3 + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated int32 item_list = 1 [(tableau.field) = {name:"Item"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + 1, + 2, + 3 + ] +} +``` + +{{< /details >}} + +## 枚举列表 + +*common.proto* 中的枚举类型 `FruitType` 预定义为: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + FRUIT_TYPE_APPLE + FRUIT_TYPE_ORANGE + FRUIT_TYPE_BANANA + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType fruit_list = 1 [(tableau.field) = {name:"Fruit"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "fruitList": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] +} +``` + +{{< /details >}} + +## 单元格内标量列表 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1, 2, 3 + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated int32 item_list = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + 1, + 2, + 3 + ] +} +``` + +{{< /details >}} + +## 单元格内枚举列表 + +*common.proto* 中的枚举类型 `FruitType` 预定义为: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + FRUIT_TYPE_APPLE, FRUIT_TYPE_ORANGE, FRUIT_TYPE_BANANA + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType fruit_list = 1 [(tableau.field) = {name:"Fruit" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "fruitList": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] +} +``` + +{{< /details >}} + +## 结构体列表 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item item_list = 1 [(tableau.field) = {name:"Item"}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ] +} +``` + +{{< /details >}} + +## 预定义结构体列表 + +*common.proto* 中的 `Item` 预定义为: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.Item item_list = 1 [(tableau.field) = {name:"Item"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemList": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ] +} +``` + +{{< /details >}} + +## 列表中的列表 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Country country_list = 1 [(tableau.field) = {name:"Country"}]; + message Country { + string country = 1 [(tableau.field) = {name:"Country"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + repeated Item item_list = 3 [(tableau.field) = {name:"Item"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countryList": [ + { + "country": "USA", + "desc": "A country in North America.", + "itemList": [ + { + "name": "apple", + "num": 10 + }, + { + "name": "orange", + "num": 20 + } + ] + }, + { + "country": "China", + "desc": "A country in East Asia.", + "itemList": [ + { + "name": "apple", + "num": 100 + }, + { + "name": "orange", + "num": 200 + } + ] + } + ] +} +``` + +{{< /details >}} + +## 列表中的字典 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Country country_list = 1 [(tableau.field) = {name:"Country"}]; + message Country { + string country = 1 [(tableau.field) = {name:"Country"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + map item_map = 3 [(tableau.field) = {name:"Item" key:"Name"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countryList": [ + { + "country": "USA", + "desc": "A country in North America.", + "itemMap": { + "apple": { + "name": "apple", + "num": 10 + }, + "orange": { + "name": "orange", + "num": 20 + } + } + }, + { + "country": "China", + "desc": "A country in East Asia.", + "itemMap": { + "apple": { + "name": "apple", + "num": 100 + }, + "orange": { + "name": "orange", + "num": 200 + } + } + } + ] +} +``` + +{{< /details >}} diff --git a/content/zh/docs/xml/map.md b/content/zh/docs/xml/map.md new file mode 100644 index 000000000..deb319ff8 --- /dev/null +++ b/content/zh/docs/xml/map.md @@ -0,0 +1,470 @@ +--- +title: "字典" +description: "XML 字典指南。" +lead: "XML 字典指南。" +date: 2026-01-09T15:21:01+08:00 +lastmod: 2026-01-09T15:21:01+08:00 +draft: false +images: [] +weight: 5300 +toc: true +--- + +## 单元格内标量字典 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1:dog,2:bird,3:cat + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": "dog", + "2": "bird", + "3": "cat" + } +} +``` + +{{< /details >}} + +## 单元格内枚举字典 + +*common.proto* 中的枚举类型 Enum type `FruitType` 和 `FruitFlavor` 预定义为: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +enum FruitFlavor { + FRUIT_FLAVOR_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_FLAVOR_FRAGRANT = 1 [(tableau.evalue).name = "Fragrant"]; + FRUIT_FLAVOR_SOUR = 2 [(tableau.evalue).name = "Sour"]; + FRUIT_FLAVOR_SWEET = 3 [(tableau.evalue).name = "Sweet"]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + FRUIT_TYPE_APPLE:FRUIT_FLAVOR_FRAGRANT, FRUIT_TYPE_ORANGE:FRUIT_FLAVOR_SOUR + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {name:"Item" key:"@key" layout:LAYOUT_INCELL span:SPAN_INNER_CELL}]; + message ItemValue { + protoconf.FruitType key = 1 [(tableau.field) = {name:"@key"}]; + protoconf.FruitFlavor value = 2 [(tableau.field) = {name:"@value"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "key": "FRUIT_TYPE_APPLE", + "value": "FRUIT_FLAVOR_FRAGRANT" + }, + "3": { + "key": "FRUIT_TYPE_ORANGE", + "value": "FRUIT_FLAVOR_SOUR" + } + } +} +``` + +{{< /details >}} + +## 结构体字典 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {name:"Item" key:"Name"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "apple": { + "name": "apple", + "num": 10 + }, + "banana": { + "name": "banana", + "num": 30 + }, + "orange": { + "name": "orange", + "num": 20 + } + } +} +``` + +{{< /details >}} + +## 枚举键结构体字典 + +*common.proto* 中的枚举类型 `FruitType` 预定义为: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map item_map = 1 [(tableau.field) = {name:"Item" key:"Key"}]; + message EnumItem { + protoconf.FruitType key = 1 [(tableau.field) = {name:"Key"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + int32 num = 3 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "itemMap": { + "1": { + "key": "FRUIT_TYPE_APPLE", + "name": "apple", + "num": 10 + }, + "3": { + "key": "FRUIT_TYPE_ORANGE", + "name": "orange", + "num": 20 + }, + "4": { + "key": "FRUIT_TYPE_BANANA", + "name": "banana", + "num": 30 + } + } +} +``` + +{{< /details >}} + +## 列表中的字典 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map country_map = 1 [(tableau.field) = {name:"Country" key:"Key"}]; + message Country { + string key = 1 [(tableau.field) = {name:"Key"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + repeated Item item_list = 3 [(tableau.field) = {name:"Item"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countryMap": { + "China": { + "key": "China", + "desc": "A country in East Asia.", + "itemList": [ + { + "name": "apple", + "num": 100 + }, + { + "name": "orange", + "num": 200 + } + ] + }, + "USA": { + "key": "USA", + "desc": "A country in North America.", + "itemList": [ + { + "name": "apple", + "num": 10 + }, + { + "name": "orange", + "num": 20 + } + ] + } + } +} +``` + +{{< /details >}} + +## 字典中的字典 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map country_map = 1 [(tableau.field) = {name:"Country" key:"Key"}]; + message Country { + string key = 1 [(tableau.field) = {name:"Key"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + map item_map = 3 [(tableau.field) = {name:"Item" key:"Name"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countryMap": { + "China": { + "key": "China", + "desc": "A country in East Asia.", + "itemMap": { + "apple": { + "name": "apple", + "num": 100 + }, + "orange": { + "name": "orange", + "num": 200 + } + } + }, + "USA": { + "key": "USA", + "desc": "A country in North America.", + "itemMap": { + "apple": { + "name": "apple", + "num": 10 + }, + "orange": { + "name": "orange", + "num": 20 + } + } + } + } +} +``` + +{{< /details >}} diff --git a/content/zh/docs/xml/scalar.md b/content/zh/docs/xml/scalar.md new file mode 100644 index 000000000..4a14855de --- /dev/null +++ b/content/zh/docs/xml/scalar.md @@ -0,0 +1,89 @@ +--- +title: "标量" +description: "XML 标量指南。" +lead: "XML 标量指南。" +date: 2026-01-09T11:21:01+08:00 +lastmod: 2026-01-09T11:21:01+08:00 +draft: false +images: [] +weight: 5000 +toc: true +--- + +## 标量 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1 + 10 + 20 + 30 + 0.5 + 3.14159 + apple + VGFibGVhdQ== + true + +``` + +生成: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + uint64 value = 3 [(tableau.field) = {name:"Value"}]; + int64 weight = 4 [(tableau.field) = {name:"Weight"}]; + float percentage = 5 [(tableau.field) = {name:"Percentage"}]; + double ratio = 6 [(tableau.field) = {name:"Ratio"}]; + string name = 7 [(tableau.field) = {name:"Name"}]; + bytes blob = 8 [(tableau.field) = {name:"Blob"}]; + bool ok = 9 [(tableau.field) = {name:"OK"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" open >}} + +```json +{ + "id": 1, + "num": 10, + "value": "20", + "weight": "30", + "percentage": 0.5, + "ratio": 3.14159, + "name": "apple", + "blob": "VkdGaWJHVmhkUT09", + "ok": true +} +``` + +{{< /details >}} diff --git a/content/zh/docs/xml/struct.md b/content/zh/docs/xml/struct.md new file mode 100644 index 000000000..6afdb5f15 --- /dev/null +++ b/content/zh/docs/xml/struct.md @@ -0,0 +1,358 @@ +--- +title: "结构体" +description: "XML 结构体指南。" +lead: "XML 结构体指南。" +date: 2026-01-09T15:21:01+08:00 +lastmod: 2026-01-09T15:21:01+08:00 +draft: false +images: [] +weight: 5100 +toc: true +--- + +## 一般结构体 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + 1h + + + +``` + +> 提示: +> - 知名类型:[日期时间 →]({{< relref "../basics/wellknown-types/#datetime" >}}) +> - 知名类型:[持续时间 →]({{< relref "../basics/wellknown-types/#duration" >}}) + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item"}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + google.protobuf.Timestamp start_time = 2 [(tableau.field) = {name:"StartTime"}]; + google.protobuf.Duration expiry = 3 [(tableau.field) = {name:"Expiry"}]; + } + OtherItem item_2 = 2 [(tableau.field) = {name:"Item2"}]; + message OtherItem { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "startTime": "2024-10-01T02:10:10Z", + "expiry": "3600s" + }, + "item2": { + "id": 1, + "name": "gold" + } +} +``` + +{{< /details >}} + +## 重用同级结构体 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + 1h + + + 2h + + + 3h + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item"}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + google.protobuf.Timestamp start_time = 2 [(tableau.field) = {name:"StartTime"}]; + google.protobuf.Duration expiry = 3 [(tableau.field) = {name:"Expiry"}]; + } + Item new_item = 2 [(tableau.field) = {name:"NewItem"}]; + Item other_item = 3 [(tableau.field) = {name:"OtherItem"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "startTime": "2024-10-01T02:10:10Z", + "expiry": "3600s" + }, + "newItem": { + "id": 2, + "startTime": "2026-10-01T02:10:10Z", + "expiry": "7200s" + }, + "otherItem": { + "id": 3, + "startTime": "2028-10-01T02:10:10Z", + "expiry": "10800s" + } +} +``` + +{{< /details >}} + +## 预定义结构体 + +*common.proto* 中的 `Item` 预定义为: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + + + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Item item = 1 [(tableau.field) = {name:"Item"}]; + protoconf.Item item_2 = 2 [(tableau.field) = {name:"Item2"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "num": 10 + }, + "item2": { + "id": 2, + "num": 20 + } +} +``` + +{{< /details >}} + +## 单元格内结构体 + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1, 10 + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + OtherItem item_2 = 2 [(tableau.field) = {name:"Item2" span:SPAN_INNER_CELL}]; + message OtherItem { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 2, + "num": 20 + }, + "item2": { + "id": 1, + "num": 10 + } +} +``` + +{{< /details >}} + +## 单元格内预定义结构体 + +*common.proto* 中的 `Item` 预定义为: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.xml* 中的工作表 `ItemConf`: + +```xml + + + + 1, 10 + +``` + +生成: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.xml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Item item = 1 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; + protoconf.Item item_2 = 2 [(tableau.field) = {name:"Item2" span:SPAN_INNER_CELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 2, + "num": 20 + }, + "item2": { + "id": 1, + "num": 10 + } +} +``` + +{{< /details >}} diff --git a/content/zh/docs/yaml/_index.md b/content/zh/docs/yaml/_index.md new file mode 100644 index 000000000..504a394ba --- /dev/null +++ b/content/zh/docs/yaml/_index.md @@ -0,0 +1,10 @@ +--- +title : "YAML" +description: "YAML 指南。" +lead: "YAML 指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4000 +--- \ No newline at end of file diff --git a/content/zh/docs/yaml/enum.md b/content/zh/docs/yaml/enum.md new file mode 100644 index 000000000..6319c9b48 --- /dev/null +++ b/content/zh/docs/yaml/enum.md @@ -0,0 +1,75 @@ +--- +title: "枚举" +description: "YAML 枚举指南。" +lead: "YAML 枚举指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4100 +toc: true +--- + +## 使用预定义枚举类型 + +*common.proto* 中的枚举类型 `FruitType` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +ID: uint32 +Type: "enum<.FruitType>" +Desc: string + +--- +"@sheet": ItemConf +ID: 1 +Type: FRUIT_TYPE_APPLE +Desc: A kind of delicious fruit. +``` + +生成的内容: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.FruitType type = 2 [(tableau.field) = {name:"Type"}]; + string desc = 3 [(tableau.field) = {name:"Desc"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" open >}} + +```json +{ + "id": 1, + "type": "FRUIT_TYPE_APPLE", + "desc": "A kind of delicious fruit." +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/yaml/list.md b/content/zh/docs/yaml/list.md new file mode 100644 index 000000000..4c4cf0723 --- /dev/null +++ b/content/zh/docs/yaml/list.md @@ -0,0 +1,569 @@ +--- +title: "列表" +description: "YAML 列表指南。" +lead: "YAML 列表指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4400 +toc: true +--- + +## 标量列表 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: "[int32]" + +--- +"@sheet": ItemConf +Items: [1, 2, 3] +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated int32 items = 1 [(tableau.field) = {name:"Items"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": [ + 1, + 2, + 3 + ] +} +``` + +{{< /details >}} + +## 枚举列表 + +*common.proto* 中的枚举类型 `FruitType` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Fruits: "[enum<.FruitType>" + +--- +"@sheet": ItemConf +Fruits: [FRUIT_TYPE_APPLE, FRUIT_TYPE_ORANGE, FRUIT_TYPE_BANANA] +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType fruits = 1 [(tableau.field) = {name:"Fruits"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "fruits": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] +} +``` + +{{< /details >}} + +## 单元格内标量列表 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: "[]int32" + +--- +"@sheet": ItemConf +Items: "1, 2, 3" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated int32 items = 1 [(tableau.field) = {name:"Items" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": [ + 1, + 2, + 3 + ] +} +``` + +{{< /details >}} + +## 单元格内枚举列表 + +*common.proto* 中的枚举类型 `FruitType` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Fruits: "[]enum<.FruitType>" + +--- +"@sheet": ItemConf +Fruits: "FRUIT_TYPE_APPLE, FRUIT_TYPE_ORANGE, FRUIT_TYPE_BANANA" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.FruitType fruits = 1 [(tableau.field) = {name:"Fruits" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "fruits": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] +} +``` + +{{< /details >}} + +## 结构体列表 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: + "@type": "[Item]" + "@struct": + ID: uint32 + Num: int32 + +--- +"@sheet": ItemConf +Items: + - ID: 1 + Num: 10 + - ID: 2 + Num: 20 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Item items = 1 [(tableau.field) = {name:"Items"}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ] +} +``` + +{{< /details >}} + +## 预定义结构体列表 + +*common.proto* 中的 `Item` 预定义如下: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: "[.Item]" + +--- +"@sheet": ItemConf +Items: + - ID: 1 + Num: 10 + - ID: 2 + Num: 20 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.Item items = 1 [(tableau.field) = {name:"Items"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": [ + { + "id": 1, + "num": 10 + }, + { + "id": 2, + "num": 20 + } + ] +} +``` + +{{< /details >}} + +## 列表嵌套列表 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Countries: + "@type": "[Country]" + "@struct": + Country: string + Desc: string + Items: + "@type": "[Item]" + "@struct": + Name: string + Num: int32 + +--- +"@sheet": ItemConf +Countries: + - Country: USA + Desc: A country in North America. + Items: + - Name: apple + Num: 10 + - Name: orange + Num: 20 + - Country: China + Desc: A country in East Asia. + Items: + - Name: apple + Num: 100 + - Name: orange + Num: 200 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Country countries = 1 [(tableau.field) = {name:"Countries"}]; + message Country { + string country = 1 [(tableau.field) = {name:"Country"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + repeated Item items = 3 [(tableau.field) = {name:"Items"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countries": [ + { + "country": "USA", + "desc": "A country in North America.", + "items": [ + { + "name": "apple", + "num": 10 + }, + { + "name": "orange", + "num": 20 + } + ] + }, + { + "country": "China", + "desc": "A country in East Asia.", + "items": [ + { + "name": "apple", + "num": 100 + }, + { + "name": "orange", + "num": 200 + } + ] + } + ] +} +``` + +{{< /details >}} + +## 列表嵌套字典 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Countries: + "@type": "[Country]" + "@struct": + Country: string + Desc: string + Items: + "@type": "map" + "@struct": + "@key": Name + Num: int32 + +--- +"@sheet": ItemConf +Countries: + - Country: USA + Desc: A country in North America. + Items: + apple: + Num: 10 + orange: + Num: 20 + - Country: China + Desc: A country in East Asia. + Items: + apple: + Num: 100 + orange: + Num: 200 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated Country countries = 1 [(tableau.field) = {name:"Countries"}]; + message Country { + string country = 1 [(tableau.field) = {name:"Country"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + map items = 3 [(tableau.field) = {name:"Items" key:"@key"}]; + message Item { + string name = 1 [(tableau.field) = {name:"@key"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countries": [ + { + "country": "USA", + "desc": "A country in North America.", + "items": { + "apple": { + "name": "apple", + "num": 10 + }, + "orange": { + "name": "orange", + "num": 20 + } + } + }, + { + "country": "China", + "desc": "A country in East Asia.", + "items": { + "apple": { + "name": "apple", + "num": 100 + }, + "orange": { + "name": "orange", + "num": 200 + } + } + } + ] +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/yaml/map.md b/content/zh/docs/yaml/map.md new file mode 100644 index 000000000..003e21aa2 --- /dev/null +++ b/content/zh/docs/yaml/map.md @@ -0,0 +1,805 @@ +--- +title: "字典" +description: "YAML 字典指南。" +lead: "YAML 字典指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4500 +toc: true +--- + +## 标量字典 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: "map" + +--- +"@sheet": ItemConf +Items: + 1: dog + 2: bird + 3: cat +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": "dog", + "2": "bird", + "3": "cat" + } +} +``` + +{{< /details >}} + +## 枚举键字典 + +*common.proto* 中的枚举类型 `FruitType` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: "map, string>" + +--- +"@sheet": ItemConf +Items: + FRUIT_TYPE_APPLE: apple + FRUIT_TYPE_ORANGE: orange + FRUIT_TYPE_BANANA: banana +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" key:"@key" span:SPAN_INNER_CELL}]; + message ItemsValue { + protoconf.FruitType key = 1 [(tableau.field) = {name:"@key"}]; + string value = 2 [(tableau.field) = {name:"@value"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": { + "key": "FRUIT_TYPE_APPLE", + "value": "apple" + }, + "3": { + "key": "FRUIT_TYPE_ORANGE", + "value": "orange" + }, + "4": { + "key": "FRUIT_TYPE_BANANA", + "value": "banana" + } + } +} +``` + +{{< /details >}} + +## 枚举键值字典 + +*common.proto* 中的枚举类型 `FruitType` 和 `FruitFlavor` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} + +enum FruitFlavor { + FRUIT_FLAVOR_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_FLAVOR_FRAGRANT = 1 [(tableau.evalue).name = "Fragrant"]; + FRUIT_FLAVOR_SOUR = 2 [(tableau.evalue).name = "Sour"]; + FRUIT_FLAVOR_SWEET = 3 [(tableau.evalue).name = "Sweet"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: "map, enum<.FruitFlavor>>" + +--- +"@sheet": ItemConf +Items: + FRUIT_TYPE_APPLE: FRUIT_FLAVOR_FRAGRANT + FRUIT_TYPE_ORANGE: FRUIT_FLAVOR_SOUR + FRUIT_TYPE_BANANA: FRUIT_FLAVOR_SWEET +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" key:"@key" span:SPAN_INNER_CELL}]; + message ItemsValue { + protoconf.FruitType key = 1 [(tableau.field) = {name:"@key"}]; + protoconf.FruitFlavor value = 2 [(tableau.field) = {name:"@value"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": { + "key": "FRUIT_TYPE_APPLE", + "value": "FRUIT_FLAVOR_FRAGRANT" + }, + "3": { + "key": "FRUIT_TYPE_ORANGE", + "value": "FRUIT_FLAVOR_SOUR" + }, + "4": { + "key": "FRUIT_TYPE_BANANA", + "value": "FRUIT_FLAVOR_SWEET" + } + } +} +``` + +{{< /details >}} + +## 单元格内标量字典 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: + "@type": "map" + "@incell": true + +--- +"@sheet": ItemConf +Items: "1:dog,2:bird,3:cat" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" layout:LAYOUT_INCELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": "dog", + "2": "bird", + "3": "cat" + } +} +``` + +{{< /details >}} + +## 单元格内枚举字典 + +*common.proto* 中的枚举类型 `FruitType` 和 `FruitFlavor` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} + +enum FruitFlavor { + FRUIT_FLAVOR_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_FLAVOR_FRAGRANT = 1 [(tableau.evalue).name = "Fragrant"]; + FRUIT_FLAVOR_SOUR = 2 [(tableau.evalue).name = "Sour"]; + FRUIT_FLAVOR_SWEET = 3 [(tableau.evalue).name = "Sweet"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: + "@type": "map, enum<.FruitFlavor>>" + "@struct": CustomMapValue + "@incell": true + +--- +"@sheet": ItemConf +Items: "FRUIT_TYPE_APPLE:FRUIT_FLAVOR_FRAGRANT, FRUIT_TYPE_ORANGE:FRUIT_FLAVOR_SOUR" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" key:"@key" layout:LAYOUT_INCELL span:SPAN_INNER_CELL}]; + message CustomMapValue { + protoconf.FruitType key = 1 [(tableau.field) = {name:"@key"}]; + protoconf.FruitFlavor value = 2 [(tableau.field) = {name:"@value"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": { + "key": "FRUIT_TYPE_APPLE", + "value": "FRUIT_FLAVOR_FRAGRANT" + }, + "3": { + "key": "FRUIT_TYPE_ORANGE", + "value": "FRUIT_FLAVOR_SOUR" + } + } +} +``` + +{{< /details >}} + +## 结构体字典 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: + "@type": "map" + "@struct": + Name: string + Num: int32 + +--- +"@sheet": ItemConf +Items: + 1: + Name: apple + Num: 10 + 2: + Name: orange + Num: 20 + 3: + Name: banana + Num: 30 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" key:"@key"}]; + message Item { + uint32 key = 1 [(tableau.field) = {name:"@key"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + int32 num = 3 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": { + "key": 1, + "name": "apple", + "num": 10 + }, + "2": { + "key": 2, + "name": "orange", + "num": 20 + }, + "3": { + "key": 3, + "name": "banana", + "num": 30 + } + } +} +``` + +{{< /details >}} + +## 枚举键结构体字典 + +*common.proto* 中的枚举类型 `FruitType` 预定义如下: + +```protobuf +enum FruitType { + FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"]; + FRUIT_TYPE_APPLE = 1 [(tableau.evalue).name = "Apple"]; + FRUIT_TYPE_ORANGE = 3 [(tableau.evalue).name = "Orange"]; + FRUIT_TYPE_BANANA = 4 [(tableau.evalue).name = "Banana"]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: + "@type": "map, EnumItem>" + "@struct": + Name: string + Num: int32 + +--- +"@sheet": ItemConf +Items: + FRUIT_TYPE_APPLE: + Name: apple + Num: 10 + FRUIT_TYPE_ORANGE: + Name: orange + Num: 20 + FRUIT_TYPE_BANANA: + Name: banana + Num: 30 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" key:"@key"}]; + message EnumItem { + protoconf.FruitType key = 1 [(tableau.field) = {name:"@key"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + int32 num = 3 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": { + "key": "FRUIT_TYPE_APPLE", + "name": "apple", + "num": 10 + }, + "3": { + "key": "FRUIT_TYPE_ORANGE", + "name": "orange", + "num": 20 + }, + "4": { + "key": "FRUIT_TYPE_BANANA", + "name": "banana", + "num": 30 + } + } +} +``` + +{{< /details >}} + +## 自定义键结构体字典 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Items: + "@type": "map" + "@struct": + "@key": CustomKey + Name: string + Num: int32 + +--- +"@sheet": ItemConf +Items: + 1: + Name: apple + Num: 10 + 2: + Name: orange + Num: 20 + 3: + Name: banana + Num: 30 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map items = 1 [(tableau.field) = {name:"Items" key:"CustomKey"}]; + message Item { + uint32 custom_key = 1 [(tableau.field) = {name:"CustomKey"}]; + string name = 2 [(tableau.field) = {name:"Name"}]; + int32 num = 3 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "items": { + "1": { + "key": 1, + "name": "apple", + "num": 10 + }, + "2": { + "key": 2, + "name": "orange", + "num": 20 + }, + "3": { + "key": 3, + "name": "banana", + "num": 30 + } + } +} +``` + +{{< /details >}} + +## 字典嵌套列表 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Countries: + "@type": "map" + "@struct": + Desc: string + Items: + "@type": "[Item]" + "@struct": + Name: string + Num: int32 + +--- +"@sheet": ItemConf +Countries: + USA: + Desc: A country in North America. + Items: + - Name: apple + Num: 10 + - Name: orange + Num: 20 + China: + Desc: A country in East Asia. + Items: + - Name: apple + Num: 100 + - Name: orange + Num: 200 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map countries = 1 [(tableau.field) = {name:"Countries" key:"@key"}]; + message Country { + string key = 1 [(tableau.field) = {name:"@key"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + repeated Item items = 3 [(tableau.field) = {name:"Items"}]; + message Item { + string name = 1 [(tableau.field) = {name:"Name"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countries": { + "China": { + "key": "China", + "desc": "A country in East Asia.", + "items": [ + { + "name": "apple", + "num": 100 + }, + { + "name": "orange", + "num": 200 + } + ] + }, + "USA": { + "key": "USA", + "desc": "A country in North America.", + "items": [ + { + "name": "apple", + "num": 10 + }, + { + "name": "orange", + "num": 20 + } + ] + } + } +} +``` + +{{< /details >}} + +## 字典嵌套字典 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Countries: + "@type": "map" + "@struct": + Desc: string + Items: + "@type": "map" + "@struct": + "@key": Name + Num: int32 + +--- +"@sheet": ItemConf +Countries: + USA: + Desc: A country in North America. + Items: + apple: + Num: 10 + orange: + Num: 20 + China: + Desc: A country in East Asia. + Items: + apple: + Num: 100 + orange: + Num: 200 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + map countries = 1 [(tableau.field) = {name:"Countries" key:"@key"}]; + message Country { + string key = 1 [(tableau.field) = {name:"@key"}]; + string desc = 2 [(tableau.field) = {name:"Desc"}]; + map items = 3 [(tableau.field) = {name:"Items" key:"@key"}]; + message Item { + string name = 1 [(tableau.field) = {name:"@key"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "countries": { + "China": { + "key": "China", + "desc": "A country in East Asia.", + "items": { + "apple": { + "name": "apple", + "num": 100 + }, + "orange": { + "name": "orange", + "num": 200 + } + } + }, + "USA": { + "key": "USA", + "desc": "A country in North America.", + "items": { + "apple": { + "name": "apple", + "num": 10 + }, + "orange": { + "name": "orange", + "num": 20 + } + } + } + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/yaml/metasheet.md b/content/zh/docs/yaml/metasheet.md new file mode 100644 index 000000000..3ed071128 --- /dev/null +++ b/content/zh/docs/yaml/metasheet.md @@ -0,0 +1,36 @@ +--- +title: "元表" +description: "YAML 元表 @TABLEAU 指南。" +lead: "YAML 元表 \"@TABLEAU\" 指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4600 +toc: true +--- + +## 概述 + +名为 "@TABLEAU" 的元表用于指定 Tableau 解析器选项。 +详细信息请阅读 [元表 →]({{< relref "../excel/metasheet" >}})。 + +YAML 元表示例: + +```yaml +# define metasheet +"@sheet": "@TABLEAU" +Sheet1: + Alias: ItemConf + OrderedMap: true + Index: "(ID,Type)@Item" +Sheet2: + Alias: FruitConf + Sep: "," + Subsep: ":" + FieldPresence: true +``` + +## 待办事项 + +> 更多详情... diff --git a/content/zh/docs/yaml/scalar.md b/content/zh/docs/yaml/scalar.md new file mode 100644 index 000000000..3bf0e026f --- /dev/null +++ b/content/zh/docs/yaml/scalar.md @@ -0,0 +1,87 @@ +--- +title: "标量" +description: "YAML 标量指南。" +lead: "YAML 标量指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4000 +toc: true +--- + +## 标量 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +ID: uint32 +Num: int32 +Value: uint64 +Weight: int64 +Percentage: float +Ratio: double +Name: string +Blob: bytes +OK: bool + +--- +"@sheet": ItemConf +ID: 1 +Num: 10 +Value: 20 +Weight: 30 +Percentage: 0.5 +Ratio: 3.14159 +Name: apple +Blob: "VGFibGVhdQ==" # base64 of "Tableau" +OK: true +``` + +生成的内容: + +{{< details "hello_world.proto" open >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + uint64 value = 3 [(tableau.field) = {name:"Value"}]; + int64 weight = 4 [(tableau.field) = {name:"Weight"}]; + float percentage = 5 [(tableau.field) = {name:"Percentage"}]; + double ratio = 6 [(tableau.field) = {name:"Ratio"}]; + string name = 7 [(tableau.field) = {name:"Name"}]; + bytes blob = 8 [(tableau.field) = {name:"Blob"}]; + bool ok = 9 [(tableau.field) = {name:"OK"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" open >}} + +```json +{ + "id": 1, + "num": 10, + "value": "20", + "weight": "30", + "percentage": 0.5, + "ratio": 3.14159, + "name": "apple", + "blob": "VkdGaWJHVmhkUT09", + "ok": true +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/yaml/struct.md b/content/zh/docs/yaml/struct.md new file mode 100644 index 000000000..0b69470e0 --- /dev/null +++ b/content/zh/docs/yaml/struct.md @@ -0,0 +1,371 @@ +--- +title: "结构体" +description: "YAML 结构体指南。" +lead: "YAML 结构体指南。" +date: 2024-06-22T10:00:00+08:00 +lastmod: 2024-06-22T10:00:00+08:00 +draft: false +images: [] +weight: 4300 +toc: true +--- + +## 通用结构体 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Item: + "@type": "{Item}" + ID: uint32 + StartTime: datetime + Expiry: duration + +--- +"@sheet": ItemConf +Item: + ID: 1 + StartTime: 2024-10-01 10:10:10 + Expiry: 1h +``` + +> 提示 +> +> - 知名类型:[datetime →]({{< relref "../basics/wellknown-types/#datetime" >}}) +> - 知名类型:[duration →]({{< relref "../basics/wellknown-types/#duration" >}}) + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item"}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + google.protobuf.Timestamp start_time = 2 [(tableau.field) = {name:"StartTime"}]; + google.protobuf.Duration expiry = 3 [(tableau.field) = {name:"Expiry"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "startTime": "2024-10-01T02:10:10Z", + "expiry": "3600s" + } +} +``` + +{{< /details >}} + +## 复用同级结构体 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Item: + "@type": "{Item}" + ID: uint32 + StartTime: datetime + Expiry: duration +NewItem: "{Item}" # reuse predefined struct type Item above + +--- +"@sheet": ItemConf +Item: + ID: 1 + StartTime: 2024-10-01 10:10:10 + Expiry: 1h +NewItem: + ID: 2 + StartTime: 2026-10-01 10:10:10 + Expiry: 2h +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item"}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + google.protobuf.Timestamp start_time = 2 [(tableau.field) = {name:"StartTime"}]; + google.protobuf.Duration expiry = 3 [(tableau.field) = {name:"Expiry"}]; + } + Item new_item = 2 [(tableau.field) = {name:"NewItem"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "startTime": "2024-10-01T02:10:10Z", + "expiry": "3600s" + }, + "newItem": { + "id": 2, + "startTime": "2026-10-01T02:10:10Z", + "expiry": "7200s" + } +} +``` + +{{< /details >}} + +## 预定义结构体 + +*common.proto* 中的 `Item` 预定义如下: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Item: "{.Item}" + +--- +"@sheet": ItemConf +Item: + ID: 1 + Num: 10 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Item item = 1 [(tableau.field) = {name:"Item"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "num": 10 + } +} +``` + +{{< /details >}} + +## 单元格内结构体 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Item: "{uint32 ID, int32 Num}Item" + +--- +"@sheet": ItemConf +Item: "1, 10" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "num": 10 + } +} +``` + +{{< /details >}} + +## 单元格内通用结构体 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Item: + "@type": "{Item}" + "@incell": true + ID: uint32 + Num: int32 + +--- +"@sheet": ItemConf +Item: "1, 10" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + Item item = 1 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; + } +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "num": 10 + } +} +``` + +{{< /details >}} + +## 单元格内预定义结构体 + +*common.proto* 中的 `Item` 预定义如下: + +```protobuf +message Item { + int32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} +``` + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Item: + "@type": "{.Item}" + "@incell": true + +--- +"@sheet": ItemConf +Item: "1, 10" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Item item = 1 [(tableau.field) = {name:"Item" span:SPAN_INNER_CELL}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "item": { + "id": 1, + "num": 10 + } +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/content/zh/docs/yaml/union.md b/content/zh/docs/yaml/union.md new file mode 100644 index 000000000..493b7a49e --- /dev/null +++ b/content/zh/docs/yaml/union.md @@ -0,0 +1,276 @@ +--- +title: "联合类型" +description: "YAML 联合类型指南。" +lead: "YAML 联合类型指南。" +date: 2024-09-02T19:21:01+08:00 +lastmod: 2024-09-02T19:21:01+08:00 +draft: false +images: [] +weight: 4350 +toc: true +--- + +## 联合类型定义 + +例如,*common.proto* 中的联合类型 `Target` 预定义如下: + +```protobuf +// Predefined union type. +message Target { + option (tableau.union) = true; + + Type type = 9999 [(tableau.field) = { name: "Type" }]; + oneof value { + option (tableau.oneof) = { + field: "Field" + }; + Pvp pvp = 1; // Binded to enum value 1: TYPE_PVP. + Pve pve = 2; // Binded to enum value 2: TYPE_PVP. + Story story = 3; // Binded to enum value 3: TYPE_STORY. + Skill skill = 4; // Binded to enum value 4: TYPE_SKILL. + } + + enum Type { + TYPE_NIL = 0; + TYPE_PVP = 1 [(tableau.evalue) = { name: "PVP" }]; + TYPE_PVE = 2 [(tableau.evalue) = { name: "PVE" }]; + TYPE_STORY = 3 [(tableau.evalue) = { name: "Story" }]; + TYPE_SKILL = 4 [(tableau.evalue) = { name: "Skill" }]; + } + message Pvp { + int32 type = 1; // scalar + int64 damage = 2; // scalar + repeated protoconf.FruitType types = 3; // incell enum list + } + message Pve { + Mission mission = 1; // incell struct + repeated int32 heros = 2; // incell list + map dungeons = 3; // incell map + + message Mission { + int32 id = 1; + uint32 level = 2; + int64 damage = 3; + } + } + message Story { + protoconf.Item cost = 1; // incell predefined struct + map fruits = 2; // incell map with value as enum type + map flavors = 3; // incell map with key as enum type + message Flavor { + protoconf.FruitFlavor key = 1 [(tableau.field) = { name: "Key" }]; + int32 value = 2 [(tableau.field) = { name: "Value" }]; + } + } + message Skill { + int32 id = 1; // scalar + int64 damage = 2; // scalar + // no field tag 3 + } +} +``` + +## 预定义联合类型 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Target: "{.Target}" +--- +"@sheet": ItemConf +Target: + Type: PVP + Field1: 1 + Field2: 10 + Field3: "Apple,Orange,Banana" + +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Target target = 1 [(tableau.field) = {name:"Target"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "target": { + "type": "TYPE_PVP", + "pvp": { + "type": 1, + "damage": "10", + "types": [ + "FRUIT_TYPE_APPLE", + "FRUIT_TYPE_ORANGE", + "FRUIT_TYPE_BANANA" + ] + } + } +} +``` + +{{< /details >}} + +## 预定义单元格内联合类型 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Target: + "@type": "{.Target}|{form:FORM_TEXT}" + "@incell": true +--- +"@sheet": ItemConf +Target: "type:TYPE_PVE pve:{mission:{id:1 level:100 damage:999} heros:1 heros:2 heros:3 dungeons:{key:1 value:10} dungeons:{key:2 value:20} dungeons:{key:3 value:30}}" +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + protoconf.Target target = 1 [(tableau.field) = {name:"Target" span:SPAN_INNER_CELL prop:{form:FORM_TEXT}}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "target": { + "type": "TYPE_PVE", + "pve": { + "mission": { + "id": 1, + "level": 100, + "damage": "999" + }, + "heros": [ + 1, + 2, + 3 + ], + "dungeons": { + "1": "10", + "2": "20", + "3": "30" + } + } + } +} +``` + +{{< /details >}} + +## 预定义联合类型列表 + +*HelloWorld.yaml* 中的工作表 `ItemConf`: + +```yaml +# define metasheet: generate all sheets +"@sheet": "@TABLEAU" +--- +# define schema +"@sheet": "@ItemConf" +Targets: "[.Target]" +--- +"@sheet": ItemConf +Targets: + - Type: Story + Field1: "1001,10" + Field2: "1:Apple,2:Orange" + Field3: "Fragrant:1,Sour:2" + - Type: Skill + Field1: 1 + Field2: 2 +``` + +生成的内容: + +{{< details "hello_world.proto" >}} + +```protobuf +// --snip-- +import "common.proto"; +option (tableau.workbook) = {name:"HelloWorld.yaml"}; + +message ItemConf { + option (tableau.worksheet) = {name:"ItemConf"}; + + repeated protoconf.Target targets = 1 [(tableau.field) = {name:"Targets"}]; +} +``` + +{{< /details >}} + +{{< details "ItemConf.json" >}} + +```json +{ + "targets": [ + { + "type": "TYPE_STORY", + "story": { + "cost": { + "id": 1001, + "num": 10 + }, + "fruits": { + "1": "FRUIT_TYPE_APPLE", + "2": "FRUIT_TYPE_ORANGE" + }, + "flavors": { + "1": { + "key": "FRUIT_FLAVOR_FRAGRANT", + "value": 1 + }, + "2": { + "key": "FRUIT_FLAVOR_SOUR", + "value": 2 + } + } + } + }, + { + "type": "TYPE_SKILL", + "skill": { + "id": 1, + "damage": "2" + } + } + ] +} +``` + +{{< /details >}} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 03baa36fc..6a6562ad5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,6 +106,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -1983,6 +1984,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2367,6 +2369,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -3160,6 +3163,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, + "peer": true, "engines": { "node": ">=12" } @@ -6413,6 +6417,7 @@ "url": "https://tidelift.com/funding/github/npm/postcss" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -6640,6 +6645,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7529,6 +7535,7 @@ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.0.tgz", "integrity": "sha512-yUI+4xXfPHVnueYddSQ/e1GuEA/2wVhWQbGj16AmWLtQJtn28lVxfS4b0CsWyVRPgd3Auzi0NXOthIEUhtQmmA==", "dev": true, + "peer": true, "dependencies": { "@csstools/selector-specificity": "^2.0.2", "balanced-match": "^2.0.0", @@ -8329,6 +8336,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -9649,7 +9657,8 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -9894,6 +9903,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -10467,7 +10477,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "dev": true + "dev": true, + "peer": true }, "d3-shape": { "version": "3.1.0", @@ -12918,6 +12929,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -13055,6 +13067,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13729,6 +13742,7 @@ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.0.tgz", "integrity": "sha512-yUI+4xXfPHVnueYddSQ/e1GuEA/2wVhWQbGj16AmWLtQJtn28lVxfS4b0CsWyVRPgd3Auzi0NXOthIEUhtQmmA==", "dev": true, + "peer": true, "requires": { "@csstools/selector-specificity": "^2.0.2", "balanced-match": "^2.0.0",