文章开始前,假设你已将csv读取并分割成了行,仅仅需要将单元格分割开来就好了。
这里引用百度百科的csv规则:
- 开头是不留空,以行为单位。
- 可含或不含列名,含列名则居文件第一行。
- 一行数据不跨行,无空行。
- 以半角逗号(即,)作分隔符,列为空也要表达其存在。
- 列内容如存在半角引号(即"),替换成半角双引号("")转义,即用半角引号(即"")将该字段值包含起来。
- 文件读写时引号,逗号操作规则互逆。
- 内码格式不限,可为 ASCII、Unicode 或者其他。
- 不支持数字
- 不支持特殊字符
方法一:循环
方法内部处理了分隔符、字符串边界标识符及其转义字符、码点等主要逻辑。
/**
* 分割csv单元格
* @param row {string} csv行
* @param stringBoundaryIdentifier {string} 字符串边界标识符,一般情况下用的是半角双引号
* @param splitter {string} 单元格分隔符,一般情况下用的是半角逗号
* @return {string[]}
*/
function splitCsvRow(row, stringBoundaryIdentifier = "\"", splitter = ",") {
const result = [];
// 是否在字符串边界标识符内
let inStringBoundaryIdentifier = false;
// 单元格内容
let value = "";
// 当前字符的索引
let i = -1;
for (const char of row) { // 用for...of遍历字符串,可以避免长度超过1的字符被拆分,下面codePointAt、String.fromCodePoint也是如此
i++;
const lastCodePoint = row.codePointAt(i - 1);
const nextCodePoint = row.codePointAt(i + 1);
const lastChar = lastCodePoint !== void 0 ? String.fromCodePoint(lastCodePoint) : void 0;
const nextChar = nextCodePoint !== void 0 ? String.fromCodePoint(nextCodePoint) : void 0;
if (char === stringBoundaryIdentifier && (lastChar === splitter || nextChar === splitter)) { // 判断是否在两个边界标识符内,且其包裹了整个单元格
inStringBoundaryIdentifier = !inStringBoundaryIdentifier;
continue;
}
if (char === splitter && !inStringBoundaryIdentifier) { // 当遇到分隔符且不在单元格边界标识符内时,表示单元格结束,将单元格内容加入结果数组
result.push(value);
value = "";
continue;
}
if (char === stringBoundaryIdentifier && nextChar === stringBoundaryIdentifier) { // 当出现连续两个边界标识符时,表示转义,只保留第一个
i++;
}
value += char; // 将字符加入单元格内容
}
result.push(value);
return result;
}
方法二:正则表达式
不建议使用这个方法,正则表达式由于过于复杂,可能消耗大量内存,导致JavaScript heap out of memory
错误。
/**
* 用正则表达式分割csv单元格
* @param row {string} csv行
* @param sbi {string} 字符串边界标识符,一般情况下用的是半角双引号
* @param splitter {string} 单元格分隔符,一般情况下用的是半角逗号
* @return {string[]}
*/
function splitCsvRowByRegex(row, sbi = "\"", splitter = ",") {
// const splitCsvRowRegex = /(?:^|,)(?:"([^"]*(?:""[^"]*)*)"|([^,]*))/g;
const splitCsvRowRegex = new RegExp(
`(?:^|${splitter})(?:${sbi}([^${sbi}]*(?:${sbi}${sbi}[^${sbi}]*)*)${sbi}|([^${splitter}]*))`,
"g",
);
const result = [];
let match;
const escapeCharacter = new RegExp(sbi + sbi, "g");
while (match = splitCsvRowRegex.exec(row)) {
const value = match[1] || match[2] || "";
const valueWithoutQuote = value.replace(escapeCharacter, sbi);
result.push(valueWithoutQuote);
}
return result;
}
网友评论