Приключения вайбкодера в Ворде, или Как две нейронки не осилили простенькую задачку - QubStore

Приключения вайбкодера в Ворде, или Как две нейронки не осилили простенькую задачку

Содержание статьи

Проблема

Мой коллега, физик-теоретик, попросил меня помочь с оформлением своих научных отчетов и статей. Пишет он эти документы в обычном Ворде с использованием программы под названием MathType — это, по-простоиму, такой визуальный LaTeX для написания уравнений, штука достаточно удобная, а главное — она сама и номера уравнений вставляет и чертовски здорово оперирует с ними, даже если их переносишь из одного документа в другой.

Проблема возникла, когда он начал использовать нейронку (Copilot) для “вычистки” своих статей: если статья написана так, что и нейронка ее хорошо понимает, значит, есть шанс, что и дураки белковые собратья поймут. Увы — нейронки не очень понимают этот самый MathType. Приходится переводить формулы в обычный текстовый LaTeX (MathType это делает легко и просто, одним кликом во всем документе), потом скармливать сей нечитаемый (для человека) текст нейронке, что-то с ним делать, а потом снова конвертировать формулы в MathType. Он это тоже умеет, но… только по одной формуле за раз; почему — неведомо. А что, если этих формул там больше тыщи? При том, что мой коллега тот еще любитель каллиграфии и даже выражения типа “икс в квадрате” набирает через MathType, это та еще заморочка…

То бишь, надо было автоматизировать процедуру конверсии математических формул во всем вордовском документе из LaTeX’a в MathType.

Первые пробы

Что до меня — в Экселе я работаю много и по-разному, это прекрасный инструмент для прототипирования и проверки мат. Моделей (в том числе и весьма сложных) на работоспособность, с готовым визуальным интерфейсом, в котором можно посмотреть, в принципе, все, что угодно, плюс замечательная возможность правки кода прямо во время выполнения, не сильно теряя при этом в скорости счета. Но Ворд — это зверь малость другой породы: там тоже VBA, но малость другой. Примерно как микролитражка и автобус — это, по сути, две “легковушки”, но вождение чуток разное.

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

Понятно, чтобы не мучиться, решил прибегнуть к помощи нейронки. Использовал две: Microsoft Copilot, у которого под капотом OpenAI (безлимит, агент встроен прямо в Ворд), и Google Gemini (из браузера, бесплатный). УВы, ни тот, ни другой (и ни третий — который доступен через стандартный агентский интерфейс) не могут сами влезть в Ворд и смастерить макруху — то ли не хватает им привилегий, то ли мне не хватило знаний, как запустить их внутрь вордовского VBA. Пришлось консультироваться, так сказать, дистанционно.

Начал, понятное дело, с Copilot — вроде как он свой, майкрософтовский, должен знать офисный софт от и до. Может быть, даже и знает… Но MathType — это все же не родная офисная тулза, а внешняя библиотека. Агент про нее вполне себе осведомлен, но напрямую с ней работать не может. Первое, что он мне сказал, — что ему было бы проще, если бы формулы были не в MathType-е, а в родном офисовском формате. И даже набросал макрос, который этим занимается.

Макрос, правда, оказался нерабочим. Но худо-бедно я смог его починить. Для начала было неплохо. Но я все же настаивал на MathType… как потом выяснилось, зря я это делал, но — задним умом все сильны. В итоге, после нескольких итераций, мы с ним даже сделали макрос, который таки сам переводил LaTeX в MathType. Вот только работал он… ммм… не всегда. Контрольную задачку “икс равно минус а плюс-минус корень из бэ-квадрат минус четыре а цэ пополам” он в итоге решил, но только формулы у коллеги были малость посложнее — и с ними он справиться почему-то не мог…

Я обратился к Гуглу. Говорю, так и так, есть такая задача, нужно сделать вот это. Он сказал — нет проблем, и выдал макруху. Естественно, нерабочую. После серии “переговоров”, в которых он каждый раз предлагал изменение, которое обязательно должно сработать (и подробно объяснял, почему), но на самом деле почему-то не работало, я на это плюнул — и начал ваять код руками…

Ручная работа

К моему удивлению (после всех этих мытарств), решение оказалось и в самом деле очень простым. Надо было только три вещи:

  1. Точно (и, главное, грамотно) отыскать все латексные формулы в документе.

  2. Преобразовать каждую формулу в родное офисное уравнение.

  3. Из офисного формата преобразовать формулы в MathType. 

