Verilerde virgül içeren bir CSV dizesini JavaScript ile nasıl ayrıştırabilirim?


99

Aşağıdaki türden dizeye sahibim

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

Dizeyi her virgülde bir diziye bölmek istiyorum, ancak yalnızca tek tırnak işaretlerinin dışındaki virgüller.

Bölünme için doğru normal ifadeyi bulamıyorum ...

string.split(/,/)

bana verecek

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

ancak sonuç şöyle olmalıdır:

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

Çapraz tarayıcı çözümü var mı?


Her zaman tek tırnak mıdır? Tırnaklı bir dizenin içinde hiç tek tırnak var mı? Öyleyse, nasıl kaçtı (ters eğik çizgi, ikiye katlanmış)?
Phrogz

Ya alıntı karakterleri JavaScript ve HTML / XML kodunda olduğu gibi çift ve tek tırnak karakterleri arasında tamamen değiştirilebilirse? Öyleyse, bu, CSV'den daha kapsamlı bir ayrıştırma işlemi gerektirir.
austincheney

aslında evet, içinde tek bir alıntı olabilir, ters eğik çizgiyle kaçmak iyi olur.
Hans

Bir değer çift tırnaklı bir dizge olabilir mi?
ridgerunner

1
Papa Parse iyi bir iş çıkarır. Yerel bir CSV Dosyasını JavaScript ve Papa Ayrıştırma ile Ayrıştırma: joyofdata.de/blog/…
Raffael

Yanıtlar:


217

Feragatname

2014-12-01 Güncellemesi: Aşağıdaki yanıt yalnızca çok özel bir CSV biçimi için işe yarar. Doğru belirttiği gibi yorumlarında DG tarafından , bu çözüm yok değil CSV RFC 4180 tanımına uygun ve aynı zamanda yok değil Microsoft Excel biçimine uyacak. Bu çözüm, basitçe, dizelerin kaçan tırnak ve virgül içerebileceği dize türlerinin bir karışımını içeren bir (standart olmayan) CSV satırının nasıl ayrıştırılabileceğini gösterir.

Standart olmayan bir CSV çözümü

As austincheney doğru işaret , gerçekten düzgün kaçan karakterler içerebilir tırnakla idare etmek isterseniz baştan sona dize ayrıştırmak gerekir. Ayrıca OP, bir "CSV dizesinin" gerçekte ne olduğunu açıkça tanımlamaz. Öncelikle, geçerli bir CSV dizesini neyin oluşturduğunu ve bireysel değerlerini tanımlamalıyız.

Verilen: "CSV Dizesi" Tanımı

Bu tartışmanın amacı doğrultusunda, bir "CSV dizisi", birden çok değerin virgülle ayrıldığı sıfır veya daha fazla değerden oluşur. Her bir değer şunlardan oluşabilir:

  1. Çift tırnaklı bir dize (çıkış karaktersiz tek tırnaklar içerebilir).
  2. Tek tırnaklı bir dize (çıkış karaktersiz çift tırnak içerebilir).
  3. Olmayan bir alıntı dize (olabilir değil tırnak, virgül ve).
  4. Boş bir değer. (Tüm boşluklar boş olarak kabul edilir.)

Kurallar / Notlar:

  • Alıntılanan değerler virgül içerebilir.
  • Alıntılanan değerler kaçan herhangi bir şey içerebilir, ör 'that\'s cool'.
  • Tırnak işareti, virgül veya ters eğik çizgi içeren değerler tırnak içine alınmalıdır.
  • Baştaki veya sondaki boşluk içeren değerler tırnak içine alınmalıdır.
  • Ters eğik çizgi tüm: \'tek tırnaklı değerlerden kaldırılır .
  • Ters eğik çizgi tüm: \"çift ​​tırnaklı değerlerden kaldırılır .
  • Alıntı yapılmamış dizeler, baştaki ve sondaki boşluklardan kırpılır.
  • Virgül ayırıcı bitişik boşluklara sahip olabilir (ki bu yok sayılır).

Bul:

Geçerli bir CSV dizesini (yukarıda tanımlandığı gibi) bir dizi değerleri dizisine dönüştüren bir JavaScript işlevi.

Çözüm:

