将包含逗号和双引号的字符串写入CSV文件

23
我正在尝试在NetSuite中生成 Google Shopping 商品数据源,其中包含30,000多个项目。NetSuite是一个客户关系管理系统,运行服务器端JavaScript,并调用Suitescript 2.0。本质上,它只是带有几个限制的JavaScript。我的任务是将此产品数据源输出为CSV文件。
问题在于这些项目的产品描述包含变量数量的逗号、双引号、单引号和HTML。起初,只有逗号会导致问题,所以经过一段时间的研究后,我将要输出的字符串用双引号括起来。
//This function isn't terribly important, but is referenced below

function sanitizeString (desc) {
    var itemDesc;
    if (desc) {
        itemDesc = desc.replace(/(\r\n|\n|\r|\s+|\t| )/gm,' ');
        itemDesc = itemDesc.replace(/,/g, '\,');
        itemDesc = itemDesc.replace(/"/g, '\"');
        itemDesc = itemDesc.replace(/'/g, '\'');
        itemDesc = itemDesc.replace(/ +(?= )/g,'');
    } else {
        itemDesc = '';
    }
    return itemDesc;
}

var row = '';

for (var i = 0; i < columns.length; i++) {
    var col = columns[i];
    row += '"' + sanitizeString(val[col]) + '"';
    if (i != columns.length - 1) {
        row += ',';
    }
}
newFeed.appendLine({value: row});

然而,似乎这些双引号与字符串内部的双引号交互起来会导致一些奇怪的格式问题,尽管我的sanitizeString()函数应该对它们进行转义。每当描述中包含双引号时,下一行就不会单独成行,而是被附加到最后一列。
因此,我自然而然地对外部引号进行了转义,如下所示:
row += '\"' + sanitizeString(val[col]) + '\"';

这样做会使事情彻底失控,许多项目不会被推到新行,而我可以使用的列数达到最大值,因为它只是继续进行。

另一个自然的解决方案是编辑产品描述,但我不想为30,000多个项目做那件事...

有人知道这里可能出了什么问题吗?我觉得我忽略了一些非常简单的东西...


如果你的转义函数需要在输出中添加反斜杠,那么你需要在函数中的字符串字面量中转义反斜杠,即使用'\\,'而不是'\,',或者对于包含单引号的字符串,可以使用'\\\''"\\'" - nnnnnn
不,它不应该添加反斜杠。它只是为了最终字符串而转义它们。添加转义的反斜杠会再次使事情失控,并且换行符不会被添加,而是附加到包含引号的行的末尾... - B1gJ4k3
“escape them for the final string” 对你来说是什么意思?这难道不意味着最终字符串将添加反斜杠吗?例如,如果特定字段的输入为Hello, good bye,则输出应为Hello\, good bye,对吗?对于CSV,考虑到您正在删除换行符并将每个字段放入双引号中,我认为只需要转义双引号-尽管出于某种原因,您似乎将整个放入双引号中,这对于CSV来说并不正常。请[编辑]您的问题以显示示例两行输入及相应的期望输出。 - nnnnnn
我想我的意思是,它们被转义了,以便在最终输出中不会引起看似正在引起的确切问题。我编辑了问题,以更清楚地解释我对“row”变量的使用(如果您看不到循环,则命名不太好)。我查看了CSV规范,发现双引号内的双引号需要表示为两个双引号。因此,“Hello,""goodbye"",is a string”。但用两个双引号替换双引号是有效的,但我的输出现在有两个双引号... - B1gJ4k3
好的,没关系。我解决了。原来是我运行了两次sanitizeString()函数。根据CSV规范,引号内的双引号需要表示为两个双引号("")。我运行了两次该函数,导致产生了4个双引号,最终转换成了两个。 - B1gJ4k3
3个回答

37

根据CSV规范,如果要在已经被引用的字符串中包含双引号,则需要使用两个双引号 ("")。我进行了更改:

itemDesc = itemDesc.replace(/"/g, '\"');

itemDesc = itemDesc.replace(/"/g, '""');

我也移除了

itemDesc = itemDesc.replace(/,/g, '\,');
itemDesc = itemDesc.replace(/'/g, '\'');

由于CSV中的列已经被引用,因此这些内容是不必要的。


3
如果字符串不以引号开头,则可能包含转义引号,例如 Yes "" I am。如果以引号开头,则必须以引号结尾。即使该术语未被引用,也必须转义引号。边缘情况。 - Ray Foss

5

我使用这个简单的函数将string[][]转换为csv文件。如果单元格包含",或其他空白字符(除了空格),它会引用该单元格:

/**
 * Takes an array of arrays and returns a `,` sparated csv file.
 * @param {string[][]} table
 * @returns {string}
 */
export function toCSV(table: string[][]) {
    return table
        .map(row =>
            row
                .map(cell => {
                    // We remove blanks and check if the column contains
                    // other whitespace,`,` or `"`.
                    // In that case, we need to quote the column.
                    if (cell.replace(/ /g, '').match(/[\s,"]/)) {
                        return '"' + cell.replace(/"/g, '""') + '"';
                    }
                    return cell;
                })
                .join(',')
        )
        .join('\n');
}

真的很有帮助! - undefined

2

在我的情况下,我不想引用不需要引用的字符串。因此,在引用之前,我会测试字符串是否包含有害字符。

function escapeCSV (term) {
  if (term.match && term.match(/,|"/))  {
    return `"${term.replace('"','""')}"`
  } else {
    return term
  }
}

4
小心:replace 只会替换第一个出现的。使用带有 g 修饰符 (/"/g) 的正则表达式或新的 replaceAll 函数。 - Aron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接