La storia di un piccolo progetto lungo dodici anni (su BIRMA.NET per la prima volta e francamente in prima persona)

La nascita di questo progetto può essere considerata una piccola idea venutami da qualche parte alla fine del 2007, destinata a trovare la sua forma definitiva solo 12 anni dopo (a questo punto - ovviamente, anche se l'attuale attuazione, secondo per l'autore, è molto soddisfacente).

Tutto è iniziato quando, mentre svolgevo i miei allora compiti ufficiali in biblioteca, ho attirato l'attenzione sul fatto che il processo di inserimento dei dati dal testo scansionato degli indici delle pubblicazioni di libri (e musica) nel database esistente, apparentemente, può essere notevolmente semplificato e automatizzato, sfruttando la proprietà di ordine e ripetibilità di tutti i dati richiesti per l'input, come il nome dell'autore dell'articolo (se si tratta di una raccolta di articoli), il titolo di l'articolo (o il sottotitolo riportato nel sommario) e il numero di pagina dell'elemento corrente del sommario. All'inizio ero praticamente convinto che su Internet si potesse trovare facilmente un sistema adatto a svolgere questo compito. Quando una certa sorpresa è stata causata dal fatto che non sono riuscito a trovare un progetto del genere, ho deciso di provare a realizzarlo da solo.

Dopo un tempo abbastanza breve ha iniziato a funzionare il primo prototipo, che ho subito iniziato ad utilizzare nelle mie attività quotidiane, debuggandolo contemporaneamente su tutti gli esempi che mi sono capitati tra le mani. Fortunatamente, nel mio solito posto di lavoro, dove non ero affatto un programmatore, potevo comunque farla franca con "tempi di inattività" visibili nel mio lavoro, durante i quali stavo intensamente debuggando il mio frutto - una cosa quasi impensabile nelle realtà attuali, che implica resoconti giornalieri del lavoro svolto durante la giornata. Il processo di perfezionamento del programma è durato complessivamente non meno di un anno, ma anche dopo il risultato difficilmente può essere definito completamente positivo: inizialmente sono stati stabiliti troppi concetti diversi che non erano del tutto chiari per l'implementazione: elementi opzionali che possono essere saltato; visualizzazione anticipata degli elementi (allo scopo di sostituire gli elementi precedenti nei risultati di ricerca); anche il nostro tentativo di implementare qualcosa come le espressioni regolari (che hanno una sintassi unica). Devo dire che prima avevo abbandonato un po' la programmazione (per circa 8 anni, se non di più), quindi la nuova opportunità di applicare le mie capacità a un compito interessante e necessario ha catturato completamente la mia attenzione. Non sorprende che il codice sorgente risultante - in assenza di un approccio chiaro alla sua progettazione da parte mia - sia diventato abbastanza rapidamente un miscuglio inimmaginabile di pezzi disparati nel linguaggio C con alcuni elementi del C++ e aspetti di programmazione visiva (inizialmente era si è deciso di utilizzare un sistema di progettazione come Borland C++ Builder - "quasi Delphi, ma in C"). Tuttavia, tutto ciò alla fine ha dato i suoi frutti nell’automatizzazione delle attività quotidiane della nostra biblioteca.

