Pull to refresh

Регулярные выражения в WinEdt: поиск формул с неиспользуемыми номерами

Reading time 6 min
Views 5.8K
После более детального ознакомления с мануалом редактора WinEdt (предназначенного почти исключительно для создания LaTeX-документов) открыл дополнительные возможности инструмента поиска/замены этой программы. Чтобы активировать «умный» поиск, нужно поставить галочку в чекбоксе Regular Expressions в меню Find или Find and Replace, в результате чего строка поиска превратится, по сути, в командную строку, с помощью которой можно творить чудеса. То есть сделать с текстом можно будет практически всё, другой вопрос, что иногда чересчур извращённо (поэтому в случае серьёзных задач создание соответствующих макросов выглядит более уместным).

Анекдот про гинеколога
Гинеколог приходит устраиваться на работу в автосервис. Его просят разобрать-собрать двигатель. Он выполняет и интересуется оценкой своей работы. Ему отвечают: «в принципе ничего, только вот мы первый раз видим, чтобы всё это делалось через выхлопную трубу».

Приведу пример. Нужно найти все неиспользуемые метки \label, то есть те, на которые в тексте работы нет ни одной ref-ссылки (all labels that are never referred to, как сказано в англоязычном руководстве). Лишняя метка, безобидная сама по себе, может сигнализировать, в частности, об избыточной «занумерованности» формул латеховского документа (т. е. о наличии в нём формул с неиспользуемыми номерами). В случае, если текст достаточно большой и имеет много занумерованных соотношений, возникновение таких меток практически неизбежно (вы когда-то ссылались на данное уравнение, потом изменили текст, удалив ссылку, а убрать номер у уравнения, скорее всего, забыли). Вместе с тем, ручное обнаружение «лишних» меток превращается (опять-таки в силу большого объёма материала) в чересчур громоздкую и, главное тупую механическую работу, характер которой просто вопиёт о рациональной альтернативе.

Итак, решим поставленную задачу с помощью «умного» поиска редактора WinEdt (версии 5.3 заведомо должно хватить). Прежде всего, замечу, что WinEdt резервирует ячейки памяти (регистры) с именами %!0, ..., %!9 для пользовательских нужд. Причём надо иметь в виду, что память эта существенно оперативна в том смысле, что она обнуляется при каждом перезапуске WinEdt. Используем эту память, чтобы сохранить содержимое всех ссылок \ref в виде одной длинной строки: нажимаем ctrl+F, не забываем про галочку в чекбоксе Regular Expressions открывшегося меню, в строке поиска которого вводим следующий текст:

