Формирование и регистрация стандартных документов Word с помощью Excel
Формирование шаблона документа
Введение
Несколько лет назад мне пришлось сотрудничать с юридическим бюро, специализирующимся на регистрации предприятий. Существенной частью работы юристов было составление учредительных документов. Для регистрации нового предприятия составлялся целый пакет документов: учредительный договор, устав, заявление, протоколы и справки. Пакеты документов несколько отличались разнообразием, в зависимости от организационной структуры (акционерное общество, паевое товарищество), количества участников (один или несколько), формирования уставного капитала (денежные или не денежные взносы). Для формирования документов использовался WinWord под управлением Windows 3.x. Сначала каждый документ сохранялся в виде отдельного файла с замысловатым именем, включающим название фирмы и вид документа, причем все это «утрамбовывалось» в восемь символов — стандарт именования DOS. За год накапливалось несколько тысяч таких файлов. Естественно, найти нужный файл было очень трудно, хотя такая потребность возникала довольно часто — в связи с необходимостью внесения изменений или заимствования.
Чтобы как-то облегчить работу юристов, я предложил им сформировать несколько шаблонов, содержащих целые пакеты документов, разделенных на секции. В шаблонах появились оглавления. В начале шаблонов помещались таблицы, в которых были сведены все закладки (bookmarks), используемые в секциях в качестве перекрестных ссылок. Работа существенно упростилась, но проблемы с поиском файлов остались. Кроме того, ввод данных в пространство между квадратных скобок, задающих поля закладок, и обновление документа требовали определенного навыка и применения разных хитростей вроде «нерасширяемых пробелов».
Второй раз с проблемами формирования юридических документов я столкнулся недавно, при реализации проекта класса Time Billing System — системы учета затрат времени специалистов (юристов), связанных с клиентскими договорами обслуживания. Одной из задач была возможность как генерировать стандартные документы, так и учитывать время работы с ними. На этот раз в моем распоряжении были Microsoft Office и VBA, а документы готовились с помощью Microsoft Word.
В результате этих работ и появились пути решения, предлагаемые вниманию читателя.
Формирование шаблона документа
Задача формирования стандартных документов возникает достаточно часто. Обычно для этой цели используются шаблоны, включающие поля подстановки. Но если документ не может быть представлен в виде бланка с привычными элементами управления, имеющимися на панели инструментов «Формы», задача существенно усложняется.
Рассмотрим решение данной задачи на примере составления упрощенного учредительного договора с переменным числом участников. Допустим, первоначальный шаблон этого документа выглядит как на рис. 1. Для придания примеру большей достоверности пришлось реализовать особенности известной пишущей машинки с отсутствующей литерой «Е».
Переменная информация для данного шаблона выделена на рисунке цветом. Особенность документа в том, что количество участников договора может меняться. Следовательно, при подготовке шаблона не обойтись без полей { IF }.
Первая проблема, возникающая у разработчика, состоит в выборе объектов для хранения переменных значений текста. В качестве основного кандидата обычно рассматривается коллекция Bookmarks (закладки). Именно эти объекты фигурируют в большинстве примеров и в качестве аргументов полей подстановки. Для вставки закладки требуется указать место в документе или использовать выделенный фрагмент текста. Это не всегда удобно, особенно если документ объемный. Разбросанные по всему тексту закладки легко пропустить при визуальном просмотре. А если значение закладки «пусто», ее можно даже ошибочно удалить.
Другой кандидат — коллекция Variables (переменные). Объекты этого типа вообще не видны в тексте документа, что позволяет строить различные шаблоны на одном множестве переменных, не заботясь о возможных потерях. Встроенных средств создания и редактирования этих объектов, к сожалению, не существует, и разработчику документа приходится решать эту проблему дополнительно, путем программирования на VBA диалоговых форм для доступа к переменным.
Следует отметить, что и закладки, и переменные должны отвечать общим правилам именования VBA: имена объектов не должны состоять из цифр, содержать пробелов и специальных символов.
Всех этих недостатков лишена коллекция CustomDocumentProperties (Свойства документа). Так же как и закладки, свойства легко используются в полях. Так же как и переменные, свойства не видны в тексте. Кроме того, существует стандартный инструмент редактирования свойств — это команда: «Файл. Свойства», вкладка «Прочие». В именах свойств допустимы пробелы. Более того, свойству можно присвоить определенный тип: текст, дата, число или булев.
Для того чтобы автоматизировать утомительную процедуру превращения исходного документа в шаблон с вынесением переменных значений в коллекцию свойств, воспользуемся макросом, представленным ниже. Алгоритм его вполне логичен. Пользователь выделяет в исходном тексте фрагмент, который должен стать полем постановки, и запускает макрос. В диалоговом окне уточняется имя создаваемого свойства, а затем все вхождения выделенного текста заменяются полем DOCPROPERTY.
Sub SetProperties() Dim strName As String Dim strVal As String Dim strFld As String Dim objDp As DocumentProperties Dim blnFound As Boolean Dim objTest As Object Dim intAns As Integer strName = Trim(Selection.Text) strVal = strName strName = InputBox(“Value = “ & strVal & Chr(13) & Chr(13) _ & “Enter Name, Please.”, “Set Property”, strName) If strName <> Empty Then blnFound = False For Each objTest In ActiveDocument.CustomDocumentProperties If objTest.Name = strName Then blnFound = True Next If blnFound Then intAns = MsgBox(“Name Already Exist”, vbCritical) Exit Sub End If Set objDp = ActiveDocument.CustomDocumentProperties objDp.Add Name:=strName, LinkToContent:=False, _ Type:=msoPropertyTypeString, Value:=strVal strFld = “DOCPROPERTY “ & “””” & strName & “””” With ActiveDocument.Content.Find .ClearFormatting Do While .Execute(FindText:=strVal, Forward:=True, _ Format:=True) = True With .Parent .Select intAns = MsgBox(“Replace “ & “””” & Selection _ & “””” & Chr(13) & “ With “ _ & strFld, vbYesNo + vbQuestion) If intAns = vbYes Then Selection.Fields.Add Range:=Selection.Range, _ Type:=wdFieldEmpty, Text:= _ strFld, PreserveFormatting:=False End If .StartOf Unit:=wdWord, Extend:=wdMove .Move Unit:=wdWord, Count:=1 End With Loop End With Else Exit Sub End If End Sub
На рис. 2, 3 показаны фрагменты работы этого макроса.
Однако на этом работа над шаблоном не заканчивается. Осталась нерешенной проблема списков переменной длины. В нашем примере это количество участников договора. Здесь придется поработать вручную. Предварительно используем маленькую хитрость. Добавим к списку свойств два элемента. Первое свойство назовем «количество участников», а второе «CR». Свойству CR присвоим значение CHR(13). Это нам пригодится для размещения с новой строки имени каждого участника.
Теперь список, состоящий максимально из трех участников, можно «запрограммировать» следующим образом:
{ IF { DOCPROPERTY “количество участников” } > “0” _ “{ DOCPROPERTY “участник 1” }{ DOCPROPERTY “CR” }” } _ { IF { DOCPROPERTY “количество участников” } > “1” _ “{ DOCPROPERTY “участник 2” }{ DOCPROPERTY “CR” }” } _ { IF { DOCPROPERTY “количество участников” } > “2” _ “{ DOCPROPERTY “участник 3” }{ DOCPROPERTY “CR” }” }
Поскольку свойство CR, вызывающее перевод строки, вставляется только при выполнении необходимого условия, полученный в результате текст не будет иметь ни одного «лишнего» символа. Если значение свойства «количество участников» меньше единицы, символы в тексте вообще не появятся.
С помощью аналогичного приема можно легко сформировать даже места для переменного числа подписей внизу документа. Работа формул для разного количества участников показана на рис. 4, 5.
На этом проектирование шаблона можно считать завешенным — получен полноценный объект, задавая свойства которого можно существенно видоизменять его внешнее представление.
Форма для шаблона
Чтобы получить законченную конструкцию, потребуется разработка формы для ввода переменных значений свойств документа. Для реализации формы воспользуемся рабочей книгой Excel. Форма на листах Excel, по сравнению с экранными формами VBA, обладает целым рядом преимуществ. Во-первых, это простота реализации, позволяющая полностью автоматизировать процесс проектирования, во-вторых — удобство ввода дат и числовых значений, в-третьих — практически неограниченные размеры.
Следует отметить, что в большинстве случаев приходится проектировать две формы. Одна из них предназначена для быстрого ввода исходных данных, а вторая, скрытая от пользователя, выполняет преобразования исходных данных к рабочему формату свойств текстового документа. Примерами необходимости такого преобразования могут служить ввод дат и сумм в виде чисел, а также подстановка их в текстовый документ в виде текстовых выражений. Помимо этого примерами являются разного рода вычисления, выполняемые в промежуточной форме, или формирование сокращенной записи имени и отчества людей. При обычном подходе, использующем экранные формы VBA, все эти преобразования пришлось бы реализовывать в виде программного кода. Гораздо проще применить обычные формулы в ячейках Excel.
Выбрав для формы рабочий лист Excel, можно, не задумываясь о дизайне, изобразить ее в виде таблицы, состоящей из двух колонок. Слева расположить названия параметров, а справа зарезервировать места для значений. Вспомним, что выбор в качестве носителей переменных значений DocumentProperties позволяет использовать имена с пробелами. Это означает, что на долю нашей фантазии относительно того, как заполнить левый столбец формы, практически ничего не остается. Поэтому сгенерируем форму автоматически, записав в левый столбец имена всех свойств из подготовленного ранее шаблона документа.
Public Sub GetPropertiesFromWord(strPath As String) Dim objWord As New Word.Application Dim objWordDoc As Word.Document Dim strName As String Dim strVal As String Dim objProp As Object ActiveCell.Select With objWord .Visible = True .WindowState = wdWindowStateMaximize Set objWordDoc = .Documents.Open(strPath) End With For Each objProp In objWordDoc.CustomDocumentProperties If objProp.Type = msoPropertyTypeString Then strName = objProp.Name strVal = objProp.Value Selection.Range(“A1”).Value = strName Selection.Range(“B1”).Value = strVal Selection.Range(“A2”).Select End If Next objWordDoc.Application.Quit End Sub
Теперь можно поработать над форматированием. В случае необходимости придется сделать промежуточную форму, а главное — предусмотреть для пользователя командные кнопки. Первая и самая простая в реализации кнопка — «Печать». Макрос, соответствующий этой кнопке, очевиден. Необходимо создать в Word новый документ на основании заданного шаблона, присвоить свойствам этого документа значения из формы, обновить поля в документе и, наконец, напечатать его. Ниже приведена соответствующая программа.
Public Sub PrintWordDocument(strPath As String, blnNew As Boolean) Dim objWord As New Word.Application Dim objWordDoc As Word.Document Dim strName As String Dim strVal As String Dim varVal As Variant Dim objTbl As Range Dim objCell As Range Dim strInput As String Dim intCopies As Integer intCopies = 1 strInput = InputBox(“Enter Number Of Copies, Please.”, _ “Printing”, intCopies) If strInput <> Empty Then intCopies = Val(strInput) If intCopies < 1 Then intCopies = 1 Else Exit Sub End If With objWord .Visible = True .WindowState = wdWindowStateMaximize If blnNew Then Set objWordDoc = .Documents.Add(Template:= _ strPath, NewTemplate:=False) Else Set objWordDoc = .Documents.Open(strPath) End If End With Set objTbl = ActiveCell.CurrentRegion objTbl.Offset(0, 0).Resize(objTbl.Rows.Count, 1).Select For Each objCell In Selection strName = objCell.Value varVal = objCell.Offset(0, 1).Value strVal = varVal objWordDoc.CustomDocumentProperties(strName).Value = strVal Next objWordDoc.Fields.Update objWordDoc.PrintOut Background:=False, Copies:=intCopies objWordDoc.Close wdDoNotSaveChanges objWord.Quit End Sub
Следует отметить, что для работы приведенных выше процедур необходимо в Excel VBA-проекте подключить библиотеку Microsoft Word Object Library. Для этого требуется поставить «галочку» против названия библиотеки в окне доступных ссылок (меню «Сервис», команда «Ссылки»).
Регистрация документов
Итак, мы получили конструкцию «форма + документ», часто используемую на практике. Однако эта конструкция достаточно громоздка, поскольку в ней задействованы два приложения. Напомним, что целью разработки является не только формирование, но и регистрация документов — важнейший элемент правильного делопроизводства. Обычно регистрация событий производится в журнале. Проще всего реализовать журнал в виде списка Excel, тем более что для этого имеется подходящая книга.
Сформировать список очень просто. Для строки заголовков нужно скопировать левую колонку формы, которая содержит наименования свойств, и вставить их в строку заголовков списка путем транспонирования. После печати экземпляра документа в «тело» списка достаточно транспонировать правую колонку формы. Полученную конструкцию можно разнообразить макросами SaveToList и ExtractFromList, обеспечивающими обмен информацией между формой и списком. Приводить примеры этих макросов автор считает нецелесообразным.
Несмотря на законченность, построенная система документов далека от идеальной. Критически настроенный пользователь может заметить, что хорошо бы было предусмотреть не только возможность печати стандартного документа, но и его модификацию (при необходимости). И будет прав. Если уж в процессе работы открывается Word, то почему бы не остановиться и не продемонстрировать навыки машинописи. Однако в этом случае вся наша работа с журналом регистрации рушится. Оставив пользователя наедине с Word, мы не только теряем момент регистрации (после печати), но и ставим под сомнение достоверность параметров, использованных при формировании документа. Ведь никто не может гарантировать, что при ручной правке пользователь не изменил до неузнаваемости содержание документа. Картину дополняют возможности Word одновременно открывать и редактировать несколько документов и печатать все подряд.
Вероятно, идеальное решение в условиях такой неопределенности найти невозможно, однако, оговорив некоторые требования к пользователю, можно попытаться решить поставленную задачу. Прежде всего следует четко определить событие, означающее конец работы с документом, за которым следует регистрация. Выбор тут небольшой. Из трех событий, связанных с объектом Word.Document, для наших целей подходит событие Close, тем более что даже при завершении работы Word последовательно закрывает все открытые документы.
Сложнее определить, сумел ли пользователь изменить стандартный документ до неузнаваемости. Будем рассуждать следующим образом. Если пользователь внес в документ существенные изменения, то он, с целью регистрации исходящих документов, обязан сохранить «нестандартную» копию. Если же он этого не сделал, то доказать, что документ, зарегистрированный как стандартный, был модифицирован вручную, невозможно.
Для того чтобы определить факт сохранения документа, воспользуемся особенностью процедуры именования новых документов Word. Каждый, кто работал с Word, знает, что вновь созданный документ получает имя «ДокументN» (в русской редакции), где N — последовательный номер нового документа в текущем сеансе работы. При сохранении документа это имя обязательно изменяется. Даже если пользователь задумает сохранить имя в первоначальном виде «ДокументN», то при записи на диск к этому имени добавится полный путь. Таким образом, факт изменения имени при создании и закрытии документа может использоваться в качестве индикатора сохранения.
Кроме того, если документ был сохранен, то отпадает необходимость регистрации параметров, использованных для его создания. Достаточно зафиксировать гиперссылку на сохраненный документ. При необходимости документ можно будет воспроизвести, воспользовавшись оригиналом.
Для реализации указанного подхода прежде всего необходимо создать модуль класса WithEvents.
‘ Класс WordDocWithEvents Public WithEvents wdDoc As Word.Document Private Sub wdDoc_Close() gstrDocNewName = gobjWordDocWithEv.wdDoc.FullName Set gobjWordDocWithEv = Nothing ‘ Освобождаем ссылку на документ. If gstrDocNewName <> gstrDocOldName Then FixCloseName End Sub
После того как класс создан, можно написать и основную процедуру, запускающую Word и создающую новый документ. Здесь следует обратить внимание на следующее. Прежде всего в проекте используются глобальные переменные, поскольку после открытия Word до наступления события Close никакие процедуры не выполняются. В отличие о процедуры печати экземпляр приложения Word открывается только при необходимости. Если в момент запуска макроса Word уже был открыт, новый документ просто добавляется к его коллекции. Передача параметров и обновление полей выполнятся так же, как и в процедуре печати, однако после этого происходит инициализация нового объекта WordDocWithEvents. При этом используется позднее связывание объектов. Это несколько неудобно при программировании, поскольку интеллектуальная поддержка объектов недоступна. Но зато конструкция при этом работает надежно.
Option Explicit Public gobjWord As Object ‘ Переменная для сохранения _ ссылки на Microsoft Word. Public gobjWordDocWithEv As Object ‘ Переменная для сохранения _ ссылки на Microsoft Word _ Document With Events. Public gblnWordWasNotRunning As Boolean ‘ Флаг для выхода из _ приложения. Public gstrDocOldName As String ‘ Имя документа при открытии Public gstrDocNewName As String ‘ Имя документа при закрытии Public Sub CreateWord(strPath As String, blnNew As Boolean) Dim objWordDoc As Word.Document Dim strName As String Dim strVal As String Dim varVal As Variant Dim objTbl As Range Dim objCell As Range ‘ Проверка, выполняется ли Microsoft Word. On Error Resume Next ‘ Отложенный перехват ошибок. ‘ Функция GetObject, вызванная без указания первого аргумента, ‘ возвращает ссылку на экземпляр приложения. Если это приложение ‘ не запущено, возвращается ошибка. Set gobjWord = GetObject(, “Word.Application”) gblnWordWasNotRunning = (Err.Number <> 0) Err.Clear ‘ Очищаем объект Err на случай ошибки. If gblnWordWasNotRunning Then Set gobjWord = New Word.Application With gobjWord .Visible = True .WindowState = wdWindowStateMaximize If blnNew Then Set objWordDoc = .Documents.Add(Template:= _ strPath, NewTemplate:=False) Else Set objWordDoc = .Documents.Open(strPath) End If gstrDocOldName = objWordDoc.FullName objWordDoc.Application.Activate End With Set objTbl = ActiveCell.CurrentRegion objTbl.Offset(0, 0).Resize(objTbl.Rows.Count, 1).Select For Each objCell In Selection strName = objCell.Value varVal = objCell.Offset(0, 1).Value strVal = varVal objWordDoc.CustomDocumentProperties(strName).Value = strVal Next objWordDoc.Fields.Update ActiveCell.Select Set gobjWordDocWithEv = New WordDocWithEvents Set gobjWordDocWithEv.wdDoc = objWordDoc End Sub
Необходимо отметить еще одно важное обстоятельство. Выбор для передачи параметров коллекции DocumentProperties и внешней по отношению к Word вызывающей среды позволяет на одном множестве переменных построить несколько шаблонов, связанных одинаковым предметным содержанием. Так, для формирования целого пакета учредительных документов предприятия, включающего учредительный договор, устав, заявление и разного рода справки, достаточно единственной пары «форма + список». Естественно, в форме потребуется еще один элемент управления, например ComboBox, для выбора названия используемого шаблона. Примерный вид реальной формы и списка показаны на рис. 6, 7. Обратите внимание на гиперссылки, выделенные синим цветом на рис. 7. Они свидетельствуют о том, что документы были несколько видоизменены по сравнению с шаблоном. Для открытия этих документов в Word достаточно щелкнуть мышью.
Заключение
Представленные выше решения дают ощутимый эффект в работе предприятий, связанных с массовым производством юридических документов. Это прежде всего нотариальные конторы или фирмы, специализирующиеся на регистрации предприятий. Помимо этого в подобных документах даты и суммы обычно изображаются прописью, что легко реализовать в промежуточной форме Excel. Нотариальные документы легко снабдить записями о взимаемой плате, присовокупив соответствующую квитанцию.
Другой пример применения — это создание системы учета договоров предприятия. В этом случае форма может содержать только реквизиты партнеров по договору. А все виды договоров, имеющие различные шаблоны, будут формироваться из одной формы и регистрироваться в едином списке со сквозной нумерацией. В этом случае, конечно, каждый договор будет «дописываться» вручную и сохраняться, зато поиск документов по списку с гиперссылками станет тривиальной задачей.
Хочется предостеречь и от чрезмерного увлечения подобными конструкциями. Несмотря на кажущуюся простоту, настройка системы документов, состоящей из шаблона, формы и списка, реализованных в разных приложениях, — задача, требующая определенной квалификации. Поэтому не следует использовать описанную технологию для формирования платежных поручений, счетов и прочих достаточно примитивных документов. Для этого существуют более простые решения.
КомпьютерПресс 10'2000