Bu çözüm tarafından kullanılan normal ifadeler karmaşıktır. Ve (IMHO) tüm önemsiz olmayan düzenli ifadeler, çok sayıda yorum ve girinti ile serbest aralık modunda sunulmalıdır. Maalesef, JavaScript serbest boşluk moduna izin vermiyor. Bu nedenle, bu çözüm tarafından uygulanan normal ifadeler ilk olarak yerel düzenli ifadeler sözdiziminde sunulur (Python'un kullanışlı r'''...'''ham çok satırlı dizgi sözdizimi kullanılarak ifade edilir ).

Birincisi, bir CVS dizesinin yukarıdaki gereksinimleri karşıladığını doğrulayan normal bir ifadedir:

Bir "CSV dizesini" doğrulamak için normal ifade:

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.
"""

Bir dizge yukarıdaki normal ifadeyle eşleşirse, bu dize geçerli bir CSV dizesidir (daha önce belirtilen kurallara göre) ve aşağıdaki normal ifade kullanılarak ayrıştırılabilir. Aşağıdaki normal ifade daha sonra CSV dizesindeki bir değeri eşleştirmek için kullanılır. Daha fazla eşleşme bulunmayana (ve tüm değerler ayrıştırılana) kadar tekrar tekrar uygulanır.

Geçerli bir CSV dizesinden bir değeri ayrıştırmak için normal ifade:

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.
"""

Bu normal ifadenin eşleşmediği özel bir durum değeri vardır - bu değer boş olduğunda en son değer. Bu özel "boş son değer" durumu, aşağıdaki JavaScript işlevi için test edilir ve işlenir.

CSV dizesini ayrıştırmak için JavaScript işlevi:

// 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;
};

Örnek giriş ve çıkış:

Aşağıdaki örneklerde {result strings},. (Bu, baştaki / sondaki boşlukları ve sıfır uzunluklu dizeleri görselleştirmeye yardımcı olmak içindir.)

// 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] = {} */

Ek Notlar:

Bu çözüm, CSV dizesinin "geçerli" olmasını gerektirir. Örneğin, tırnaksız değerler ters eğik çizgi veya tırnak işareti içeremez, örneğin aşağıdaki CSV dizesi geçerli değildir :

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

Bu gerçekten bir sınırlama değildir, çünkü herhangi bir alt dizge tek veya çift tırnaklı bir değer olarak temsil edilebilir. Ayrıca bu çözümün "virgülle ayrılmış değerler" için yalnızca bir olası tanımı temsil ettiğine dikkat edin.

Geçmişi düzenle

  • 2014-05-19: Sorumluluk reddi eklendi.
  • 2014-12-01: Sorumluluk reddi en üste taşındı.

1
@Evan Plaice - Güzel sözler için teşekkürler. Elbette herhangi bir ayırıcı kullanabilirsiniz. Normal ifademdeki her virgülü, tercih edilen ayırıcıyla değiştirin (ancak ayırıcı boşluk olamaz). Şerefe.
ridgerunner

2
@Evan Plaice - Normal ifadelerimden herhangi birini istediğiniz herhangi bir amaç için kullanabilirsiniz. Bir tanıma notu güzel olurdu ama gerekli değil. Eklentinizde iyi şanslar. Şerefe!
ridgerunner

1
Harika, işte code.google.com/p/jquery-csv projesi . Sonunda, CSV'ye SSV (Structured Separated Values) adında bir uzantı formatı eklemek istiyorum.
Evan Plaice

1
Bu harika uygulama için çok teşekkürler - bunu bir Node.js modülü ( csv-iterator ) için temel olarak kullandım .
mirkokiefer

3
Cevabınızın detayını alkışlıyorum ve netleştiriyorum, ancak bir yerlerde CSV tanımınızın, CSV için bir standarda en yakın şey olan ve anekdot olarak yaygın olarak kullanıldığını söyleyebileceğim RFC 4180'e uymadığına dikkat edilmelidir. Özellikle bu, bir dizgi alanındaki çift tırnak karakterinden "kaçmanın" normal yolu olabilir: "field one", "field two", "a ""final"" field containing two double quote marks"Trevor Dixon'ın bu sayfadaki cevabını test etmedim, ancak CSV'nin RFC 4180 tanımına hitap eden bir cevaptır.
DG.

55

RFC 4180 çözümü

Formatı RFC 4180 ile uyumlu olmadığından bu, sorudaki dizeyi çözmez; kabul edilebilir kodlama, çift tırnakla çift tırnaktan kaçıyor. Aşağıdaki çözüm, google e-tablolardaki CSV dosyaları d / l ile doğru şekilde çalışır.

GÜNCELLEME (3/2017)

Tek satırı ayrıştırmak yanlış olur. RFC 4180'e göre alanlar, herhangi bir satır okuyucunun CSV dosyasını kırmasına neden olacak CRLF içerebilir. İşte CSV dizesini ayrıştıran güncellenmiş bir sürüm:

'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));

ESKİ CEVAP

(Tek hat çözümü)

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));

Ve eğlence için, işte CSV'yi diziden nasıl oluşturacağınız:

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);


1
bu işi benim için yaptı, diğeri değil
WtFudgE

7

FakeRainBrigand'ın cevabını beğendim, ancak birkaç problem içeriyor: Bir alıntı ve virgül arasındaki boşlukları kaldıramaz ve 2 ardışık virgülü desteklemez. Cevabını düzenlemeyi denedim, ancak düzenlemem kodumu anlamadığı anlaşılan gözden geçirenler tarafından reddedildi. İşte FakeRainBrigand kodunun benim versiyonum. Bir de keman var: 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('|'));

7

Http://en.wikipedia.org/wiki/Comma-separated_values ​​adresinde RFC 4180 örneklerini işleyen PEG (.js) dilbilgisi :

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 '"'; }
  / [^"]

Http://jsfiddle.net/knvzk/10 veya https://pegjs.org/online adresinde test edin .

Oluşturulan ayrıştırıcıyı https://gist.github.com/3362830 adresinden indirin .


6

Hücreleri Google E-Tablolar'dan web uygulamama kopyalamak istediğim çok özel bir kullanım durumum vardı. Hücreler çift tırnak işaretlerini ve yeni satır karakterlerini içerebilir. Kopyala ve yapıştır kullanılarak hücreler sekme karakterleriyle sınırlandırılır ve tek verilere sahip hücreler çift tırnak içine alınır. Bu ana çözümü, regexp, Jquery-CSV ve CSVToArray kullanan bağlantılı makale denedim. http://papaparse.com/ Kutudan çıkan tek şey. Kopyalama ve yapıştırma, varsayılan otomatik algılama seçeneklerine sahip Google E-Tablolar ile sorunsuzdur.


1
Bu çok daha yüksek sıralanmalıdır, asla kendi CSV ayrıştırıcınızı döndürmeye çalışmayın, özellikle düzenli ifadeler kullanırken doğru çalışmayacaktır . Papaparse harika - kullanın!
cbley

4

İnsanlar bunun için RegEx'e karşı görünüyordu. Neden?

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

İşte kod. Ben de keman yaptım .

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('|'));

3
Hmm, regexp'inizin bazı sorunları var: Alıntı ve virgül arasındaki boşlukları işleyemez ve 2 ardışık virgülü desteklemez. Cevabınızı her iki sorunu da gideren kodla güncelledim ve yeni bir keman oluşturdum: jsfiddle.net/xTezm/43
HammerNL

Bazı nedenlerden dolayı, kodunuzda yaptığım düzenleme, "gönderinin orijinal amacından sapacağı" için reddedildi. Çok ilginç!? Kodunuzu aldım ve onunla iki sorunu çözdüm. Bu, gönderinin amacını nasıl değiştirir !? Her neyse ... Bu soruya yeni bir cevap ekledim.
HammerNL

Cevabınızda güzel soru, @FakeRainBrigand. Ben regex için hepim ve bu nedenle, iş için yanlış araç olduğunu kabul ediyorum.
niry

2
@niry kodum berbat. Son 6 yılda daha iyi olduğuma söz veriyorum: -p
Brigand

4

Listeye bir tane daha eklemek, çünkü yukarıdakilerin hepsinin yeterince "KISS" olmadığını düşünüyorum.

Bu, alıntılanan öğeleri atlarken virgül veya yeni satırları bulmak için normal ifadeyi kullanır. Umarım bu, çaylakların kendi başlarına okuyabileceği bir şeydir. splitFinderRegexp'in o (a tarafından bölünmüş yapar üç şey vardır |):

  1. , - virgül bulur
  2. \r?\n - yeni satırlar bulur, (ihracatçı iyi davrandıysa potansiyel olarak satır başı ile)
  3. "(\\"|[^"])*?"- tırnak içine alınmış herhangi bir şeyi atlar çünkü virgül ve satırsonu burada önemli değildir. Teklif \\"edilen öğede kaçan bir teklif varsa, bir son teklif bulunmadan önce bu alıntı yakalanacaktır.

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);


Dosyamı fileReader aracılığıyla okursam ve sonucumu belirlediğim Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 sütunlara göre nasıl ayrıştırabilirim?
bluePearl

2d dizisini aldıktan sonra, ilk dizini kaldırın (bunlar sizin özellik isimlerinizdir), ardından dizinin geri kalanı üzerinde yineleyin, her bir değerle özellik olarak nesneler oluşturun. Şöyle görünecek:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Seph Reed

3

Tırnak sınırlayıcınızın çift tırnak olmasını sağlayabiliyorsanız, bu, CSV verilerini ayrıştırmak için Örnek JavaScript kodunun bir kopyasıdır .

Önce tüm tek tırnakları çift tırnağa çevirebilirsiniz:

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

... veya bu sorudaki normal ifadeyi çift tırnak yerine tek tırnakları tanımak için düzenleyebilirsiniz:

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

Ancak bu, sorunuza göre net olmayan belirli işaretlemeleri varsayar. Lütfen sorunuzla ilgili yorumuma göre, çeşitli işaretleme olasılıklarının neler olabileceğini açıklayın.


2

Cevabım, girişinizin, tek ve çift tırnak karakterlerinin kaçışsız eşleşen bir küme olarak gerçekleşmeleri koşuluyla tamamen birbirinin yerine geçebildiği web kaynaklarından gelen kod / içeriğin bir yansıması olduğunu varsayar.

Bunun için normal ifadeyi kullanamazsınız. Bölmek istediğiniz dizeyi analiz etmek için aslında bir mikro ayrıştırıcı yazmanız gerekir. Bu cevap uğruna, dizelerinizin alıntılanan kısımlarını alt dizeler olarak arayacağım. Özellikle ipin üzerinden geçmeniz gerekir. Şu durumu düşünün:

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.";

Bu durumda, bir karakter örüntüsü için girişi basitçe analiz ederek bir alt dizenin nerede başladığı veya bittiği konusunda kesinlikle hiçbir fikriniz olmaz. Bunun yerine, bir alıntı karakterinin bir alıntı karakteri kullanıp kullanmadığına, kendisinin tırnaksız olup olmadığına ve tırnak karakterinin bir çıkıştan sonra olmadığına karar vermek için mantık yazmalısınız.

Sizin için bu karmaşıklık düzeyini yazmayacağım, ancak yakın zamanda yazdığım, ihtiyacınız olan kalıba sahip bir şeye bakabilirsiniz. Bu kodun virgüllerle ilgisi yoktur, ancak aksi takdirde kendi kodunuzu yazarken takip etmeniz için yeterince geçerli bir mikro ayrıştırıcıdır. Aşağıdaki uygulamanın asifix işlevine bakın:

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


2

Bu cevabı tamamlamak için

Başka bir alıntıyla kaçan alıntıları çözümlemeniz gerekiyorsa, örneğin:

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

Kullanabilirsiniz

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;
}

Bunun hala ayrıştırmada başarısız olduğunu buldum"jjj "" kkk""","123"
niry

2

CSV dosyasını bir dizeye okurken, dizeler arasında boş değerler içerir, bu nedenle \ 0 satır satır deneyin . Benim için çalışıyor.

stringLine = stringLine.replace(/\0/g, "" );

2

Bir CSV dosyasını ayrıştırmam gerektiğinde de aynı tür problemle karşılaştım.

Dosya, ',' içeren bir sütun adresi içerir.

Bu CSV dosyasını JSON olarak ayrıştırdıktan sonra, anahtarları bir JSON dosyasına dönüştürürken eşleşmeyen anahtarlar alıyorum.

Dosyayı ve baby parse ve csvtojson gibi kitaplıkları ayrıştırmak için Node.js kullandım .

Dosya örneği -

address,pincode
foo,baar , 123456

JSON'da bebek ayrıştırmayı kullanmadan doğrudan ayrıştırırken şunu alıyordum:

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

Bu yüzden, her alanda başka bir sınırlayıcıyla virgül (,) 'yi kaldıran bir kod yazdım:

/*
 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;
    },'')
}

Döndürülen işlev csvtojson kitaplığına aktarılabilir ve böylece sonuç kullanılabilir.

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 */
  })

Şimdi çıktıyı şu şekilde alabilirsiniz:

[{
  address: 'foo, bar',
  pincode: 123456
}]

2

Okunabilir regexp yok ve https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules'e göre :

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;
}

1

Göre bu blog yayınına , bu işlev yapmalı:

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;
};

Ona şöyle diyorsun:

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

Bu jsfiddle türü işe yarıyor, ancak görünüşe göre bazı elemanların önünde boşluklar var.


Tüm bunları bir normal ifadede yapmak zorunda olduğunuzu hayal edin. Bu nedenle, normal ifadeler bazen ayrıştırmak için pek uygun değildir.
CanSpice

Bu çözüm işe yaramıyor. Orijinal test dizesi verildiğinde:, "'string, duppi, du', 23, lala"bu işlev şunu döndürür:["'string"," duppi"," du'"," 23"," lala"]
ridgerunner

@ridgerunner: Haklısın. İşlevi düzeltmek için yanıtı ve jsfiddle'ı düzenledim. Temel olarak, açık "'"için '"'ve tersi de geçerlidir.
CanSpice

Bu yardımcı oldu, ancak artık işlev, çift tırnaklı değerlere sahip tek tırnaklı CSV dizelerini yanlış şekilde işliyor. Örneğin, orijinal test dizgisinin alıntı türlerini tersine '"string, duppi, du", 23, lala'['"string',' duppi'.' du"',' 23',' lala']
çevirmek

@CanSpice, yorumunuz bana RegEx ile denemem için ilham verdi. Pek çok özelliğe sahip değil, ancak kolayca eklenebilirler. (İlgileniyorsanız cevabım bu sayfada.)
Brigand

0

Kurtarmaya düzenli ifadeler! Bu birkaç satırlık kod, RFC 4180 standardına göre katıştırılmış virgül, tırnak işareti ve satırsonu içeren doğru şekilde alıntılanmış alanları işler.

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' ] ]

Başka bir yerde belirtilmedikçe, sonlu durum makinesine ihtiyacınız yoktur. Normal ifade, pozitif arkaya bakma, negatif arkaya bakma ve pozitif ilerleme sayesinde RFC 4180'i doğru şekilde işler.

Https://github.com/peterthoeny/parse-csv-js adresinde klonlama / indirme kodu


0

Ridgerunner'ın mükemmel ve eksiksiz cevabının yanı sıra, arka ucunuzun PHP çalıştırması için çok basit bir çözüm düşündüm.

Alan adınızın arka uç için bu PHP dosyasını ekleyin (söz hakkından: 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")));
?>

Şimdi bu işlevi JavaScript araç setinize ekleyin (inandığım çapraz tarayıcı yapmak için biraz revize edilmelidir).

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));
}

Size bir Ajax çağrısına mal olacak, ancak en azından kodu çoğaltmayacak veya herhangi bir harici kitaplık eklemeyeceksiniz.

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


0

Papaparse.js'yi aşağıdaki örnekte olduğu gibi kullanabilirsiniz :

<!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>

Papaparse.js'yi aynı klasöre eklemeyi unutmayın.


0

Regex'i birkaç kez kullandım, ancak her seferinde her seferinde yeniden öğrenmem gerekiyor, bu da sinir bozucu :-)

İşte normal ifade olmayan bir çözüm:

function csvRowToArray(row, delimiter = ',', quoteChar = '"'){
    let nStart = 0, nEnd = 0, a=[], nRowLen=row.length, bQuotedValue;
    while (nStart <= nRowLen) {
        bQuotedValue = (row.charAt(nStart) === quoteChar);
        if (bQuotedValue) {
            nStart++;
            nEnd = row.indexOf(quoteChar + delimiter, nStart)
        } else {
            nEnd = row.indexOf(delimiter, nStart)
        }
        if (nEnd < 0) nEnd = nRowLen;
        a.push(row.substring(nStart,nEnd));
        nStart = nEnd + delimiter.length + (bQuotedValue ? 1 : 0)
    }
    return a;
}

Nasıl çalışır:

  1. Csv dizesini içeri aktarın row.
  2. Sonraki değerin başlangıç ​​konumu satır içindeyken aşağıdakileri yapın:
    • Bu değer kote edilmişse nEnd, kapanış teklifine ayarlayın.
    • Değer alıntılanmadıysa, nEndsonraki sınırlayıcıya ayarlayın.
    • Değeri bir diziye ekleyin.
    • Set nStartiçin nEnddelimeter uzunluğu artı.

Bazen bir kitaplık kullanmak yerine kendi küçük işlevinizi yazmak iyidir. Kendi kodunuz iyi performans gösterecek ve yalnızca küçük bir ayak izi kullanacaktır. Ek olarak, kendi ihtiyaçlarınıza uyacak şekilde kolayca ince ayar yapabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.