Артефакт

Интересный пример использования функции Find & Replace / 8 мая 2018 г.

Постановка задачи

Есть документ Word с текстом книги, заголовки в котором отображаются следующим образом:

Нужно:

1. Преобразовать строку с номером главы в Sentence case.

2. Преобразовать строку с названием главы в UPPERCASE.

3. Активировать опцию Keep with next для этих двух абзацев и следующего за ними, содержащего пустую строку и отбивающего название главы от её первого абзаца.

4. Отформатировать все три упомянутых абзаца с выключкой влево.

Попытка решить

Сначала я попробовал попрограммировать (хотя, как вы успели заметить, программист из меня, как из дизентерийной амёбы граната). Нужно было:

1. Программно искать подстроку «CAPÍTULO·» (напомню, что точка в средней позиции в данном случае соответствует обычному пробелу) и проделывать с содержащим её абзацем последовательно 1), 3) и 4).

2  Переходить к следующему абзацу и проделывать с ним 2), 3) и 4).

3. Преходить к следующему абзацу, далее 3) и 4).

Моего знания объектной модели документа Word на всё это не хватило, а когда выяснилось, что будут проблемы с программным поиском «CAPÍTULO·» из-за 4-го символа, я почти навсегда бросил программировать.

Уточню, что подстрока искалась примерно таким образом:

Sub ChangeaPragraphContainingString()
    Dim search As String
    search = "CAP" & ChrW(205) & "TULO "
    Dim para As Paragraph
    For Each para In ActiveDocument.Paragraphs
        Dim txt As String
        txt = para.Range.Text
        If InStr(txt, search) Then
            ...там-тарам и трах-тибидох...
        End If
    Next
End Sub

И вот этот самый If из 8-й строки (Вася из 19-й квартиры, ага) работать отказался, после чего в моём организме резко упал уровень энергии, необходимой для реализации программных методов поиска. А уж когда стало понятно, что, даже найдя нужную подстроку и определившись с абзацем, я не знал бы, как перейти к следующему, энергия ушла в ноль. Погуляв и нажравшись халявного кислорода, организм догадался, что, скорее всего, нужно было просто спросить у злонамеренного абзаца его индекс и потом, соответственно, по нему ссылаться на следующие, но было уже поздно. Я окончательно переключился на методы для домохозяек, а именно — нажал Ctrl + H и стал разбираться, что можно сделать из этого милого диалогового окна.

Попытка вторая

Сперва я проделал над несчастным текстом следующее:

На выходе, соответственно, получено было вот что:

Зачем я пошёл на этот выглядящий ненужным шаг? Все объяснения — чуть позже.

Сейчас я лишь прошу обратить внимание на то, что выключка влево у обоих абзацев образовалась совершенно без моего участия. Глюк это или разумный замысел, но каждый раз при использовании подстановочных знаков и упоминании символа перевода строки «^0013» в шаблоне происходит именно такой вот сброс красной строки в ноль. Если подстановочные знаки не включать и ссылаться на этот символ как на «^p», ничего подобного не случается.

Далее я снова вызвал уже знакомое нам диалоговое окно, очистил поле Replace with и перешёл на закладку Find. В строке поиска на этот раз было введено «^0013CAPÍTULO *^0013», после чего я (следите за руками!) раскрыл выпадающий список Find in и выбрал Main Document. Получилось вот что:

Обращаем внимание на детали.

1. Документ промотался туда, где искомая подстрока встречается впервые, но это неважно.

2. Искомая подстрока (а также безразличные нам в данном контексте символы перевода строки/конца абзаца) выделена.

3. И, что самое главное, она выделена ВО ВСЁМ ДОКУМЕНТЕ. Можете промотать, не снимая выделения, и удостовериться.

И вот теперь можно щёлкнуть курсором в документе (отобрав таким образом фокус у открытого диалогового окна), пойти в меню Format, открыть диалог Change case и выбрать Sentence case, как и было обещано.

