Der im jquery-csv- Plug-in verwendete CSV-Parser
Es ist ein grundlegender Chomsky- Typ-III-Grammatikparser .
Mit einem Regex-Tokenizer werden die Daten char-by-char ausgewertet. Wenn ein Steuerzeichen angetroffen wird, wird der Code zur weiteren Auswertung basierend auf dem Startzustand an eine switch-Anweisung übergeben. Nicht-Steuerzeichen werden gruppiert und massenweise kopiert, um die Anzahl der erforderlichen Zeichenfolgenkopiervorgänge zu verringern.
Der Tokenizer:
var tokenizer = /("|,|\n|\r|[^",\r\n]+)/;
Die erste Gruppe von Übereinstimmungen besteht aus den Steuerzeichen: Wertbegrenzer ("), Werttrennzeichen (,) und Eingabetrennzeichen (alle Variationen von Zeilenvorschub). Die letzte Übereinstimmung behandelt die Nicht-Steuerzeichen-Gruppierung.
Es gibt 10 Regeln, die der Parser erfüllen muss:
- Regel 1 - Ein Eintrag pro Zeile, jede Zeile endet mit einem Zeilenumbruch
- Regel 2 - Zeilenumbruch am Ende der Datei weggelassen
- Regel 3 - Die erste Zeile enthält Kopfdaten
- Regel 4 - Leerzeichen werden als Daten betrachtet und Einträge sollten kein nachstehendes Komma enthalten
- Regel 5 - Zeilen können durch doppelte Anführungszeichen getrennt sein oder nicht
- Regel 6 - Felder mit Zeilenumbrüchen, Anführungszeichen und Kommas sollten in Anführungszeichen eingeschlossen werden
- Regel Nr. 7 - Wenn Felder in Anführungszeichen gesetzt werden, muss ein Anführungszeichen, das in einem Feld steht, durch ein weiteres Anführungszeichen ersetzt werden
- Änderungsantrag Nr. 1 - Ein nicht zitiertes Feld darf oder darf
- Änderung Nr. 2 - Ein angegebenes Feld kann oder kann nicht
- Änderung Nr. 3 - Das letzte Feld in einem Eintrag kann einen Nullwert enthalten oder nicht
Hinweis: Die Top-7-Regeln sind direkt von IETF RFC 4180 abgeleitet . Die letzten drei wurden hinzugefügt, um Randfälle abzudecken, die von modernen Tabellenkalkulationsanwendungen (z. B. Excel, Google Spreadsheet) eingeführt wurden, bei denen standardmäßig nicht alle Werte eingegrenzt (dh in Anführungszeichen gesetzt) werden. Ich habe versucht, die Änderungen in den RFC einzubringen, habe aber noch keine Antwort auf meine Anfrage erhalten.
Genug mit dem Aufziehen, hier ist das Diagramm:
Zustände:
- Ausgangszustand für einen Eintrag und / oder einen Wert
- Ein Eröffnungszitat wurde gefunden
- Ein zweites Zitat wurde gefunden
- Es wurde ein Wert ohne Anführungszeichen festgestellt
Übergänge:
- ein. Überprüft, ob sowohl Werte in Anführungszeichen (1) als auch nicht in Anführungszeichen (3), Nullwerte (0), Nulleinträge (0) und neue Einträge (0) vorhanden sind.
- b. sucht nach einem zweiten Anführungszeichen (2)
- c. sucht nach einem maskierten Anführungszeichen (1), dem Ende des Werts (0) und dem Ende der Eingabe (0)
- d. prüft das Ende des Wertes (0) und das Ende der Eingabe (0)
Hinweis: Es fehlt tatsächlich ein Bundesstaat. Es sollte eine Zeile von 'c' -> 'b' sein, die mit dem Status '1' markiert ist, da ein maskierter zweiter Begrenzer bedeutet, dass der erste Begrenzer noch offen ist. In der Tat wäre es wahrscheinlich besser, es als einen weiteren Übergang darzustellen. Diese zu erschaffen ist eine Kunst, es gibt keinen einzigen richtigen Weg.
Hinweis: Es fehlt auch ein Beendigungsstatus, aber bei gültigen Daten endet der Parser immer beim Übergang 'a', und keiner der Status ist möglich, da nichts mehr zum Analysieren übrig ist.
Der Unterschied zwischen Zuständen und Übergängen:
Ein Staat ist endlich, was bedeutet, dass er nur eine Sache bedeuten kann.
Ein Übergang stellt den Fluss zwischen Zuständen dar, sodass er viele Dinge bedeuten kann.
Grundsätzlich ist die Beziehung zwischen Zustand und Übergang 1 -> * (dh Eins-zu-Viele). Der Zustand definiert 'was es ist' und der Übergang definiert 'wie es gehandhabt wird'.
Hinweis: Machen Sie sich keine Sorgen, wenn sich die Anwendung von Zuständen / Übergängen nicht intuitiv anfühlt, sondern nicht intuitiv ist. Es dauerte einige umfangreiche Korrespondenz mit jemandem viel schlauer als ich, bevor ich endlich das Konzept bekam, zu bleiben.
Der Pseudo-Code:
csv = // csv input string
// init all state & data
state = 0
value = ""
entry = []
output = []
endOfValue() {
entry.push(value)
value = ""
}
endOfEntry() {
endOfValue()
output.push(entry)
entry = []
}
tokenizer = /("|,|\n|\r|[^",\r\n]+)/gm
// using the match extension of string.replace. string.exec can also be used in a similar manner
csv.replace(tokenizer, function (match) {
switch(state) {
case 0:
if(opening delimiter)
state = 1
break
if(new-line)
endOfEntry()
state = 0
break
if(un-delimited data)
value += match
state = 3
break
case 1:
if(second delimiter encountered)
state = 2
break
if(non-control char data)
value += match
state = 1
break
case 2:
if(escaped delimiter)
state = 1
break
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
case 3:
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
}
}
Hinweis: Dies ist das Wesentliche, in der Praxis gibt es noch viel mehr zu beachten. Zum Beispiel Fehlerprüfung, Nullwerte, eine nachgestellte Leerzeile (dh welche ist gültig) usw.
In diesem Fall ist der Zustand der Zustand der Dinge, wenn der reguläre Übereinstimmungsblock eine Iteration beendet. Der Übergang wird als case-Anweisung dargestellt.
Als Menschen haben wir eine Tendenz niedrige Niveaus Operationen in höherer Ebene Abstracts zu vereinfachen , aber die Arbeit mit einem FSM ist die Arbeit mit niedrigem Level - Operationen. Während Zustände und Übergänge sehr einfach einzeln zu bearbeiten sind, ist es von Natur aus schwierig, das Ganze auf einmal zu visualisieren. Ich fand es am einfachsten, den einzelnen Ausführungspfaden immer wieder zu folgen, bis ich verstehen konnte, wie sich die Übergänge abspielen. Es ist wie das Erlernen von Grundlagen der Mathematik, Sie werden nicht in der Lage sein, den Code von einer höheren Ebene aus zu bewerten, bis die Details auf niedriger Ebene automatisch werden.
Nebenbei: Wenn Sie sich die tatsächliche Implementierung ansehen, fehlen viele Details. Erstens lösen alle unmöglichen Pfade bestimmte Ausnahmen aus. Es sollte unmöglich sein, sie zu treffen, aber wenn etwas kaputt geht, lösen sie im Testläufer absolut Ausnahmen aus. Zweitens sind die Parserregeln für das, was in einer "legalen" CSV-Datenzeichenfolge zulässig ist, ziemlich locker, sodass der Code für viele bestimmte Randfälle erforderlich ist. Unabhängig davon wurde der FSM auf diese Weise vor allen Fehlerkorrekturen, Erweiterungen und Feinabstimmungen verspottet.
Wie bei den meisten Designs handelt es sich nicht um eine exakte Darstellung der Implementierung, sondern um die wichtigsten Teile. In der Praxis gibt es tatsächlich drei verschiedene Parserfunktionen, die von diesem Entwurf abgeleitet sind: einen CSV-spezifischen Zeilensplitter, einen einzeiligen Parser und einen vollständigen mehrzeiligen Parser. Sie arbeiten alle auf ähnliche Weise und unterscheiden sich im Umgang mit Zeilenumbrüchen.