Как собрать все редакции гоголевского «Ревизора» на одной странице? Как показать правки и отличия разных версий? DH-исследователь Евгений Дуненков сделал такой инструмент и рассказал, как над ним работал и с какими сложностями столкнулся
В жизни мне довелось работать с юридическими документами и с кодом. В обеих областях текст не создается раз и навсегда. Он постоянно меняется, может одновременно существовать в разных версиях. Можно зайти в «КонсультантПлюс» или в Git, открыть историю изменений, сравнить редакции, увидеть эволюцию текста.
Художественные тексты тоже проходят подобную эволюцию. Наброски на салфетках превращаются в рукописи, обрастают правками, проходят через редактуру и цензуру, печатаются в первом, втором издании, бронзовеют в академическом. Однако большая часть этого процесса скрыта от читателя. Текст, который мы читаем в книге, за исключением редких примечаний, — лишь один кадр эволюции. Целиком ее наблюдают только исследователи-текстологи.
Было бы классно, подумалось мне как-то, если бы эволюцию текста можно было увидеть на одной интерактивной странице. Собрать разные версии текста в наглядной демонстрации, где можно будет переключаться между версиями, наблюдать, как они переходят друг в друга, видеть общую картину.
Перед тем как писать код, нужно было найти подходящий текст на роль подопытного. Перебрав несколько вариантов, я остановился на «Ревизоре» Гоголя (для демо я взял явление 6 третьего действия, которое заканчивается знаменитым восклицанием «Лабардан! Лабардан!». Это произведение хорошо изучено, в академическом издании ему и его разным версиям посвящен целый том.
Сначала договоримся о понятиях и формализуем задачу. Надеюсь, мои упрощения не оскорбят профессиональных текстологов.
Выделим три основные сущности:
Удобно представить все версии текста в виде графа. Каждая версия — это отдельный узел, а переходы между связанными версиями — это ребра.
В своей демонстрации я хотел визуализировать две вещи:
В плеере мне хотелось сделать:
Вот ссылка на демонстрацию. Вы можете самостоятельно проследить за историей работы над «Ревизором».
Советы пользователям:
А может быть, вы хотите не смотреть историю «Ревизора», а собрать демонстрацию для другого текста? Я расскажу, как это сделать, в конце статьи. А теперь расскажу о том, как я создавал этот инструмент.
Как должен выглядеть результат, я себе представил — оставалось только написать код. Что, впрочем, вышло не сразу.
Сначала я попытался сделать универсальный комбайн, чтобы на одной веб-странице скрипт собирал тексты, подсчитывал различия между версиями и отображал их в браузере. Однако код быстро стал слишком громоздким и медленным, и от этой идеи пришлось отказаться.
Затем я решил разделить подсчет различий между версиями и визуализатор на независимые модули и стал писать первый. По ходу работы возникло много вопросов: как считать разницу, делать ли токенизацию, как хранить информацию о правках. На них было тяжело ответить, не зная, как будет работать визуализатор. А писать модуль визуализатора, не зная, что будет поступать на вход, было слишком сложно. Получился какой-то порочный круг.
В какой-то момент я сообразил, что нужно просто придумать формат, в котором будут храниться тексты и правки. И тогда сразу станет ясно, что должен делать и в какой форме отдавать сделанное первый модуль, а также как устроить визуализатор.
Дальше довольно быстро был набросан формат JSON-файла, в котором хранится текст версий и правки. В файле предполагалось три части:
Формат был зафиксирован и гордо назван TDJ — Text Dynamics JSON. Остальная часть проекта уже была делом вайбкодинга.
Модуль подсчета разницы, или TDJ Builder, я написал на Python и устроил его следующим образом.
На вход модуль принимает папку с TXT-файлами версий, а также комментарий с информацией о редакциях и связях между ними в отдельном конфигурационном файле.
Далее скрипт берет конфигурационный файл, строит на базе него граф версий, обходит каждое ребро, берет тексты из левого и правого узла и автоматически строит последовательность правок на основе разницы между текстами.
Как вычисляется разница между текстами? Исходный и целевой тексты разбиваются на токены (каждый токен — это слово, знак препинания или граница абзаца, пробелы отбрасываются). Затем исходная и целевая последовательности токенов сравниваются с помощью SequenceMatcher из стандартной библиотеки difflib. Получившуюся разницу скрипт переводит в последовательность удалений и вставок, которую и сохраняет в TDJ-файл в разделе текущего ребра (конкретной связки целевой и исходной версий).
В первой версии TDJ-файла я решил представить текст не просто как последовательность токенов, а как последовательность абзацев, состоящих из токенов. То есть ввел лишнюю единицу структуры, которая не требовалась. Мне казалось, что так будет больше порядка. А в итоге из-за этого считать разницу между текстами стало на порядок сложнее.
Сначала надо было однозначно сопоставить между собой абзацы исходной и целевой версии (что уже превращается в нетривиальную задачу, если в одной из версий абзац был удален или добавлен), а затем подсчитывать разницу для каждой пары абзацев. И не дай Бог правка окажется распределенной между несколькими абзацами.
В общем, тут я тоже долго думал, писал костыли, пока не сообразил, что такой структурной единицы не требуется ни для TDJ Builder, ни для визуализатора. Всем им достаточно просто символа перевода строки как рядового токена. На душе сразу стало так легко, а где-то на небесах улыбнулся старина Оккам…
Модуль визуализации я сделал в виде HTML-страницы с несколькими JS-скриптами, обрабатывающими входящий файл формата TDJ. Один скрипт отрисовывает карту версий и следит, чтобы на ней было отражено текущее положение плеера. Другой скрипт визуализирует текущий переход между конкретными исходной и целевой версиями: отображает исходный текст и пошагово вносит в него правки сообразно положению слайдера.
Собственно, после некоторых доработок (сделал возможным ветвление, которого сначала не было, предусмотрел небольшой язык разметки для явного указания правок и возможности делать документ двухслойным, — все это описано в документации), поставленная задача была решена.
Конечно, я бы не стал писать два больших модуля, чтобы визуализировать работу над одним лишь фрагментом «Ревизора». Модуль визуализации поддерживает возможность ставить пользовательские демо. Главное, чтобы они соответствовали формату TDJ.
Надеюсь, вам было интересно, и вы попробовали поиграться с плеером версий. Если у вас возникли замечания или предложения, напишите мне [dunenkov.evgeniy@gmail.com]. Если вы филолог, историк или просто исследователь-любитель, я буду рад обратной связи, так как хотел бы сделать полезный инструмент. Также приветствуются советы по поводу структуры формата TDJ. Мне кажется, он может быть гораздо удобнее, но пока у меня нет свежих идей по этому поводу.
По оценке Anthropic, роботы способны выполнять 74% физических задач в США, но заменять людей пока почти везде невыгодно
Почему «программистка» звучит естественно, а «авторка» и «членка» многим режут слух? И может ли сознательное употребление феминитивов сработать против целей…
Если представить цифровую среду как живой организм, то ее сердцем будет дата-центр. Он работает без выходных и пауз, прогоняет через…