Jak mogę przeanalizować ciąg CSV za pomocą JavaScript, który zawiera przecinek w danych?

96

Mam następujący typ ciągu

var string = "'string, duppi, du', 23, lala"

Chcę podzielić ciąg na tablicę w każdym przecinku, ale tylko przecinki poza pojedynczymi cudzysłowami.

Nie mogę znaleźć odpowiedniego wyrażenia regularnego do podziału ...

string.split(/,/)

da mi

["'string", " duppi", " du'", " 23", " lala"]

ale wynik powinien być:

["string, duppi, du", "23", "lala"]

Czy istnieje rozwiązanie dla różnych przeglądarek?

Hans
źródło
Czy zawsze są to apostrofy? Czy w ciągu cudzysłowu występuje kiedykolwiek pojedynczy cudzysłów? Jeśli tak, w jaki sposób został on pominięty (odwrotny ukośnik, podwojony)?
Phrogz,
Co się stanie, jeśli znaki cudzysłowu są całkowicie wymienne między znakami podwójnego i pojedynczego cudzysłowu, jak w kodzie JavaScript i HTML / XML? Jeśli tak, to wymaga to bardziej rozbudowanej operacji analizy niż CSV.
austincheney
właściwie tak, wewnątrz może znajdować się pojedynczy cudzysłów, ucieczka z ukośnikiem odwrotnym byłaby w porządku.
Hans,
Czy wartość może być ciągiem w cudzysłowie?
ridgerunner
1
Papa Parse wykonuje świetną robotę. Przetwarzanie lokalnego pliku CSV za pomocą JavaScript i Papa Parse: joyofdata.de/blog/…
Raffael,

Odpowiedzi:

216

Zrzeczenie się

Aktualizacja 2014-12-01: Poniższa odpowiedź działa tylko dla jednego bardzo konkretnego formatu CSV. Jak słusznie wskazała DG w komentarzach , rozwiązanie to nie pasuje do definicji CSV RFC 4180, a także nie pasuje do formatu Microsoft Excel. To rozwiązanie po prostu pokazuje, jak można przeanalizować jeden (niestandardowy) wiersz danych wejściowych CSV, który zawiera mieszankę typów ciągów, gdzie łańcuchy mogą zawierać cudzysłowy i przecinki.

Niestandardowe rozwiązanie CSV

Jak słusznie wskazuje austincheney , naprawdę musisz przeanalizować łańcuch od początku do końca, jeśli chcesz poprawnie obsługiwać ciągi w cudzysłowie, które mogą zawierać znaki ucieczki. Ponadto PO nie definiuje jasno, czym naprawdę jest „ciąg CSV”. Najpierw musimy zdefiniować, co stanowi prawidłowy ciąg CSV i jego poszczególne wartości.

Biorąc pod uwagę: Definicja „Ciąg CSV”

Na potrzeby tej dyskusji „ciąg CSV” składa się z zera lub większej liczby wartości, przy czym wiele wartości jest oddzielonych przecinkiem. Każda wartość może składać się z:

  1. Ciąg w cudzysłowie (może zawierać pojedyncze cudzysłowy bez znaku zmiany znaczenia).
  2. Ciąg znaków w pojedynczym cudzysłowie (może zawierać cudzysłowy bez znaku zmiany znaczenia).
  3. Ciąg bez cudzysłowu ( nie może zawierać cudzysłowów, przecinków ani ukośników odwrotnych).
  4. Pusta wartość. (Wszystkie białe znaki są uważane za puste).

Zasady / uwagi:

  • Cytowane wartości mogą zawierać przecinki.
  • Cytowane wartości mogą zawierać znaki ucieczki, np 'that\'s cool'.
  • Wartości zawierające cudzysłowy, przecinki lub ukośniki odwrotne muszą być cytowane.
  • Wartości zawierające początkowe lub końcowe białe znaki muszą być cytowane.
  • Odwrotny ukośnik jest usuwany ze wszystkich: \'w pojedynczych cudzysłowach.
  • Odwrotny ukośnik jest usuwany ze wszystkich: \"w podwójnych cudzysłowach.
  • Ciągi bez cudzysłowów są usuwane z wszelkich spacji wiodących i końcowych.
  • Separator przecinka może mieć sąsiadujące białe znaki (które są ignorowane).

Odnaleźć:

Funkcja JavaScript, która konwertuje prawidłowy ciąg CSV (zgodnie z powyższą definicją) na tablicę wartości ciągów.

Rozwiązanie:

