Podziel ciąg CSV, pomijając wiersze zawarte w cudzysłowie
Jeśli poniższe wyrażenie regularne może podzielić ciąg csv według linii.
<code>var lines = csv.split(/\r|\r?\n/g); </code>
Jak można to dostosować do pominięcia znaków nowej linii zawartych w wartości CSV (tj. Między cudzysłowami / cudzysłowami)?
Przykład:
<code>2,"Evans & Sutherland","230-132-111AA",,"Visual","P CB",,1,"Offsite", </code>
Jeśli go nie widzisz, mamy wersję z widocznymi znakami nowej linii:
<code>2,"Evans & Sutherland","230-132-111AA",,"Visual","P\r\nCB",,1,"Offsite",\r\n </code>
Część, którą próbuję pominąć, to nowa linia zawarta w środku wpisu „PCB”.
Aktualizacja:
Prawdopodobnie powinienem o tym wcześniej wspomnieć, ale jest to część dedykowanej biblioteki analizującej CSV o nazwiejquery-csv. Aby zapewnić lepszy kontekst, dodałem poniżej bieżącą implementację parsera.
Oto kod do sprawdzania poprawności i analizowania wpisu (tj. Jeden wiersz):
<code>$.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; }; </code>
Uwaga: Jeszcze nie testowałem, ale myślę, że prawdopodobnie mógłbym dołączyć ostatni mecz do głównego podziału / wywołania zwrotnego.
To jest kod, który wykonuje część podzieloną według linii:
<code>$.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; }; </code>
Aby dowiedzieć się, w jaki sposób działa regesta odpowiedź. Moja jest nieco dostosowana wersja. Skonsolidowałem dopasowanie pojedynczej i podwójnej wyceny tak, aby pasowało tylko do jednego ogranicznika tekstu i sprawiło, że separator / separatory były dynamiczne. Świetnie sprawdza się w zatwierdzaniu zgłoszeń, ale rozwiązanie dzielenia linii, które dodałem na górze, jest dość słabe i załamuje się w przypadku krawędzi opisanym powyżej.
Po prostu szukam rozwiązania, które przenosi ciąg wyodrębniający poprawne wpisy (aby przekazać do parsera wejścia) lub nie działa na złych danych, zwracając błąd wskazujący linię, w której przetwarzanie nie powiodło się.
Aktualizacja:
<code>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; } </code>
Wystarczył 4 stany dla rozdzielacza linii:
0: początek wpisu1: cytowany jest następujący tekst2: napotkano drugi cytat3: nie cytuje się poniższegoTo prawie kompletny parser. W moim przypadku użycia chciałem tylko rozgałęźnika linii, dzięki czemu mogłem zapewnić bardziej granularne podejście do przetwarzania danych CSV.
Uwaga: Kredyt dla tego podejścia trafia do innego deva, którego nie podam publicznie bez jego zgody. Wszystko, co zrobiłem, to dostosowanie go z kompletnego parsera do rozdzielacza linii.
Aktualizacja:
Odkryłem kilka przypadków złamania krawędzi w poprzedniej implementacji lineSplitter. Podany powinien być w pełniRFC 4180 zgodny.