Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_6311.html
 

Разработка руководства пользователя в MS Word с VBA

Опубликовано 03.09.2014 г. 18:13

Давно собирался написать эту статью. Напрямую она правда не будет связана с Delphi, но тем не менее любой разработчик рано или поздно выпускает документацию (руководство пользователя) к своим продуктам.

Наш рабочий продукт несколько специфичен.  Во-первых это расчетное ПО, которое поставляется каждому отдельному клиенту с различными численными показателями статистики. Во-вторых, есть в зависимости от пожеланий и финансовых возможностей клиента в проекте может быть различное число расчетных (функциональных) блоков и различная степень детализации результатов (вернее не степень, а добавляются различные разрезы показателей). Клиентов у нас не много, и это долгое время позволяло нам вполне спокойно подправлять руководство в MS Word при необходимости и генерировали PDF вариант клиенту.

Но в целом этот подход стал затратным и не удобным. Еще в январе месяце я в G+ спрашивал, кто чем пользуется, и тогда в голову пришла идея оставить всю документацию как и было в MS Word, но формировать конечный результат в PDF c помощью VBA скриптов.

Итак, что требовалось: для каждого конкретного клиента генератор руководства пользователя должен был подгрузить на нужные места соответствующие скриншоты, а также в зависимости от конфигурации модулей скрыть из конечной документации разделы, параграфы, пункты и т.п. Общие скриншоты, конечно, вставлялись в текст сразу.

Комплект нужных скриншотов складывался в отдельную директорию каждого клиента. Имена файлов для каждого конкретного скриншота у разных клиентов были естественно одинаковые. Например, первый скриншот формы расчета численности занятых в экономике назывался WorkersMain_Common. Далее в самом тексте MS Word документа вставлялись элементы управления "Рисунок" (вкладка Разработчик-Элементы управления - Рисунок). Каждый такой элемент управления имеет два поля - "Название" и "Тег". В поле "Название" указывалось имя соответствующего файла скриншотов.

 

Этот подход решал вопрос загрузки нужных изображений. А вот вопрос с текстом (разделами) решался с помощью другого элемента управления - "Форматированный текст". Этот элемент также имел те же Название и Тег. Но в данном случае использовался именно тег. Например, если какой то модуль не всегда присутствует в справке, то весь раздел заключался в этот форматированный текст и помечался тегом, например, USE_MODULE_XXX.  И соответственно, если при генерации документации в конфигураторе для данного клиента подобная настройка отсутствовала, то фрагмент исключался целиком. Фрагменты подобные были разной величины, иногда целые разделы по модулю, иногда отдельные абзацы, либо пункты в списке, либо порой даже части предложений. Внутри таких фрагментов естественно могли быть и элементы управления рисунками, то есть допускается вложенность.