А теперь вернёмся к объяснению того, на кой чёрт я предыдущим шагом вносил эту самую пафосную разметку <UC>...</UC>.

Ну, во-первых, для наглядности (UpperCase, открывающие/закрывающие теги, умереть — не встать).

Во-вторых, для того, чтобы следующим шагом нам было за что зацепиться и точно таким же образом выделить ВО ВСЁМ ДОКУМЕНТЕ абзац с названием главы. Ненужную разметку мы потом, понятное дело, удалим.

Поехали. Формируем шаблон поиска и снова выбираем Main Document:

Обратите внимание на дополнительные символы обратной наклонной черты, которых тут вроде как быть не должно. Это — расплата за наглядность (в моём понимании), когда элементы внесённой разметки совпали со служебными символами, которые Word в режиме Use wildcards трактует по-своему, и для того, чтобы этот механизм не сработал, пришлось «подсечь» каждый такой символ обратной наклонной чертой, «заэскейпить» его, как иногда в припадке просторечия говорят серьёзные дяди-программисты.

Итак, жмём на Find in и выбираем Main Document. Далее Change case, UPPERCASE и... всё.

А теперь можно и избавиться от разметки.

Впрочем... а нужно ли? :) Попробуем вернуться к первой версии макроса и переделать его под эту самую разметку. Уж теперь-то всё должно быть просто!

Sub ChangeParagraphContainingString()
    Dim search As String
    search = "<UC>"
    ParCount = ActiveDocument.Paragraphs.Count
    Dim para As String
    For i = 1 To ParCount
        para = ActiveDocument.Paragraphs(i).Range.Text
        If InStr(para, search) Then
            MsgBox (">" + CStr(i) + "<")
        End If
    Next
End Sub

Ура! Работает. Дописываем:

Sub ChangeParagraphContainingString()
    Dim search As String
    search = "<UC>"
    ParCount = ActiveDocument.Paragraphs.Count
    Dim para As String
    For i = 1 To ParCount
        para = ActiveDocument.Paragraphs(i).Range.Text
        If InStr(para, search) Then
            For j = -1 To 1
               If i + j <= ActiveDocument.Paragraphs.Count AND i + j >= 1 Then
                  ActiveDocument.Paragraphs(i + j).KeepWithNext = True
               End If
            Next
        End If
    Next
End Sub

Условно говоря, задача решена, поскольку решением в данном случае считается любая последовательность действий, отличная от перелопачивания текста вручную. Дальше в один проход удаляем <UC>...</UC> за ненадобностью.

Продолжение и окончание истории

Через денёк захотелось мне окончательно разобраться со всей это бедой. Сперва был записан (именно «за», а не «на») макрос, добавляющий к многострадальным заголовкам довольно идиотскую разметку:

Sub AddSillyMarkup()
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^0013(CAP" & ChrW(205) & "TULO *)^0013(*)^0013(^0013)"
.Replacement.Text = "^0013*LKSC\1^0013*LKUC\2^0013*LK\3"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub

После его запуска заголовки приобретали следующий вид (почему разметка именно такая, видно из текста второго макроса, см. ниже):

Идея состояла в том, чтобы остаток работы доделать на автомате, сперва написав (на этот раз уже «на») ещё один макрос, который должен был переформатировать заголовки в соответствии с разметкой, а потом слепив два макроса вместе: скорее всего, во имя Аллаха, милостивого и милосердного.

