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