Der beste Weg, um eine Datei zu analysieren

9

Ich versuche, eine bessere Lösung zu finden, um einen Parser für einige der bekannten Dateiformate wie EDIFACT und TRADACOMS zu erstellen .

Wenn Sie mit diesen Standards nicht vertraut sind, lesen Sie dieses Beispiel aus Wikipedia:

Im Folgenden finden Sie ein Beispiel für eine EDIFACT-Nachricht, die zur Beantwortung einer Produktverfügbarkeitsanfrage verwendet wird: -

UNA:+.? '
UNB+IATB:1+6XPPC+LHPPC+940101:0950+1'
UNH+1+PAORES:93:1:IA'
MSG+1:45'
IFT+3+XYZCOMPANY AVAILABILITY'
ERC+A7V:1:AMD'
IFT+3+NO MORE FLIGHTS'
ODI'
TVL+240493:1000::1220+FRA+JFK+DL+400+C'
PDI++C:3+Y::3+F::1'
APD+714C:0:::6++++++6X'
TVL+240493:1740::2030+JFK+MIA+DL+081+C'
PDI++C:4'
APD+EM2:0:130::6+++++++DA'
UNT+13+1'
UNZ+1+1'

Das UNA-Segment ist optional. Wenn vorhanden, gibt es die Sonderzeichen an, die zur Interpretation des Restes der Nachricht verwendet werden sollen. Es gibt sechs Zeichen nach UNA in dieser Reihenfolge:

  • Komponentendatenelementtrennzeichen (: in diesem Beispiel)
  • Datenelementtrennzeichen (+ in diesem Beispiel)
  • Dezimalbenachrichtigung (. in diesem Beispiel)
  • Release-Charakter (? in diesem Beispiel)
  • reserviert, muss ein Platz sein
  • Segmentterminator ('in diesem Beispiel)

Wie Sie sehen, sind es nur einige Daten, die auf spezielle Weise formatiert wurden und darauf warten, analysiert zu werden (ähnlich wie XML- Dateien).

Jetzt basiert mein System auf PHP und ich konnte einen Parser mit regulären Ausdrücken für jedes Segment erstellen, aber das Problem ist, dass nicht jeder den Standard perfekt implementiert.

Einige Lieferanten neigen dazu, optionale Segmente und Felder vollständig zu ignorieren. Andere senden möglicherweise mehr Daten als andere. Aus diesem Grund musste ich Validatoren für Segmente und Felder erstellen, um zu testen, ob die Datei korrekt war oder nicht.

Sie können sich den Albtraum der regulären Ausdrücke vorstellen, die ich gerade habe. Darüber hinaus benötigt jeder Lieferant viele Änderungen an den regulären Ausdrücken, sodass ich für jeden Lieferanten einen Parser erstelle.


Fragen:

1- Ist dies die beste Vorgehensweise zum Parsen von Dateien (unter Verwendung regulärer Ausdrücke)?

2- Gibt es eine bessere Lösung für das Parsen von Dateien (vielleicht gibt es da draußen eine fertige Lösung)? Kann es zeigen, welches Segment fehlt oder ob die Datei beschädigt ist?

3- Wenn ich meinen Parser trotzdem erstellen muss, welches Entwurfsmuster oder welche Methodik sollte ich verwenden?

Anmerkungen:

Ich habe irgendwo über Yacc und ANTLR gelesen, aber ich weiß nicht, ob sie meinen Bedürfnissen entsprechen oder nicht!

Songo
quelle
Nachdem ich diese EDIFACT-Grammatik, Parser und Bibliotheken (Java) gesehen habe, frage ich mich, ob die Verwendung eines Lexers / Parsers funktionieren würde. Wenn ich es wäre, würde ich zuerst den Parser-Kombinator ausprobieren. :)
Guy Coder

Antworten:

18

Was Sie brauchen, ist ein echter Parser. Reguläre Ausdrücke behandeln Lexing, nicht Parsing. Das heißt, sie identifizieren Token in Ihrem Eingabestream. Das Parsen ist der Kontext der Token, dh wer wohin und in welcher Reihenfolge geht.

