Priča o jednom malom projektu dugom dvanaest godina (o BIRMA.NET-u prvi put i iskreno iz prve ruke)

Rođenje ovog projekta može se smatrati malom idejom koja mi je sinula negdje krajem 2007. godine, a kojoj je suđeno da svoj konačni oblik dobije tek 12 godina kasnije (u ovom trenutku – naravno, iako trenutna realizacija, prema autoru, vrlo je zadovoljavajuće) .

Sve je počelo kada sam, u procesu obavljanja svojih tadašnjih službenih obveza u knjižnici, skrenuo pozornost da proces unosa podataka iz skeniranog teksta kazala sadržaja knjižnih (i notnih) publikacija u postojeću bazu podataka, tj. naizgled, može znatno pojednostaviti i automatizirati, koristeći svojstvo urednosti i ponovljivosti svih podataka potrebnih za unos, kao što su ime autora članka (ako je riječ o zborniku), naslov članak (ili podnaslov prikazan u sadržaju) i broj stranice trenutne stavke sadržaja. U početku sam bio praktički uvjeren da se sustav prikladan za obavljanje ovog zadatka može lako pronaći na internetu. Kad je izazvano iznenađenje činjenicom da nisam mogao pronaći takav projekt, odlučio sam ga pokušati implementirati sam.

Nakon prilično kratkog vremena proradio je prvi prototip kojeg sam odmah počeo koristiti u svojim svakodnevnim aktivnostima, istovremeno ga ispravljajući na svim primjerima koji su mi došli pod ruku. Srećom, na svom uobičajenom radnom mjestu, gdje nipošto nisam bio programer, tada sam se ipak izvukao s vidljivim “stankom” u poslu, tijekom kojeg sam intenzivno otklanjao pogreške u svojoj zamisli - što je gotovo nezamisliva stvar u sadašnjoj stvarnosti koja podrazumijeva dnevna izvješća o obavljenom radu tijekom dana. Proces brušenja programa trajao je ukupno ne manje od godinu dana, no i nakon toga rezultat se teško mogao nazvati posve uspješnim – u startu je postavljeno previše različitih koncepata koji nisu bili posve jasni za implementaciju: izborni elementi koji se mogu biti preskočen; pregled elemenata unaprijed (u svrhu zamjene prethodnih elemenata u rezultatima pretraživanja); čak i naš vlastiti pokušaj da implementiramo nešto poput regularnih izraza (koji imaju jedinstvenu sintaksu). Moram reći da sam prije ovoga pomalo odustao od programiranja (oko 8 godina, ako ne i više), tako da je nova prilika da svoje vještine primijenim na zanimljiv i potreban zadatak potpuno zaokupila moju pozornost. Nije iznenađujuće da je rezultirajući izvorni kod - u nedostatku bilo kakvog jasnog pristupa njegovom dizajnu s moje strane - vrlo brzo postao nezamisliva mješavina različitih dijelova u jeziku C s nekim elementima C++ i aspektima vizualnog programiranja (u početku je to odlučeno je koristiti takav sustav dizajna kao što je Borland C++ Builder - “skoro Delphi, ali u C”). No, sve je to u konačnici urodilo plodom u automatizaciji svakodnevnih aktivnosti naše knjižnice.

U isto vrijeme, odlučio sam, za svaki slučaj, pohađati tečajeve za obuku profesionalnih programera softvera. Ne znam je li tamo zapravo moguće naučiti “biti programer” od nule, ali uzimajući u obzir vještine koje sam već imao u to vrijeme, uspio sam donekle ovladati tehnologijama koje su u to vrijeme bile relevantnije, npr. kao C#, Visual Studio za razvoj pod .NET, kao i neke tehnologije vezane uz Javu, HTML i SQL. Cjelokupna obuka trajala je ukupno dvije godine i poslužila je kao polazište za još jedan moj projekt koji se u konačnici protegao na nekoliko godina – no to je tema za posebnu objavu. Ovdje bi samo bilo prikladno napomenuti da sam pokušao prilagoditi razvoj koji sam već imao na opisanom projektu kako bih stvorio potpunu prozorsku aplikaciju u C# i WinForms koja implementira potrebnu funkcionalnost i upotrijebio je kao osnovu za nadolazeći diplomski projekt.
S vremenom mi se ta ideja počela činiti vrijednom izlaganja na takvim godišnjim konferencijama uz sudjelovanje predstavnika raznih knjižnica kao što su „LIBKOM“ i „KRIM“. Ideja, da, ali ne i moja provedba iste u to vrijeme. Tada sam se također nadao da će ga netko prepisati koristeći kompetentnije pristupe. Ovako ili onako, do 2013. odlučio sam napisati izvješće o svom pripremnom radu i poslati ga Organizacijskom odboru skupa uz prijavu za dodjelu bespovratnih sredstava za sudjelovanje na skupu. Na moje pomalo iznenađenje, moja prijava je odobrena i počeo sam unositi neka poboljšanja u projekt kako bih ga pripremio za predstavljanje na konferenciji.

Do tada je projekt već dobio novo ime BIRMA, stekao razne dodatne (ne toliko potpuno implementirane, već pretpostavljene) sposobnosti - sve detalje možete pronaći u mom izvješću.

Iskreno rečeno, teško je BIRMU 2013. bilo nazvati nečim cjelovitim; Iskreno govoreći, bila je to vrlo lukava letjelica napravljena na brzinu. Što se tiče koda, praktički nije bilo nikakvih posebnih inovacija, osim prilično bespomoćnog pokušaja stvaranja neke vrste unificirane sintakse za parser, koja izgledom podsjeća na jezik za formatiranje IRBIS 64 (a zapravo i na sustav ISIS - sa zagradama kao cikličkim strukturama; zašto sam tada mislio da izgleda prilično cool). Parser je beznadno naletio na te krugove zagrada odgovarajućeg tipa (budući da su zagrade imale i drugu ulogu, naime označavale su neobavezne strukture tijekom parsiranja koje se mogu preskočiti). Sve koji se žele pobliže upoznati s tada teško zamislivom, neopravdanom sintaksom BIRMA ponovno upućujem na svoje tadašnje izvješće.

Općenito, osim što se mučim s vlastitim parserom, nemam više što reći o kodu ove verzije - osim obrnute konverzije postojećih izvora u C++ uz očuvanje nekih tipičnih značajki .NET koda (da budem iskren, to je teško razumjeti , što me točno potaknulo da sve vratim unatrag - vjerojatno neki glupi strah da svoje izvorne kodove držim u tajnosti, kao da je to nešto što je ekvivalentno tajnom receptu Coca-Cole).

Možda je u ovoj glupoj odluci i razlog poteškoća pri uparivanju nastale DLL biblioteke s postojećim sučeljem domaće radne stanice za unos podataka u elektronički katalog (da, nisam spomenuo još jednu važnu činjenicu: od sada će svi kod BIRMA “motora” je očekivan, odvojen je od dijela sučelja i upakiran u odgovarajući DLL). Zašto je za te potrebe bilo potrebno napisati zasebnu radnu stanicu, koja je ionako svojim izgledom i načinom interakcije s korisnikom besramno kopirala istu radnu stanicu „Katalogizator“ sustava IRBIS 64 - to je zasebno pitanje. Ukratko: dao je potrebnu čvrstoću mojim tadašnjim razvojima za diplomski rad (inače sam neprobavljivi parser motor nekako nije bio dovoljan). Osim toga, tada sam naišao na neke poteškoće u implementaciji sučelja Cataloger radne stanice sa svojim vlastitim modulima, implementiranim u C++ i C#, i izravnom pristupu mom stroju.