Вторую из этих операций мы с агентом уже освоили в ходе наших мучений, а третья — делается в один клик самим MathType.

Оставалось, таким образом, сделать только первый пункт — искалку для латексных формул в тексте вордовского документа. После пары часов проб и ошибок, с задачей я справился. См. процедуру wd_ConvertAllLatex2EqObjects в коде ниже. Только, пожалуйста, не бросайте в меня гнилыми помидорами за качество кода… Хоть эта процедура и не вайбленая, но вылизывать ее у меня не было ни времени, ни желания. Она работает, в том числе и на тыще с лишним формул разной степени мерзости сложности, а больше мне от нее ничего не надо, продавать ее никому не собираюсь.

Снова к нейронке

Оставалось сделать последний шажок: встроить процедурину непосредственно в вордовский интерфейс, чтобы в код не ходить. И здесь, надо отдать должное Гуглу, он если не с самой первой, то со второй попытки предложил решение, которое меня уже в принципе устроило: макрос на автозапуск файла, и после этого в контекстном меню появляется дополнительная строчка, напрямую вызывающая макрос. Еще немного игр с бубном, и этот макрос заработал в любом открытом документе. Заняло это от силы полчаса. После отработки макроса документ, в принципе, можно использовать и так — офисные формулы от маттайповских на вид неотличимы. А если хочется все-таки в MathType — делается это одним кликом.

Всё. Задача решена.

Итог

Код привожу ниже. Опять же, частично он навайбленный, частично мой, но написан на коленке. Уверен, что ему далеко до оптимальности, и спецы по автоматизации ворда легко сделали бы его нормальным, и, скорее всего, и само решение было бы совсем не таким. Но, повторюсь, это не коммерческий продукт, не корпоративный, и даже не пет-проект — конкретная “мулька” для конкретного человека. Если кому-то она тоже пригодится — буду рад.

Что надо сделать:

  1. Открыть в ворде пустой документ.

  2. Перейти в редактор кода по Alt+F11. 

  3. Вставить в код документа VBA-модуль. Записать в него первый фрагмент кода.

  4. В код ThisDocument вставить второй фрагмент.

  5. Файл записать с расширением .dotm — шаблон с макросами.

  6. Сохраненный файл скопировать в фолдер, в котором хранятся файлы самого ворда (это выяснить очень легко и многими путями).

  7. Открыть документ с латексными формулами.

  8. Нажать правую кнопку и из контекстного меню выбрать образовавшийся нижний пункт.

Возможно, не у всех заработает, с виндой это часто бывает, прошу прощения заранее.

Вот первый фрагмент кода:

