Najlepszy sposób na parsowanie pliku

9

Próbuję znaleźć lepsze rozwiązanie do tworzenia parsera niektórych znanych formatów plików, takich jak: EDIFACT i TRADACOMS .

Jeśli nie znasz tych standardów, sprawdź ten przykład z Wikipedii:

Poniżej znajduje się przykład komunikatu EDIFACT wykorzystanego do odpowiedzi na żądanie dostępności produktu: -

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'

Segment UNA jest opcjonalny. Jeśli jest obecny, określa znaki specjalne, których należy użyć do interpretacji pozostałej części wiadomości. UNA ma sześć znaków w następującej kolejności:

  • separator elementu danych komponentu (: w tym przykładzie)
  • separator elementu danych (+ w tym przykładzie)
  • powiadomienie dziesiętne (w tej próbce)
  • znak zwolnienia (? w tym przykładzie)
  • zastrzeżone, musi być spacją
  • terminator segmentu („w tym przykładzie)

Jak widać, to tylko niektóre dane sformatowane w specjalny sposób, które czekają na parsowanie (podobnie jak pliki XML ).

Teraz mój system jest oparty na PHP i byłem w stanie stworzyć parser używając wyrażeń regularnych dla każdego segmentu, ale problem nie polega na tym, że wszyscy doskonale implementują standard.

Niektórzy dostawcy całkowicie ignorują opcjonalne segmenty i pola. Inni mogą zdecydować o wysłaniu większej ilości danych niż inni. Dlatego zmuszono mnie do utworzenia walidatorów dla segmentów i pól, aby sprawdzić, czy plik jest poprawny, czy nie.

Możesz sobie wyobrazić koszmar wyrażeń regularnych, który mam teraz. Ponadto każdy dostawca potrzebuje wielu modyfikacji wyrażeń regularnych, które zwykle buduję analizator składni dla każdego dostawcy.


Pytania:

1- Czy to najlepsza praktyka do analizowania plików (przy użyciu wyrażeń regularnych)?

2- Czy jest lepsze rozwiązanie do analizowania plików (być może istnieje gotowe rozwiązanie)? Czy będzie w stanie pokazać, którego segmentu brakuje lub czy plik jest uszkodzony?

3- Jeśli muszę mimo to zbudować analizator składni, jakiego wzoru lub metodologii powinienem użyć?

Uwagi:

Czytałem gdzieś o yacc i ANTLR, ale nie wiem, czy odpowiadają moim potrzebom, czy nie!

Songo
źródło
Po obejrzeniu gramatyki, parserów i bibliotek EDIFACT (Java) zastanawiam się, czy użycie lexera / parsera by działało. Gdybym to był ja, najpierw wypróbowałbym parser. :)
Guy Coder

Odpowiedzi:

18

Potrzebujesz prawdziwego parsera. Wyrażenia regularne obsługują leksykację, a nie parsowanie. Oznacza to, że identyfikują tokeny w twoim strumieniu wejściowym. Analiza jest kontekstem tokenów, tzn. Kto idzie gdzie i w jakiej kolejności.

Klasycznym narzędziem analizującym jest yacc / bizon . Klasyczny lexer to lex / flex . Ponieważ php pozwala na integrację kodu C , możesz użyć flex i bison, aby zbudować parser, poprosić php o wywołanie go w pliku wejściowym / strumieniu, a następnie uzyskać wyniki.

Będzie on niesamowicie szybki i o wiele łatwiejszy w obsłudze, gdy zrozumiesz narzędzia . Proponuję przeczytać Lex i Yacc 2nd Ed. od O'Reilly. Na przykład utworzyłem projekt flex and bison na github z plikiem makefile. W razie potrzeby jest on kompatybilny z systemem Windows.

Jest to skomplikowane, ale jak dowiedział się, co trzeba zrobić, to skomplikowane. Istnieje wiele „rzeczy”, które należy wykonać dla poprawnie działającego parsera, a flex i bizon radzą sobie z bitami mechanicznymi. W przeciwnym razie znajdziesz się w niemożliwej do pozazdroszczenia pozycji pisania kodu na tej samej warstwie abstrakcji co asembler.