Općenito, što je čudno, upravo je ovaj prilično nespretni prototip budućeg BIRMA.NET-a bio predodređen da postane moj “radni konj” sljedeće četiri godine. Ne može se reći da za to vrijeme nisam barem pokušao pronaći načine za novu, cjelovitiju provedbu jedne dugogodišnje ideje. Između ostalih inovacija, već su trebali postojati ugniježđeni ciklički nizovi koji su mogli uključivati ​​izborne elemente - tako sam namjeravao oživjeti ideju o univerzalnim predlošcima za bibliografske opise publikacija i razne druge zanimljive stvari. No, u mojim tadašnjim praktičnim aktivnostima sve je to bilo malo traženo, a implementacija koju sam tada imao bila je sasvim dovoljna za unos kazala. Osim toga, vektor razvoja naše knjižnice počeo je sve više skrenuti prema digitalizaciji muzejske arhivske građe, izvješćivanju i drugim meni malo interesantnim djelatnostima, što me je na kraju primoralo da je konačno napustim, ustupivši mjesto onima koji bi biti zadovoljniji sa svim ovim .

Paradoksalno, upravo nakon ovih dramatičnih događaja projekt BIRMA, koji je tada već imao sva obilježja tipične dugotrajne gradnje, kao da je počeo dobivati ​​svoj dugo očekivani novi život! Imao sam više slobodnog vremena za prazne misli, ponovno sam počeo češljati World Wide Web u potrazi za nečim sličnim (srećom, sada sam već mogao pogoditi da sve to potražim ne bilo gdje, već na GitHubu), a negdje u At početkom ove godine konačno sam naišao na odgovarajući proizvod poznate tvrtke Salesforce pod beznačajnim imenom Gorp. Sam po sebi, mogao je učiniti gotovo sve što sam trebao od takvog stroja za parser - naime, inteligentno izolirati pojedinačne fragmente iz proizvoljnog, ali jasno strukturiranog teksta, dok je imao prilično korisničko sučelje za krajnjeg korisnika, uključujući tako razumljive suštine, kao obrazac, predložak i pojavu, a pritom koristeći poznatu sintaksu regularnih izraza, koja postaje neusporedivo čitljivija zbog podjele na naznačene semantičke skupine za raščlanjivanje.

Općenito, odlučio sam da je to taj Gorp (Pitam se što ovo ime znači? Možda neka vrsta "opće orijentiranog regularnog parsera"?) - upravo ono što sam dugo tražio. Istina, njegova neposredna implementacija za moje potrebe imala je toliki problem da je ovaj motor zahtijevao prestrogo pridržavanje strukturalnog slijeda izvornog teksta. Za neka izvješća kao što su log datoteke (naime, programeri su ih postavili kao jasne primjere korištenja projekta), to je sasvim prikladno, ali za iste tekstove skeniranih tablica sadržaja malo je vjerojatno. Uostalom, ista stranica sa sadržajem može započeti riječima "Sadržaj", "Sadržaj" i bilo kojim drugim preliminarnim opisima koje ne trebamo staviti u rezultate predviđene analize (i ručno ih odrezati svaki put je i nezgodno). Osim toga, između pojedinih elemenata koji se ponavljaju, kao što su ime autora, naslov i broj stranice, stranica može sadržavati određenu količinu smeća (na primjer, crteže i samo nasumične znakove), što bi također bilo lijepo da se može odrezati. No, zadnji aspekt još nije bio toliko značajan, već zbog prvog, postojeća implementacija nije mogla s određenog mjesta krenuti u traženje potrebnih struktura u tekstu, nego ga je jednostavno obrađivala od samog početka, nije pronalazila specificirao uzorke tamo i... završio svoj posao. Očito je bilo potrebno malo dotjerivanja kako bi se barem omogućio razmak između struktura koje se ponavljaju, a to me natjeralo na posao.