Wyrażenia regularne używane w tym rozwiązaniu są złożone. I (IMHO) wszystkie nietrywialne wyrażenia regularne powinny być prezentowane w trybie wolnych odstępów z dużą ilością komentarzy i wcięć. Niestety JavaScript nie pozwala na tryb wolnego odstępu. W związku z tym wyrażenia regularne zaimplementowane przez to rozwiązanie są najpierw prezentowane w natywnej składni wyrażeń regularnych (wyrażonych za pomocą przydatnej składni r'''...'''surowych, wieloliniowych ciągów znaków Pythona ).

Najpierw tutaj jest wyrażenie regularne, które potwierdza, że ​​łańcuch CVS spełnia powyższe wymagania:

Wyrażenie regularne do walidacji „ciągu CSV”:

re_valid = r"""
# Validate a CSV string having single, double or un-quoted values.
^                                   # Anchor to start of string.
\s*                                 # Allow whitespace before value.
(?:                                 # Group for value alternatives.
  '[^'\\]*(?:\\[\S\s][^'\\]*)*'     # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*"     # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)*    # or Non-comma, non-quote stuff.
)                                   # End group of value alternatives.
\s*                                 # Allow whitespace after value.
(?:                                 # Zero or more additional values
  ,                                 # Values separated by a comma.
  \s*                               # Allow whitespace before value.
  (?:                               # Group for value alternatives.
    '[^'\\]*(?:\\[\S\s][^'\\]*)*'   # Either Single quoted string,
  | "[^"\\]*(?:\\[\S\s][^"\\]*)*"   # or Double quoted string,
  | [^,'"\s\\]*(?:\s+[^,'"\s\\]+)*  # or Non-comma, non-quote stuff.
  )                                 # End group of value alternatives.
  \s*                               # Allow whitespace after value.
)*                                  # Zero or more additional values
$                                   # Anchor to end of string.
"""

Jeśli ciąg pasuje do powyższego wyrażenia regularnego, to jest to prawidłowy ciąg CSV (zgodnie z wcześniej określonymi regułami) i może być analizowany przy użyciu następującego wyrażenia regularnego. Następujące wyrażenie regularne jest następnie używane do dopasowania jednej wartości z ciągu CSV. Jest stosowany wielokrotnie, aż nie zostaną znalezione żadne dopasowania (a wszystkie wartości zostaną przeanalizowane).

Wyrażenie regularne do analizowania jednej wartości z prawidłowego ciągu CSV:

re_value = r"""
# Match one value in valid CSV string.
(?!\s*$)                            # Don't match empty last value.
\s*                                 # Strip whitespace before value.
(?:                                 # Group for value alternatives.
  '([^'\\]*(?:\\[\S\s][^'\\]*)*)'   # Either $1: Single quoted string,
| "([^"\\]*(?:\\[\S\s][^"\\]*)*)"   # or $2: Double quoted string,
| ([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)  # or $3: Non-comma, non-quote stuff.
)                                   # End group of value alternatives.
\s*                                 # Strip whitespace after value.
(?:,|$)                             # Field ends on comma or EOS.
"""

Zwróć uwagę, że istnieje jedna wartość przypadku specjalnego, której to wyrażenie regularne nie pasuje - ostatnia wartość, gdy ta wartość jest pusta. Ten specjalny przypadek „pustej ostatniej wartości” jest testowany i obsługiwany przez następującą funkcję JavaScript.

Funkcja JavaScript do analizy ciągu CSV:

// Return array of string values, or NULL if CSV string not well formed.
function CSVtoArray(text) {
    var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
    var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;

    // Return NULL if input string is not well formed CSV string.
    if (!re_valid.test(text)) return null;

    var a = []; // Initialize array to receive values.
    text.replace(re_value, // "Walk" the string using replace with callback.
        function(m0, m1, m2, m3) {

            // Remove backslash from \' in single quoted values.
            if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));

            // Remove backslash from \" in double quoted values.
            else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
            else if (m3 !== undefined) a.push(m3);
            return ''; // Return empty string.
        });

    // Handle special case of empty last value.
    if (/,\s*$/.test(text)) a.push('');
    return a;
};

Przykładowe dane wejściowe i wyjściowe:

W poniższych przykładach nawiasy klamrowe służą do oddzielania {result strings}. (Ma to pomóc w wizualizacji spacji wiodących / końcowych i ciągów o zerowej długości).

// Test 1: Test string from original question.
var test = "'string, duppi, du', 23, lala";
var a = CSVtoArray(test);
/* Array has three elements:
    a[0] = {string, duppi, du}
    a[1] = {23}
    a[2] = {lala} */
// Test 2: Empty CSV string.
var test = "";
var a = CSVtoArray(test);
/* Array has zero elements: */
// Test 3: CSV string with two empty values.
var test = ",";
var a = CSVtoArray(test);
/* Array has two elements:
    a[0] = {}
    a[1] = {} */