Spencer Rathbun
źródło
1
+1 Świetna odpowiedź, szczególnie biorąc pod uwagę, że pochodzi z przykładowego analizatora składni.
Caleb
@caleb dzięki, dużo pracuję z flex / bizonem, ale jest bardzo mało przyzwoitych (czytaj: skomplikowanych) przykładów. To nie jest najlepszy parser, ponieważ nie ma wielu komentarzy, więc zachęcamy do wysyłania aktualizacji.
Spencer Rathbun
@SpencerRathbun bardzo dziękuję za szczegółową odpowiedź i przykład. Nie mam żadnej wiedzy na temat żadnej z wymienionych terminów (yacc / bison, lex / flex, ... itd.), Ponieważ moje doświadczenie dotyczy głównie tworzenia stron internetowych. Czy „Lex and Yacc 2nd Ed” wystarczy, żebym wszystko zrozumiał i zbudował dobry parser? czy są jeszcze inne tematy i materiały, które powinienem najpierw omówić?
Songo,
@songo Książka zawiera wszystkie istotne szczegóły i jest dość krótka, zawiera około 300 średnich stron. Nie obejmuje użycia c lub projektowania języka . Na szczęście dostępnych jest wiele odnośników c, takich jak K&R The C Programming Language i nie trzeba projektować języka, wystarczy postępować zgodnie ze wskazanymi standardami. Należy pamiętać, że zaleca się czytanie od deski do deski, ponieważ autorzy wspominają coś raz i zakładają, że jeśli zajdzie taka potrzeba, wrócicie i ponownie przeczytacie. W ten sposób niczego nie przegapisz.
Spencer Rathbun
Nie sądzę, aby standardowy leksykon obsługiwał dynamiczne separatory, które może określać linia UNA. Przynajmniej będziesz potrzebował leksykonu z postaciami, które można dostosować w czasie wykonywania dla 5 separatorów.
Kevin
3

ouch .. parser „prawdziwy”? maszyny stanowe?

przepraszam, ale od czasu rozpoczęcia pracy przeszedłem z akademickiego na hakera. Powiedziałbym, że są łatwiejsze sposoby .. chociaż może nie tak „wyrafinowany” akademicko :)

Spróbuję zaproponować alternatywne podejście, z którym niektórzy mogą się zgadzać lub nie, ale MOŻE być bardzo praktyczne w środowisku pracy.

Ja bym;

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

stamtąd użyłbym klas dla typów danych. dzielenie separatorów komponentów i elementów oraz iteracja po zwróconych tablicach.

Dla mnie jest to ponowne użycie kodu, OO, niska kohezja i wysoce modułowy .. i łatwy do debugowania i programowania. prostsze jest lepsze.

do parsowania pliku nie potrzebujesz maszyn stanów lub czegokolwiek całkowicie skomplikowanego ... maszyny stanów dobrze nadają się do parsowania kodu, będziesz zaskoczony, jak potężny może być powyższy kod pseduo, gdy jest używany w kontekście OO.

ps. wcześniej pracowałem z bardzo podobnymi plikami :)


Więcej pseudo kodu opublikowano tutaj:

klasa

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

możesz użyć tego w ten sposób ...

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

i powiedz, że masz więcej niż jeden segment. użyj kolejki, aby je dodać, i zdobądź pierwszy, drugi itd. według potrzeb. Naprawdę reprezentujesz msg w obiekcie obj i podajesz obiektowym metodom wywoływania danych. możesz skorzystać z tego, tworząc również niestandardowe metody .. do dziedziczenia .. cóż, to inne pytanie i myślę, że możesz z łatwością je zastosować, jeśli rozumiesz