Allo stesso tempo, ho deciso, per ogni evenienza, di seguire corsi per formare sviluppatori di software professionisti. Non so se sia effettivamente possibile imparare da zero a "fare il programmatore", ma tenendo conto delle competenze che già avevo in quel momento, sono riuscito a padroneggiare un po' le tecnologie che allora erano più rilevanti, come come C#, Visual Studio per lo sviluppo in .NET, nonché alcune tecnologie relative a Java, HTML e SQL. L'intera formazione è durata complessivamente due anni ed è servita come punto di partenza per un altro mio progetto, che alla fine si è protratto per diversi anni, ma questo è un argomento per una pubblicazione separata. Qui sarebbe giusto notare che ho tentato di adattare gli sviluppi che avevo già sul progetto descritto per creare un'applicazione a finestra completa in C# e WinForms che implementa le funzionalità necessarie e utilizzarla come base per il prossimo progetto di diploma.
Nel corso del tempo, questa idea cominciò a sembrarmi degna di essere espressa in conferenze annuali con la partecipazione di rappresentanti di varie biblioteche come “LIBKOM” e “CRIMEA”. L'idea, sì, ma non la mia implementazione in quel momento. Poi speravo anche che qualcuno lo riscrivesse utilizzando approcci più competenti. In un modo o nell'altro, entro il 2013 ho deciso di scrivere un rapporto sul mio lavoro preliminare e di inviarlo al Comitato organizzatore della conferenza con una richiesta di finanziamento per partecipare alla conferenza. Con mia certa sorpresa, la mia richiesta è stata approvata e ho iniziato ad apportare alcuni miglioramenti al progetto per prepararlo alla presentazione alla conferenza.

A quel punto, il progetto aveva già ricevuto un nuovo nome BIRMA, acquisito varie capacità aggiuntive (non tanto completamente implementate, ma piuttosto presunte) - tutti i dettagli possono essere trovati nel mio rapporto.

