Het verhaal van een klein project van twaalf jaar (voor het eerst over BIRMA.NET en eerlijk gezegd uit de eerste hand)

De geboorte van dit project kan worden beschouwd als een klein idee dat ergens eind 2007 bij mij opkwam en dat voorbestemd was om pas twaalf jaar later zijn definitieve vorm te vinden (op dit moment - natuurlijk, hoewel de huidige implementatie, volgens voor de auteur, is zeer bevredigend).

Het begon allemaal toen ik, tijdens het vervullen van mijn toenmalige officiële taken in de bibliotheek, de aandacht vestigde op het feit dat het proces van het invoeren van gegevens uit de gescande tekst van de inhoudsopgaven van boek- (en muziek-)publicaties in de bestaande database, blijkbaar kunnen aanzienlijk worden vereenvoudigd en geautomatiseerd, waarbij gebruik wordt gemaakt van de eigenschap van ordelijkheid en herhaalbaarheid van alle gegevens die nodig zijn voor invoer, zoals de naam van de auteur van het artikel (als we het hebben over een verzameling artikelen), de titel van het artikel (of de ondertitel weergegeven in de inhoudsopgave) en het paginanummer van het huidige inhoudsopgave-item. In eerste instantie was ik er praktisch van overtuigd dat een systeem dat geschikt was om deze taak uit te voeren gemakkelijk op internet te vinden was. Toen er enige verrassing ontstond doordat ik zo'n project niet kon vinden, besloot ik het zelf te proberen uit te voeren.

Na vrij korte tijd begon het eerste prototype te werken, dat ik onmiddellijk begon te gebruiken in mijn dagelijkse activiteiten, terwijl ik het tegelijkertijd debugde op alle voorbeelden die ik tegenkwam. Gelukkig kwam ik op mijn gebruikelijke werkplek, waar ik absoluut geen programmeur was, nog steeds weg met zichtbare ‘downtime’ in mijn werk, waarbij ik intensief bezig was met het debuggen van mijn geesteskind – iets bijna ondenkbaar in de huidige realiteit, wat impliceert dagelijkse rapporten over het werk dat gedurende de dag is gedaan. Het proces van het oppoetsen van het programma duurde in totaal maar liefst ongeveer een jaar, maar ook daarna was het resultaat nauwelijks helemaal succesvol te noemen - er waren te veel verschillende concepten die niet helemaal begrijpelijk waren voor implementatie: optionele elementen die overgeslagen kunnen worden ; vooruitkijken van elementen (met als doel eerdere elementen in zoekresultaten te vervangen); zelfs onze eigen poging om zoiets als reguliere expressies (die een unieke syntaxis hebben) te implementeren. Ik moet zeggen dat ik daarvoor het programmeren enigszins had opgegeven (ongeveer 8 jaar, zo niet langer), dus de nieuwe kans om mijn vaardigheden toe te passen op een interessante en noodzakelijke taak trok volledig mijn aandacht. Het is niet verrassend dat de resulterende broncode – bij gebrek aan enige duidelijke benadering van het ontwerp van mijn kant – vrij snel een onvoorstelbare mengelmoes werd van ongelijksoortige stukken in de C-taal met enkele elementen van C++ en aspecten van visueel programmeren (aanvankelijk was het werd besloten een dergelijk ontwerpsysteem te gebruiken als Borland C++ Builder - “bijna Delphi, maar in C”). Dit alles wierp uiteindelijk echter zijn vruchten af ​​bij het automatiseren van de dagelijkse activiteiten van onze bibliotheek.

Tegelijkertijd besloot ik, voor het geval dat, cursussen te gaan volgen om professionele softwareontwikkelaars op te leiden. Ik weet niet of het daadwerkelijk mogelijk is om daar helemaal opnieuw te leren “programmeur te worden”, maar rekening houdend met de vaardigheden die ik toen al had, kon ik technologieën die toen relevanter waren enigszins onder de knie krijgen, zoals zoals C#, Visual Studio voor ontwikkeling onder .NET, evenals enkele technologieën gerelateerd aan Java, HTML en SQL. De hele opleiding duurde in totaal twee jaar en diende als uitgangspunt voor een ander project van mij, dat zich uiteindelijk over meerdere jaren uitstrekte – maar dit is een onderwerp voor een aparte publicatie. Hier zou het alleen maar gepast zijn om op te merken dat ik een poging heb gedaan om de ontwikkelingen die ik al had in het beschreven project aan te passen om een ​​volwaardige vensterapplicatie in C# en WinForms te creëren die de noodzakelijke functionaliteit implementeert, en deze te gebruiken als basis voor de aankomend diplomaproject.
In de loop van de tijd begon dit idee mij de moeite waard te lijken om te worden geuit op dergelijke jaarlijkse conferenties met deelname van vertegenwoordigers van verschillende bibliotheken als "LIBKOM" en "CRIMEA". Het idee, ja, maar niet mijn uitvoering ervan destijds. Toen hoopte ik ook dat iemand het zou herschrijven met een competentere aanpak. Op de een of andere manier besloot ik in 2013 een rapport over mijn voorbereidende werk te schrijven en dit naar het organisatiecomité van de conferentie te sturen met een aanvraag voor een subsidie ​​voor deelname aan de conferentie. Tot mijn enigszins verbazing werd mijn aanvraag goedgekeurd en begon ik enkele verbeteringen aan het project aan te brengen om het voor te bereiden op presentatie op de conferentie.

Tegen die tijd had het project al een nieuwe naam BIRMA gekregen en verschillende aanvullende (niet zozeer volledig geïmplementeerde, maar eerder veronderstelde) mogelijkheden verworven - alle details zijn te vinden in mijn rapport.

Eerlijk gezegd was het moeilijk om BIRMA 2013 iets compleets te noemen; Eerlijk gezegd was het een zeer hacky-vaartuig dat in haast werd gemaakt. Op het gebied van code waren er vrijwel helemaal geen speciale innovaties, behalve een nogal hulpeloze poging om een ​​soort uniforme syntaxis voor de parser te creëren, die qua uiterlijk doet denken aan de opmaaktaal IRBIS 64 (en in feite ook het ISIS-systeem - met haakjes als cyclische structuren; waarom ik destijds vond dat het er best cool uitzag). De parser stuitte hopeloos op deze cirkels van haakjes van het juiste type (aangezien haakjes ook een andere rol vervulden, namelijk ze markeerden tijdens het parseren optionele structuren die kunnen worden overgeslagen). Iedereen die nader kennis wil maken met de destijds moeilijk voor te stellen, ongerechtvaardigde syntaxis van BIRMA verwijs ik nogmaals naar mijn verslag uit die tijd.