Скрытый текстOption Explicit DefLng I-N, Q DefSng X DefStr S DefDbl D DefBool B #If VBA7 Then ‘ 64-bit Windows API Declarations for Clipboard Management Private Declare PtrSafe Function OpenClipboard Lib «user32» (ByVal hWnd As LongPtr) As Long Private Declare PtrSafe Function CloseClipboard Lib «user32» () As Long Private Declare PtrSafe Function EmptyClipboard Lib «user32» () As Long Private Declare PtrSafe Function SetClipboardData Lib «user32» (ByVal wFormat As Long, ByVal hMem As LongPtr) As LongPtr Private Declare PtrSafe Function GlobalAlloc Lib «kernel32» (ByVal wFlags As Long, ByVal dwBytes As LongPtr) As LongPtr Private Declare PtrSafe Function GlobalLock Lib «kernel32» (ByVal hMem As LongPtr) As LongPtr Private Declare PtrSafe Function GlobalUnlock Lib «kernel32» (ByVal hMem As LongPtr) As Long Private Declare PtrSafe Sub MoveMemory Lib «kernel32» Alias «RtlMoveMemory» (ByVal Destination As LongPtr, ByVal Source As String, ByVal Length As LongPtr) #Else ‘ 32-bit Legacy Declarations (for backwards compatibility) Private Declare Function OpenClipboard Lib «user32» (ByVal hwnd As Long) As Long Private Declare Function CloseClipboard Lib «user32» () As Long Private Declare Function EmptyClipboard Lib «user32» () As Long Private Declare Function SetClipboardData Lib «user32» (ByVal wFormat As Long, ByVal hMem As Long) As Long Private Declare Function GlobalAlloc Lib «kernel32» (ByVal wFlags As Long, ByVal dwBytes As Long) As Long Private Declare Function GlobalLock Lib «kernel32» (ByVal hMem As Long) As Long Private Declare Function GlobalUnlock Lib «kernel32» (ByVal hMem As Long) As Long Private Declare Sub MoveMemory Lib «kernel32» Alias «RtlMoveMemory» (ByVal Destination As Long, ByVal Source As String, ByVal Length As Long) #End If Private Const GMEM_MOVEABLE As Long = &H2 Private Const CF_TEXT As Long = 1 ‘ ============================================================================== ‘ MAIN AUTOMATION ENTRY POINT ‘ ============================================================================== Public Sub wd_ConvertAllLatex2EqObjects() Dim Doc As Document Dim Sel As Selection Dim Prg As Word.Paragraph Dim sEq As String, sWd As String Dim sStart, sEnd Dim iPstart, iPend, iEqCountInline, iEqCountCentered, iPrg Dim bStarted, bEnded iPstart = 0 Set Doc = ActiveDocument Set Sel = Selection bStarted = False bEnded = False sEnd = «» ‘====================== ‘ Inline equations ‘====================== sStart = «$» sEnd = «$» Sel.HomeKey wdStory iEqCountInline = 0 Do Do ‘ Find starting delimiter SetSelectionFind sStart, bWildcards:=False If Not Sel.Find.Execute() Then Exit Do End If Set Prg = Sel.Paragraphs(1) iPstart = Sel.Start ‘ Find ending delimiter Sel.MoveRight wdCharacter SetSelectionFind sEnd, bWildcards:=False If Not Sel.Find.Execute() Then Exit Do End If If Sel.End > Prg.Range.End Then ‘ second delimiter out of paragraph Sel.EndKey wdLine Exit Do End If iPend = Sel.End ‘ Convert content inside delimiters Doc.Range(iPstart, iPend).Select ConvertSelectedLaTeXToOfficeEquation sStart, sEnd With Sel .MoveRight wdCharacter End With iEqCountInline = iEqCountInline + 1 Loop If Sel.Paragraphs(1).Range.End >= Doc.Range.End — 1 Then Exit Do End If Sel.EndKey wdLine Sel.MoveDown wdParagraph Loop ‘====================== ‘ Centered equations ‘====================== sStart = «[» sEnd = «]» Sel.HomeKey wdStory iEqCountCentered = 0 Do ‘ Find starting delimiter SetSelectionFind sStart, bWildcards:=False If Not Sel.Find.Execute() Then Exit Do End If iPstart = Sel.Start ‘ Find ending delimiter Sel.MoveRight wdCharacter SetSelectionFind sEnd, bWildcards:=False If Not Sel.Find.Execute() Then Exit Do End If iPend = Sel.End ‘ Convert content inside delimiters Doc.Range(iPstart, iPend).Select ConvertSelectedLaTeXToOfficeEquation sStart, sEnd With Sel .MoveRight wdCharacter .Delete wdCharacter .HomeKey wdLine .MoveLeft wdCharacter .TypeText Text:=vbTab .ParagraphFormat.Alignment = wdAlignParagraphJustify ‘wdAlignParagraphCenter .HomeKey wdLine .MoveLeft wdCharacter .Delete .MoveLeft wdCharacter .Delete .MoveDown wdParagraph End With iEqCountCentered = iEqCountCentered + 1 Loop ‘ Finish MsgBox CStr(iEqCountCentered) & » right-numbered and » _ & CStr(iEqCountInline) & » inline equation(s) converted» pExit: Set Sel = Nothing Set Doc = Nothing Set Prg = Nothing End Sub ‘ ============================================================================== ‘ PRIVATE HELPER ROUTINES & API INTERFACES ‘ ============================================================================== Private Sub ConvertSelectedLaTeXToOfficeEquation _ (Optional Start As String = «[«, Optional sEnd As String = «]») Dim latexString As String Dim originalRange As Range Dim insertRange As Range If Selection.Range.Start = Selection.Range.End Then MsgBox «Please select a LaTeX expression.», vbExclamation Exit Sub End If Set originalRange = Selection.Range.Duplicate latexString = Trim$(originalRange.Text) ‘ Remove paragraph or table-cell terminators. If Right$(latexString, 2) = Chr$(13) & Chr$(7) Then latexString = Left$(latexString, Len(latexString) — 2) ElseIf Right$(latexString, 1) = Chr$(13) Then latexString = Left$(latexString, Len(latexString) — 1) End If ‘ Add display-math delimiters only when they are absent. If Left$(latexString, Len(Start)) <> Start Or Right$(latexString, Len(sEnd)) <> sEnd Then latexString = Start & latexString & sEnd End If CopyToClipboardAPI latexString Wait Selection.Paste End Sub Private Sub CopyToClipboardAPI(ByVal textToCopy As String) Dim hGlobalMemory As LongPtr Dim lpGlobalMemory As LongPtr ‘ Allocate moveable memory for the string hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, Len(textToCopy) + 1) If hGlobalMemory = 0 Then Exit Sub ‘ Lock the block and copy string into it lpGlobalMemory = GlobalLock(hGlobalMemory) If lpGlobalMemory <> 0 Then MoveMemory lpGlobalMemory, textToCopy, Len(textToCopy) + 1 GlobalUnlock hGlobalMemory ‘ Open clipboard and pass the data handle If OpenClipboard(0) <> 0 Then EmptyClipboard SetClipboardData CF_TEXT, hGlobalMemory CloseClipboard End If End If End Sub Private Sub Wait(Optional xTimeSec = 0.1) Dim pauseTime As Single pauseTime = Timer() + xTimeSec ‘ Midnight rollover protection checking if timer overflows past 24 hours If pauseTime >= 86400 Then pauseTime = pauseTime — 86400 Do While Timer() < pauseTime DoEvents Loop End Sub Private Sub SetSelectionFind( _ sFind As String, _ Optional sReplace As String = «», _ Optional bStop As Boolean = True, _ Optional bForward As Boolean = True, _ Optional bWholeWord As Boolean = False, _ Optional bWildcards As Boolean = True _ ) With Selection.Find .ClearFormatting .Text = sFind .Replacement.Text = sReplace .Forward = bForward If bStop Then .Wrap = wdFindStop Else .Wrap = wdFindContinue End If .Format = False .MatchByte = False .MatchWildcards = bWildcards .MatchAllWordForms = False .MatchWholeWord = bWholeWord End With End Sub

