Il modo migliore per analizzare un file


9

Sto cercando di trovare una soluzione migliore per creare un parser in alcuni dei formati di file famosi come EDIFACT e TRADACOMS .

Se non hai familiarità con questi standard, dai un'occhiata a questo esempio da Wikipedia:

Vedi sotto per un esempio di un messaggio EDIFACT utilizzato per rispondere a una richiesta di disponibilità del prodotto: -

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'

Il segmento UNA è facoltativo. Se presente, specifica i caratteri speciali che devono essere utilizzati per interpretare il resto del messaggio. Ci sono sei caratteri che seguono UNA in questo ordine:

  • separatore elemento dati componente (: in questo esempio)
  • separatore elemento dati (+ in questo esempio)
  • notifica decimale (. in questo esempio)
  • carattere di rilascio (? in questo esempio)
  • riservato, deve essere uno spazio
  • terminatore di segmento ('in questo esempio)

Come puoi vedere sono solo alcuni dati formattati in un modo speciale in attesa di essere analizzati (proprio come i file XML ).

Ora il mio sistema è basato su PHP e sono stato in grado di creare un parser usando espressioni regolari per ogni segmento, ma il problema non è che tutti implementano perfettamente lo standard.

Alcuni fornitori tendono a ignorare completamente segmenti e campi opzionali. Altri possono scegliere di inviare più dati di altri. Ecco perché sono stato costretto a creare validatori per segmenti e campi per verificare se il file era corretto o meno.

Puoi immaginare l'incubo delle espressioni regolari che sto vivendo in questo momento. Inoltre, ogni fornitore ha bisogno di molte modifiche alle espressioni regolari che tendo a costruire un parser per ogni fornitore.


Domande:

1- È questa la migliore pratica per l'analisi dei file (usando espressioni regolari)?

2- Esiste una soluzione migliore per l'analisi dei file (forse ci sono soluzioni già pronte là fuori)? Sarà in grado di mostrare quale segmento manca o se il file è danneggiato?

3- Se devo compilare il mio parser comunque quale modello di progettazione o metodologia dovrei usare?

Appunti:

Ho letto da qualche parte su Yacc e ANTLR, ma non so se soddisfino le mie esigenze o no!


Dopo aver visto questa grammatica, parser e librerie (Java) EDIFACT, mi chiedo se l'uso di un lexer / parser funzionerebbe. Se fossi in me proverei prima il combinatore parser. :)
Guy Coder

Risposte:


18

Ciò di cui hai bisogno è un vero parser. Le espressioni regolari gestiscono il lexing, non l'analisi. Cioè, identificano i token all'interno del flusso di input. L'analisi è il contesto dei token, ovvero chi va dove e in quale ordine.

Il classico strumento di analisi è yacc / bison . Il classico lexer è lex / flex . Poiché php consente di integrare il codice C , è possibile utilizzare flex e bison per creare il proprio parser, fare in modo che php lo chiami sul file / flusso di input e quindi ottenere i risultati.

Sarà velocissimo e molto più facile lavorare con una volta compresi gli strumenti . Suggerisco di leggere Lex e Yacc 2nd Ed. di O'Reilly. Per un esempio, ho creato un progetto flex e bison su github , con un makefile. È cross-compilabile per Windows se necessario.

Si è complesso, ma come hai scoperto, ciò che è necessario fare è complesso. C'è un sacco di "roba" che deve essere fatta per un parser che funzioni correttamente, e flex e bisonte affrontano i bit meccanici. Altrimenti, ti trovi nella posizione non invidiabile di scrivere il codice sullo stesso livello di astrazione dell'assembly.


1
+1 Ottima risposta, soprattutto considerando che viene fornito con un parser di esempio.
Caleb,

@caleb grazie, lavoro molto con flex / bison, ma ci sono pochissimi esempi decenti (leggi: complessi). Questo non è il miglior parser di sempre, poiché non ci sono molti commenti, quindi sentiti libero di inviare aggiornamenti.
Spencer Rathbun

@SpencerRathbun grazie mille per la risposta dettagliata e l'esempio. Non so che cosa mai della terminologia che hai citato (yacc / bison, lex / flex, ecc.) Dato che la mia esperienza riguarda principalmente lo sviluppo web. È "Lex e Yacc 2nd Ed" sufficiente per me per capire tutto e costruire un buon parser? o ci sono altri argomenti e materiali che dovrei trattare per primi?
Songo

@songo Il libro tratta tutti i dettagli rilevanti ed è piuttosto breve, con circa 300 pagine di medie dimensioni. Non copre l'uso di c o il design del linguaggio . Fortunatamente, ci sono molti riferimenti c disponibili, come K&R The C Programming Language e non è necessario progettare un linguaggio, basta seguire gli standard a cui si fa riferimento. Si consiglia di leggere la copertina per copertina, poiché gli autori menzioneranno qualcosa una volta e assumeranno che se ne avrai bisogno tornerai indietro e rileggerai. In questo modo non ti perdi nulla.
Spencer Rathbun

Non credo che un lexer standard possa gestire separatori dinamici, che la linea UNA può specificare. Quindi almeno avrai bisogno di un lexer con caratteri personalizzabili in runtime per i 5 separatori.
Kevin,

3

ahi .. parser "vero"? macchine a stati ??

scusate ma sono stato convertito da accademico a hacker da quando ho iniziato il mio lavoro .. quindi direi che ci sono modi più semplici .. anche se forse non come "raffinato" accademicamente :)

