csv - 如何读取非UTF8编码的csv文件?

标签 csv character-encoding rust windows-1252

随着csv crate 和最新的 Rust 版本 1.31.0,我想用 ANSI (Windows 1252) 编码读取 CSV 文件,就像在 UTF-8 中一样容易。

在阅读 Vec<u8> 中的整个文件后,我尝试过的事情(没有运气) :

确实,在我的公司,我们有很多 CSV 文件,ANSI 编码。

此外,如果可能的话,我希望不要将整个文件加载到 Vec<u8> 中。但需要逐行阅读(CRLF 结尾),因为许多文件都很大(50 Mb 或更多……)。

在文件 Cargo.toml 中,我有这个依赖项:

[dependencies]
csv = "1"

test.csv 由以下内容组成,保存为 Windows-1252 编码:

Café;au;lait
Café;au;lait

main.rs 文件中的代码:

extern crate csv;

use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::process;

fn example() -> Result<(), Box<Error>> {
    let file_name = r"test.csv";
    let file_handle = File::open(Path::new(file_name))?;
    let reader = BufReader::new(file_handle);

    let mut rdr = csv::ReaderBuilder::new()
        .delimiter(b';')
        .from_reader(reader);

    // println!("ANSI");
    // for result in rdr.byte_records() {
    //    let record = result?;
    //    println!("{:?}", record);
    // }

    println!("UTF-8");
    for result in rdr.records() {
        let record = result?;
        println!("{:?}", record);
    }
    Ok(())
}

fn main() {
    if let Err(err) = example() {
        println!("error running example: {}", err);
        process::exit(1);
    }
}

输出是:

UTF-8
error running example: CSV parse error: record 0 (line 1, field: 0, byte: 0): invalid utf-8: invalid UTF-8 in field 0 near byte index 3
error: process didn't exit successfully: `target\debug\test-csv.exe` (exit code: 1)

使用 rdr.byte_records() 时(取消注释代码的相关部分),输出为:

ANSI
ByteRecord(["Caf\\xe9", "au", "lait"])

最佳答案

我怀疑这个问题没有具体说明。特别是,不清楚您为什么使用 ByteRecord API 不足。在 csv crate 中,字节记录专门针对这种情况存在,在这种情况下,您的 CSV 数据不是严格的 UTF-8,而是采用替代编码,例如与 ASCII 兼容的 Windows-1252。 (ASCII 兼容编码是一种编码,其中 ASCII 是一个子集。Windows-1252 和 UTF-8 都是 ASCII 兼容的。UTF-16 不是。)上面的代码示例显示您正在使用字节记录,但没有不要解释为什么这是不够的。

话虽如此,如果您的目标是将数据转换为 Rust 的字符串数据类型(String/&str),那么您唯一的选择 是将 CSV 数据的内容从 Windows-1252 转码为 UTF-8。这是必要的,因为 Rust 的字符串数据类型使用 UTF-8 作为其在内存中的表示。您不能使用 Windows-1252 编码的 Rust String/&str,因为 Windows-1252 不是 UTF-8 的子集。

其他评论推荐使用encoding crate。但是,我建议使用 encoding_rs ,如果您的用例与 Encoding Standard 解决的相同用例一致,这是专门针对网络的。幸运的是,我相信存在这种一致性。

为了满足您以流式方式读取 CSV 数据而不首先将整个内容加载到内存中的标准,您需要在 encoding_rs 箱子周围使用一个包装器,为您实现流式解码。 encoding_rs_io crate 为你提供了这个。 (它在 ripgrep 内部用于在搜索 UTF-8 之前进行快速流解码。)

这是一个示例程序,它使用 Rust 2018 将上述所有内容放在一起:

use std::fs::File;
use std::process;

use encoding_rs::WINDOWS_1252;
use encoding_rs_io::DecodeReaderBytesBuilder;

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

fn try_main() -> csv::Result<()> {
    let file = File::open("test.csv")?;
    let transcoded = DecodeReaderBytesBuilder::new()
        .encoding(Some(WINDOWS_1252))
        .build(file);
    let mut rdr = csv::ReaderBuilder::new()
        .delimiter(b';')
        .from_reader(transcoded);
    for result in rdr.records() {
        let r = result?;
        println!("{:?}", r);
    }
    Ok(())
}

使用 Cargo.toml:

[package]
name = "so53826986"
version = "0.1.0"
edition = "2018"

[dependencies]
csv = "1"
encoding_rs = "0.8.13"
encoding_rs_io = "0.1.3"

输出:

$ cargo run --release
   Compiling so53826986 v0.1.0 (/tmp/so53826986)
    Finished release [optimized] target(s) in 0.63s
     Running `target/release/so53826986`
StringRecord(["Café", "au", "lait"])

特别是,如果您将 rdr.records() 换成 rdr.byte_records(),那么我们可以更清楚地看到发生了什么:

$ cargo run --release
   Compiling so53826986 v0.1.0 (/tmp/so53826986)
    Finished release [optimized] target(s) in 0.61s
     Running `target/release/so53826986`
ByteRecord(["Caf\\xc3\\xa9", "au", "lait"])

即,您的输入包含 Caf\xE9,但字节记录现在包含 Caf\xC3\xA9。这是将 233 的 Windows-1252 代码点值(编码为其文字字节 \xE9)转换为 U+00E9 LATIN SMALL LETTER E WITH 的结果ACUTE,UTF-8编码为\xC3\xA9

关于csv - 如何读取非UTF8编码的csv文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53826986/

相关文章:

java - 如何使用Java将Excel(2003和2007版本)文件导入到MySQL?

c# - 有效地读取 c# 中的 .csv 文件?

python - 使用Python将阿拉伯语单词编号与字典中的相应编号进行匹配

java - Core Java 如何将20字节字节数组转换为字符串

string - 声明char以防 rust

input - 如何避免在 Rust 中使用 stdin 换行

R:使用 textconnection 命令加载 R 表或 csv 文件

excel - 在 VBA 中处理大型分隔文本文件

r - 使用 ISO-8859-1 编码而不是 UTF-8 导出 csv

rust - 我可以以某种方式修改 '&' 数组吗?