После того как мы обрабатываем все эти элементы управления и получаем почти готовый документ, нам необходимо также обновить автособираемое содержание документа. Так что вся генерация PDF справки проходит в 3 этапа. Написание кода на VBA было каким то мучением, ибо в конце каждой строки всегда хотелось поставить точку с запятой и т.п. (: Да и в целом это был первый опыт написания чего либо на VBA, поэтому вопросов было много (:

Общая процедура обработки справки имеет следующий вид:

Public Sub ProcessUserManual(si As SubjectInfo)
    CurrentSubject = si
    SubjectId = si.id
    ImagePath = ThisDocument.Path & "\Images\" & si.id & "\"
    NotFoundImage = ThisDocument.Path & "\Images\ImageNotFound.png"
        
    Dim fso As FileSystemObject
    Dim target_file As String
    Set fso = CreateObject("Scripting.FileSystemObject")
    target_file = ThisDocument.Path & "\compile\" & si.id & ".docm"
    
    Call fso.CopyFile(ThisDocument.FullName, target_file, True)
   
    Dim td As Document
    Set td = Documents.Open(target_file, Visible:=False)
        Call ProcessModules(td)
        Call ProcessNamedImages(td)
        Call td.TablesOfContents(1).Update
    On Error GoTo CloseTargetDoc
       
    Dim pdf As String
    pdf = Replace(target_file, ".docm", ".pdf")
       
    Call td.ExportAsFixedFormat(pdf, wdExportFormatPDF)
    
CloseTargetDoc:
    Call td.Close(wdSaveChanges)
    
End Sub

Здесь  на вход подается структура с информацией о клиенте, из которой нас волнует только его ID, чтобы сформировать путь до папки скриншотов. Далее в директории \Compile\ формируется копия текущего файла документации под именем ClientID.docm. После чего этот документ открывается (но не показывается на экран) и происходит последовательный вызов трех функций: обработка блоков текста (скрытие модулей и т.п.), подгрузка нужных изображений, и обновление содержания (td.TablesOfContents(1).Update). Ну и в конце - экспорт документа в PDF формат (td.ExportAsFixedFormat(pdf, wdExportFormatPDF)).

Сами элементы управления содержимым называются ContentControls, и почитать об их использовании можно где-нить здесь: http://msdn.microsoft.com/en-us/library/office/ff192425%28v=office.14%29.aspx

Обработка текстовых контролов выполняется достаточно легко. У нас имеется несколько тегов, которые нам необходимо либо удалить, либо оставить. Поэтому последовательно находятся все ContentControl'ы с нужными тегами и они просто удаляются из текста:

Private Sub ProcessModules(d As Document)
    Dim ExcludeTags() As String
    Dim tags As String
    Dim mt As Variant
    
    Dim ccs As ContentControls
    Dim c As ContentControl
            
    If Not CurrentSubject.UseBTR Then Call ExcludeElements(tags, "USE_BTR")
    If Not CurrentSubject.UseCities Then Call ExcludeElements(tags, "USE_CITIES")
    If Not CurrentSubject.UseGz Then Call ExcludeElements(tags, "USE_GZ")
    If Not CurrentSubject.UseSpecs Then Call ExcludeElements(tags, "USE_SPEC")
    
    ExcludeTags() = Split(tags, ",")
    For Each mt In ExcludeTags
        Set ccs = d.SelectContentControlsByTag(mt)
        For Each c In ccs
            If c.Type = wdContentControlRichText Then
                c.Delete (True)
            End If
        Next c
    Next mt
End Sub

 

А для загрузки нужных скриншотов теперь необходимо перебрать все элементы, имеющие тип - изображение. Получить у элемента "Название", и загрузить в него соответствующую этому названию картинку из нужной директории. Если картинка не найдена, то подгружается заглушка "not found", которую сложно будет потом просмотреть в конечном документе :)

Private Sub ProcessNamedImages(d As Document)
    Dim c As ContentControl
    Dim fn As String
    
    
    For Each c In d.ContentControls
        If c.Type = wdContentControlPicture Then
            If c.title <> Empty Then
                fn = ImagePath & c.title & ".png"
                
                If Len(Dir(fn)) = 0 Then
                    fn = NotFoundImage
                End If
                    
                Call c.Range.InlineShapes(1).Delete
                c.Range.InlineShapes.AddPicture (fn)
            End If
        End If
    Next
End Sub

 

Замечу, что в документе таки в элементы управления изначально вставлены картинки, чтобы было понятно о чем писать в документации. То есть сначала удаляется имеющаяся картинка, а потом подгружается нужная.

В общем и целом такое весьма простое решение вполне удовлетворило нашим текущим потребностям. Справку тоже целиком переписали, так что стало гораздо лучше, на мой взгляд, чем было. Генерировать скриншоты для каждого клиента вручную, конечно, не очень удобно. Но возможно и этот процесс как-нибудь автоматизируется в будущем.

PS: блог последние пол года что-то не пополняется статьями. Работа над основным проектом на работе скажем там встала, и занимался написанием большого числа парсеров на Delphi. А там особо писать не о чем, хотя встречались забавные баги и сложности. Дома что-то потребностей в программировании тоже куда то пропали, но с выходом XE7, который состоялся на днях, надеюсь появятся и новые статьи.

Метки:  VBA 

Комментарии

Нет комментариев
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно