ESPN DeportesReal Madrid: Fans protestan contra Tebas y Caso NegreiraESPNRanking the top 10 Super Bowl performances by a rookieRTP DesportoLyon de Paulo Fonseca vence e sobe a primeiro em comando repartido por cincoThe Jerusalem PostVienna flight from Tel Aviv diverted to Bulgaria over bomb threatInquirerPCSO lotto results Oct. 18: No winner for ₱410.97-M jackpotDaily MaverickUS resumes intelligence-sharing with Ukraine after pause, Zelenskiy saysZDF heuteEntdecken Sie das ZDF-NachrichtenstudioFootball ItaliaWhat Juventus fans did after 0-3 home loss to Lazio, and why away supporters didn’t boycottObservador DesportoAs notícias das 22hInteriaPremier Albanii odmawia ustąpienia. "Chcą tego, czy członkostwa w UE?"الشرقكينيا تسجل إصابة جديدة بإيبولا وترفع الحصيلة إلى 8 حالاتSBS 뉴스추미애 사과에도 파장 계속…김민석 '공연 중단' 사과
The Daily Newsstand · Free, Always
Sunday, October 18, 2026

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

Translate

Мой коллега, физик-теоретик, попросил меня помочь с оформлением своих научных отчетов и статей. Пишет он эти документы в обычном Ворде с использованием программы под названием 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-шных строк - задача, думаю, и нейронке тоже посильная… если ее правильно попросить.

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

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

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.