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

时间:2018-12-18 05:41:01

标签: csv character-encoding rust windows-1252

有了csv板条箱和最新的Rust版本1.31.0,我想像utf-8一样容易地读取ANSI(Windows 1252)编码的CSV文件。

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

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

此外,如果可能的话,我希望不要将整个文件加载到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"])

1 个答案:

答案 0 :(得分:7)

我怀疑此问题未指定。特别是,尚不清楚为什么您对ByteRecord API的使用不足。在csv条板箱中,字节记录专门用于此类情况,其中CSV数据并非严格地为UTF-8,而是采用替代编码,例如与ASCII兼容的Windows-1252。 (ASCII兼容编码是其中ASCII是子集的编码。Windows-1252和UTF-8都与ASCII兼容。UTF-16不兼容。)上面的代码示例显示您正在使用字节记录,但没有解释为什么这还不够。

话虽如此,如果您的目标是将数据转换为Rust的字符串数据类型(String / &str),那么您的 only选项是对内容进行转码Windows-1252到UTF-8的CSV数据。这是必需的,因为Rust的字符串数据类型使用UTF-8作为其内存中表示形式。您不能拥有Windows-1252编码的Rust String / &str,因为Windows-1252不是UTF-8的子集。

其他评论建议使用encoding条板箱。但是,如果您的用例与encoding_rs解决的相同用例保持一致,则我建议您使用Encoding Standard,后者专门针对网络。幸运的是,我相信存在这样的一致。

为了满足您以流式方式读取CSV数据的标准而无需先将全部内容加载到内存中,您需要在encoding_rs条板箱周围使用包装器,该包装器为您实现了流式解码。 encoding_rs_io条板箱为您提供了此功能。 (它在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。这是由于将Windows-1252代码点值233(编码为原义字节\xE9)转换为U+00E9 LATIN SMALL LETTER E WITH ACUTE(其是UTF-8编码为\xC3\xA9)的结果