Teilen Sie eine CSV-Zeichenfolge, indem Sie Zeilenumbrüche zwischen Anführungszeichen überspringen

Wenn der folgende reguläre Ausdruck eine CSV-Zeichenfolge zeilenweise aufteilen kann.

<code>var lines = csv.split(/\r|\r?\n/g);
</code>

Wie könnte dies angepasst werden, um Zeilenumbrüche zu überspringen, die in einem CSV-Wert enthalten sind (dh zwischen Anführungszeichen / doppelten Anführungszeichen)?

Beispiel:

<code>2,"Evans & Sutherland","230-132-111AA",,"Visual","P
CB",,1,"Offsite",
</code>

Wenn Sie es nicht sehen, ist hier eine Version mit den sichtbaren Zeilenumbrüchen:

<code>2,"Evans & Sutherland","230-132-111AA",,"Visual","P\r\nCB",,1,"Offsite",\r\n 
</code>

Der Teil, den ich überspringen möchte, ist der Zeilenumbruch in der Mitte des "PCB" -Eintrags.

Aktualisieren:

Ich hätte das wahrscheinlich schon früher erwähnen sollen, aber dies ist Teil einer speziellen CSV-Parsing-Bibliothek namensjquery-csv. Um einen besseren Kontext zu bieten, habe ich die aktuelle Parser-Implementierung unten hinzugefügt.

Hier ist der Code zum Validieren und Parsen eines Eintrags (dh einer Zeile):

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

Hinweis: Ich habe noch nicht getestet, aber ich denke, ich könnte wahrscheinlich das letzte Match in die Hauptaufteilung / den Rückruf einbeziehen.

Dies ist der Code, der den zeilenweisen Teil ausführt:

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

Für eine Aufschlüsselung, wie das reges funktioniert, werfen Sie einen Blick aufdiese Antwort. Meins ist eine leicht angepasste Version. Ich habe die einfache und die doppelte Anführungszeichenübereinstimmung so konsolidiert, dass sie nur einem Texttrennzeichen entsprechen, und das Trennzeichen / die Trennzeichen dynamisch gemacht. Es macht eine großartige Arbeit, um Ganzheiten zu validieren, aber die Zeilenaufteilungslösung, die ich oben hinzugefügt habe, ist ziemlich zerbrechlich und bricht an dem Randfall, den ich oben beschrieben habe.

Ich bin nur auf der Suche nach einer Lösung, die die Zeichenfolge durchsucht, um gültige Einträge zu extrahieren (um sie an den Eintragsparser weiterzuleiten), oder die bei fehlerhaften Daten fehlschlägt und einen Fehler zurückgibt, der die Zeile angibt, in der die Analyse fehlgeschlagen ist.

Aktualisieren:

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

Für einen Leitungsteiler waren lediglich 4 Zustände erforderlich:

0: Beginn eines Eintrags1: Folgendes wird zitiert2: Ein zweites Zitat wurde gefunden3: Folgendes wird nicht zitiert

Es ist fast ein vollständiger Parser. Für meinen Anwendungsfall wollte ich nur einen Zeilensplitter, um die Verarbeitung von CSV-Daten granularer zu gestalten.

Hinweis: Die Anerkennung für diesen Ansatz geht an einen anderen Entwickler, den ich ohne seine Erlaubnis nicht öffentlich benennen werde. Alles, was ich getan habe, war, es von einem vollständigen Parser zu einem Zeilensplitter zu machen.

Aktualisieren:

In der vorherigen lineSplitter-Implementierung wurden einige Broken Edge-Fälle entdeckt. Die bereitgestellte sollte vollständig seinRFC 4180 konform.

Antworten auf die Frage(3)

Ihre Antwort auf die Frage