Історія одного невеликого проекту довжиною в дванадцять років (про BIRMA.NET вперше і начистоту з перших рук)

Народженням цього проекту можна вважати маленьку ідею, яка відвідала мене десь наприкінці 2007 року, якій судилося набути свого остаточного вигляду лише через 12 років (на даний момент часу – звичайно ж, хоч і поточна реалізація, на думку автора, дуже задовільна) .

Все почалося з того, що в процесі виконання своїх тодішніх службових обов'язків у бібліотеці я звернув увагу на ту обставину, що процес введення даних із відсканованого тексту змісту книжкових (і нотних) видань до наявної бази даних, мабуть, можна значно спростити і автоматизувати, користуючись властивістю впорядкованості та повторюваності всіх даних, необхідних для введення, таких як ім'я автора статті (якщо ми говоримо про збірнику статей), назву статті (або відображений у змісті підзаголовок) та номер сторінки поточного пункту змісту. Спочатку я був практично переконаний у тому, що систему, придатну для здійснення даного завдання, можна легко знайти на просторах Інтернету. Коли ж якийсь подив викликав той факт, що подібний проект мені розшукати так і не вдалося, я вирішив спробувати реалізувати його власними силами.

Через досить нетривалий час запрацював перший прототип, який я відразу ж почав використав у своїй повсякденній діяльності, принагідно налагоджуючи його на всіх прикладах, що трапляються мені під руку. На щастя, на моєму звичному робочому місці, де я вважався аж ніяк не програмістом, мені тоді ще сходили з рук видимі «простої» в роботі, протягом яких я посилено займався налагодженням свого дітища - практично немислима справа в нинішніх реаліях, що мають на увазі щоденні звіти про виконаною за день роботі. Процес шліфування програми зайняв загалом ні багато ні мало близько року, але навіть після цього результат складно було назвати цілком успішним – надто багато там спочатку було закладено різних не цілком зрозумілих для реалізації концепцій: необов'язкові елементи, які можна пропускати; випереджаючий перегляд елементів (для цілей підстановки до результатів пошуку попередніх елементів); навіть власна спроба реалізації чогось на кшталт регулярних виразів (що має самобутній синтаксис). Треба сказати, що до цього я встиг дещо підкинути програмування (років так на 8, якщо не більше), так що нова можливість прикласти свої навички до цікавого та потрібного завдання повністю оволоділа моєю увагою. Не дивно, що отриманий в результаті вихідний код - у відсутності з мого боку будь-яких виразних підходів до його проектування - досить швидко став неймовірною мішаниною розрізнених шматків мовою Сі з деякими елементами Сі++ і аспектами візуального програмування (спочатку було вирішено використовувати таку систему проектування, як Borland C++ Builder - "майже Delphi, але на Сі"). Проте все це зрештою дало свої плоди у справі автоматизації повсякденної діяльності нашої бібліотеки.

Одночасно з цим я вирішив про всяк випадок пройти курси з підготовки професійних розробників ПЗ. Не знаю, чи можна там насправді з нуля вивчитися «на програміста», але з урахуванням навичок, що вже були у мене на той час, мені вдалося трохи освоїти такі вже більш актуальні на той момент технології, як C#, Visual Studio для розробки під . NET, а також небагато з технологій, що відносяться до Java, HTML та SQL. Все навчання зайняло два роки, і послужило відправною точкою для ще одного мого проекту, що зрештою розтягнувся на кілька років – але це вже тема для окремої публікації. Тут же буде доречно відзначити лише те, що я зробив спробу адаптувати вже напрацювання, що були в мене, за описуваним проектом для створення повноцінного віконного додатка на C# і WinForms, що реалізує потрібний функціонал, і покласти його в основу майбутнього дипломного проекту.
Згодом ця ідея почала здаватися мені гідною бути озвученою і на таких щорічних конференціях за участю представників різних бібліотек, як «ЛИБКОМ» та «КРИМ». Ідея – так, але аж ніяк не моя тодішня її реалізація. Тоді ще я сподівався навіть на те, що хтось перепише її із застосуванням більш грамотних підходів. Так чи інакше, до 2013 року я наважився скласти доповідь про свою попередню роботу та надіслати її до Оргкомітету конференції із заявкою на отримання гранту для участі у конференції. На мій деякий подив, моя заявка була задоволена, і я почав вносити деякі доробки в проект для його підготовки до подання на конференції.