\\ref\(\{*\}\)\X{\"|GetTag(0,0);LetReg(1,"%!1%!0");|}



Некоторые пояснения (частично раскрывающие смысл последней строки). При включённом режиме Regular Expressions некоторые символы (например \, { и }) приобретают служебный смысл; если же они нужны нам в своём непосредственном значении (то есть как соответствующие символы), их следует использовать вместе с косой чертой впереди (например \\, \{ и \}). Но есть и исключения: например, круглые скобки сами по себе не являются служебными символами (тем самым, означая буквально круглые скобки), а вот в сочетаниях \( и \), наоборот, имеют специальный смысл. Текст, заключённый между \( и \), превращается в так называемый тег (выражение-метку или отмеченный текст) и может быть использован в дальнейшем: обращение к этому тексту (например, в той же строке поиска для обнаружения повторяющегося фрагмента или в строке «заменить на») осуществляется посредством команды \0 (нуль — это устанавливаемый по умолчанию номер затегированного фрагмента). Если есть необходимость в выделении нескольких частей, следует использовать конструкции вида:

\(0 какой-то текст \), …, \(9 какой-то текст \)

и команды \0, …, \9 для обращения к соответствующим частям.

А что там за звёздочка * между \{ и \} в начале введённого текста? Эта звёздочка называется шаблоном и означает произвольную последовательность символов (в том числе пустую) в пределах одной строки (замечу, что, начиная с версии WinEdt 5.3, сочетание ** кодирует произвольный текст, включающий разрывы строк).

Таким образом, набор символов:

\\ref\(\{*\}\)

то есть первая часть рассматриваемого выражения, задаёт поиск любых сочетаний вида: \ref{произвольный текст}. При обнаружении такого сочетания, запускаются макросы, о чём свидетельствует вторая часть выражения, начинающаяся с \X (при отсутствии команды запуска макросов, WinEdt просто переходит к найденному сочетанию, выделяя его в тексте документа). Причём команда запуска макросов может начинаться и с \x (регистр имеет значение!), а также с \Xx и \xX. Дело в том, что в зависимости от результатов исполнения макросов WinEdt может как перейти к найденному фрагменту (в нашем случае это — \ref{произвольный текст}), так и проигнорировать его (как если бы он отличался от заданного в поисковой строке), перейдя к поиску следующего совпадения. А то, какую из этих двух альтернатив он предпочтёт, определяется регистром “x-команды” и значением используемой WinEdt булевой переменной IFOK (по умолчанию равном истине), которое часть макросов могут изменять. В случае команды \X реакция WinEdt согласуется со значением IFOK: если значение IFOK равно истине, WinEdt переходит к найденному фрагменту; если же оно равно лжи, WinEdt игнорирует этот фрагмент. В случае команды \x реакция WinEdt на значение IFOK прямо противоположна, а при использовании \Xx или \xX, WinEdt независимо от значения IFOK отображает обнаруженный текст.

Рассмотрим подробнее вторую часть анализируемой строки, т. е. команду:

\X{\"|GetTag(0,0);LetReg(1,"%!1%!0");|}

Она запускает два макроса: GetTag(0,0) и LetReg(1,"%!1%!0"). Макрос GetTag(n,m) записывает содержимое n-го тега (в нашем случае нулевого тега, т. е. аргумента команды \ref вместе с обрамляющими его фигурными скобками) в m-й регистр, т. е. в ячейку памяти с именем %!m (в нашем случае – с именем %!0). Макрос LetReg(k,«строка») записывает в k-ю ячейку свой второй аргумент (без обрамляющих кавычек). Получается, что в нашем случае LetReg перезаписывает первый регистр (изначально в нём ничего нет), добавляя к нему содержимое 0-го регистра, т. е. заключённый в фигурные скобки аргумент обнаруженной поиском WinEdt команды \ref. Тем самым, для занесения в ячейку %!0 последовательности аргументов всех команд \ref, встречающихся в тексте, можно, введя в поисковую строку:

\\ref\(\{*\}\)\X{\"|GetTag(0,0);LetReg(1,"%!1%!0");|}

пройтись поиском через весь документ. Это делается сравнительно просто и быстро: после первого успешного обнаружения заданного текста поиск всех последующих вхождений осуществляем нажимая и удерживая клавишу F3 (для документа, содержащего многие сотни занумерованных соотношений, удерживать F3 пришлось не более 30 секунд). Впрочем, есть и альтернативный вариант — можно воспользоваться инструментом замены редактора WinEdt: нажимаем ctrl+R, в строке поиска вводим:

\\ref\(\{*\}\)\X{\"|GetTag(0,0);LetReg(1,"%!1%!0");|}

в строке «заменить на»:

\ref\(\{*\}\)

при появлении запроса о подтверждении замены выбираем All и вперёд (не забываем про галочку в чекбоксе Regular Expressions).

Подготовительная работа завершена. Теперь обнаружение неиспользуемых меток осуществляется вызовом поиска с выражением:

\\label{\{\(*\)\}}\x{FindInString("%!1","\0");}

в поисковой строке (вспомните о связи \x c IFOK!). Поиск с аргументом:

\\label{\{\(*\)\}}\X{FindInString("%!1","\0");}

решает обратный вопрос, показывая лишь те метки, которые фигурируют в аргументе хотя бы одной из ref-команд. Замечу, что наличие внешних фигурных скобок в выражении: \\label{\(\{*\}\)} является синтаксически излишним, однако в случае их отсутствия поиск WinEdt даёт, вообще говоря, некорректный результат. Эта особенность не имеет рационального объяснения — её необходимо запомнить (в англоязычном руководстве просто сказано, что it is important to use: {\{\(*\)\}} because \{\(*\)\} will not work here!).

Анекдот про грузинскую школу
Учитель на уроке русского языка в грузинской школе: «Дети, запомните: слова сол, фасол и вермишел пишутся с мягким знаком, а слова вилька, булька и тарелька – без. Это необъяснимо и надо просто запомнить!»

Отмечу ещё, что обрамление фигурными скобками аргументов ref-команд при записи в регистры %!0 и %!1, не являясь строго обязательным, тем не менее, весьма целесообразно, т. к. позволяет избегать ошибок в случаях, подобном приведённому ниже:

… \label{h1} … \label{h2} … \label{1h} … \label{h3} … \ref{h1} … \ref{h2} … \label{h}

(если вместо конструкции \\ref\(\{*\}\) использовать \\ref\{\(*\)\}, не включив { и } в затегированный фрагмент, то содержимое ссылок образует строку: h1h2, поиск по которой даст ложный результат об использовании меток с именами h и 1h). Это, правда, не избавляет нас ото всех возможных ошибок, так как аргументы меток и ссылок сами могут содержать (разумеется, парным образом) фигурные скобки (например, \label{h{1}}). Для полного исключения недоразумений проще всего отказаться от использования фигурных скобок при именовании ссылок; если же вы успели создать огромный документ с неимоверным количеством ссылок, в именах которых присутствуют данные скобки, то без специального макроса, пожалуй, не обойтись.

Итак, изложенный здесь метод позволяет (с указанною выше оговоркой) обнаружить все случаи, когда какое-либо окружение, порождающее номер (например, equation) содержит неиспользуемые метки \label. Но «лишний» номер может появиться и тогда, когда подобное окружение и вовсе не содержит \label. К счастью, с использованием механизмов «продвинутого» поиска WinEdt легко обнаружить (поле Search for):

\\begin\{equation\}\(**\)\end\{equation\}\x{FindInString("\0","\label");}

и даже исправить (поле Replace with):

\begin\{equation\*\}\0\end\{equation\*\}

все такие недоразумения (для определённости рассмотрен случай упомянутого выше окружения equation).
Tags:
Hubs:
+3
Comments 3
Comments Comments 3

Articles