А вот второй фрагмент:

Скрытый текстOption Explicit ‘ ============================================================================== ‘ GLOBAL AUTOEXEC ADD-IN RIBBON INITIALIZATION ‘ ============================================================================== Public Sub Autoexec() Dim contextMenu As CommandBar Dim customButton As CommandBarButton Set contextMenu = Application.CommandBars(«Text») On Error Resume Next contextMenu.Controls(«LaTeX -> Equations»).Delete On Error GoTo 0 Set customButton = contextMenu.Controls.Add(Type:=msoControlButton, Temporary:=True) With customButton .Caption = «LaTeX -> Equations» .OnAction = «wd_ConvertAllLatex2EqObjects» .FaceId = 353 End With End Sub

Так в чем же была сложность?

Если по-честному — проблема была, как водится, в постановке исходной задачи: одним кликом перевести все формулы в документе из латекса в MathType. Ибо кликов для этого понадобилось не один, а два. А еще надо было понять, как разделить задачу на части (да-да, задним умом все сильны!), ну и, конечно, написать ту самую простенькую искалку на сотню VBA-шных строк — задача, думаю, и нейронке тоже посильная… если ее правильно попросить.

Увы, сделать этот переход — решить задачу не в один клик, а в два, — нейронке, даже двум, оказалось не по силам. Для этого понадобилась “мясная прокладка” в виде вашего покорного слуги.

К сожалению, модифицировать постановку задачи, исходя из особенностей реальной ситуации (для моего коллеги большой разницы между одним и двумя-тремя кликами нет никакой), — это то, в чем наши кремниевые коллеги пока что не сильно справляются. И это, думаю, то, что неплохо понимать всем, кто сталкивается с разнообразными вайбкодными проблемами: если решение не получается найти — значит, весьма вероятно, что-то не то в постановке задачи. А это — то, что нейронки пока что, увы, сами обнаружить не в состоянии.

 Всем успехов — с вайбкодингом и без!

Ссылка на основную публикацию