Если следующее регулярное выражение может разбивать строку csv по строке.
var lines = csv.split(/\r|\r?\n/g);
Как это можно было бы адаптировать для пропуска символов новой строки, которые содержатся в значении CSV (т.е. между кавычками/двойными кавычками)?
Пример:
2,"Evans & Sutherland","230-132-111AA",,"Visual","P
CB",,1,"Offsite",
Если вы этого не видите, здесь видна версия с новыми символами:
2,"Evans & Sutherland","230-132-111AA",,"Visual","P\r\nCB",,1,"Offsite",\r\n
Часть, которую я пытаюсь пропустить, это новая строка, содержащаяся в середине записи "PCB".
Update:
Вероятно, я должен был упомянуть об этом раньше, но это часть выделенной библиотеки синтаксического анализа CSV под названием jquery-csv. Чтобы обеспечить лучший контекст, я добавил текущую реализацию парсера ниже.
Здесь код для проверки и разбора записи (т.е. одна строка):
$.csvEntry2Array = function(csv, meta) {
var meta = (meta !== undefined ? meta : {});
var separator = 'separator' in meta ? meta.separator : $.csvDefaults.separator;
var delimiter = 'delimiter' in meta ? meta.delimiter : $.csvDefaults.delimiter;
// build the CSV validator regex
var reValid = /^\s*(?:D[^D\\]*(?:\\[\S\s][^D\\]*)*D|[^SD\s\\]*(?:\s+[^SD\s\\]+)*)\s*(?:S\s*(?:D[^D\\]*(?:\\[\S\s][^D\\]*)*D|[^SD\s\\]*(?:\s+[^SD\s\\]+)*)\s*)*$/;
reValid = RegExp(reValid.source.replace(/S/g, separator));
reValid = RegExp(reValid.source.replace(/D/g, delimiter));
// build the CSV line parser regex
var reValue = /(?!\s*$)\s*(?:D([^D\\]*(?:\\[\S\s][^D\\]*)*)D|([^SD\s\\]*(?:\s+[^SD\s\\]+)*))\s*(?:S|$)/g;
reValue = RegExp(reValue.source.replace(/S/g, separator), 'g');
reValue = RegExp(reValue.source.replace(/D/g, delimiter), 'g');
// Return NULL if input string is not well formed CSV string.
if (!reValid.test(csv)) {
return null;
}
// "Walk" the string using replace with callback.
var output = [];
csv.replace(reValue, function(m0, m1, m2) {
// Remove backslash from any delimiters in the value
if (m1 !== undefined) {
var reDelimiterUnescape = /\\D/g;
reDelimiterUnescape = RegExp(reDelimiterUnescape.source.replace(/D/, delimiter), 'g');
output.push(m1.replace(reDelimiterUnescape, delimiter));
} else if (m2 !== undefined) {
output.push(m2);
}
return '';
});
// Handle special case of empty last value.
var reEmptyLast = /S\s*$/;
reEmptyLast = RegExp(reEmptyLast.source.replace(/S/, separator));
if (reEmptyLast.test(csv)) {
output.push('');
}
return output;
};
Примечание. Я еще не тестировал, но думаю, что я мог бы включить последнее совпадение в основной раздел/обратный вызов.
Это код, который разделяет по частям:
$.csv2Array = function(csv, meta) {
var meta = (meta !== undefined ? meta : {});
var separator = 'separator' in meta ? meta.separator : $.csvDefaults.separator;
var delimiter = 'delimiter' in meta ? meta.delimiter : $.csvDefaults.delimiter;
var skip = 'skip' in meta ? meta.skip : $.csvDefaults.skip;
// process by line
var lines = csv.split(/\r\n|\r|\n/g);
var output = [];
for(var i in lines) {
if(i < skip) {
continue;
}
// process each value
var line = $.csvEntry2Array(lines[i], {
delimiter: delimiter,
separator: separator
});
output.push(line);
}
return output;
};
Для описания того, как это работает, просмотрите этот ответ. Мой - слегка адаптированная версия. Я объединил одно и двойную кавычку, чтобы соответствовать только одному разделителю текста и сделал динамический разделитель/разделители. Он отлично справляется с проверкой энтузиазма, но решение для разделения строк, которое я добавил сверху, довольно хрупкое и разбивается на край, который я описал выше.
Я просто ищу решение, которое обрабатывает строку, извлекающую действительные записи (для перехода к парсеру), или сбой при неудачных данных, возвращающих ошибку, указывающую на то, что строка, на которую не удалось выполнить синтаксический анализ, не удалось.
Update:
splitLines: function(csv, delimiter) {
var state = 0;
var value = "";
var line = "";
var lines = [];
function endOfRow() {
lines.push(value);
value = "";
state = 0;
};
csv.replace(/(\"|,|\n|\r|[^\",\r\n]+)/gm, function (m0){
switch (state) {
// the start of an entry
case 0:
if (m0 === "\"") {
state = 1;
} else if (m0 === "\n") {
endOfRow();
} else if (/^\r$/.test(m0)) {
// carriage returns are ignored
} else {
value += m0;
state = 3;
}
break;
// delimited input
case 1:
if (m0 === "\"") {
state = 2;
} else {
value += m0;
state = 1;
}
break;
// delimiter found in delimited input
case 2:
// is the delimiter escaped?
if (m0 === "\"" && value.substr(value.length - 1) === "\"") {
value += m0;
state = 1;
} else if (m0 === ",") {
value += m0;
state = 0;
} else if (m0 === "\n") {
endOfRow();
} else if (m0 === "\r") {
// Ignore
} else {
throw new Error("Illegal state");
}
break;
// un-delimited input
case 3:
if (m0 === ",") {
value += m0;
state = 0;
} else if (m0 === "\"") {
throw new Error("Unquoted delimiter found");
} else if (m0 === "\n") {
endOfRow();
} else if (m0 === "\r") {
// Ignore
} else {
throw new Error("Illegal data");
}
break;
default:
throw new Error("Unknown state");
}
return "";
});
if (state != 0) {
endOfRow();
}
return lines;
}
Все, что требуется, - это 4 состояния для разделителя строк:
- 0: начало записи
- 1: цитируется следующее
- 2: вторая цитата встречается
- 3: следующее не цитируется
Это почти полный парсер. Для моего варианта использования я просто хотел использовать разделитель строк, чтобы я мог предоставить более гранулярный подход к обработке CSV-данных.
Примечание. Кредит для этого подхода относится к другому разработчику, которого я не буду публично публиковать без его разрешения. Все, что я сделал, это адаптировать его от полного анализатора к разделителю строк.
Update:
Обнаружено несколько случаев с разбитыми краями в предыдущей реализации lineSplitter. Предоставленный должен полностью соответствовать RFC 4180.