На той час проект вже отримав нове ім'я BIRMA, знайшов різні додаткові (не так повноцінно реалізовані, як передбачувані) можливості – всі подробиці можна знайти в моїй доповіді.

Зізнатись чесно, BIRMA зразка 2013 року складно було назвати чимось закінченим; відверто кажучи, вона була виконаною нашвидкуруч досить халтурною виробою. По кодовій частині взагалі мало було ніяких спеціальних нововведень, крім досить безпорадну спробу створення деякого уніфікованого синтаксису для парсера, за своїм зовнішнім виглядом нагадує мову форматування ІРБІС 64 (а по суті, ще системи ISIS – з круглими дужками в ролі циклічних структур; тоді мені здавалося, що це виглядає дуже круто). Синтаксичний аналізатор безнадійно спотикався цих круговертах з дужок відповідного виду (оскільки круглі дужки виконували і ще одну роль, саме – ними відзначалися необов'язкові структури під час розбору, які можна пропустити). Усіх охочих ознайомитися докладніше з тодішнім важко уявним, нічим не виправданим синтаксисом BIRMA, я знову ж таки відсилаю до своєї доповіді того часу.

Загалом, якщо не вважати боротьби з власним синтаксичним аналізатором, то щодо коду цієї версії мені більше сказати і нічого – якщо не вважати зворотної конвертації наявних вихідників на Сі++ зі збереженням деяких типових рис .NET-коду (чесно кажучи, важко зрозуміти , Що саме спонукало мене перенести все назад - напевно, якесь безглузде побоювання за збереження в таємниці своїх вихідних кодів, немов це було щось рівноцінне секретному рецепту Coca-Cola).

Можливо, в цьому безглуздому рішенні криється також і причина складнощів у поєднанні отриманої DLL-бібліотеки з інтерфейсом самопального АРМа для введення даних в електронний каталог (так, я ж не згадав ще про один важливий факт: відтепер весь код «движка» BIRMA був, як і належить, відокремлений від інтерфейсної частини та упакований у відповідну DLL). Навіщо знадобилося писати ще й окремий АРМ для цих цілей, який все одно за своїм зовнішнім виглядом та способом взаємодії з користувачем безпардонно копіював той самий АРМ «Каталогізатор» системи ІРБІС 64 – це окреме питання. Якщо коротко: він надавав належну солідність моїм тодішнім напрацюванням для дипломного проекту (а то одного лише незручного двигуна парсера було якось обмаль). Крім того, я тоді зіткнувся з деякими труднощами при реалізації сполучення АРМ «Каталогізатор» із власними модулями, реалізованими що на C++, що на C#, і звертаються безпосередньо до мого двигуна.

Загалом, хоч як це дивно, але саме цьому досить незграбному прототипу майбутньої BIRMA.NET і судилося стати моєю «робочою конячкою» ще на наступні чотири роки. Не можна сказати, що протягом цього часу я не намагався хоча б знайти шляхи для нової, вже більш повноцінної реалізації давньої задуми. Там мали вже бути в числі інших нововведень і вкладені циклічні послідовності, які могли б містити в тому числі й необов'язкові елементи – саме так я збирався втілити в життя ідею універсальних шаблонів для бібліографічного опису видань та інших цікавих речей. Однак у моїй тодішній практичній діяльності все це було слабо затребуване, а для введення змісту цілком вистачало і наявної в мене на той момент реалізації. Крім того, вектор напряму розвитку нашої бібліотеки почав все більше ухилятися у бік оцифрування музейних архівів, формування звітності та інших малоцікавих для мене занять, що врешті-решт і змусило мене остаточно залишити її, поступившись своїм місцем тим, кому все це було б приємніше. .

Як не парадоксально, але саме після цих драматичних подій проект BIRMA, який на той момент вже володів усіма характерними рисами типового довгобуду, здавалося, почав знаходити своє довгоочікуване нове життя! У мене з'явилося більше вільного часу на пусті роздуми, я знову почав прочісувати Всесвітню мережу в пошуках чогось схожого (благо, тепер я вже міг здогадатися шукати все це не будь-де, а саме на GitHub'е), і десь у На початку цього року я нарешті натрапив на відповідний виріб відомої контори Salesforce під малозначною назвою Горп. Вона вже сама по собі могла робити практично все, що мені було потрібно від такого парсерного движка - а саме, по-розумному вичленяти окремі фрагменти з довільного, але має чітку структуру тексту, при цьому володіючи досить зручним інтерфейсом для кінцевого користувача, що включає такі зрозумілі сутності, як патерн, шаблон і входження, і в той же час задіяний звичний синтаксис регулярних виразів, що стає незрівнянно більш читабельним через розбиття на зазначені смислові групи для розбору.