Cercherò di offrire un approccio alternativo con cui alcuni potrebbero o meno essere d'accordo, ma PUO 'essere molto pratico in un ambiente di lavoro.

Vorrei;

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

da lì userei le classi per i tipi di dati. suddivisione dei separatori di componenti ed elementi e iterazione sugli array restituiti.

Per me, si tratta di riutilizzo del codice, OO, bassa coesione e altamente modulare .. e facile da eseguire il debug e programmare. più semplice è meglio.

per analizzare un file non hai bisogno di macchine a stati o nulla di completamente complicato .. le macchine a stati sono adatte per analizzare il codice, sarai sorpreso da quanto potente possa essere il codice pseduo sopra usato quando usato in un contesto OO.

ps. ho lavorato con file molto simili prima :)


Più pseudo codice pubblicato qui:

classe

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

potresti quindi usarlo in questo modo ..

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

e dire che hai più di un segmento .. usa una coda per aggiungerli e ottenere il primo, il secondo ecc. di cui hai bisogno. Stai davvero rappresentando il messaggio in un obj e stai fornendo agli oggetti metodi per chiamare i dati. potresti trarne vantaggio creando anche metodi personalizzati ... per ereditarietà ... beh questa è una domanda diversa e penso che potresti facilmente applicarla se la capisci


3
L'ho già fatto prima e ho scoperto che non è sufficiente per nulla oltre uno o due casi di recognize X token and do Y. Non esiste alcun contesto, non è possibile avere più stati, passare oltre un numero banale di casi blocca il codice e la gestione degli errori è difficile. Trovo di aver bisogno di queste funzionalità nel mondo reale in quasi tutti i casi. Ciò lascia da parte gli errori al crescere della complessità. La parte più difficile è creare uno scheletro e imparare come funziona lo strumento. Superalo ed è altrettanto veloce montare qualcosa.
Spencer Rathbun

è un messaggio, di quali stati hai bisogno? sembrerebbe che un tale messaggio, che è organizzato in una struttura di compositi e segmenti, si adatterebbe perfettamente a questo approccio OO. la gestione degli errori viene eseguita per classe e eseguita correttamente è possibile costruire un parser che sia molto efficiente ed estensibile. messaggi come questo si prestano a classi e funzioni soprattutto quando più fornitori inviano sapori diversi dello stesso formato. Un esempio potrebbe essere una funzione in una classe UNA che ha restituito un valore particolare per un fornitore specifico.
Ross,

