Как прочитать CSV-файл не в кодировке UTF8? - PullRequest
0 голосов
/ 18 декабря 2018

С ящиком csv и последней версией Rust 1.31.0 я хотел бы читать файлы CSV с кодировкой ANSI (Windows 1252), так же легко, как в UTF-8.

Вещи, которые я пытался (без удачи), после прочтения всего файла в Vec<u8>:

Действительно, в моей компании у нас много CSV-файлов в формате ANSI.

Кроме того, я бы хотел, если возможно, не загружатьвесь файл в Vec<u8>, но чтение построчно (CRLF окончание), так как многие файлы большие (50 МБ или более…).

В файле 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 голосов
/ 18 декабря 2018

Я подозреваю, что этот вопрос не указан.В частности, неясно, почему использование API ByteRecord недостаточно.В ящике csv байтовые записи специально существуют именно для таких случаев, как этот, где ваши CSV-данные не являются строго UTF-8, но находятся в альтернативной кодировке, такой как Windows-1252, которая совместима с ASCII.(ASCII-совместимая кодировка - это кодировка, в которой ASCII является подмножеством. Windows-1252 и UTF-8 совместимы с ASCII. UTF-16 - нет.) В приведенном выше примере кода показано, что вы используете байтовые записи, но неОбъяснить, почему этого недостаточно.

С учетом сказанного, если ваша цель состоит в том, чтобы перевести ваши данные в строковый тип данных Rust (String / &str), тогда ваш вариант only это перекодировать содержимое ваших CSV-данных из Windows-1252 в UTF-8.Это необходимо, потому что строковый тип данных Rust использует UTF-8 для представления в памяти.У вас не может быть Rust String / &str, который закодирован в Windows-1252, потому что Windows-1252 не является подмножеством UTF-8.

В других комментариях рекомендуется использовать ящик encoding.Однако вместо этого я бы рекомендовал использовать encoding_rs, если ваш сценарий использования совпадает с теми же сценариями использования, которые определены в стандарте Encoding , специально предназначенном для Интернета.К счастью, я считаю, что такое выравнивание существует.

Для того, чтобы удовлетворить ваши критерии для чтения данных 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.

...