Загалом, я вирішив, що цей самий Горп (цікаво, що ця назва означає? Можливо, якийсь «general oriented regular parser»?) – саме те, що я давно й шукав. Правда, його негайне використання для моїх власних потреб мало таку проблему, що цей двигун вимагав занадто точного дотримання структурної послідовності вихідного тексту. Для будь-яких звітів типу log-файлів (а саме вони і були поміщені розробниками як наочні приклади використання проекту) це цілком пригодиться, а ось для тих же текстів відсканованих змістів – навряд чи. Адже та сама сторінка з змістом може починатися зі слова «Зміст», «Зміст» та ще будь-яких попередніх описів, які нам зовсім не потрібно поміщати в результати передбачуваного розбору (а відсікати їх щоразу вручну теж незручно). Крім того, між окремими елементами, що повторюються, такими як ім'я автора, назва і номер сторінки, на сторінці може міститися деяка кількість сміття (наприклад, малюнки, та й просто випадкові знаки), яке теж непогано б вміти відсікати. Втім, останній аспект був ще не такий значний, а ось з першого наявна реалізація не могла почати шукати потрібні структури в тексті з якогось певного місця, а натомість просто обробляла його з самого початку, не знаходила там зазначених шаблонів і… закінчувала свою роботу. Очевидно, що була потрібна відповідна доробка, що дозволила б принаймні залишати деякі проміжки між структурами, що повторюються, і це змусило мене знову засісти за роботу.

Інша проблема полягала в тому, що сам проект був реалізований на Java, а я, якщо і планував надалі реалізовувати деякі засоби поєднання цієї технології зі звичними додатками для введення даних у наявні бази (такими як ірбісовський «Каталогізатор»), то вже принаймні мірою робити це на C# та .NET. Не те, щоб Java сама по собі була поганою мовою - колись я навіть саме на ньому реалізував цікавий віконний додаток, що реалізує функціонал вітчизняного програмованого калькулятора (в рамках курсового проекту). Та й за синтаксисом він дуже схожий з тим же Сішарпом. Що ж, це тільки плюс: тим легше мені буде доопрацювати вже існуючий проект. Однак мені не хотілося знову занурюватися в цей досить незвичний світ віконних (вірніше, десктопних) Java-технологій – зрештою, сама мова не була «заточена» на таке використання, а я зовсім не жадав повторення колишнього досвіду. Можливо, саме через те, що C# у зв'язці з WinForms набагато ближче до Delphi, з якого багато хто з нас колись починав. На щастя, потрібне рішення знайшлося досить швидко – в особі проекту IKVM.NET, що дозволяє легко перетранслювати наявні програми на Java в керований код .NET. Щоправда, сам проект до того моменту вже був покинутий авторами, проте його остання реалізація дозволила мені цілком успішно виконати потрібні дії для вихідних текстів. Горп.

Так що я вніс усі необхідні редагування і скомпонував це все в DLL відповідного виду, яку вже легко могли «підхопити» будь-які проекти для .NET Framework, створювані в Visual Studio. Між тим я створив ще один прошарок для зручного представлення результатів, що повертаються Горп, як відповідних структур даних, які було б зручно обробляти в табличному поданні (причому беручи за основу як рядки, так і стовпці; як словникові ключі, так і чисельні індекси). Ну, а самі необхідні утиліти для обробки та відображення отриманих результатів написалися вже досить швидко.

Так само особливих ускладнень не викликав і процес адаптації шаблонів для нового двигуна з метою навчити його розбирати існуючі зразки відсканованих текстів змісту. По суті мені навіть взагалі не довелося звертатися до своїх колишніх заготовок: я просто створив усі необхідні шаблони з нуля. Більше того, якщо шаблони, призначені для роботи з попередньою версією системи, задавали досить вузькі рамки для текстів, що піддаються правильному розбору за їх допомогою, то новий двигун вже дозволяв розробляти досить універсальні шаблони, які підходять для декількох типів розмітки одночасно. Я навіть спробував написати якийсь всеосяжний шаблон для будь-якого довільного тексту змісту, хоча, звичайно, навіть при всіх нових можливостях, що відкриваються для мене, включають, зокрема, обмежену можливість реалізації все тих же вкладених повторюваних послідовностей (таких, як, наприклад, прізвища та ініціали кількох авторів, що йдуть поспіль), це виявилося утопією.