@Ross Quindi, in pratica si avrà una "class UNA" per il segmento "UNA" e al suo interno ci sarà un metodo parse per ogni fornitore ( parseUNAsegemntForVendor1(), parseUNAsegemntForVendor2(), parseUNAsegemntForVendor3(), ... ecc), giusto?
Songo,

2
@Ross Ci sono sezioni del messaggio, valide in diversi punti durante l'analisi. Questi sono gli stati di cui parlavo. Il design OO è intelligente e non sto dicendo che non funzionerà . Spingo flex e bisonte perché, come i concetti di programmazione funzionale, si adattano meglio di altri strumenti, ma la maggior parte delle persone crede che siano troppo complicate per disturbare l'apprendimento.
Spencer Rathbun

@Songo .. no, analizzeresti indipendentemente dal venditore (a meno che tu non sia nuovo). l'analisi sarebbe nell'INIT della classe. Trasformi il tuo messaggio in un oggetto dati basato sulle stesse regole utilizzate per costruire il messaggio. Se avessi bisogno di prendere qualcosa dal messaggio comunque ... ed è rappresentato in modo diverso tra i tuoi fornitori, allora avresti le diverse funzioni .. Ma perché ti piace? utilizzare una classe di base e disporre di una classe separata per ciascun fornitore, sostituendo solo quando necessario, molto più semplice. approfittare dell'eredità.
Ross,

1

Hai provato a cercare su Google "PHP EDIFACT"? Questo è uno dei primi risultati che sono comparsi: http://code.google.com/p/edieasy/

Anche se potrebbe non essere sufficiente per il tuo caso d'uso, potresti essere in grado di ottenere alcune idee da esso. Non mi piace il codice con i suoi molti nidificati per loop e condizioni, ma potrebbe essere un inizio.


1
Ho controllato molti progetti là fuori, ma il problema era principalmente nelle diverse implementazioni dei fornitori che utilizzavano lo standard. Potrei forzare un fornitore a inviarmi un determinato segmento, ma potrei considerarlo facoltativo per un altro fornitore. Ecco perché probabilmente dovrò comunque creare il mio parser personalizzato.
Songo

1

Bene, dal momento che Yacc / Bison + Flex / Lex sono stati menzionati, potrei anche aggiungere un'altra alternativa importante: i combinatori di parser. Questi sono popolari nella programmazione funzionale come con Haskell, ma se riesci a interfacciarti con il codice C puoi usarli e, cosa ne sai, qualcuno ne ha scritto uno anche per PHP. (Non ho esperienza con quella particolare implementazione, ma se funziona come la maggior parte di loro, dovrebbe essere abbastanza carino.)

Il concetto generale è che si inizia con un set di parser piccoli e facili da definire, generalmente tokenizzatori. Come se avessi una funzione parser per ciascuno dei 6 elementi di dati che hai citato. Quindi si usano combinatori (funzioni che combinano funzioni) per creare parser più grandi che catturano elementi più grandi. Come un segmento opzionale sarebbe il optionalcombinatore che opera sul parser di segmento.

Non sono sicuro di come funzioni in PHP, ma è un modo divertente per scrivere un parser e mi piace moltissimo usarli in altre lingue.


0

invece di armeggiare con regex, crea la tua macchina a stati

questo sarà più leggibile (e sarà in grado di avere commenti migliori) in situazioni non banali e sarà più facile eseguire il debug della scatola nera che è regex


5
Una breve nota, questo è ciò che fanno flex e bisonte sotto il cofano. Solo loro lo fanno bene .
Spencer Rathbun

0

Non so cosa vuoi fare esattamente con questi dati in seguito e se non è un martello per un pazzo, ma ho avuto buone esperienze con eli . Descrivi le frasi lessicali e quindi la sintassi concreta / astratta e generi ciò che vuoi generare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.