A dire il vero era difficile definire BIRMA 2013 qualcosa di completo; Francamente, è stato un mestiere molto complicato, fatto in fretta. In termini di codice, non ci sono state praticamente innovazioni speciali, ad eccezione di un tentativo piuttosto indifeso di creare una sorta di sintassi unificata per il parser, che in apparenza ricorda il linguaggio di formattazione IRBIS 64 (e in effetti, anche il sistema ISIS - con parentesi come strutture cicliche; perché in quel momento pensavo che fosse piuttosto interessante). Il parser si è imbattuto irrimediabilmente in questi cerchi di parentesi del tipo appropriato (poiché le parentesi svolgevano anche un altro ruolo, vale a dire contrassegnavano strutture opzionali durante l'analisi che possono essere saltate). Rimando ancora una volta tutti coloro che vogliono conoscere in modo più dettagliato la sintassi allora difficile da immaginare e ingiustificata di BIRMA al mio rapporto di quel tempo.

In generale, a parte lottare con il mio parser, non ho altro da dire riguardo al codice di questa versione - fatta eccezione per la conversione inversa dei sorgenti esistenti in C++ preservando alcune caratteristiche tipiche del codice .NET (a dire il vero, è difficile capire cosa mi abbia spinto esattamente a spostare tutto indietro - probabilmente qualche stupida paura di mantenere segreti i miei codici sorgente, come se fossero qualcosa di equivalente alla ricetta segreta della Coca-Cola).

Forse questa stupida decisione è anche la ragione delle difficoltà nell'accoppiare la libreria DLL risultante con l'interfaccia esistente di una workstation fatta in casa per l'inserimento dei dati in un catalogo elettronico (sì, non ho menzionato un altro fatto importante: d'ora in poi, tutti il codice del “motore” BIRMA era come previsto, è separato dalla parte interfaccia e impacchettato nell'apposita DLL). Perché è stato necessario scrivere per questi scopi una workstation separata, che comunque, nel suo aspetto e nel metodo di interazione con l'utente, copiava spudoratamente la stessa workstation "Catalogizer" del sistema IRBIS 64 - questa è una questione a parte. In breve: ha dato la solidità necessaria ai miei sviluppi per il mio progetto di laurea (altrimenti l'indigeribile motore parser da solo per qualche motivo non sarebbe stato sufficiente). Inoltre, ho poi riscontrato alcune difficoltà nell'implementare l'interfaccia della workstation Cataloger con i miei moduli, implementati sia in C++ che in C#, e nell'accedere direttamente al mio motore.

In generale, stranamente, è stato questo prototipo piuttosto goffo del futuro BIRMA.NET a diventare il mio "cavallo di battaglia" per i prossimi quattro anni. Non si può dire che durante questo periodo non abbia almeno provato a trovare modi per una nuova e più completa attuazione di un'idea di vecchia data. Tra le altre innovazioni, avrebbero dovuto già esserci sequenze cicliche nidificate che avrebbero potuto includere elementi opzionali: è così che avrei dato vita all'idea di modelli universali per le descrizioni bibliografiche delle pubblicazioni e varie altre cose interessanti. Tuttavia, nelle mie attività pratiche in quel momento, tutto questo era poco richiesto e l'implementazione che avevo in quel momento era abbastanza sufficiente per inserire i sommari. Inoltre, il vettore di sviluppo della nostra biblioteca ha cominciato a deviare sempre di più verso la digitalizzazione degli archivi museali, la reportistica e altre attività di scarso interesse per me, che alla fine mi hanno costretto a lasciarla definitivamente, lasciando il posto a chi avrebbe voluto sii più soddisfatto di tutto questo.

Paradossalmente, è stato dopo questi drammatici eventi che il progetto BIRMA, che a quel tempo aveva già tutti i tratti caratteristici di un tipico progetto di costruzione a lungo termine, sembrava iniziare ad assumere la tanto attesa nuova vita! Ho avuto più tempo libero per pensieri oziosi, ho ricominciato a setacciare il World Wide Web alla ricerca di qualcosa di simile (per fortuna ora potevo già immaginare di cercare tutto questo non solo ovunque, ma su GitHub), e da qualche parte in At the All'inizio di quest'anno mi sono finalmente imbattuto in un prodotto corrispondente della nota azienda Salesforce dal nome insignificante Gorp. Di per sé, poteva fare quasi tutto ciò di cui avevo bisogno da un motore di analisi di questo tipo, vale a dire isolare in modo intelligente singoli frammenti da testo arbitrario, ma chiaramente strutturato, pur avendo un'interfaccia abbastanza user-friendly per l'utente finale, comprese essenze comprensibili, come un modello, un modello e un'occorrenza, e allo stesso tempo utilizzando la sintassi familiare delle espressioni regolari, che diventa incomparabilmente più leggibile grazie alla divisione in gruppi semantici designati per l'analisi.

In generale, ho deciso che questo è quello giusto Gorp (Mi chiedo cosa significhi questo nome? Forse una sorta di “parser regolare orientato al generale”?) – esattamente quello che stavo cercando da molto tempo. È vero, la sua implementazione immediata per le mie esigenze presentava un tale problema che questo motore richiedeva un'aderenza troppo rigorosa alla sequenza strutturale del testo di partenza. Per alcuni rapporti come i file di registro (vale a dire, sono stati inseriti dagli sviluppatori come chiari esempi di utilizzo del progetto), questo è abbastanza adatto, ma per gli stessi testi dei sommari scansionati è improbabile. Dopotutto, la stessa pagina con un sommario può iniziare con le parole "Sommario", "Contenuto" e qualsiasi altra descrizione preliminare che non abbiamo bisogno di inserire nei risultati dell'analisi prevista (e tagliandole manualmente ogni volta è anche scomodo). Inoltre, tra i singoli elementi ripetuti, come il nome dell'autore, il titolo e il numero di pagina, la pagina può contenere una certa quantità di spazzatura (ad esempio disegni e solo caratteri casuali), che sarebbe anche bello poter tagliare. Tuttavia, l'ultimo aspetto non era ancora così significativo, ma a causa del primo l'implementazione esistente non poteva iniziare a cercare le strutture necessarie nel testo da un certo punto, ma invece lo ha semplicemente elaborato fin dall'inizio, non ha trovato il punto ho specificato i modelli lì e... ho concluso il mio lavoro. Ovviamente, erano necessarie alcune modifiche per lasciare almeno un po' di spazio tra le strutture ripetute, e questo mi ha riportato al lavoro.

Un altro problema era che il progetto stesso era implementato in Java, e se in futuro avessi pianificato di implementare alcuni mezzi per interfacciare questa tecnologia con applicazioni familiari per l'immissione di dati nei database esistenti (come "Cataloguer" di Irbis), allora almeno Almeno farlo in C# e .NET. Non è che Java in sé sia ​​un linguaggio scurrile: una volta l'ho usato anche per implementare un'interessante applicazione a finestre che implementava la funzionalità di una calcolatrice programmabile domestica (come parte di un progetto del corso). E in termini di sintassi è molto simile allo stesso Do diesis. Bene, questo è solo un vantaggio: più facile sarà per me finalizzare un progetto esistente. Tuttavia, non volevo tuffarmi di nuovo in questo mondo piuttosto insolito delle tecnologie Java Windows (o meglio, desktop) - dopo tutto, il linguaggio stesso non era "su misura" per tale uso, e non desideravo affatto una ripetizione di l'esperienza precedente. Forse è proprio perché C# insieme a WinForms è molto più vicino a Delphi, con cui molti di noi hanno iniziato. Fortunatamente la soluzione necessaria è stata trovata abbastanza rapidamente, sotto forma di progetto IKVM.NET, che semplifica la traduzione dei programmi Java esistenti in codice .NET gestito. È vero, a quel tempo il progetto stesso era già stato abbandonato dagli autori, ma la sua ultima implementazione mi ha permesso di eseguire con successo le azioni necessarie per i testi di origine Gorp.

Ho quindi apportato tutte le modifiche necessarie e assemblato il tutto in una DLL del tipo appropriato, che potesse essere facilmente “recepita” da qualsiasi progetto per .NET Framework creato in Visual Studio. Nel frattempo ho creato un altro livello per una comoda presentazione dei risultati restituiti Gorp, sotto forma di strutture dati corrispondenti che sarebbe conveniente elaborare in una vista tabellare (prendendo come base sia righe che colonne; sia chiavi del dizionario che indici numerici). Bene, le stesse utilità necessarie per l'elaborazione e la visualizzazione dei risultati sono state scritte abbastanza rapidamente.

Inoltre, il processo di adattamento dei modelli per il nuovo motore per insegnargli ad analizzare campioni esistenti di testi scansionati del sommario non ha causato particolari complicazioni. In effetti, non ho dovuto nemmeno fare riferimento ai miei modelli precedenti: ho semplicemente creato da zero tutti i modelli necessari. Inoltre, se i modelli progettati per funzionare con la versione precedente del sistema stabilivano un quadro abbastanza ristretto per i testi che potevano essere analizzati correttamente con il loro aiuto, il nuovo motore consentiva già di sviluppare modelli abbastanza universali adatti a diversi tipi di markup a livello una volta. Ho anche provato a scrivere una sorta di modello completo per qualsiasi testo di sommario arbitrario, anche se, ovviamente, anche con tutte le nuove possibilità che mi si aprono, inclusa, in particolare, la capacità limitata di implementare le stesse sequenze ripetute annidate ( come, ad esempio, cognomi e iniziali di più autori di seguito), questa si è rivelata un'utopia.

Forse in futuro sarà possibile implementare un certo concetto di meta-modelli, che sarà in grado di verificare contemporaneamente la conformità del testo di partenza con diversi modelli disponibili e quindi, in base ai risultati ottenuti, selezionare quelli quello più adatto, utilizzando una sorta di algoritmo intelligente. Ma ora ero più preoccupato per un'altra questione. Un parser come Gorp, nonostante tutta la sua versatilità e le modifiche che ho apportato, era ancora intrinsecamente incapace di fare una cosa apparentemente semplice che il mio parser autoprodotto era in grado di fare fin dalla primissima versione. Vale a dire: aveva la capacità di trovare ed estrarre dal testo di partenza tutti i frammenti che corrispondono alla maschera specificata nel modello utilizzato nel posto giusto, pur non essendo affatto interessato a ciò che il testo dato contiene negli spazi tra questi frammenti. Finora ho migliorato solo leggermente il nuovo motore, permettendogli di cercare tutte le possibili nuove ripetizioni di una data sequenza di tali maschere dalla posizione corrente, lasciando la possibilità della presenza nel testo di insiemi di caratteri arbitrari che erano completamente non contabilizzati nell'analisi, racchiusi tra le strutture ripetute rilevate. Tuttavia, ciò non ha permesso di impostare la maschera successiva indipendentemente dai risultati della ricerca del frammento precedente utilizzando la maschera corrispondente: il rigore della struttura del testo descritta non lasciava ancora spazio ad inclusioni arbitrarie di caratteri irregolari.

E se per gli esempi di sommari in cui mi sono imbattuto questo problema non sembrava ancora così serio, allora quando provo ad applicare un nuovo meccanismo di analisi a un compito simile di analisi dei contenuti di un sito web (cioè lo stesso parsing), è i limiti sono qui apparsi con tutta la loro evidenza. Dopotutto, è abbastanza semplice impostare le maschere necessarie per i frammenti di markup web, tra i quali dovrebbero trovarsi i dati che stiamo cercando (che devono essere estratti), ma come possiamo forzare il parser a passare immediatamente a quello successivo? frammento simile, nonostante tutti i possibili tag e attributi HTML che possono essere inseriti negli spazi tra di loro?

Dopo averci pensato un po', ho deciso di introdurre un paio di modelli di servizio (%all_before) и (%all_after), con l'ovvio scopo di garantire che tutto ciò che può essere contenuto nel testo di partenza venga saltato prima di qualsiasi modello (maschera) che li segue. Inoltre, se (%all_before) quindi semplicemente ignorarono tutte queste inclusioni arbitrarie (%all_after), al contrario, permetteva di aggiungerli al frammento desiderato dopo essersi spostati dal frammento precedente. Sembra abbastanza semplice, ma per implementare questo concetto ho dovuto spulciare nuovamente i sorgenti gorp per apportare le modifiche necessarie in modo da non rompere la logica già implementata. Alla fine, siamo riusciti a farlo (sebbene sia stata scritta anche la primissima, anche se molto difettosa, implementazione del mio parser, e anche più velocemente, in un paio di settimane). Da quel momento in poi il sistema assunse una forma veramente universale, ben 12 anni dopo i primi tentativi di farlo funzionare.

Naturalmente questa non è la fine dei nostri sogni. Puoi anche riscrivere completamente il parser del modello gorp in C#, utilizzando una qualsiasi delle librerie disponibili per implementare una grammatica libera. Penso che il codice dovrebbe essere notevolmente semplificato e questo ci consentirà di sbarazzarci dell'eredità sotto forma di sorgenti Java esistenti. Ma con il tipo di motore esistente è anche possibile fare diverse cose interessanti, tra cui il tentativo di implementare i meta-template di cui ho già parlato, per non parlare dell'analisi di vari dati provenienti da vari siti web (non escludo però che gli strumenti software specializzati esistenti sono più adatti a questo scopo (solo che non ho ancora avuto l'esperienza adeguata per usarli).

A proposito, quest'estate ho già ricevuto un invito via e-mail da un'azienda che utilizza le tecnologie Salesforce (lo sviluppatore dell'originale Gorp), superare un colloquio per un successivo lavoro a Riga. Sfortunatamente, al momento non sono pronto per tali ridistribuzioni.

Se questo materiale suscita un certo interesse, nella seconda parte cercherò di descrivere più in dettaglio la tecnologia per la compilazione e successivamente l'analisi dei modelli utilizzando l'esempio dell'implementazione utilizzata in Salesforce Gorp (le mie aggiunte, con l'eccezione di un paio di parole funzione già descritte, non apportano praticamente alcuna modifica alla sintassi del modello stesso, quindi quasi tutta la documentazione per il sistema originale Gorp Adatto anche alla mia versione).

Fonte: habr.com

Aggiungi un commento