// Test 4: Double quoted CSV string having single quoted values.
var test = "'one','two with escaped \' single quote', 'three, with, commas'";
var a = CSVtoArray(test);
/* Array has three elements:
    a[0] = {one}
    a[1] = {two with escaped ' single quote}
    a[2] = {three, with, commas} */
// Test 5: Single quoted CSV string having double quoted values.
var test = '"one","two with escaped \" double quote", "three, with, commas"';
var a = CSVtoArray(test);
/* Array has three elements:
    a[0] = {one}
    a[1] = {two with escaped " double quote}
    a[2] = {three, with, commas} */
// Test 6: CSV string with whitespace in and around empty and non-empty values.
var test = "   one  ,  'two'  ,  , ' four' ,, 'six ', ' seven ' ,  ";
var a = CSVtoArray(test);
/* Array has eight elements:
    a[0] = {one}
    a[1] = {two}
    a[2] = {}
    a[3] = { four}
    a[4] = {}
    a[5] = {six }
    a[6] = { seven }
    a[7] = {} */

Dodatkowe uwagi:

To rozwiązanie wymaga, aby ciąg CSV był „prawidłowy”. Na przykład, wartości nienotowane nie mogą zawierać backslashy lub cytaty, np następujący ciąg CSV nie obowiązuje:

var invalid1 = "one, that's me!, escaped \, comma"

W rzeczywistości nie jest to ograniczenie, ponieważ każdy podłańcuch może być reprezentowany jako wartość w pojedynczym lub podwójnym cudzysłowie. Należy również zauważyć, że to rozwiązanie reprezentuje tylko jedną możliwą definicję „wartości rozdzielanych przecinkami”.

Edytuj historię

  • 2014-05-19: Dodano wyłączenie odpowiedzialności.
  • 2014-12-01: Przeniesiono wyłączenie odpowiedzialności na górę.
ridgerunner
źródło
1
@Evan Plaice - Dzięki za miłe słowa. Oczywiście, możesz użyć dowolnego separatora. Po prostu zastąp każdy przecinek w moim wyrażeniu regularnym wybranym separatorem (ale separatorem nie może być biały znak). Twoje zdrowie.
ridgerunner
2
@Evan Plaice - Możesz używać dowolnego z moich wyrażeń regularnych w dowolnym celu. Nota uznania byłaby miła, ale niekonieczna. Powodzenia z wtyczką. Twoje zdrowie!
ridgerunner
1
Świetnie, oto projekt code.google.com/p/jquery-csv . Ostatecznie chcę dodać format rozszerzenia do CSV o nazwie SSV (Structured Separated Values), który jest po prostu CSV z dołączonymi metadanymi (tj. Separatorem, separatorem, zakończeniem wiersza itp.).
Evan Plaice
1
Wielkie dzięki za tę świetną implementację - użyłem jej jako podstawy dla modułu Node.js ( csv-iterator ).
mirkokiefer
3
Doceniam szczegóły i wyjaśniam twoją odpowiedź, ale należy gdzieś zauważyć, że twoja definicja CSV nie pasuje do RFC 4180, który jest zamknięciem do standardu CSV, i który, mogę powiedzieć, jest powszechnie używany. W szczególności byłby to normalny sposób na „uniknięcie” podwójnego cudzysłowu w polu ciągu: "field one", "field two", "a ""final"" field containing two double quote marks"nie testowałem odpowiedzi Trevora Dixona na tej stronie, ale jest to odpowiedź, która odnosi się do definicji CSV RFC 4180.
DG.
54

Rozwiązanie RFC 4180

Nie rozwiązuje to łańcucha w pytaniu, ponieważ jego format nie jest zgodny z RFC 4180; dopuszczalnym kodowaniem jest unikanie podwójnego cudzysłowu z podwójnym cudzysłowem. Poniższe rozwiązanie działa poprawnie z plikami CSV d / l z arkuszy kalkulacyjnych Google.

AKTUALIZACJA (3/2017)

Analiza pojedynczej linii byłaby błędna. Zgodnie z RFC 4180 pola mogą zawierać CRLF, co spowoduje, że dowolny czytnik linii złamie plik CSV. Oto zaktualizowana wersja, która analizuje ciąg CSV:

'use strict';

function csvToArray(text) {
    let p = '', row = [''], ret = [row], i = 0, r = 0, s = !0, l;
    for (l of text) {
        if ('"' === l) {
            if (s && l === p) row[i] += l;
            s = !s;
        } else if (',' === l && s) l = row[++i] = '';
        else if ('\n' === l && s) {
            if ('\r' === p) row[i] = row[i].slice(0, -1);
            row = ret[++r] = [l = '']; i = 0;
        } else row[i] += l;
        p = l;
    }
    return ret;
};