Ross
źródło
3
Zrobiłem to wcześniej i stwierdziłem, że nie jest wystarczające dla niczego poza jednym lub dwoma przypadkami recognize X token and do Y. Nie ma kontekstu, nie można mieć wielu stanów, przejście przez trywialną liczbę przypadków powoduje rozdęcie kodu, a obsługa błędów jest trudna. Uważam, że potrzebowałem tych funkcji w prawdziwym świecie w prawie wszystkich przypadkach. To pomija błędy w miarę wzrostu złożoności. Najtrudniejszą częścią jest ustawienie szkieletu i poznanie działania narzędzia. Przekrocz to i równie szybko coś wymieszaj.
Spencer Rathbun,
to wiadomość, jakich stanów potrzebujesz? wydaje się, że taki komunikat, zorganizowany w strukturę kompozytów i segmentów, idealnie pasowałby do tego podejścia OO. obsługa błędów jest wykonywana dla każdej klasy i poprawnie wykonana, można zbudować analizator składni, który jest bardzo wydajny i rozszerzalny. takie wiadomości nadają się do klas i funkcji, zwłaszcza gdy wielu dostawców wysyła różne smaki tego samego formatu. Przykładem może być funkcja w klasie UNA, która zwróciła określoną wartość dla konkretnego dostawcy.
Ross
@Ross więc w zasadzie można mieć „UNA klasa” dla segmentu „Una” , a wewnątrz niego nie będzie parse metoda dla każdego dostawcy ( parseUNAsegemntForVendor1(), parseUNAsegemntForVendor2(), parseUNAsegemntForVendor3(), ... itd), prawda?
Songo
2
@ Ross Istnieją sekcje wiadomości, ważne w różnych punktach podczas analizowania. O tych stanach mówiłem. Projekt OO jest sprytny i nie twierdzę, że nie zadziała . Pcham flex i bizon, ponieważ podobnie jak funkcjonalne koncepcje programowania, pasują one lepiej niż inne narzędzia, ale większość ludzi uważa, że ​​są zbyt skomplikowane, aby przeszkadzać w nauce.
Spencer Rathbun,
@Songo .. nie, parsowałbyś niezależnie od dostawcy (chyba że nowy kto). parsowanie będzie w INIT klasy. Zamieniasz wiadomość w obiekt danych oparty na tych samych regułach, które zastosowano do zbudowania wiadomości. Jeśli jednak chciałbyś pobrać coś z wiadomości… i jest on reprezentowany w różny sposób u różnych dostawców, miałbyś różne funkcje tak… Ale dlaczego to się tak dzieje? użyj klasy podstawowej i miej osobną klasę dla każdego dostawcy, zastępując tylko w razie potrzeby, o wiele łatwiej. skorzystać z dziedziczenia.
Ross
1

Czy próbowałeś google dla „PHP EDIFACT”? To jeden z pierwszych wyników, który się pojawił: http://code.google.com/p/edieasy/

Chociaż może nie być wystarczający dla twojego przypadku użycia, możesz być w stanie uzyskać z niego kilka pomysłów. Nie podoba mi się kod z wieloma zagnieżdżonymi pętlami i warunkami, ale może to być początek.

Chiborg
źródło
1
Sprawdziłem wiele projektów, ale problem polegał głównie na różnych wdrożeniach dostawców stosujących ten standard. Mogę zmusić jednego dostawcę do przesłania mi określonego segmentu, ale mogę uznać go za opcjonalny dla innego dostawcy. Dlatego prawdopodobnie i tak będę musiał zbudować własny spersonalizowany parser.
Songo,
1

Odkąd wspomniano o Yacc / Bison + Flex / Lex, równie dobrze mogę rzucić jedną z innych głównych alternatyw: kombinatory parsera. Są one popularne w programowaniu funkcjonalnym, takim jak Haskell, ale jeśli możesz połączyć się z kodem C, możesz ich użyć i, co wiesz, ktoś napisał również dla PHP. (Nie mam doświadczenia z tą konkretną implementacją, ale jeśli działa jak większość z nich, powinna być całkiem niezła).

Ogólna koncepcja jest taka, że ​​zaczynasz od zestawu małych, łatwych do zdefiniowania parserów, zwykle tokenizerów. Tak jakbyś miał jedną funkcję analizatora składni dla każdego z 6 wspomnianych elementów danych. Następnie używasz kombinatorów (funkcji łączących funkcje) do tworzenia większych parserów, które chwytają większe elementy. Podobnie jak opcjonalny segment byłby optionalkombinator działający na analizatorze segmentów.

Nie jestem pewien, jak dobrze działa w PHP, ale to świetny sposób na napisanie parsera i bardzo lubię je używać w innych językach.

CodexArcanum
źródło
0

zamiast majstrować przy wyrażeniach regularnych stwórz własną maszynę stanową

będzie to bardziej czytelne (i będzie mogło mieć lepsze komentarze) w sytuacjach nietrywialnych i łatwiej będzie debugować tę czarną skrzynkę, która jest wyrażeniem regularnym

maniak zapadkowy
źródło
5
Krótka uwaga, oto co robią flex i bizon pod maską. Tylko oni robią to dobrze .
Spencer Rathbun
0

Nie wiem, co chcesz później zrobić z tymi danymi i czy nie jest to młot do orzechów, ale miałem dobre doświadczenia z Eli . Opisujesz zwroty leksykalne, a następnie konkretną / abstrakcyjną składnię i generujesz to, co chcesz wygenerować.

Sebastian Bauer
źródło