#value #white-space #separated #file-format #config-parser #wsv

whitespacesv

Stenway 定义的白空间分隔值 (WSV) 格式的解析器/写入器 Rust 实现。请参阅 https://dev.stenway.com/WSV/。WSV 提供了 CSV 的明确替代方案。

8 个版本 (3 个稳定)

1.0.2 2024 年 4 月 10 日
1.0.1 2024 年 2 月 3 日
1.0.0 2024 年 1 月 27 日
0.2.0 2024 年 1 月 25 日
0.1.3 2024 年 1 月 24 日

#365解析器实现


2 个crate中使用(通过 simpleml

MIT 许可证

2MB
1.5K SLoC

WhitespaceSV

概述

白空间分隔值 (WSV) 是一种文件格式,旨在解决逗号分隔值 (CSV) 文件格式的问题。它可以无歧义地解析,并且不需要任何配置即可由解析器解析。

此 crate 提供了基于 Rust 的 WSV 标准 实现。此实现旨在尽可能高效。在急切(非懒或标准)解析中,此解析器尽可能接近零拷贝。它仅在需要替换转义序列的情况下分配字符串。crate 中仅公开了少量 API,但它们应该能够处理所有用例。

更新说明

1.0.2

修复了 写入交错数组时的恐慌

解析

要使用此 crate 解析 WSV 文件,只需调用提供的解析函数之一。目前有 3 个,所以请选择适合您用例的一个。大多数用例可能应该使用标准 parse() 函数。

  1. parse_with_col_count - 如果可以安全地急切解析您的 WSV(它适合内存)并且您的 WSV 是具有已知列数的标准表格,请使用此 API。这将避免解析过程中不必要的 Vecs 重新分配。
  2. parse_lazy - 如果您有一个大输入,应该只分批加载(可能是因为它不适合内存),请使用此 API。此 API 将逐行懒解析输入。如果您需要逐值解析,请直接使用 WSVLazyTokenizer 以获得完全控制。
  3. parse - 对于所有其他情况请使用此函数。

急切解析

这里没有太多要说的。急切解析 API 的工作方式与您预期的差不多。它们返回一个 Result,如果解析成功,则值为 Ok 变体,其中包含 Option<Cow<'_, str>> 的二维 Vec。Cow 中的值已处理以下情况

  1. "/" 转义序列已被替换为 \n
  2. "" 转义序列已被替换为 "
  3. 任何包裹的引号都将被移除。例如:"hello, world!" 将变为 hello, world
  4. - 值将作为 Option 枚举的 None 变体返回。其他所有内容都将是一个 Some 变体。

例如,下面的输入将返回 [[Some(1), None], [Some(3), Some(String(This is a string " with escape sequences \n))]] (为了清晰起见,已删除字符串值周围的引号和 \ 转义序列)

1 -
3 "This is a string "" with escape sequences "/""

惰性解析

除了标准解析之外,此 crate 还支持通过迭代器进行惰性解析。通过创建一个迭代器管道,您可以处理不适合内存的文件。例如,假设我有一个 300 GB 的文件,我真正想要的是该文件每行的总和。我可以设置一个迭代器管道来读取 WSV 并将总和输出回 WSV,如下面的代码所示。

请注意,示例代码仍然会贪婪地评估 WSV 的每一行。如果您需要进行更细粒度的惰性解析,请直接使用此 crate 的 WSVLazyTokenizer 来完成您所需的操作。

惰性解析 API 和 WSVLazyTokenizer 接受一个 char 迭代器,以下是一些有用的资源来获取此迭代器

use std::fs::File;
use std::io::BufReader;
// I recommend you pull in the utf8-chars crate as a 
// dependency if you need lazy parsing of utf-8
use whitespacesv::{parse_lazy, WSVWriter};
use utf8_chars::BufReadCharsExt;

let mut reader = 
    BufReader::new(
        File::open("./my_very_large_file.txt")
            .unwrap());

let chars = reader.chars().map(|ch| ch.unwrap());

let lines_lazy = parse_lazy(chars).map(|line| {
    // For this example we will assume we have valid WSV
    let sum = line
        .unwrap()
        .into_iter()
        // We're counting None as 0 in my case,
        // so flat_map the Nones out.
        .flat_map(|opt| opt)
        .map(|value| value.parse::<i32>().unwrap_or(0))
        .sum::<i32>();

    // The writer needs a 2D iterator of Option<String>,
    // so wrap the value in a Some and .to_string() it.
    // Also wrap in a Vec to make it a 2D iterator
    vec![Some(sum.to_string())]
});
// CAREFUL: Don't call .collect() here or we'll run out of memory!

// The WSVWriter when using ColumnAlignment::Packed
// (the default) is also lazy, so we can pass our
// result in directly.
for ch in WSVWriter::new(lines_lazy) {
    // Your code to dump the output to a file goes here.
    print!("{}", ch);
}

写入

有两种方式可以使用提供的 API 来写入 WSV 文件。

  1. WSVWriter 的 to_string() 方法 - 这允许您根据需要将列对齐到左侧或右侧。大多数用例应该使用此方法。
  2. WSVWriter 迭代器实现。这允许您懒惰地评估值。如果您需要写入过大而无法放入内存中的值存储,请使用此方法。此实现不尊重列对齐,并且是为纯速度而构建的。

to_string()

此 API 将仅在必要时用引号包围字符串。此 2D IntoIterator 结构中的值必须是 Options,其中内部值是实现了

  1. AsRef,
  2. From<&'static str>, 和
  3. ToString.

&str, Cow<'_, str>, String, 和 &String 类型都支持这些类型约束。

通过 WSVWriter::new() API 支持的一些类型示例

  • LinkedList<LinkedList<Option<Cow<'_, str>>>>
  • Vec<Vec<Option<&'_ str>>>
  • VecDeque<Vec<Option<&String>>>
  • Iter<Iter<Option<String>>> 其中 Iter 是任何实现了 Iterator 的类型。
use whitespacesv::{WSVWriter, ColumnAlignment};

let values = vec![
    vec!["1", "2", "3"], // In this example, each value is &str,
    vec!["4", "5", "6"], // but String and Cow<'_, str> also work
    vec!["My string with a \n character"],
    vec!["My string with many \"\"\" characters"],
];

let values_as_opts = values
    .into_iter()
    .map(|row| row.into_iter().map(|value| Some(value)));

let wsv = WSVWriter::new(values_as_opts)
    // The default alignment is packed, but left and 
    // right aligned are also options in cases where 
    // your .wsv file will be looked at by people and 
    // not just machines.
    .align_columns(ColumnAlignment::Left)
    .to_string();

/// Output:
/// 1                                       2 3
/// 4                                       5 6
/// "My string with a "/" character"       
/// "My string with many """""" characters"
println!("{}", wsv);

Iterator

这个WSVWriter的实现利用迭代器的懒评估,允许您写入巨大的文件。通过将迭代器传递给WSVWriter并使用WSVWriter提供的迭代器实现,您可以写入磁盘空间可以容纳的任意大小的文件。以一个例子来说,假设我需要以WSV格式将序列0到9的4,294,967,295(u32::MAX)行打印到我的终端。我可以使用以下代码完成这个任务:

注意:此API也只在必要时用引号包围字符串。

use whitespacesv::WSVWriter;

let values = (0..u32::MAX).map(|_| (0..10).into_iter().map(|val| Some(val.to_string())));
// NOTE: column alignment is not respected when using this iterator implementation.
for ch in WSVWriter::new(values) {
    print!("{}", ch);
    // This is so that my computer doesn't fry when running unit tests.
    break;
}

无运行时依赖。