Melhor maneira de analisar um arquivo

9

Estou tentando encontrar uma solução melhor para fazer um analisador para alguns dos famosos formatos de arquivo existentes, como: EDIFACT e TRADACOMS .

Se você não estiver familiarizado com esses padrões, verifique este exemplo da Wikipedia:

Veja abaixo um exemplo de uma mensagem EDIFACT usada para responder a uma solicitação de disponibilidade do produto: -

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'

O segmento UNA é opcional. Se presente, especifica os caracteres especiais que devem ser usados ​​para interpretar o restante da mensagem. Existem seis caracteres após UNA nesta ordem:

  • separador de elemento de dados do componente (: neste exemplo)
  • separador de elemento de dados (+ neste exemplo)
  • notificação decimal (. neste exemplo)
  • caractere de liberação (? neste exemplo)
  • reservado, deve ser um espaço
  • terminador de segmento ('neste exemplo)

Como você pode ver, são apenas alguns dados formatados de uma maneira especial, esperando para serem analisados ​​(como arquivos XML ).

Agora, meu sistema é construído em PHP e eu consegui criar um analisador usando expressões regulares para cada segmento, mas o problema é que nem todo mundo implementa o padrão perfeitamente.

Alguns fornecedores tendem a ignorar totalmente segmentos e campos opcionais. Outros podem optar por enviar mais dados do que outros. Por isso fui forçado a criar validadores para segmentos e campos para testar se o arquivo estava correto ou não.

Você pode imaginar o pesadelo das expressões regulares que estou tendo agora. Além disso, cada fornecedor precisa de muitas modificações nas expressões regulares que tendem a criar um analisador para cada fornecedor.


Questões:

1- Essa é a melhor prática para analisar arquivos (usando expressões regulares)?

2- Existe uma solução melhor para a análise de arquivos (talvez exista uma solução pronta para uso)? Será capaz de mostrar qual segmento está faltando ou se o arquivo está corrompido?

3- Se eu tiver que construir meu analisador de qualquer maneira, qual padrão ou metodologia de design devo usar?

Notas:

Eu li em algum lugar sobre yacc e ANTLR, mas não sei se eles correspondem às minhas necessidades ou não!

Songo
fonte
Depois de ver essa gramática, analisadores e bibliotecas EDIFACT (Java) , pergunto-me se o uso de um lexer / parser funcionaria. Se fosse eu, tentaria primeiro o combinador do analisador. :)
Guy Coder

Respostas:

18

O que você precisa é de um verdadeiro analisador. Expressões regulares manipulam lexing, não analisando. Ou seja, eles identificam tokens no seu fluxo de entrada. A análise é o contexto dos tokens, ou seja, quem vai aonde e em que ordem.

A ferramenta de análise clássica é o yacc / bison . O lexer clássico é lex / flex . Como o php permite a integração do código C , você pode usar o flex e o bison para criar seu analisador, fazer com que o php o chame no arquivo / fluxo de entrada e obtenha seus resultados.

Será rápido demais e muito mais fácil trabalhar com você depois de entender as ferramentas . Sugiro ler Lex e Yacc 2nd Ed. de O'Reilly. Por exemplo, eu configurei um projeto flex e bison no github , com um makefile. É compilável para janelas, se necessário.

Ele é complexo, mas como você descobriu, o que você precisa fazer é complexa. Há uma grande quantidade de "coisas" que devem ser feitas para um analisador que funcione corretamente, e o flex e o bison lidam com os bits mecânicos. Caso contrário, você se encontrará na posição inviável de escrever código na mesma camada de abstração que a montagem.

Spencer Rathbun
fonte
11
+1 Ótima resposta, especialmente considerando que ele vem com um analisador de amostra.
Caleb
@ Caleb obrigado, eu trabalho muito com flex / bison, mas existem muito poucos exemplos decentes (leia-se: complexos). Este não é o melhor analisador de todos os tempos, já que não há muitos comentários, portanto, fique à vontade para enviar atualizações.
Spencer Rathbun
@ SpencerRathbun, muito obrigado por sua resposta e exemplo detalhados. Eu não tenho nenhum conhecimento de alguma das terminologias que você mencionou (yacc / bison, lex / flex, etc. etc), pois eu minha experiência é principalmente sobre desenvolvimento web. É "Lex e Yacc 2ª Ed" suficiente para mim entender tudo e construir um bom analisador? ou existem outros tópicos e materiais que eu deveria abordar primeiro?
Songo
@songo O livro cobre todos os detalhes relevantes e é bastante curto, chegando a ~ 300 páginas de tamanho médio. Não abrange o uso de c ou design de idioma . Felizmente, existem muitas referências c disponíveis, como K&R The C Programming Language e você não precisa criar um idioma, basta seguir os padrões que você referenciou. Observe que a leitura de capa a capa é recomendada, uma vez que os autores mencionarão algo uma vez e, se necessário, você voltará e relerá. Dessa forma, você não perde nada.
Spencer Rathbun
Eu não acho que um lexer padrão possa lidar com separadores dinâmicos, que a linha UNA pode especificar. Então, pelo menos, você precisará de um lexer com caracteres personalizáveis ​​em tempo de execução para os 5 separadores.
21712 Kevin
3

ai .. analisador 'true'? máquinas de estado ??

desculpe, mas eu fui convertido de acadêmico para hacker desde que comecei meu emprego .. então eu diria que existem maneiras mais fáceis .. embora talvez não seja tão "refinado" academicamente :)

Tentarei oferecer uma abordagem alternativa com a qual alguns possam concordar ou não, mas PODE ser muito prático em um ambiente de trabalho.

Eu gostaria;

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

a partir daí eu usaria classes para os tipos de dados. separando componentes e elementos e iterando sobre as matrizes retornadas.