Drugi je problem bio taj što je sam projekt bio implementiran u Javi, a ako sam planirao u budućnosti implementirati neki način sučelja ove tehnologije s poznatim aplikacijama za unos podataka u postojeće baze podataka (kao što je Irbisov “Cataloguer”), onda barem barem učinite to u C# i .NET. Nije da je Java sama po sebi loš jezik - jednom sam je čak upotrijebio za implementaciju zanimljive prozorske aplikacije koja je implementirala funkcionalnost domaćeg programabilnog kalkulatora (kao dio kolegija). A u smislu sintakse vrlo je sličan istom C-sharpu. Pa, ovo je samo plus: lakše ću finalizirati postojeći projekt. Međutim, nisam želio ponovno uroniti u ovaj prilično neobičan svijet prozorskih (ili bolje rečeno desktop) Java tehnologija - uostalom, sam jezik nije bio "skrojen" za takvu upotrebu, a ja uopće nisam žudio za ponavljanjem prethodno iskustvo. Možda upravo zato što je C# u sprezi s WinForms puno bliži Delphiju s kojim su mnogi od nas nekada počinjali. Srećom, vrlo brzo je pronađeno potrebno rješenje - u obliku projekta IKVM.NET, što olakšava prevođenje postojećih Java programa u upravljani .NET kod. Istina, autori su u to vrijeme već napustili sam projekt, ali njegova posljednja implementacija omogućila mi je da prilično uspješno provedem potrebne radnje za izvorne tekstove Gorp.

Tako sam napravio sve potrebne izmjene i sve to sastavio u DLL odgovarajućeg tipa, koji bi lako mogli "pokupiti" bilo koji projekti za .NET Framework kreirani u Visual Studiju. U međuvremenu sam napravio još jedan sloj za prikladnu prezentaciju vraćenih rezultata Gorp, u obliku odgovarajućih struktura podataka koje bi bilo zgodno obraditi u prikazu tablice (uzimajući kao osnovu i retke i stupce; i ključeve rječnika i numeričke indekse). Pa, sami potrebni uslužni programi za obradu i prikaz rezultata napisani su prilično brzo.

Također, proces prilagodbe predložaka za novi motor kako bi ga se naučilo parsirati postojeće uzorke skeniranih tekstova tablica sadržaja nije izazvao posebne komplikacije. Zapravo, uopće se nisam morao pozivati ​​na svoje prethodne predloške: jednostavno sam stvorio sve potrebne predloške od nule. Štoviše, ako su predlošci dizajnirani za rad s prethodnom verzijom sustava postavili prilično uzak okvir za tekstove koji bi se uz njihovu pomoć mogli pravilno raščlaniti, novi je mehanizam već omogućio razvoj prilično univerzalnih predložaka prikladnih za nekoliko vrsta označavanja na jednom. Čak sam pokušao napisati neku vrstu sveobuhvatnog predloška za bilo koji proizvoljni tekst tablice sadržaja, iako, naravno, čak i uz sve nove mogućnosti koje su mi se otvarale, uključujući, posebno, ograničenu mogućnost implementacije istih ugniježđenih ponavljajućih nizova ( kao što su npr. prezimena i inicijali više autora za redom), to se pokazalo kao utopija.

Možda će u budućnosti biti moguće implementirati određeni koncept metapredložaka, koji će moći provjeriti usklađenost izvornog teksta s nekoliko dostupnih predložaka odjednom, a zatim, u skladu s dobivenim rezultatima, odabrati najprikladniji, koristeći neku vrstu inteligentnog algoritma. Ali sada me više brinulo drugo pitanje. Parser poput Gorp, usprkos svoj svojoj svestranosti i izmjenama koje sam napravio, još uvijek je inherentno bio nesposoban učiniti jednu naizgled jednostavnu stvar koju je moj samonapisani parser mogao učiniti od prve verzije. Naime: imao je mogućnost pronaći i izdvojiti iz izvornog teksta sve fragmente koji odgovaraju maski navedenoj unutar korištenog predloška na pravom mjestu, a da ga uopće nije zanimalo što zadani tekst sadrži u razmacima između tih fragmenata. Do sada sam samo malo poboljšao novi motor, dopuštajući mu da traži sva moguća nova ponavljanja zadanog niza takvih maski iz trenutne pozicije, ostavljajući mogućnost prisutnosti u tekstu skupova proizvoljnih znakova koji su bili potpuno neobjašnjeni u raščlanjivanju, zatvoreni između otkrivenih ponavljajućih struktura. Međutim, to nije omogućilo postavljanje sljedeće maske bez obzira na rezultate traženja prethodnog fragmenta pomoću odgovarajuće maske: strogost opisane strukture teksta još uvijek nije ostavljala mjesta proizvoljnim uključivanjima nepravilnih znakova.