Можливо, надалі і можна буде реалізувати якусь концепцію меташаблонів, які зможуть перевіряти вихідний текст на відповідність відразу декільком із наявних шаблонів, а потім відповідно до отриманих результатів вибирати з них найбільш підходящий, використовуючи якийсь інтелектуальний алгоритм. Але мене зараз більше хвилювало інше питання. Такий парсер, як Горп, незважаючи на всю свою універсальність і внесені мною модифікації, як і раніше, був за своєю природою не здатний виконувати одну, здавалося б, просту річ, яку мій власноручно написаний парсер умів робити з найпершої версії. А саме: він володів здатністю знаходити і витягувати з вихідного тексту всі фрагменти, що збігаються із зазначеною в рамках використовуваного шаблону в потрібному місці маскою, при цьому зовсім не цікавлячись тим, що текст містить у проміжках між цими фрагментами. Досі я лише трохи вдосконалив новий двигун, дозволяючи йому шукати всі можливі нові повтори заданої послідовності таких масок з поточної позиції, залишаючи можливість для наявності в тексті абсолютно не врахованих при розборі наборів довільних символів, укладених між виявленими структурами, що повторюються. Однак це не давало можливості задавати чергову маску незалежно від результатів пошуку попереднього фрагмента по відповідній масці: строгість описуваної структури тексту, як і раніше, не залишала місця для довільних включень нерегулярних символів.

І якщо для прикладів змістів, що мені траплялися, ця проблема ще не здавалася такою вже серйозною, то ось при спробі застосування нового механізму розбору до схожого за своєю суттю завдання по розбору вмісту веб-сайту (тобто тому ж самому парсингу), його обмеження тут ж постали з усією своєю очевидністю. Адже досить просто задати потрібні маски для фрагментів веб-розмітки, між якими повинні розташовуватися дані (які необхідно витягти), але як змусити парсер відразу ж переходити до наступного подібного фрагмента, незважаючи на всі можливі теги та атрибути HTML, які можуть поміщатися в проміжках між ними?

Трохи подумавши, я вирішив запровадити пару службових патернів (% all_before) и (% All_after), службовців очевидної мети забезпечення перепусток всього те, що може утримуватися у вихідному тексті до наступного за ними патерна (маски). При цьому якщо (% all_before) просто ігнорував усі ці довільні включення, то (% All_after), Навпаки, дозволяв додати їх до шуканого фрагмента після переходу від попереднього фрагмента. Звучить досить просто, але для реалізації цієї концепції мені довелося знову «прочесати» gorp'івські вихідники для внесення потрібних модифікацій для того, щоб при цьому не поламати вже реалізовану логіку. Зрештою це вдалося зробити (хоча навіть найперша, нехай і дуже глючна реалізація мого парсера була написана і те швидше - за пару тижнів). Відтепер система набула по-справжньому універсального вигляду – через 12 років після перших спроб змусити її функціонувати.

Зрозуміло, це ще межа мрій. Можна ще повністю переписати синтаксичний аналізатор gorp'івських шаблонів на C#, використовуючи якусь із наявних бібліотек для реалізації вільної граматики. Я думаю, код при цьому повинен значно спроститися, і це дозволить позбутися спадщини у вигляді вихідних джерел на Java. Але і з наявним видом движка теж вже цілком можна робити різні цікаві речі, включаючи спробу реалізації вже згаданих мною меташаблонів, не кажучи про парсинг різних даних з різних веб-сайтів (втім, не виключаю, що вже наявні спеціалізовані програмні засоби підходять для цього більше – просто у мене ще не було відповідного досвіду їхнього використання).

До речі, цього літа я вже отримував своєю електронною поштою запрошення від компанії, що застосовує технології Salesforce (розробника оригінального). Горп), пройти співбесіду – для подальшої роботи у Ризі. На жаль, зараз я не готовий до подібних передислокацій.

Якщо цей матеріал буде викликати певний інтерес, то в другій частині я постараюся докладніше описати технологію складання та подальшого аналізу шаблонів на прикладі реалізації, що використовується в Salesforce Горп (мої власні додавання, за винятком пари вже описаних службових слів, практично не вносять змін до самого синтаксису шаблонів, так що практично вся документація для оригінальної системи Горп підходить і для моєї версії).

Джерело: habr.com

Додати коментар або відгук