Gorp.NET - uma nova biblioteca para criar modelos reversos para extrair dados de texto estruturado

O Gorp.NET é uma nova biblioteca para criar modelos reversíveis para extrair dados de texto estruturado, com base na base de código existente do Salesforce Gorp .

Nesta publicação, falarei um pouco sobre como usar a biblioteca para analisar um texto estruturado chamado Gorp (um dos exemplos de ferramentas, às vezes chamadas de sistemas de criação de modelos reversíveis ).
O que é um modelo reversível em geral? Suponha que tenhamos um determinado sistema que nos permita gerar o texto necessário com base nos dados iniciais que determinamos, de acordo com regras estritas definidas pela sintaxe dos modelos. Agora vamos imaginar uma tarefa que tem sentido oposto - temos um texto com alguma integridade estrutural que poderia ser alcançada usando um sistema baseado nos modelos do exemplo anterior. Nosso objetivo é extrair deste texto os dados de origem com base nos quais foram formados. Se tentarmos criar uma certa sintaxe generalizada para resolver esse problema, fornecida ao analisador correspondente, que analisa o texto de entrada em elementos separados, este será um exemplo de sintaxe para implementar o conceito de modelos reversíveis.

Por que eu decidi escrever especificamente sobre Gorp ? O fato é que decidi tomar esse sistema específico como base para finalizar meu próprio projeto - a história do projeto em si, incluindo alguns detalhes de todas as alterações que fiz no projeto Gorp original, pode ser encontrada no artigo anterior . Aqui, focaremos precisamente a parte técnica, incluindo o uso de uma versão modificada do mecanismo. Por conveniência, continuarei chamando-o Gorp.NET , embora, na realidade, não seja uma versão do Gorp que não seja portada para .NET, mas apenas uma versão levemente polida e finalizada, tudo no mesmo Java. Outra coisa é que o complemento acima da própria biblioteca Gorp (na minha versão) na forma de uma biblioteca DLL gerenciada chamada BIRMA.NET usa seu próprio assembly especial - o próprio Gorp.NET , que você mesmo pode obter facilmente se executar o texto de origem ( o endereço do seu repositório é https://github.com/S-presso/gorp/ ) através do utilitário IKVM.NET .

Observarei agora que, para todos os tipos de tarefas de extração de dados de qualquer texto estruturado, as próprias ferramentas Gorp.NET serão suficientes para você - pelo menos se você tiver um pouco de comando do Java ou pelo menos saber como chamar métodos de módulos Java externos em seus projetos no O .NET Framework, bem como inclui vários tipos das bibliotecas JVM padrão existentes (consegui isso através do mesmo IKVM.NET , que, no entanto, agora já possui o status de um projeto não suportado). Bem, e o que você fará a seguir com os dados extraídos - isso, como se costuma dizer, é da sua conta pessoal. Somente o Gorp e o Gorp.NET fornecem apenas uma estrutura simples. Algumas bases para o processamento posterior de todos esses dados contêm o BIRMA.NET acima mencionado. Mas a descrição da funcionalidade BIRMA.NET já é um tópico para uma publicação separada (embora eu já tenha conseguido mencionar algo na minha revisão histórica comparativa-histórica das tecnologias BIRMA ). Aqui, olhando para o futuro, permitirei-me uma afirmação um tanto ousada de que a tecnologia usada para descrever modelos reversíveis usados ​​no Gorp.NET (e, consequentemente, no BIRMA.NET ) é algo único entre outros tipos de artesanato desse tipo (eu digo " artesanato ”, como de alguma forma as grandes empresas ainda não foram vistas por mim na promoção de suas próprias estruturas para esses fins - bem, talvez, apenas o próprio Salesforce com sua implementação original do Gorp ).

Para uma divulgação mais completa do conceito e dos aspectos técnicos subjacentes ao sistema de descrição de modelos usado no Gorp, deixo aqui um link para a documentação original em inglês . Tudo o que está indicado nele, você pode aplicar com segurança em relação ao Gorp.NET . E agora vou falar um pouco sobre a essência.

Portanto, a descrição do modelo é um tipo de documento de texto (talvez até apresentado como uma única linha grande, que pode ser passada para o método correspondente de processamento). Consiste em três partes contendo descrições seqüenciais das três entidades mais importantes: padrões , padrões e amostras (extratos).

O bloco de nível mais baixo aqui são padrões - eles podem consistir apenas em expressões regulares e referências a outros padrões. O próximo nível da hierarquia é ocupado por modelos , cuja descrição também contém links para padrões, que também podem ser nomeados, bem como inclusões na forma de literais de texto, links para modelos e extratores aninhados. Também existem padrões paramétricos que não abordarei agora (na documentação de origem, existem poucos exemplos de uso). Bem, e finalmente, existem exemplos que especificam regras sintáticas específicas que associam padrões nomeados a ocorrências específicas do texto de origem.

Pelo que entendi, o objetivo original que os criadores do Gorp estabeleceram para si mesmos foi analisar as seqüências de dados contidas nos arquivos de relatório (ou arquivos de log). Considere um exemplo simples de uma aplicação específica do sistema.

Suponha que tenhamos um relatório contendo a seguinte linha:

<86> 2015-05-12T20: 57: 53.302858 + 00: 00 10.1.11.141 RealSource: "10.10.5.3"


Vamos compor um modelo de exemplo para analisá-lo usando as ferramentas Gorp :

pattern %phrase \\S+
pattern %num \\d+\n
pattern %ts %phrase
pattern %ip %phrase

extract interm {
template <%num>$eventTimeStamp(%ts) $logAgent(%ip) RealSource: "$logSrcIp(%ip)"
}


Observe que o bloco de atribuição de modelos é omitido aqui, pois todos os modelos necessários já estão incluídos na seleção final. Todos os modelos usados ​​aqui são nomeados, seu conteúdo é indicado entre parênteses após o nome. Como resultado, um conjunto de dados de texto será criado com os nomes eventTimeStamp , logAgent e logSrcIp .

Vamos agora escrever um programa simples para extrair os dados necessários. Suponha que o modelo que criamos já esteja contido em um arquivo chamado extractions.xtr .
 import com.salesforce.gorp.DefinitionReader; import com.salesforce.gorp.ExtractionResult; import com.salesforce.gorp.Gorp; // ... DefinitionReader r = DefinitionReader.reader(new File("extractions.xtr")); Gorp gorp = r.read(); final String TEST_INPUT = "<86>2015-05-12T20:57:53.302858+00:00 10.1.11.141 RealSource: \"10.10.5.3\""; ExtractionResult result = gorp.extract(TEST_INPUT); if (result == null) { // no match, handle throw new IllegalArgumentException("no match!"); } Map<String,Object> properties = asMap(); // and then use extracted property values 


Outro exemplo de um modelo de análise simples:

# Patterns
pattern %num \d+
pattern %hostname [a-zA-Z0-9_\-\.]+
pattern %status \w+

# Templates
@endpoint $srcHost(%hostname): $srcPort(%num)

# Extraction
extract HostDefinition {
template @endpoint $status(%status)
}


Bem, acho que o ponto está claro. Também não será errado mencionar que, para o método de extração , há também uma definição com dois parâmetros de entrada, o segundo dos quais possui um tipo lógico. Se você configurá-lo como true , quando executado, o método irá percorrer todos os conjuntos de dados em potencial - até encontrar um adequado (você também pode substituir a chamada do método por extractSafe - já sem o segundo parâmetro). O padrão é falso , e o método pode "xingar" a discrepância entre os dados de entrada e o modelo usado.
Observo ao mesmo tempo que o Gorp.NET também introduziu uma nova implementação estendida do método extract : agora existe uma versão com dois parâmetros subsequentes de um tipo lógico. Usando uma chamada abreviada para a exibição extractAllFound , definimos os dois como true por padrão. O valor positivo do terceiro parâmetro nos dá uma margem ainda maior para variações: a partir de agora, podemos analisar o texto com inclusões de caracteres arbitrários nos intervalos entre as amostras já estruturadas desejadas (contendo conjuntos de dados extraídos).

Chegou, então, o momento de responder à pergunta: o que exatamente pode ser único nessa modificação da versão básica do Gorp , além da extensão do método extract?
O fato é que, quando, há vários anos, já criei uma espécie de minha própria ferramenta para extrair os dados necessários do texto (que também foi baseado no processamento de certos modelos com sua própria sintaxe específica), ele funcionou com princípios ligeiramente diferentes. Sua principal diferença em relação à abordagem implementada no Gorp e em todas as estruturas derivadas é que cada elemento de texto a ser extraído foi definido simplesmente listando suas bordas esquerda e direita (cada uma das quais por sua vez poderia fazer parte do próprio elemento ou simplesmente separá-lo de todo o texto subsequente ou anterior). Ao mesmo tempo, de fato, no caso geral, a estrutura do texto original não foi analisada, como é o caso de Gorp , mas apenas as peças necessárias foram destacadas. Quanto ao conteúdo do texto que está entre eles, ele não poderia ter sucumbido a nenhuma análise estrutural (poderia muito bem ser conjuntos de caracteres incoerentes).

É possível obter um efeito semelhante em Gorp ? Na sua versão inicial - talvez não (corrija-me se estiver enganado sobre isso). Se simplesmente escrevermos uma expressão como (. *) , Seguida imediatamente pela máscara para especificar a borda esquerda do próximo elemento a ser pesquisado, usando o quantificador "ganância", todo o texto subsequente será capturado. E não podemos usar regulares com sintaxe "não gananciosa" nas implementações Gorp existentes.
O Gorp.NET permite contornar esse problema sem problemas, introduzindo dois tipos especiais de padrões - (% all_before) e (% all_after) . O primeiro deles, de fato, é uma alternativa à versão "não gananciosa" (. *) , Adequada para uso na compilação de seus próprios modelos. Quanto a (% all_after) , ele também analisa o texto de origem até a primeira ocorrência da próxima parte do padrão descrito - mas já conta com o resultado da pesquisa do padrão anterior. Tudo o que está entre eles também cairá na substring extraível do elemento atual. Em certo sentido (% all_after) "olha para trás" e (% all_before) , pelo contrário, "olha para frente". Observo que um analógico exclusivo para (% all_before) na primeira versão do BIRMA era a borda esquerda ausente na descrição do elemento, e análogo a (% all_after) , respectivamente, era um vazio em vez da borda direita. Se os dois limites não estiverem definidos ao descrever o próximo elemento, o analisador obviamente capturará todo o texto subsequente! No entanto, tudo isso, então a implementação do BIRMA agora tem um significado puramente histórico (você pode ler um pouco mais sobre isso no meu relatório da época ).
Texto oculto
Os códigos-fonte nunca foram dispostos em nenhum lugar por causa de sua qualidade extremamente baixa - na verdade, eles poderiam servir como um monumento ao mau design dos sistemas de software.


Vejamos os recursos do uso de padrões de serviço (% all_before) e (% all_after) usando o exemplo da tarefa de extrair dados específicos do usuário de um site específico. Analisaremos o site da Amazon e, especificamente, esta página: https://www.amazon.com/B06-Plus-Bluetooth-Receiver-Streaming/product-reviews/B078J3GTRK/ ).
Texto oculto
Um exemplo é retirado de uma tarefa de teste para a vaga de um desenvolvedor com especialização em análise de dados, enviada por minha empresa que, infelizmente, não respondeu à minha solução proposta para o problema. É verdade que eles só me pediram para descrever o processo geral da solução - sem fornecer um algoritmo específico, e em resposta eu já tentei me referir aos modelos Gorp, enquanto minhas próprias extensões naquela época existiam apenas, como se costuma dizer, "no papel" "
Por uma questão de curiosidade, permitirei citar um fragmento da minha carta de resposta, que, aparentemente, é a primeira menção ao Gorp.NET , embora de natureza particular.
“Para tornar a lista acima de expressões regulares usada por mim para resolver esse problema mais visual, compilei um modelo pronto com base (anexado à carta), que pode ser usado para extrair todos os dados necessários, aplicando meu próprio desenvolvimento de natureza mais universal, apenas projetado para resolver esse tipo de problema. Seu código é baseado no projeto github.com/salesforce/gorp , e na mesma página há uma descrição geral das regras para compilar esses modelos. De um modo geral, essa descrição em si implica a atribuição de expressões regulares concretas e a lógica de seu processamento. O ponto mais difícil aqui é que, para cada amostra de dados, devemos descrever completamente através dos regulares toda a estrutura do texto que os contém, e não apenas os elementos individuais (como poderia ser feito ao escrever nosso próprio programa que pesquisa sequencialmente em um loop, como eu descrito anteriormente). "

A tarefa original era coletar os seguintes dados da página acima:

  • Nome de usuário
  • Classificação
  • Título da Revisão
  • A data
  • Text


Bem, agora vou dar a você um modelo compilado por mim, que permite concluir com rapidez e eficiência esta tarefa. Eu acho que o significado geral deve ser bastante óbvio - talvez você mesmo possa oferecer uma solução mais concisa.
Texto oculto
Foi nesse exemplo que, em geral, depurei a funcionalidade de minhas próprias extensões para o Gorp (já sem nenhum objetivo de emprego, mas com base na ideologia da "Prova de conceito").


pattern %optspace ( *)
pattern %space ( +)

pattern %cap_letter [AZ]
pattern %small_letter [az]
pattern %letter (%cap_letter|%small_letter)
pattern %endofsentence (\.|\?|\!)+
pattern %delim (\.|\?|\!\,|\:|\;)
pattern %delim2 (\(|\)|\'|\")

pattern %word (%letter|\d)+
pattern %ext_word (%delim2)*%word(%delim)*(%delim2)*

pattern %text_phrase %optspace%ext_word(%space%ext_word)+
pattern %skipped_tags <([^>]+)>

pattern %sentence (%text_phrase|%skipped_tags)+(%endofsentence)?

pattern %start <div class=\"a-fixed-right-grid view-point\">

pattern %username_start <div class=\"a-profile-content\"><span class=\"a-profile-name\">
pattern %username [^\s]+
pattern %username_end </span>

pattern %user_mark_start <i data-hook=\"review-star-rating\"([^>]+)><span class=\"a-icon-alt\">
pattern %user_mark [^\s]+
pattern %user_mark_end ([^<]+)</span>

pattern %title_start data-hook=\"review-title\"([^>]+)>(%skipped_tags)*
pattern %title [^<]+
pattern %title_end </span>

pattern %span class <span class=\"[^\"]*\">

pattern %date_start <span data-hook="review-date"([^>]+)>
pattern %date ([^<]+)
pattern %date_end </span>

pattern %content_start <span data-hook=\"review-body\"([^>]+)>(%skipped_tags)*
pattern %content0 (%sentence)+
pattern %content (%all_after)
pattern %content_end </span>

template @extractUsernameStart (%all_before)%username_start
template @extractUsername $username(%username)%username_end
template @extractUserMarkStart (%all_before)%user_mark_start
template @extractUserMark $user_mark(%user_mark)%user_mark_end
template @extractTitleStart (%all_before)%title_start
template @extractTitle $title(%title)%title_end
template @extractDateStart (%all_before)%date_start
template @extractDate $date(%date)%date_end
template @extractContentStart (%all_before)%content_start
template @extractContent $content(%content)%content_end

extract ToCEntry {
template @extractUsernameStart@extractUsername@extractUserMarkStart@extractUserMark@extractTitleStart@extractTitle@extractDateStart@extractDate@extractContentStart@extractContent
}



Provavelmente é tudo por hoje. Sobre as ferramentas de terceiros que eu implementei, nas quais essa estrutura já está totalmente envolvida, provavelmente vou contar outra hora.

Source: https://habr.com/ru/post/pt476778/


All Articles