let test = '"one","two with escaped """" double quotes""","three, with, commas",four with no quotes,"five with CRLF\r\n"\r\n"2nd line one","two with escaped """" double quotes""","three, with, commas",four with no quotes,"five with CRLF\r\n"';
console.log(csvToArray(test));

STARA ODPOWIEDŹ

(Rozwiązanie jednokreskowe)

function CSVtoArray(text) {
    let ret = [''], i = 0, p = '', s = true;
    for (let l in text) {
        l = text[l];
        if ('"' === l) {
            s = !s;
            if ('"' === p) {
                ret[i] += '"';
                l = '-';
            } else if ('' === p)
                l = '-';
        } else if (s && ',' === l)
            l = ret[++i] = '';
        else
            ret[i] += l;
        p = l;
    }
    return ret;
}
let test = '"one","two with escaped """" double quotes""","three, with, commas",four with no quotes,five for fun';
console.log(CSVtoArray(test));

A dla zabawy oto jak tworzysz CSV z tablicy:

function arrayToCSV(row) {
    for (let i in row) {
        row[i] = row[i].replace(/"/g, '""');
    }
    return '"' + row.join('","') + '"';
}

let row = [
  "one",
  "two with escaped \" double quote",
  "three, with, commas",
  "four with no quotes (now has)",
  "five for fun"
];
let text = arrayToCSV(row);
console.log(text);

niry
źródło
1
ten wykonał zadanie za mnie, a nie drugi
WtFudgE
7

Gramatyka PEG (.js) obsługująca przykłady RFC 4180 pod adresem http://en.wikipedia.org/wiki/Comma-separated_values :

start
  = [\n\r]* first:line rest:([\n\r]+ data:line { return data; })* [\n\r]* { rest.unshift(first); return rest; }

line
  = first:field rest:("," text:field { return text; })*
    & { return !!first || rest.length; } // ignore blank lines
    { rest.unshift(first); return rest; }

field
  = '"' text:char* '"' { return text.join(''); }
  / text:[^\n\r,]* { return text.join(''); }

char
  = '"' '"' { return '"'; }
  / [^"]

Przetestuj na http://jsfiddle.net/knvzk/10 lub https://pegjs.org/online .

Pobierz wygenerowany parser ze strony https://gist.github.com/3362830 .

Trevor Dixon
źródło
6

Miałem bardzo specyficzny przypadek użycia, w którym chciałem skopiować komórki z Arkuszy Google do mojej aplikacji internetowej. Komórki mogą zawierać cudzysłowy i znaki nowego wiersza. Korzystając z funkcji kopiowania i wklejania, komórki są oddzielane znakami tabulacji, a komórki z nieparzystymi danymi są umieszczane w cudzysłowie. Wypróbowałem to główne rozwiązanie, połączony artykuł przy użyciu wyrażenia regularnego, Jquery-CSV i CSVToArray. http://papaparse.com/ Jest jedynym, który zadziałał po wyjęciu z pudełka. Kopiowanie i wklejanie przebiega bezproblemowo w Arkuszach Google z domyślnymi opcjami automatycznego wykrywania.

bjcullinan
źródło
1
Powinno to mieć znacznie wyższą pozycję w rankingu, nigdy nie próbuj rzucać własnego parsera CSV, nie będzie działać poprawnie - szczególnie gdy używasz wyrażeń regularnych. Papaparse jest niesamowity - użyj go!
cbley
6

Podobała mi się odpowiedź FakeRainBrigand, jednak zawiera kilka problemów: nie obsługuje białych znaków między cudzysłowem a przecinkiem i nie obsługuje 2 kolejnych przecinków. Próbowałem edytować jego odpowiedź, ale moja zmiana została odrzucona przez recenzentów, którzy najwyraźniej nie zrozumieli mojego kodu. Oto moja wersja kodu FakeRainBrigand. Jest też skrzypce: http://jsfiddle.net/xTezm/46/

String.prototype.splitCSV = function() {
        var matches = this.match(/(\s*"[^"]+"\s*|\s*[^,]+|,)(?=,|$)/g);
        for (var n = 0; n < matches.length; ++n) {
            matches[n] = matches[n].trim();
            if (matches[n] == ',') matches[n] = '';
        }
        if (this[0] == ',') matches.unshift("");
        return matches;
}

var string = ',"string, duppi, du" , 23 ,,, "string, duppi, du",dup,"", , lala';
var parsed = string.splitCSV();
alert(parsed.join('|'));
HammerNL
źródło
4

Wydawało się, że ludzie są przeciwni RegEx za to. Czemu?

(\s*'[^']+'|\s*[^,]+)(?=,|$)

Oto kod. Zrobiłem też skrzypce .

String.prototype.splitCSV = function(sep) {
  var regex = /(\s*'[^']+'|\s*[^,]+)(?=,|$)/g;
  return matches = this.match(regex);    
}

var string = "'string, duppi, du', 23, 'string, duppi, du', lala";
var parsed = string.splitCSV();
alert(parsed.join('|'));
Bandyta
źródło
3
Hmm, twoje wyrażenie regularne ma pewne problemy: nie obsługuje białych znaków między cudzysłowem a przecinkiem i nie obsługuje 2 kolejnych przecinków. Zaktualizowałem Twoją odpowiedź kodem, który rozwiązuje oba problemy i stworzyłem nowe skrzypce: jsfiddle.net/xTezm/43
HammerNL
Z jakiegoś powodu moja zmiana w Twoim kodzie została odrzucona, ponieważ „odbiegałaby od pierwotnego przeznaczenia postu”. Bardzo dziwny!? Właśnie wziąłem twój kod i rozwiązałem z nim dwa problemy. Jak to zmienia cel postu !? W każdym razie ... po prostu dodałem nową odpowiedź na to pytanie.
HammerNL
Dobre pytanie w Twojej odpowiedzi, @FakeRainBrigand. Ja na przykład wszystko dla wyrażenia regularnego iz tego powodu przyznaję, że jest to niewłaściwe narzędzie do tego zadania.
niry
2
@niry, mój kod jest okropny. Obiecuję, że poprawiłem się w ciągu ostatnich 6 lat:
Brigand
4

Dodanie jeszcze jednego do listy, ponieważ wszystkie powyższe stwierdzenia nie są wystarczające.

Ten używa wyrażenia regularnego, aby znaleźć przecinki lub znaki nowej linii, pomijając cytowane elementy. Miejmy nadzieję, że jest to coś, co noobies mogą samodzielnie przeczytać. Wyrażenie splitFinderregularne ma trzy rzeczy, które robi (podzielone przez a |):

  1. , - znajduje przecinki
  2. \r?\n - znajduje nowe wiersze (potencjalnie z powrotem karetki, jeśli eksporter był miły)
  3. "(\\"|[^"])*?"- pomija wszystko w cudzysłowie, ponieważ przecinki i znaki nowej linii nie mają tam znaczenia. Jeśli \\"w cytowanej pozycji znajduje się cytat ze zmianą znaczenia, zostanie on przechwycony, zanim będzie można znaleźć wycenę końcową.

const splitFinder = /,|\r?\n|"(\\"|[^"])*?"/g;

function csvTo2dArray(parseMe) {
  let currentRow = [];
  const rowsOut = [currentRow];
  let lastIndex = splitFinder.lastIndex = 0;
  
  // add text from lastIndex to before a found newline or comma
  const pushCell = (endIndex) => {
    endIndex = endIndex || parseMe.length;
    const addMe = parseMe.substring(lastIndex, endIndex);
    // remove quotes around the item
    currentRow.push(addMe.replace(/^"|"$/g, ""));
    lastIndex = splitFinder.lastIndex;
  }


  let regexResp;
  // for each regexp match (either comma, newline, or quoted item)
  while (regexResp = splitFinder.exec(parseMe)) {
    const split = regexResp[0];

    // if it's not a quote capture, add an item to the current row
    // (quote captures will be pushed by the newline or comma following)
    if (split.startsWith(`"`) === false) {
      const splitStartIndex = splitFinder.lastIndex - split.length;
      pushCell(splitStartIndex);

      // then start a new row if newline
      const isNewLine = /^\r?\n$/.test(split);
      if (isNewLine) { rowsOut.push(currentRow = []); }
    }
  }
  // make sure to add the trailing text (no commas or newlines after)
  pushCell();
  return rowsOut;
}

const rawCsv = `a,b,c\n"test\r\n","comma, test","\r\n",",",\nsecond,row,ends,with,empty\n"quote\"test"`
const rows = csvTo2dArray(rawCsv);
console.log(rows);

Seph Reed
źródło
Jeśli wczytuję mój plik przez fileReader i mój wynik: Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 jak mogę przeanalizować na podstawie określonych przeze mnie kolumn?
bluePearl
Po uzyskaniu tablicy 2d usuń pierwszy indeks (jeśli są to nazwy twoich właściwości), a następnie wykonaj iterację po reszcie tablicy, tworząc obiekty z każdą wartością jako właściwością. Będzie to wyglądało tak:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Seph Reed
3

Jeśli możesz ustawić ogranicznik cudzysłowu w podwójnych cudzysłowach, oznacza to, że jest to duplikat Przykładowego kodu JavaScript do analizowania danych CSV .

Możesz najpierw przetłumaczyć wszystkie apostrofy na cudzysłowy:

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

... lub możesz edytować wyrażenie regularne w tym pytaniu, aby rozpoznawać pojedyncze cudzysłowy zamiast podwójnych cudzysłowów:

// Quoted fields.
"(?:'([^']*(?:''[^']*)*)'|" +

Jednak zakłada to pewne znaczniki, których nie wynika z twojego pytania. Proszę wyjaśnić, jakie mogą być wszystkie różne możliwości znaczników, zgodnie z moim komentarzem do twojego pytania.

Phrogz
źródło
2

Moja odpowiedź zakłada, że ​​wprowadzone dane są odzwierciedleniem kodu / treści ze źródeł internetowych, w których znaki pojedynczego i podwójnego cudzysłowu są w pełni zamienne, pod warunkiem, że występują jako zestaw pasujący bez zmiany znaczenia.

Nie możesz do tego użyć wyrażenia regularnego. W rzeczywistości musisz napisać mikro parser, aby przeanalizować ciąg, który chcesz podzielić. Ze względu na tę odpowiedź będę nazywał zacytowane części twoich strun jako podstruny. Musisz specjalnie przejść przez strunę. Rozważ następujący przypadek:

var a = "some sample string with \"double quotes\" and 'single quotes' and some craziness like this: \\\" or \\'",
    b = "sample of code from JavaScript with a regex containing a comma /\,/ that should probably be ignored.";

W tym przypadku nie masz absolutnie żadnego pojęcia, gdzie podłańcuch zaczyna się lub kończy, po prostu analizując dane wejściowe dla wzorca znakowego. Zamiast tego musisz napisać logikę, aby podejmować decyzje, czy znak cudzysłowu jest używany jako znak cudzysłowu, czy sam w sobie nie jest cytowany i czy znak cudzysłowu nie występuje po ucieczce.

Nie mam zamiaru pisać dla ciebie takiego poziomu złożoności kodu, ale możesz spojrzeć na coś, co niedawno napisałem, a które ma wzór, którego potrzebujesz. Ten kod nie ma nic wspólnego z przecinkami, ale poza tym jest wystarczającym mikro-parserem, abyś mógł go śledzić, pisząc swój własny kod. Przyjrzyj się funkcji asifix następującej aplikacji:

https://github.com/austincheney/Pretty-Diff/blob/master/fulljsmin.js

austincheney
źródło
2

Aby uzupełnić tę odpowiedź

Jeśli chcesz analizować cudzysłowy poprzedzone innym cytatem, na przykład:

"some ""value"" that is on xlsx file",123

Możesz użyć

function parse(text) {
  const csvExp = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|"([^""]*(?:"[\S\s][^""]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;

  const values = [];

  text.replace(csvExp, (m0, m1, m2, m3, m4) => {
    if (m1 !== undefined) {
      values.push(m1.replace(/\\'/g, "'"));
    }
    else if (m2 !== undefined) {
      values.push(m2.replace(/\\"/g, '"'));
    }
    else if (m3 !== undefined) {
      values.push(m3.replace(/""/g, '"'));
    }
    else if (m4 !== undefined) {
      values.push(m4);
    }
    return '';
  });

  if (/,\s*$/.test(text)) {
    values.push('');
  }

  return values;
}
BrunoLM
źródło
Okazało się, że to nadal nie działa"jjj "" kkk""","123"
niry
2

Podczas wczytywania pliku CSV do ciągu, zawiera on wartości null między ciągami, więc spróbuj z \ 0 wiersz po wierszu. Mi to pasuje.

stringLine = stringLine.replace(/\0/g, "" );
Sharathi RB
źródło
2

Napotkałem również problem tego samego typu, gdy musiałem przeanalizować plik CSV.

Plik zawiera adres kolumny zawierający znak „,”.

Po przeanalizowaniu tego pliku CSV do formatu JSON otrzymuję niezgodne mapowanie kluczy podczas konwertowania go na plik JSON.

Użyłem Node.js do analizowania pliku i bibliotek, takich jak baby parse i csvtojson .

Przykład pliku -

address,pincode
foo,baar , 123456

Podczas gdy parsowałem bezpośrednio, bez użycia analizy baby w JSON, otrzymywałem:

[{
 address: 'foo',
 pincode: 'baar',
 'field3': '123456'
}]

Napisałem więc kod, który usuwa przecinek (,) z każdym innym separatorem w każdym polu:

/*
 csvString(input) = "address, pincode\\nfoo, bar, 123456\\n"
 output = "address, pincode\\nfoo {YOUR DELIMITER} bar, 123455\\n"
*/
const removeComma = function(csvString){
    let delimiter = '|'
    let Baby = require('babyparse')
    let arrRow = Baby.parse(csvString).data;
    /*
      arrRow = [
      [ 'address', 'pincode' ],
      [ 'foo, bar', '123456']
      ]
    */
    return arrRow.map((singleRow, index) => {
        //the data will include
        /*
        singleRow = [ 'address', 'pincode' ]
        */
        return singleRow.map(singleField => {
            //for removing the comma in the feild
            return singleField.split(',').join(delimiter)
        })
    }).reduce((acc, value, key) => {
        acc = acc +(Array.isArray(value) ?
         value.reduce((acc1, val)=> {
            acc1 = acc1+ val + ','
            return acc1
        }, '') : '') + '\n';
        return acc;
    },'')
}

Zwracana funkcja może zostać przekazana do biblioteki csvtojson, dzięki czemu można użyć wyniku.

const csv = require('csvtojson')

let csvString = "address, pincode\\nfoo, bar, 123456\\n"
let jsonArray = []
modifiedCsvString = removeComma(csvString)
csv()
  .fromString(modifiedCsvString)
  .on('json', json => jsonArray.push(json))
  .on('end', () => {
    /* do any thing with the json Array */
  })

Teraz możesz uzyskać dane wyjściowe takie jak:

[{
  address: 'foo, bar',
  pincode: 123456
}]
Supermacy
źródło
2

Brak wyrażenia regularnego, czytelne i zgodnie z https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules :

function csv2arr(str: string) {
    let line = ["",];
    const ret = [line,];
    let quote = false;

    for (let i = 0; i < str.length; i++) {
        const cur = str[i];
        const next = str[i + 1];

        if (!quote) {
            const cellIsEmpty = line[line.length - 1].length === 0;
            if (cur === '"' && cellIsEmpty) quote = true;
            else if (cur === ",") line.push("");
            else if (cur === "\r" && next === "\n") { line = ["",]; ret.push(line); i++; }
            else if (cur === "\n" || cur === "\r") { line = ["",]; ret.push(line); }
            else line[line.length - 1] += cur;
        } else {
            if (cur === '"' && next === '"') { line[line.length - 1] += cur; i++; }
            else if (cur === '"') quote = false;
            else line[line.length - 1] += cur;
        }
    }
    return ret;
}
Bachor
źródło
1

Zgodnie z tym wpisem na blogu ta funkcja powinna to zrobić:

String.prototype.splitCSV = function(sep) {
  for (var foo = this.split(sep = sep || ","), x = foo.length - 1, tl; x >= 0; x--) {
    if (foo[x].replace(/'\s+$/, "'").charAt(foo[x].length - 1) == "'") {
      if ((tl = foo[x].replace(/^\s+'/, "'")).length > 1 && tl.charAt(0) == "'") {
        foo[x] = foo[x].replace(/^\s*'|'\s*$/g, '').replace(/''/g, "'");
      } else if (x) {
        foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep));
      } else foo = foo.shift().split(sep).concat(foo);
    } else foo[x].replace(/''/g, "'");
  } return foo;
};

Nazwałbyś to tak:

var string = "'string, duppi, du', 23, lala";
var parsed = string.splitCSV();
alert(parsed.join("|"));

Ten rodzaj jsfiddle działa, ale wygląda na to, że niektóre elementy mają przed sobą spacje.

CanSpice
źródło
Wyobraź sobie, że musisz to wszystko zrobić w wyrażeniu regularnym. Dlatego czasami wyrażenia regularne nie nadają się do analizowania.
CanSpice
To rozwiązanie po prostu nie działa. Biorąc pod uwagę oryginalny ciąg testowy "'string, duppi, du', 23, lala"["'string"," duppi"," du'"," 23"," lala"]
:,
@ridgerunner: Masz rację. Zmieniłem odpowiedź i plik jsfiddle, aby naprawić tę funkcję. Zasadniczo przełączyłem się "'"na '"'i odwrotnie.
CanSpice
To pomogło, ale teraz funkcja nieprawidłowo obsługuje ciągi CSV w pojedynczych cudzysłowach, które mają wartości w podwójnych cudzysłowach. np. odwrócenie typów cudzysłowów oryginalnego ciągu testowego w następujący sposób: '"string, duppi, du", 23, lala'powoduje:['"string',' duppi'.' du"',' 23',' lala']
ridgerunner
@CanSpice, Twój komentarz zainspirował mnie do wypróbowania RegEx. Nie ma tak wielu funkcji, ale można je łatwo dodać. (Moja odpowiedź jest na tej stronie, jeśli jesteś zainteresowany.)
Brigand,
0

Wyrażenia regularne na ratunek! Te kilka wierszy kodu obsługuje odpowiednio cytowane pola z osadzonymi przecinkami, cudzysłowami i znakami nowej linii w oparciu o standard RFC 4180.

function parseCsv(data, fieldSep, newLine) {
    fieldSep = fieldSep || ',';
    newLine = newLine || '\n';
    var nSep = '\x1D';
    var qSep = '\x1E';
    var cSep = '\x1F';
    var nSepRe = new RegExp(nSep, 'g');
    var qSepRe = new RegExp(qSep, 'g');
    var cSepRe = new RegExp(cSep, 'g');
    var fieldRe = new RegExp('(?<=(^|[' + fieldSep + '\\n]))"(|[\\s\\S]+?(?<![^"]"))"(?=($|[' + fieldSep + '\\n]))', 'g');
    var grid = [];
    data.replace(/\r/g, '').replace(/\n+$/, '').replace(fieldRe, function(match, p1, p2) {
        return p2.replace(/\n/g, nSep).replace(/""/g, qSep).replace(/,/g, cSep);
    }).split(/\n/).forEach(function(line) {
        var row = line.split(fieldSep).map(function(cell) {
            return cell.replace(nSepRe, newLine).replace(qSepRe, '"').replace(cSepRe, ',');
        });
        grid.push(row);
    });
    return grid;
}

const csv = 'A1,B1,C1\n"A ""2""","B, 2","C\n2"';
const separator = ',';      // field separator, default: ','
const newline = ' <br /> '; // newline representation in case a field contains newlines, default: '\n' 
var grid = parseCsv(csv, separator, newline);
// expected: [ [ 'A1', 'B1', 'C1' ], [ 'A "2"', 'B, 2', 'C <br /> 2' ] ]

O ile nie podano inaczej, nie potrzebujesz skończonej maszyny stanowej. Wyrażenie regularne poprawnie obsługuje RFC 4180 dzięki dodatnim wybiegom wstecz, ujemnym wybiegom wstecz i dodatnim wybiegom w przód.

Sklonuj / pobierz kod na https://github.com/peterthoeny/parse-csv-js

Peter Thoeny
źródło
0

Oprócz doskonałej i pełnej odpowiedzi udzielonej przez ridgerunner , pomyślałem o bardzo prostym , gdy twój backend uruchamia PHP.

Dodaj plik PHP do swojej domeny backend (słownie: csv.php)

<?php
    session_start(); // Optional
    header("content-type: text/xml");
    header("charset=UTF-8");
    // Set the delimiter and the End of Line character of your CSV content:
    echo json_encode(array_map('str_getcsv', str_getcsv($_POST["csv"], "\n")));
?>

Teraz dodaj tę funkcję do swojego zestawu narzędzi JavaScript (powinien być nieco poprawiony, aby stworzyć crossbrowser).

function csvToArray(csv) {
    var oXhr = new XMLHttpRequest;
    oXhr.addEventListener("readystatechange",
        function () {
            if (this.readyState == 4 && this.status == 200) {
                console.log(this.responseText);
                console.log(JSON.parse(this.responseText));
            }
        }
    );
    oXhr.open("POST","path/to/csv.php",true);
    oXhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
    oXhr.send("csv=" + encodeURIComponent(csv));
}

Będzie to kosztować jedno połączenie Ajax, ale przynajmniej nie zduplikujesz kodu ani nie włączysz żadnej zewnętrznej biblioteki.

Ref: http://php.net/manual/en/function.str-getcsv.php

Sebas
źródło
0

Możesz użyć papaparse.js, jak na poniższym przykładzie:

<!DOCTYPE html>
<html lang="en">

    <head>
        <title>CSV</title>
    </head>

    <body>
        <input type="file" id="files" multiple="">
        <button onclick="csvGetter()">CSV Getter</button>
        <h3>The Result will be in the Console.</h3>

        <script src="papaparse.min.js"></script>

        <script>
            function csvGetter() {

                var file = document.getElementById('files').files[0];
                Papa.parse(file, {
                    complete: function(results) {
                        console.log(results.data);
                    }
                });
            }
          </script>
    </body>

</html>

Nie zapomnij umieścić papaparse.js w tym samym folderze.

Tahseen Alaa
źródło