Sub KillTheFreakingHeaders()
Dim para, s1, s2, s3, first, second As String
s1 = "*LK" 'Edit LeftIndent & KeepWithNext
s2 = "*LKSC" '...and convert string to Sentence Case
s3 = "*LKUC" '...and convert string to UPPERCASE
ParCount = ActiveDocument.Paragraphs.Count
For i = 1 To ParCount
para = ActiveDocument.Paragraphs(i).Range.Text
If InStr(para, s1) Then
If InStr(para, s2) Then
first = ActiveDocument.Paragraphs(i).Range.Text
first = Replace(first, s2, "")
first = LCase(first)
second = UCase(Left(first, 1)) + LCase(Right(first, Len(first) - 1))
first = second
ActiveDocument.Paragraphs(i).Range.Text = first
ElseIf InStr(para, s3) Then
first = UCase(ActiveDocument.Paragraphs(i).Range.Text)
ActiveDocument.Paragraphs(i).Range.Text = Replace(first, s3, "")
End If
ActiveDocument.Paragraphs(i).Range.Text = Replace(ActiveDocument.Paragraphs(i).Range.Text, s1, "")
ActiveDocument.Paragraphs(i).LeftIndent = CentimetersToPoints(0)
ActiveDocument.Paragraphs(i).KeepWithNext = True
End If
Next
End Sub

И оно даже как-то написалось и даже справилось с тестовым файлом, но во время натурных испытаний так дико заглючило, что я плюнул и доделал всё в полуавтоматическом режиме, что оказалось не так уж кошмарно. Сейчас распишу по пунктам.

Полуавтоматический режим

1  Запускаем макрос AddSillyMarkup(), добавляя к заголовкам показанную выше разметку.

2. Включаем Use wildcards, очищаем строку замены, в строке поиска пишем «\*LKSC*^0013», жмём на Find in | Main Document. Строка с номером главы и завершающий её символ конца абзаца выделяются по всему документу.

3. НИЧЕГО БОЛЬШЕ НЕ ТРОГАЯ, переключаемся на закладку Replace. В строке поиска оставляем «\*LKSC», строку замены оставляем пустой. Жмём Replace all и потом OK. Ненужная разметка из строки ушла.

4. После нажатия на OK фокус из окна замены ушёл в документ, поэтому сразу жмём Format | Change Сase | Sentence case. Теперь Format | Paragraph, переключаемся на закладку Line and Page Breaks и делаем так, чтобы галочка напротив Keep with next была включена.

5. Щёлкаем курсором в документе где-нибудь перед заголовком и возвращаемся к окну Find & Replace, закладка Find.

6. На этот раз в поле Find набираем «\*LKUC*^0013» и снова идём в Find in | Main Document. Название главы выделено, переходим на закладку Replace, вводим в строке поиска «\*LKUC», жмём Replace all, разметка ушла, снова Format | Сhange Case, выбираем UPPERCASE, Keep with next включать уже не надо, со второй строкой заголовка всё.

7. Возвращаемся в Replace и удаляем по всему документу подстроку «\*LK». Зачем она вообще нужна была? А фиг её знает, говорю же — SillyMarkup (на самом деле у неё было некое предназначения, см. второй макрос).

8. Сохраняем готовый документ. Небольшой недостаток метода в том, что флаг KeepWithNext почему-то образуется у всех абзацев, входящих в заголовок, ВКЛЮЧАЯ ПРЕДШЕСТВУЮЩИЙ ЕМУ :) Ну, это Майкрософт. Понять нельзя, можно только запомнить.

Пока всё. Понимаю, что выглядит дико, но уж вот таким извилистым путём шла моя мысль. Ладно, чему-то новому так или иначе научился.

Если у кого-то из настоящих программистов есть свои соображения, пожалуйте в почту.

Новости раздела

8 мая 2018 г.
Интересный пример использования функции Find & Replace для форматирования заголовков

Ещё на сайте

Библиотека
Языки
Друзья
Канада
Авторский угол

Интернет

CPAN
Citforum
W3C.org
useit.com
Типомания
Code Charts
ру/ководство
Лаборатория dk
WebReference.com
Спецификация Perl
Заметки HTML-кодера
Анатомия Adobe Photoshop
The Apache Software Foundation


Рейтинг@Mail.ru

wordpress statistics

Рейтинг@Mail.ru