I ako se za primjere tablica sadržaja na koje sam naišao ovaj problem još nije činio tako ozbiljan, onda kada pokušavam primijeniti novi mehanizam raščlanjivanja na sličan zadatak raščlanjivanja sadržaja web stranice (tj. isto raščlanjivanje), njegov ograničenja su ovdje pojavila sa svom svojom očitošću. Uostalom, prilično je lako postaviti potrebne maske za fragmente web markupa, između kojih bi se trebali nalaziti podaci koje tražimo (koje treba ekstrahirati), ali kako možemo natjerati parser da odmah prijeđe na sljedeći sličan fragment, unatoč svim mogućim oznakama i HTML atributima koji se mogu postaviti u razmake između njih?

Nakon malo razmišljanja, odlučio sam uvesti nekoliko servisnih obrazaca (%sve_prije) и (%sve_nakon), služeći očitoj svrsi osiguravanja da se sve što bi moglo biti sadržano u izvornom tekstu preskoči prije bilo kojeg uzorka (maske) koji ih slijedi. Štoviše, ako (%sve_prije) jednostavno ignorirao sva ta proizvoljna uključivanja, dakle (%sve_nakon), naprotiv, omogućio im je dodavanje željenom fragmentu nakon pomicanja s prethodnog fragmenta. Zvuči prilično jednostavno, ali da bih implementirao ovaj koncept, morao sam ponovno pročešljati gorp izvore kako bih napravio potrebne izmjene kako ne bih prekršio već implementiranu logiku. Na kraju smo uspjeli u tome (iako je čak i vrlo, vrlo prva, iako vrlo bugova, implementacija mog parsera napisana, i to još brže - u nekoliko tjedana). Od sada je sustav poprimio doista univerzalni oblik - ni manje ni više nego 12 godina nakon prvih pokušaja da funkcionira.

Naravno, ovo nije kraj naših snova. Također možete potpuno prepisati gorp parser predloška u C#, koristeći bilo koju od dostupnih biblioteka za implementaciju besplatne gramatike. Mislim da bi kod trebao biti značajno pojednostavljen, a to će nam omogućiti da se riješimo naslijeđa u obliku postojećih Java izvora. Ali s postojećom vrstom motora također je sasvim moguće raditi razne zanimljive stvari, uključujući pokušaj implementacije metapredložaka koje sam već spomenuo, da ne spominjem raščlanjivanje raznih podataka s raznih web stranica (međutim, ne isključujem da su postojeći specijalizirani softverski alati prikladniji za to – samo još nisam imao odgovarajuće iskustvo u njihovoj upotrebi).

Inače, ljetos sam već dobio poziv e-poštom od tvrtke koja koristi Salesforce tehnologije (programer originala Gorp), proći intervju za kasniji rad u Rigi. Nažalost, trenutno nisam spreman za takve preraspodjele.

Ako ovaj materijal pobudi neki interes, onda ću u drugom dijelu pokušati detaljnije opisati tehnologiju za sastavljanje i naknadno analiziranje predložaka koristeći primjer implementacije korištene u Salesforceu Gorp (moji dodaci, s iznimkom nekoliko funkcijskih riječi koje su već opisane, praktički ne mijenjaju samu sintaksu predloška, ​​tako da gotovo sva dokumentacija za izvorni sustav Gorp Prikladno i za moju verziju).

Izvor: www.habr.com

Dodajte komentar