如何在某些元素中处理带有 nul char 的 CSV 行?

Posted

技术标签:

【中文标题】如何在某些元素中处理带有 nul char 的 CSV 行?【英文标题】:How to process CSV lines with nul char in some elements? 【发布时间】:2016-02-22 12:41:38 【问题描述】:

在读取和解析 CSV 文件行时,我需要处理作为某些行字段的值出现的 nul 字符。有时 CSV 文件采用 windows-1250 编码,有时采用 UTF-8,有时采用 UTF-16,这一事实使情况变得复杂。正因为如此,我开始了一些方法,后来发现nul char问题——见下文。

详细信息:我需要将 CSV 文件从第三方清理到我们的数据提取器通用的表单(即该实用程序用作过滤器 - 将一个 CSV 表单存储到另一个 CSV 表单) .

我最初的方法是以二进制模式打开 CSV 文件并检查第一个字节是否形成 BOM。我知道所有给定的 Unicode 文件都以 BOM 开头。如果没有 BOM,我知道它是 windows-1250 编码的。 转换后的 CSV 文件应使用 windows-1250 编码。因此,在检查输入文件后,我使用相关模式打开它,如下所示:

// Open the file in binary mode first to see whether BOM is there or not.
FILE * fh nullptr ;
errno_t err = fopen_s(&fh, fnameIn.string().c_str(), "rb"); // const fs::path & fnameIn
assert(err == 0);
vector<char> buf(4, '\0');
fread(&buf[0], 1, 3, fh);
::fclose(fh);

// Set the isUnicode flag and open the file according to that.
string mode "r" ;     // init 
bool isUnicode = false; // pessimistic init

if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) // UTF-8 BOM

    mode += ", ccs=UTF-8";
    isUnicode = true;

else if ((buf[0] == 0xFE && buf[1] == 0xFF)     // UTF-16 BE BOM
      || (buf[0] == 0xFF && buf[1] == 0xFE))    // UTF-16 LE BOM

    mode += ", ccs=UNICODE";
    isUnicode = true;


// Open in the suitable mode.
err = fopen_s(&fh, fnameIn.string().c_str(), mode.c_str());
assert(err == 0);

成功打开后,输入行被读取或通过fgetsfgetws——取决于是否检测到Unicode。然后的想法是,如果之前检测到 unicode,则将缓冲区内容从 Unicode 转换为 1250,或者让缓冲区为 1250。s 变量应包含 windows-1250 编码中的字符串。需要转换时使用ATL::CW2A(buf, 1250)

    const int bufsize = 4096;
    wchar_t buf[bufsize];

    // Read the line from the input according to the isUnicode flag.
    while (isUnicode ? (fgetws(buf, bufsize, fh) != NULL)
        : (fgets(reinterpret_cast<char*>(buf), bufsize, fh) != NULL))
    
        // If the input is in Unicode, convert the buffer content
        // to the string in cp1250. Otherwise, do not touch it.
        string s;
        if (isUnicode)  s = ATL::CW2A(buf, 1250);
        else            s = reinterpret_cast<char*>(buf);
        ...
        // Now processing the characters of the `s` to form the output file
    

它工作得很好......直到出现了一个使用 nul 字符作为行中值的文件。问题在于,当分配了s 变量时,nul 会切断该行的其余部分。在观察到的情况下,它发生在使用 1250 编码的文件上。但它也可能发生在 UTF 编码的文件中。

如何解决问题?

【问题讨论】:

【参考方案1】:

使用 C++ 或 Windows 函数可以解决 NUL 字符问题。在这种情况下,最简单的解决方案是MultiByteToWideChar,它将接受明确的字符串长度,因此它不会在 NUL 上停止。

【讨论】:

我承认我很懒惰并使用了具有更简单界面的ATL::CW2A(),但它在 NUL 字符之后的有效字符(在第一个 NUL 处停止)效果不佳。但是,问题出现得更早。 fgetws() 不会告诉您读取了多少字节或宽字符。我怎么能得到那个?或者我应该使用什么功能?文档说它作为fgetwc() 工作,并在第一个\n 或EOF 处停止。这就是决定在 ***.com/q/35572515/1346705 中使用 char by char 读取的原因我应该编写自己的 fgetwc() 版本吗? ... 更正。 “...我自己的fgetws() 版本?” 这确实是一个相当糟糕的设计,典型的 C 语言。它没有字符串类型。 std::getline 不会受此影响;它可以返回一个有长度的 C++ 字符串。 感谢您的帮助。我终于实现了自己的fgets_xxx,它跳过了'\0' 字符。如果我没记错的话,fgets() 实际上不能用于读取包含 nul 字符的行。没有办法知道实际读取了什么。我知道这是一个权衡。我更喜欢干净的 C++ 解决方案,但是对于我需要的那个小实用程序,这需要更多的工作(时间)。再次感谢,祝您有美好的一天。

以上是关于如何在某些元素中处理带有 nul char 的 CSV 行?的主要内容,如果未能解决你的问题,请参考以下文章

比较字符串忽略 NUL

列值采用 0 或 nul 代替 HIVE 中的 char 数据类型

将带有随机 char 元素的 char[] 转换为一个 int

如何从一行中删除某些单词,但将其余部分放入带有批处理的字符串/变量中?

如何从 python 调用带有 Char** 参数和 int* 参数的 C 方法?

如何将一个char数组中元素插入到另一个char数组中指定元素后面,在java语言环境,但不能使用java的内置函数