Para mim, isso é reutilização de código, OO, baixa coesão e altamente modular .. e fácil de depurar e programar. mais simples é melhor.

Para analisar um arquivo, você não precisa de máquinas de estado ou de qualquer coisa totalmente complicada. As máquinas de estado são bem adequadas para analisar código, você ficará surpreso com o quão poderoso o código pseduo acima pode ser quando usado em um contexto OO.

ps. Eu trabalhei com arquivos muito semelhantes antes :)


Mais pseudo-código postado aqui:

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

você pode usá-lo assim ..

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

e diga que você tem mais de um segmento. use uma fila para adicioná-los e obter o primeiro, o segundo etc. conforme necessário. Você está realmente apenas representando a mensagem em um objeto e fornecendo métodos para chamar os dados. você pode tirar vantagem disso criando também métodos personalizados .. para herança .. bem, essa é uma pergunta diferente e eu acho que você poderia aplicá-la facilmente se entender

Ross
fonte
3
Já fiz isso antes e achei insuficiente para qualquer coisa além de um ou dois casos de recognize X token and do Y. Não há contexto, você não pode ter vários estados, passar de um número trivial de casos incha o código e o tratamento de erros é difícil. Acho que precisei desses recursos no mundo real em quase todos os casos. Isso deixa de lado erros à medida que a complexidade aumenta. A parte mais difícil é montar um esqueleto e aprender como a ferramenta funciona. Supere isso e é tão rápido quanto preparar algo.
Spencer Rathbun
é uma mensagem, de que estados você precisa? parece que essa mensagem, organizada em uma estrutura de compósitos e segmentos, se encaixaria perfeitamente nessa abordagem de OO. O tratamento de erros é feito por classe e feito corretamente. Você pode construir um analisador muito eficiente e extensível. mensagens como essa se prestam a classes e funções, especialmente quando vários fornecedores enviam diferentes sabores do mesmo formato. Um exemplo seria uma função em uma classe UNA que retornasse um valor específico para um fornecedor específico.
7372 Ross
@Ross então basicamente você terá uma "classe UNA" para o segmento "UNA" e dentro dela, haverá um método de análise para cada fornecedor ( parseUNAsegemntForVendor1(), parseUNAsegemntForVendor2(), parseUNAsegemntForVendor3(), ... etc), certo?
Songo
2
@Ross Existem seções na mensagem, válidas em diferentes pontos durante a análise. Esses são os estados que eu estava falando. O design do OO é inteligente, e não estou dizendo que não funcionará . Pressiono o flex e o bisonte porque, como conceitos de programação funcional, eles se encaixam melhor do que outras ferramentas, mas a maioria das pessoas acredita que é muito complicada para se preocupar em aprender.
Spencer Rathbun
@Songo .. não, você analisaria independentemente do fornecedor (a menos que você iniciasse quem). a análise seria no INIT da classe. Você transforma sua mensagem em um objeto de dados com base nas mesmas regras usadas para construir a mensagem. Se, no entanto, você precisasse capturar algo da mensagem .. e ele estiver representado de forma diferente em seus fornecedores, você terá as diferentes funções, sim. Mas por que é assim? use uma classe base e tenha uma classe separada para cada fornecedor, substituindo apenas quando necessário, muito mais fácil. tirar proveito da herança.
7372 Ross
1

Você já tentou pesquisar no "PHP EDIFACT"? Este é um dos primeiros resultados que apareceram: http://code.google.com/p/edieasy/

Embora possa não ser suficiente para o seu caso de uso, você poderá obter algumas idéias. Não gosto do código com muitos aninhados para loops e condições, mas pode ser um começo.

Chiborg
fonte
11
Eu verifiquei muitos projetos por aí, mas o problema estava principalmente nas diferentes implementações dos fornecedores usando o padrão. Posso forçar um fornecedor a me enviar um determinado segmento, mas posso considerá-lo opcional para outro fornecedor. É por isso que provavelmente vou precisar criar meu próprio analisador personalizado de qualquer maneira.
Songo
1

Bem, desde que o Yacc / Bison + Flex / Lex foi mencionado, eu também poderia usar uma das outras principais alternativas: combinadores de analisadores. Eles são populares na programação funcional, como no Haskell, mas se você pode fazer interface com o código C, pode usá-los e, como você sabe, alguém escreveu um para o PHP também. (Não tenho experiência com essa implementação específica, mas se funcionar como a maioria delas, deve ser bastante agradável.)

O conceito geral é que você comece com um conjunto de analisadores pequenos e fáceis de definir, geralmente tokenizadores. Como se você tivesse uma função de analisador para cada um dos 6 elementos de dados mencionados. Em seguida, você usa combinadores (funções que combinam funções) para criar analisadores maiores que capturam elementos maiores. Como um segmento opcional, seria o optionalcombinador operando no analisador de segmentos.

Não tenho certeza de como ele funciona bem em PHP, mas é uma maneira divertida de escrever um analisador e eu gosto muito de usá-lo em outras linguagens.

CodexArcanum
fonte
0

em vez de mexer com expressões regulares, crie sua própria máquina de estado

isso será mais legível (e poderá ter melhores comentários) em situações não triviais e será mais fácil depurar que a caixa preta que é regex

catraca arrepiante
fonte
5
Uma observação rápida, é isso que flex e bison fazem sob o capô. Só eles fazem certo .
Spencer Rathbun
0

Não sei o que você deseja fazer exatamente com esses dados depois e se não for uma marreta para uma porca, mas tive boas experiências com eli . Você descreve as frases lexicais e, em seguida, sintaxe concreta / abstrata e gera o que deseja gerar.

Sebastian Bauer
fonte