Das klassische Parsing-Tool ist Yacc / Bison . Der klassische Lexer ist Lex / Flex . Da PHP die Integration von C-Code ermöglicht , können Sie Ihren Parser mit Flex und Bison erstellen, PHP in der Eingabedatei / im Stream aufrufen lassen und dann Ihre Ergebnisse abrufen.

Es wird blitzschnell und viel einfacher zu bearbeiten sein, sobald Sie die Werkzeuge verstanden haben . Ich schlage vor, Lex und Yacc 2nd Ed zu lesen . von O'Reilly. Zum Beispiel habe ich ein Flex- und Bison-Projekt auf Github mit einem Makefile eingerichtet. Es ist bei Bedarf für Windows crosskompilierbar.

Es ist komplex, aber wie Sie herausgefunden haben, ist das, was Sie tun müssen, komplex. Es gibt eine Menge "Dinge", die für einen ordnungsgemäß funktionierenden Parser erledigt werden müssen, und Flex und Bison kümmern sich um die mechanischen Teile. Andernfalls befinden Sie sich in der nicht beneidenswerten Position, Code auf derselben Abstraktionsschicht wie Assembly zu schreiben.

Spencer Rathbun
quelle
1
+1 Tolle Antwort, besonders wenn man bedenkt, dass es einen Beispiel-Parser gibt.
Caleb
@caleb danke, ich arbeite viel mit flex / bison, aber es gibt nur sehr wenige anständige (sprich: komplexe) Beispiele. Dies ist nicht der beste Parser aller Zeiten, da es nicht viele Kommentare gibt. Sie können also gerne Updates einschicken.
Spencer Rathbun
@SpencerRathbun vielen Dank für Ihre ausführliche Antwort und Ihr Beispiel. Ich habe keinerlei Kenntnisse über die von Ihnen erwähnte Terminologie (yacc / bison, lex / flex, ... usw.), Da ich meiner Erfahrung nach hauptsächlich mit Webentwicklung beschäftigt bin. Reicht "Lex and Yacc 2nd Ed" aus, um alles zu verstehen und einen guten Parser zu erstellen? oder gibt es andere themen und materialien, die ich zuerst behandeln sollte?
Songo
@songo Das Buch deckt alle relevanten Details ab und ist mit ca. 300 mittelgroßen Seiten recht kurz. Es wird nicht die Verwendung von c oder Sprachdesign behandelt . Glücklicherweise sind viele c-Referenzen verfügbar, z. B. K & R The C Programming Language, und Sie müssen keine Sprache entwerfen. Befolgen Sie einfach die Standards, auf die Sie verwiesen haben. Bitte beachten Sie, dass das Lesen von Cover zu Cover empfohlen wird, da die Autoren etwas einmal erwähnen und davon ausgehen, dass Sie es bei Bedarf erneut lesen werden. Auf diese Weise verpassen Sie nichts.
Spencer Rathbun
Ich glaube nicht, dass ein Standard-Lexer dynamische Trennzeichen verarbeiten kann, die in der UNA-Zeile angegeben sind. Zumindest benötigen Sie einen Lexer mit zur Laufzeit anpassbaren Zeichen für die 5 Trennzeichen.
Kevin
3

autsch .. "wahrer" Parser? Zustandsautomaten?

Entschuldigung, aber ich bin seit Beginn meiner Anstellung vom Akademiker zum Hacker konvertiert worden. Ich würde also sagen, dass es einfachere Wege gibt, obwohl ich akademisch vielleicht nicht so „verfeinert“ bin :)

Ich werde versuchen, einen alternativen Ansatz anzubieten, dem einige zustimmen oder nicht zustimmen, der jedoch in einer Arbeitsumgebung sehr praktisch sein kann.

Ich würde;

loop every line
   X = pop the first 3 letters of line
   Y = rest of line
   case X = 'UNA':
       class init (Y)

von dort würde ich Klassen für die Datentypen verwenden. Aufteilen von Komponenten- und Elementtrennzeichen und Durchlaufen der zurückgegebenen Arrays.