Over het algemeen heb ik, afgezien van het worstelen met mijn eigen parser, niets meer te zeggen over de code van deze versie - behalve de omgekeerde conversie van de bestaande bronnen naar C++ met behoud van enkele typische kenmerken van .NET-code (om eerlijk te zijn, het is moeilijk te begrijpen, wat mij er precies toe bracht alles terug te verplaatsen (waarschijnlijk een stomme angst om mijn broncodes geheim te houden, alsof het iets is dat gelijkwaardig is aan het geheime recept van Coca-Cola).

Misschien ligt deze domme beslissing ook de reden voor de moeilijkheden bij het koppelen van de resulterende DLL-bibliotheek aan de bestaande interface van een zelfgemaakt werkstation voor het invoeren van gegevens in een elektronische catalogus (ja, ik heb geen ander belangrijk feit genoemd: vanaf nu zullen alle de code van de BIRMA “engine” was zoals verwacht, deze is gescheiden van het interfacegedeelte en verpakt in de juiste DLL). Waarom het nodig was om voor deze doeleinden een apart werkstation te schrijven, dat hoe dan ook, qua uiterlijk en manier van interactie met de gebruiker, schaamteloos hetzelfde werkstation "Catalogizer" van het IRBIS 64-systeem kopieerde - dit is een aparte vraag. Kortom: het gaf de nodige stevigheid aan mijn toenmalige ontwikkelingen voor mijn afstudeerproject (anders was de onverteerbare parser-engine alleen op de een of andere manier niet genoeg). Bovendien ondervond ik enkele problemen bij het implementeren van de interface van het Cataloger-werkstation met mijn eigen modules, geïmplementeerd in zowel C++ als C#, en bij directe toegang tot mijn engine.

Over het algemeen was het, vreemd genoeg, dit nogal onhandige prototype van het toekomstige BIRMA.NET dat voorbestemd was om de komende vier jaar mijn “werkpaard” te worden. Het kan niet gezegd worden dat ik gedurende deze tijd niet op zijn minst heb geprobeerd manieren te vinden voor een nieuwe, meer volledige implementatie van een al lang bestaand idee. Naast andere innovaties hadden er al geneste cyclische reeksen moeten zijn die optionele elementen hadden kunnen bevatten - zo ging ik het idee van universele sjablonen voor bibliografische beschrijvingen van publicaties en diverse andere interessante dingen tot leven brengen. Bij mijn toenmalige praktische activiteiten was hier echter weinig vraag naar, en de implementatie die ik destijds had was ruim voldoende voor het invoeren van inhoudsopgaven. Bovendien begon de ontwikkelingsvector van onze bibliotheek steeds meer af te wijken naar de digitalisering van museumarchieven, rapportage en andere activiteiten die voor mij weinig interessant waren, wat mij uiteindelijk dwong de bibliotheek uiteindelijk te verlaten en plaats te maken voor degenen die dat wel wilden. wees meer tevreden met dit alles.

Paradoxaal genoeg leek het na deze dramatische gebeurtenissen het BIRMA-project, dat toen al alle karakteristieke kenmerken had van een typisch langetermijnbouwproject, zijn langverwachte nieuwe leven te beginnen! Ik had meer vrije tijd voor nutteloze gedachten, ik begon opnieuw het World Wide Web te kammen op zoek naar iets soortgelijks (gelukkig kon ik nu al raden dat ik dit allemaal niet zomaar ergens, maar op GitHub zou moeten zoeken), en ergens in At the Begin dit jaar kwam ik eindelijk een overeenkomstig product tegen van het bekende Salesforce-bedrijf onder de onbeduidende naam Gorp. Op zichzelf zou het bijna alles kunnen doen wat ik nodig had van zo'n parser-engine - namelijk op intelligente wijze individuele fragmenten isoleren uit willekeurige, maar duidelijk gestructureerde tekst, terwijl het een redelijk gebruiksvriendelijke interface voor de eindgebruiker had, inclusief begrijpelijke essenties, zoals een patroon, sjabloon en voorkomen, en tegelijkertijd gebruik makend van de vertrouwde syntaxis van reguliere expressies, die onvergelijkbaar beter leesbaar wordt vanwege de indeling in aangewezen semantische groepen voor parsering.

Over het algemeen heb ik besloten dat dit het is Gorp (Ik vraag me af wat deze naam betekent? Misschien een soort “algemeen georiënteerde reguliere parser”?) – precies waar ik al heel lang naar op zoek was. Toegegeven, de onmiddellijke implementatie ervan voor mijn eigen behoeften had zo'n probleem dat deze engine een te strikte naleving van de structurele volgorde van de brontekst vereiste. Voor sommige rapporten, zoals logbestanden (ze zijn namelijk door de ontwikkelaars geplaatst als duidelijke voorbeelden van het gebruik van het project), is dit redelijk geschikt, maar voor dezelfde teksten van gescande inhoudsopgaven is dit onwaarschijnlijk. Dezelfde pagina met een inhoudsopgave kan immers beginnen met de woorden “Inhoudsopgave”, “Inhoud” en eventuele andere voorlopige beschrijvingen die we niet in de resultaten van de beoogde analyse hoeven te plaatsen (en deze handmatig af te snijden elke keer is ook lastig). Bovendien kan de pagina tussen individuele herhalende elementen, zoals de naam van de auteur, de titel en het paginanummer, een bepaalde hoeveelheid rommel bevatten (bijvoorbeeld tekeningen en gewoon willekeurige tekens), wat ook leuk zou zijn om dat te kunnen doen. afsnijden. Dit laatste aspect was echter nog niet zo belangrijk, maar vanwege het eerste kon de bestaande implementatie niet vanaf een bepaalde plaats beginnen met het zoeken naar de noodzakelijke structuren in de tekst, maar deze in plaats daarvan eenvoudigweg vanaf het allereerste begin verwerken, de specificeerde daar patronen en... beëindigde mijn baan. Het was duidelijk dat er wat aanpassingen nodig waren om op zijn minst wat ruimte tussen de zich herhalende structuren te laten, en dat bracht me weer aan het werk.

Een ander probleem was dat het project zelf in Java werd geïmplementeerd, en als ik in de toekomst van plan was om een ​​manier te implementeren om deze technologie te koppelen aan bekende toepassingen voor het invoeren van gegevens in bestaande databases (zoals de “Cataloguer” van Irbis), dan zou ik op zijn minst doe dit in C# en .NET. Het is niet zo dat Java zelf een slechte taal is – ik heb het zelfs ooit gebruikt om een ​​interessante venstertoepassing te implementeren die de functionaliteit van een programmeerbare rekenmachine voor thuis implementeerde (als onderdeel van een cursusproject). En qua syntaxis lijkt het erg op dezelfde C-sharp. Nou, dit is alleen maar een pluspunt: hoe gemakkelijker het voor mij zal zijn om een ​​bestaand project af te ronden. Ik wilde me echter niet opnieuw in deze nogal ongebruikelijke wereld van Windows- (of beter gezegd desktop-) Java-technologieën storten - de taal zelf was tenslotte niet "op maat gemaakt" voor dergelijk gebruik, en ik verlangde helemaal niet naar een herhaling van de vorige ervaring. Misschien komt het juist omdat C# in combinatie met WinForms veel dichter bij Delphi staat, waarmee velen van ons ooit begonnen zijn. Gelukkig werd de noodzakelijke oplossing vrij snel gevonden – in de vorm van het project IKVM.NET, waarmee u eenvoudig bestaande Java-programma's kunt vertalen naar beheerde .NET-code. Het is waar dat het project zelf tegen die tijd al door de auteurs was verlaten, maar dankzij de laatste implementatie kon ik met succes de noodzakelijke acties voor de bronteksten uitvoeren Gorp.

Dus heb ik alle noodzakelijke wijzigingen aangebracht en alles samengevoegd tot een DLL van het juiste type, dat gemakkelijk kan worden 'opgepikt' door elk project voor het .NET Framework dat in Visual Studio is gemaakt. In de tussentijd heb ik nog een laag gemaakt voor een gemakkelijke presentatie van de geretourneerde resultaten Gorp, in de vorm van overeenkomstige gegevensstructuren die gemakkelijk in een tabelweergave kunnen worden verwerkt (met als basis zowel rijen als kolommen; zowel woordenboeksleutels als numerieke indexen). Welnu, de noodzakelijke hulpprogramma's zelf voor het verwerken en weergeven van de resultaten zijn vrij snel geschreven.

Ook het proces van het aanpassen van sjablonen voor de nieuwe engine om deze te leren bestaande voorbeelden van gescande teksten of inhoudsopgaven te ontleden, veroorzaakte geen bijzondere complicaties. Sterker nog, ik hoefde helemaal niet eens naar mijn vorige sjablonen te verwijzen: ik heb eenvoudigweg alle benodigde sjablonen helemaal opnieuw gemaakt. Bovendien, als de sjablonen die zijn ontworpen om met de vorige versie van het systeem te werken een vrij smal raamwerk vormen voor teksten die met hun hulp correct kunnen worden geparseerd, maakt de nieuwe engine het al mogelijk om tamelijk universele sjablonen te ontwikkelen die geschikt zijn voor verschillende soorten opmaak op eenmaal. Ik heb zelfs geprobeerd een soort alomvattend sjabloon te schrijven voor elke willekeurige inhoudsopgave, hoewel natuurlijk zelfs met alle nieuwe mogelijkheden die zich voor mij openden, waaronder in het bijzonder de beperkte mogelijkheid om dezelfde geneste herhalende reeksen te implementeren ( (zoals bijvoorbeeld achternamen en initialen van meerdere auteurs achter elkaar), bleek dit een utopie.

Misschien zal het in de toekomst mogelijk zijn om een ​​bepaald concept van meta-sjablonen te implementeren, die in staat zullen zijn om de brontekst te controleren op overeenstemming met meerdere van de beschikbare sjablonen tegelijk, en vervolgens, in overeenstemming met de verkregen resultaten, de meest geschikte, met behulp van een soort intelligent algoritme. Maar nu maakte ik me meer zorgen over een andere vraag. Een parser als Gorp, ondanks al zijn veelzijdigheid en de wijzigingen die ik heb aangebracht, was het inherent nog steeds niet in staat om één ogenschijnlijk eenvoudig ding te doen dat mijn zelfgeschreven parser vanaf de allereerste versie wel kon doen. Namelijk: hij had de mogelijkheid om alle fragmenten te vinden en uit de brontekst te halen die overeenkomen met het masker dat is opgegeven in het gebruikte sjabloon op de juiste plaats, terwijl hij helemaal niet geïnteresseerd was in wat de gegeven tekst in de ruimtes tussen deze fragmenten bevat. Tot nu toe heb ik de nieuwe engine slechts een klein beetje verbeterd, waardoor deze vanuit de huidige positie naar alle mogelijke nieuwe herhalingen van een bepaalde reeks van dergelijke maskers kan zoeken, waardoor de mogelijkheid overblijft voor de aanwezigheid in de tekst van sets willekeurige tekens die volledig waren niet meegenomen bij het parseren, ingesloten tussen de gedetecteerde herhalende structuren. Dit maakte het echter niet mogelijk om het volgende masker in te stellen, ongeacht de resultaten van het zoeken naar het vorige fragment met behulp van het overeenkomstige masker: de striktheid van de beschreven tekststructuur liet nog steeds geen ruimte voor willekeurige insluitsels van onregelmatige karakters.

En als dit probleem voor de voorbeelden van inhoudsopgaven die ik tegenkwam nog niet zo ernstig leek, dan is het zo dat wanneer ik een nieuw parseermechanisme probeer toe te passen op een soortgelijke taak, waarbij de inhoud van een website wordt geparseerd (d.w.z. dezelfde parsering), beperkingen zijn hier verschenen ze met al hun voor de hand liggende. Het is immers vrij eenvoudig om de nodige maskers in te stellen voor fragmenten van webmarkeringen, waartussen de gegevens die we zoeken (die moeten worden geëxtraheerd) zich moeten bevinden, maar hoe kunnen we de parser dwingen onmiddellijk door te gaan naar de volgende soortgelijk fragment, ondanks alle mogelijke tags en HTML-attributen die in de ruimtes ertussen kunnen worden geplaatst?

Nadat ik er een beetje over had nagedacht, besloot ik een aantal servicepatronen te introduceren (%all_before) и (%all_after), met het voor de hand liggende doel om ervoor te zorgen dat alles wat in de brontekst staat, wordt overgeslagen vóór elk patroon (masker) dat daarop volgt. Bovendien, als (%all_before) negeerde dus gewoon al deze willekeurige insluitsels (%all_after), integendeel, stond toe dat ze aan het gewenste fragment werden toegevoegd nadat ze vanuit het vorige fragment waren verplaatst. Het klinkt vrij eenvoudig, maar om dit concept te implementeren moest ik de gorp-bronnen opnieuw doorzoeken om de nodige wijzigingen aan te brengen om de reeds geïmplementeerde logica niet te doorbreken. Uiteindelijk zijn we erin geslaagd dit te doen (hoewel zelfs de allereerste, zij het zeer buggy, implementatie van mijn parser werd geschreven, en zelfs sneller - in een paar weken). Vanaf nu heeft het systeem een ​​waarlijk universele vorm aangenomen – maar liefst twaalf jaar na de eerste pogingen om het te laten functioneren.

Natuurlijk is dit niet het einde van onze dromen. Je kunt de gorp-sjabloonparser ook volledig herschrijven in C#, met behulp van een van de beschikbare bibliotheken voor het implementeren van een gratis grammatica. Ik denk dat de code aanzienlijk vereenvoudigd moet worden, en dit zal ons in staat stellen om van de erfenis in de vorm van bestaande Java-bronnen af ​​te komen. Maar met het bestaande type engine is het ook heel goed mogelijk om verschillende interessante dingen te doen, waaronder een poging om de meta-templates te implementeren die ik al heb genoemd, en niet te vergeten het parseren van verschillende gegevens van verschillende websites (ik sluit echter niet uit dat bestaande gespecialiseerde softwaretools hiervoor geschikter zijn – ik heb alleen nog niet de juiste ervaring met het gebruik ervan).

Overigens kreeg ik deze zomer al een uitnodiging per e-mail van een bedrijf dat Salesforce-technologieën gebruikt (de ontwikkelaar van het origineel Gorp), slagen voor een interview voor volgend werk in Riga. Helaas ben ik op dit moment niet klaar voor dergelijke herschikkingen.

Als dit materiaal enige interesse wekt, zal ik in het tweede deel proberen de technologie voor het compileren en vervolgens parseren van sjablonen gedetailleerder te beschrijven aan de hand van het voorbeeld van de implementatie die in Salesforce wordt gebruikt. Gorp (mijn eigen toevoegingen, met uitzondering van een paar functiewoorden die al zijn beschreven, brengen vrijwel geen wijzigingen aan in de sjabloonsyntaxis zelf, dus bijna alle documentatie voor het oorspronkelijke systeem Gorp Ook geschikt voor mijn versie).

Bron: www.habr.com

Voeg een reactie