TPSV:TSV(和 CSV)的另一种选择
文章介绍了TPSV(Tab-Pipe-Separated-Values),一种旨在替代CSV和TSV的表格数据格式。TPSV使用`|`开头和制表符分隔单元格,易于解析和在文本编辑器中查看。相比TSV,TPSV支持注释,更易于在编辑器中查看,并兼容Markdown管道表。相比Markdown管道表,TPSV减少了对空格的依赖,单元格内容可以包含管道符和空格。文章还提供了TPSV的语法、示例、编辑器配置建议以及Python解析代码。
TPSV
尽管 CSV(Comma-Separated-Values,逗号分隔值)无处不在,但它的近亲 TSV (Tab-Separated-Values,制表符分隔值) 在许多情况下比 CSV 具有直接的优势。 制表符通常不会出现在编码的数据中,从而避免了引用(quoting)的需要。 此外,如果您的列都具有相似的宽度,则可以在任何文本编辑器中配置制表符宽度,以便所有列都很好地对齐。 不幸的是,在许多情况下,列的宽度并不相似。
TPSV 代表 Tab-Pipe-Separated-Values(制表符-管道符分隔值),旨在成为比 CSV 和 TSV 更易于使用的替代方案。 与这两种格式一样,TPSV 能够描述表格数据,其中每个单元格都包含一个字符串值。 TPSV 的设计宗旨是易于解析,并且方便任何使用文本编辑器的人使用。
- 语法 (Syntax)
- 示例 (Example)
- 编辑器配置 (Editor configuration)
- 与其他格式的比较 (Comparison with other formats)
- 解析 (Parsing)
语法 (Syntax)
基本语法很简单。
- 单元格以
|
开头,并以一个或多个制表符结尾。 - 以单元格开头的行是一个行。 任何其他行都会被忽略。
- 第一行定义列数。
这就是您所需要的全部,但是还有一些规则可以使生活更轻松。
- 单元格太少的行,其余单元格为空字符串。
- 单元格太多的行,多余的单元格将被忽略。
- 行中的最后一列不需要以制表符结尾。
然后是最后一个可选规则,多行扩展。
- 以
\
而不是|
开头的行被视为延续行。 其单元格的非空内容附加到上一行的单元格,并用换行符分隔。
示例 (Example)
这是一个演示该格式的 TPSV 示例。 灰色箭头是制表符。
此示例突出显示了几个方面。
- 前两行不是以单元格开头的,因此被视为注释。 (第一行是一个 modeline,可以被支持它们的文本编辑器解释。在这种情况下,它将制表符宽度设置为 8。)
- 就像在此示例中一样,第一行通常用作列的标题。
- 标题下的下一行被忽略,因为它也不是以单元格开头的。 这种特殊的标题分隔符选择使该示例与 Markdown 管道表 兼容,后者在许多上下文中(包括 GitHub-flavored Markdown)都被识别为表格。 如果您想将表格数据粘贴到某个位置,这可能会很方便。
- 通常,最后一列非常适合长单元格内容。
- 带有“TODO”的单元格超出了最后一列,因此充当注释。
- 最后一行是行延续的一个示例,如果您不想让文本超出一定的宽度,这可能很好。
编辑器配置 (Editor configuration)
建议:
- 使用等宽文本编辑器。
- 配置您的编辑器以呈现制表符(以便您可以将它们与空格区分开)。
- 使用 editorconfig 为特定文件设置制表符宽度,或使用像
vim: tw=8
这样的 modeline,一些编辑器支持这些。 - 较小的制表符宽度使您可以更好地控制列宽,但需要更多的宽度管理。 我通常建议 TPSV 文件使用 8 的制表符宽度,但其他选择也很有意义。
适用于通用 tpsv 文件的 .editorconfig 示例:
[*.tpsv]
indent_style = tab
tab_width = 8
max_line_length = 999
insert_final_newline = true
与其他格式的比较 (Comparison with other formats)
相对于 TSV 的改进:
- 支持注释
- 在文本编辑器中更容易查看,而无需将制表符宽度设置得很高(这会使所有列都与最宽的列一样宽)
- 可以与 Markdown 管道表兼容
- 多行扩展
相对于 Markdown 管道表的改进:
- 更少需要管理空格以进行垂直对齐。 制表符更容易均匀间隔(除非您使用制表符宽度为 1)。
- 单元格内容能够包含管道符和前导/尾随空格。
- 多行扩展
解析 (Parsing)
这会将基本语法解析为 python 列表的迭代器。
[](https://chtenb.dev/<#cb2-1>)def parse_tpsv(file):
[](https://chtenb.dev/<#cb2-2>) row_len = -1
[](https://chtenb.dev/<#cb2-3>) for line in file:
[](https://chtenb.dev/<#cb2-4>) if line.startswith('|'):
[](https://chtenb.dev/<#cb2-5>) cells = [c.rstrip('\t') for c in line[1:].rstrip('\n').split('\t|')]
[](https://chtenb.dev/<#cb2-6>) row_len = len(cells) if row_len < 0 else row_len # 确定列数
[](https://chtenb.dev/<#cb2-7>) yield cells[:row_len] + [''] * (row_len - len(cells)) # 规范化行大小
要也解析多行扩展,可以这样做:
[](https://chtenb.dev/<#cb3-1>)def parse_tpsv_multiline(file):
[](https://chtenb.dev/<#cb3-2>) row_len, row = -1, None
[](https://chtenb.dev/<#cb3-3>) for line in file:
[](https://chtenb.dev/<#cb3-4>) if line.startswith('|'):
[](https://chtenb.dev/<#cb3-5>) if row:
[](https://chtenb.dev/<#cb3-6>) yield row
[](https://chtenb.dev/<#cb3-7>) cells = [c.rstrip('\t') for c in line[1:].rstrip('\n').split('\t|')]
[](https://chtenb.dev/<#cb3-8>) row_len = len(cells) if row_len < 0 else row_len
[](https://chtenb.dev/<#cb3-9>) row = cells[:row_len] + [''] * (row_len - len(cells))
[](https://chtenb.dev/<#cb3-10>) elif line.startswith('\\') and row:
[](https://chtenb.dev/<#cb3-11>) cells = [c.rstrip('\t') for c in line[1:].rstrip('\n').split('\t|')]
[](https://chtenb.dev/<#cb3-12>) for i, c in enumerate(cells[:row_len]):
[](https://chtenb.dev/<#cb3-13>) row[i] += '\n' + c if c else '' # 如果需要,继续单元格内容
[](https://chtenb.dev/<#cb3-14>) if row:
[](https://chtenb.dev/<#cb3-15>) yield row