Für mich ist dies Code-Wiederverwendung, OO, geringe Kohäsion und hochmodular .. und einfach zu debuggen und zu programmieren. einfacher ist besser.

Um eine Datei zu analysieren, benötigen Sie keine Zustandsautomaten oder etwas völlig Kompliziertes. Zustandsmaschinen eignen sich gut zum Analysieren von Code. Sie werden überrascht sein, wie leistungsfähig der obige Pseduo-Code sein kann, wenn er in einem OO-Kontext verwendet wird.

ps. Ich habe schon mit sehr ähnlichen Dateien gearbeitet :)


Weitere Pseudocodes finden Sie hier:

Klasse

UNA:

init(Y):
 remove ' from end
 components = Y.split(':') 
 for c in components
     .. etc..

 getComponents():
   logic..
   return

 getSomethingElse():
   logic..
   return

class UNZ:
   ...

Parser(lines):

Msg = new obj;

for line in lines
   X = pop the first 3 letters of line
   Y = rest of line
   case X = 'UNA':
      Msg.add(UNA(Y))

msg.isOK = true
return Msg

Sie könnten es dann so verwenden ..

msg = Main(File.getLines());
// could put in error checking
// if msg.isOK:
msg.UNA.getSomethingElse();

und sagen Sie, Sie haben mehr als ein Segment. Verwenden Sie eine Warteschlange, um sie hinzuzufügen und das erste, zweite usw. nach Bedarf abzurufen. Sie stellen die Nachricht wirklich nur in einem Objekt dar und geben die Objektmethoden zum Aufrufen der Daten an. Sie könnten dies ausnutzen, indem Sie auch benutzerdefinierte Methoden für die Vererbung erstellen. Nun, das ist eine andere Frage, und ich denke, Sie könnten sie leicht anwenden, wenn Sie sie verstehen

Ross
quelle
3
Ich habe das schon einmal gemacht und festgestellt, dass es für nichts anderes als ein oder zwei Fälle von ausreicht recognize X token and do Y. Es gibt keinen Kontext, Sie können nicht mehrere Zustände haben, wenn Sie eine triviale Anzahl von Fällen überwinden, wird der Code aufgebläht, und die Fehlerbehandlung ist schwierig. Ich finde, dass ich diese Funktionen in fast allen Fällen in der realen Welt benötigt habe. Das lässt Fehler beiseite, wenn die Komplexität zunimmt. Am schwierigsten ist es, ein Skelett aufzubauen und zu lernen, wie das Werkzeug funktioniert. Überwinde das und es ist genauso schnell, etwas zu zaubern.
Spencer Rathbun
Es ist eine Nachricht, welche Zustände brauchen Sie? Es scheint, dass eine solche Botschaft, die in einer Struktur aus Verbundwerkstoffen und Segmenten organisiert ist, perfekt zu diesem OO-Ansatz passt. Die Fehlerbehandlung erfolgt pro Klasse und ordnungsgemäß. Sie können einen Parser erstellen, der sehr effizient und erweiterbar ist. Nachrichten wie diese eignen sich für Klassen und Funktionen, insbesondere wenn mehrere Anbieter unterschiedliche Varianten desselben Formats senden. Ein Beispiel wäre eine Funktion in einer UNA-Klasse, die einen bestimmten Wert für einen bestimmten Anbieter zurückgibt.
Ross
@Ross so dass im Grunde haben Sie eine „UNA - Klasse“ für das Segment „UNA“ und im Innern wird es für jeden Lieferanten eine Parse - Methode ( parseUNAsegemntForVendor1(), parseUNAsegemntForVendor2(), parseUNAsegemntForVendor3(), ... etc), nicht wahr?
Songo
2
@ Ross Die Nachricht enthält Abschnitte, die an verschiedenen Stellen während des Parsens gültig sind. Das sind die Staaten, über die ich gesprochen habe. Das OO-Design ist clever und ich sage nicht, dass es nicht funktioniert . Ich drücke auf Flex und Bison, weil sie wie funktionale Programmierkonzepte besser passen als andere Tools, aber die meisten Leute glauben, dass sie zu kompliziert sind, um sich um das Lernen zu kümmern.
Spencer Rathbun
@Songo .. nein, Sie würden unabhängig vom Anbieter analysieren (es sei denn, Sie neu wer). Die Analyse würde in der INIT der Klasse sein. Sie verwandeln Ihre Nachricht in ein Datenobjekt, das auf denselben Regeln basiert, die zum Erstellen der Nachricht verwendet wurden. Wenn Sie jedoch etwas aus der Nachricht herausholen müssten, und es wird von Ihren Anbietern unterschiedlich dargestellt, dann hätten Sie die verschiedenen Funktionen, ja. Aber warum ist das so? Verwenden Sie eine Basisklasse und haben Sie für jeden Anbieter eine eigene Klasse, die nur bei Bedarf überschrieben wird. Dies ist viel einfacher. Vererbung nutzen.
Ross
1

Haben Sie versucht, nach "PHP EDIFACT" zu googeln? Dies ist eines der ersten Ergebnisse, die aufgetaucht sind: http://code.google.com/p/edieasy/

Während es für Ihren Anwendungsfall möglicherweise nicht ausreicht, können Sie möglicherweise einige Ideen daraus gewinnen. Ich mag den Code mit seinen vielen verschachtelten for-Schleifen und Bedingungen nicht, aber es kann ein Anfang sein.

Chiborg
quelle
1
Ich habe viele Projekte dort draußen überprüft, aber das Problem lag hauptsächlich in den unterschiedlichen Implementierungen der Anbieter, die den Standard verwenden. Ich kann einen Anbieter zwingen, mir ein bestimmtes Segment zu senden, aber ich kann es für einen anderen Anbieter als optional betrachten. Deshalb muss ich wahrscheinlich sowieso meinen eigenen angepassten Parser erstellen.
Songo
1

Nun, da Yacc / Bison + Flex / Lex erwähnt wurden, könnte ich genauso gut eine der anderen Hauptalternativen einwerfen: Parser-Kombinatoren. Diese sind in der funktionalen Programmierung wie bei Haskell beliebt, aber wenn Sie eine Schnittstelle zu C-Code herstellen können, können Sie sie verwenden, und was wissen Sie, jemand hat auch eine für PHP geschrieben. (Ich habe keine Erfahrung mit dieser speziellen Implementierung, aber wenn es wie die meisten von ihnen funktioniert, sollte es ziemlich nett sein.)

Das allgemeine Konzept besteht darin, dass Sie mit einer Reihe kleiner, einfach zu definierender Parser beginnen, normalerweise Tokenizer. Als hätten Sie eine Parser-Funktion für jedes der 6 Datenelemente, die Sie erwähnt haben. Dann verwenden Sie Kombinatoren (Funktionen, die Funktionen kombinieren), um größere Parser zu erstellen, die größere Elemente erfassen. Wie ein optionales Segment wäre der optionalKombinator, der auf dem Segmentparser arbeitet.

Ich bin mir nicht sicher, wie gut es in PHP funktioniert, aber es macht Spaß, einen Parser zu schreiben, und ich genieße es sehr, sie in anderen Sprachen zu verwenden.

CodexArcanum
quelle
0

Anstatt mit Regexen zu spielen, erstellen Sie Ihre eigene Zustandsmaschine

Dies ist in nicht trivialen Situationen besser lesbar (und kann bessere Kommentare abgeben) und es ist einfacher, die Blackbox zu debuggen, bei der es sich um Regex handelt

Ratschenfreak
quelle
5
Eine kurze Anmerkung, dies ist, was Flex und Bison unter der Haube tun. Nur sie machen es richtig .
Spencer Rathbun
0

Ich weiß nicht, was Sie danach genau mit diesen Daten machen wollen und ob es kein Vorschlaghammer für eine Nuss ist, aber ich hatte gute Erfahrungen mit eli . Sie beschreiben die lexikalischen Phrasen und dann die konkrete / abstrakte Syntax und generieren, was Sie generieren möchten.

Sebastian Bauer
quelle