Тверской государственный университет
Опубликован: 13.09.2006 | Доступ: свободный | Студентов: 2719 / 260 | Оценка: 4.03 / 3.74 | Длительность: 19:48:00
Специальности: Программист, Менеджер
Лекция 2:

Программная работа с документами Word

Общий план решения задачи

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

  • Составление списка персон (фамилия, имя, отчество) и предъявление этого списка пользователю.
  • Составление списка персон, выбранных пользователем.
  • Для каждой выбранной персоны получение информации, требуемой для контакта в Outlook и создание очередного контакта. Одновременно с этим создание события, связанного с контактом.

И я начну с того, что приведу описание глобальных переменных, которые будут использоваться в процессе решения задачи. Эти переменные описаны в модуле Tool, в который и будут помещены все основные процедуры, требуемые для решения задачи.

Option Explicit
'Объект Outlook и его компоненты
Public myOl As Outlook.Application, olNameSpace As NameSpace
'Коллекция избранных личностей
Public CollectionOfPersons As New Collection
'Коллекция номеров абзацев, задающих начало записей
Public Numbers As New Collection
Public Con As New Collection
'Определение типа - записи, характеризующей личность
Public Type Person
	FirstName As String
	LastName As String
	MiddleName As String
	Post As String
	DOB As Date
	Address As String
	Tel As String
	Email As String
	Fax As String
	Other As String
End Type
Листинг 2.27.

Нам потребовалось описать объект Outlook и связанный с ним объект NameSpace, задающий пространство имен. Описание этих объектов необходимо для обеспечения взаимодействия двух приложений, с тем, чтобы мы могли в проекте документа Word работать с объектами Outlook. Коллекция CollectionOfPersons содержит фамилии персон, входящих в справочник. Две следующие коллекции Numbers и Con носят вспомогательный характер, но важный для общего понимания алгоритма решения. Первая из них содержит номера абзацев, начинающих описание персоны, вторая - порядковые номера персон, отобранных пользователем. Создание этих коллекций позволяет избежать лишних просмотров полного текста документа и получать доступ непосредственно к нужному абзацу. Наконец, в разделе общих объявлений дано определение пользовательского типа Person, формально описывающего запись и поля этой записи, которые будут заполняться в процессе анализа записанной информации, а затем будут переноситься в поля контакта Outlook.

Приведу теперь текст основной процедуры модуля Tool, с вызова которой и начинается решение нашей задачи:

Public Sub WTOOL()
'Процедура преобразует справочник персоналий
'в базу данных Контакты приложения Outlook

'Формирование списка персоналий и выбор пользователя
Call FormList
'Создание записей для избранных
Call SelectPerson
End Sub
Листинг 2.28.

Она, как это и должно быть для основных процедур, достаточно проста, чтобы можно было понять ее действие. Кроме комментариев она содержит вызовы двух процедур, решающих задачи первого и третьего этапа в соответствии с нашим планом. А как же быть со вторым этапом, спросите Вы. Конечно же, и он не забыт. Просто вызываемая на первом этапе процедура FormList заканчивает свою работу вызовом формы с созданным списком персоналий, с которым и будет работать пользователь. В ответ на его выбор будет вызываться обработчик соответствующего события, в котором и будет решена задача второго этапа. После чего продолжит свою работу процедура WTOOL, вызвав процедуру SelectPerson. Эта процедура и выполняет, по существу, главную работу, анализируя содержимое соответствующих фрагментов текстового документа, создавая запись и преобразуя ее содержимое в контакт приложения Outlook.

Создание списка персоналий

Для представления списка персоналий я спроектировал, как обычно, форму, в которую поместил элемент управления типа ListBox и командную кнопку. Элементы списка ListBox будут отображать фамилии персон справочника, а обработчик события, возникающего при нажатии командной кнопки, будет вызывать процедуру, выполняющую нужные действия в ответ на выбор пользователя. О том, как проектировать подобную форму, как заполнять элементы списка ListBox, говорилось уже неоднократно, в том числе и в данной лекции. Поэтому основные объяснения будут связаны с тем, как из текстового документа выделить фрагменты, соответствующие записи информации об очередной персоне, как выделить фамилию персоны, отображаемую в списке. Рассмотрением этих вопросов сейчас и займемся. Начну с текста процедуры FormList, решающей поставленную задачу:

Public Sub FormList()
'Эта процедура формирует список личностей
Dim par As Paragraph, parStyle As String
Dim i As Integer, n As Integer
With ActiveDocument
parStyle = .Paragraphs(1).Style
n = ActiveDocument.Paragraphs.Count
i = 1
For Each par In .Paragraphs
 If par.Style = parStyle Then
 'Добавить элемент в список и номер абзаца в коллекцию
	frmPersons.lstPersons.AddItem par.Range.Text
	Numbers.Add i
 End If
 i = i + 1
Next par
End With
frmPersons.Show
End Sub
Листинг 2.29.

Как видите, процедура, полностью решающая задачу, получилась довольно короткая и простая. Все это, конечно, благодаря тем возможностям, которые предоставляет Word для этих целей. В цикле (конечно же, For Each ) по всем абзацам текста получаем очередной абзац - объект par. Далее существенно используется тот факт, что информация о каждой персоне начинается с нового абзаца, имеющего специальный стиль, в данном случае, задаваемый переменной parStyle, характерный только для таких абзацев. Используется также и тот факт, что этот абзац содержит только фамилию, имя и отчество персоны.

Затем создаются элементы списка формы. Нетрудно догадаться, что frmPersons - это имя формы, lstPersons - имя списка (элемента управления ListBox ) этой формы. Хочу обратить внимание, на то, что по ходу дела создается коллекция Numbers с номерами абзацев, начинающих информацию об очередной персоне. Эта информация получается почти бесплатно, но в дальнейшем существенно сократит время работы, позволив лишний раз не проходить по всему документу. Последний оператор этой процедуры показывает на экране форму с заполненным списком фамилий персон справочника. Вот как выглядит эта форма в процессе работы с ней:

Форма, содержащая список персон справочника "Кто есть кто"

Рис. 2.4. Форма, содержащая список персон справочника "Кто есть кто"

Создание коллекции "избранных" персон

При нажатии командной кнопки "Выбери нас" создается список, содержащий фамилии персон, выбранных пользователем для занесения информации о них в папку "Контакты". Вот текст вызываемых процедур:

Private Sub cmdSelectPerson_Click()
Dim intLoop As Integer, intSelect As Integer
Dim strSelect As String
Dim ВыборСделан As Boolean
Dim Num As Integer
ВыборСделан = False
intLoop = 0
intSelect = 0
'Поиск выделенных элементов
Do
 If frmPersons.lstPersons.Selected(intLoop) Then
	'Найден очередной элемент
	strSelect = frmPersons.lstPersons.List(intLoop)
	Num = intLoop + 1
	ВыборСделан = True
	intSelect = intSelect + 1
	CollectionOfPersons.Add strSelect
	Con.Add Num
 End If
 intLoop = intLoop + 1
Loop Until intLoop = frmPersons.lstPersons.ListCount
If ВыборСделан Then
	Unload Me
	Set myOl = CreateObject("Outlook.Application")
Else
	MsgBox ("Выбор не сделан")
End If
'Печать коллекции
'For Each pers In CollectionOfPersons
 '	Debug.Print pers
'Next pers
End Sub
Листинг 2.30.

Здесь, как бычно, в цикле по всем персонам анализируется множественный выбор пользователя, и фамилии выбранных персон добавляются в коллекцию CollectionOfPersons. Одновременно, добавляются элементы в коллекцию Con, позволяя запомнить порядковые номера выбранных персон в списке. Процедура анализирует, сделал ли пользователь свой выбор, и, если таковой сделан, то после заполнения коллекций форма закрывается. При желании можно включить отладочную печать элементов коллекции CollectionOfPersons. Обратите внимание, в конце работы этой процедуры я создаю объект Outlook, подготавливая почву для следующего этапа работы, когда потребуется создание контактов. Теперь все готово для продолжения работы процедуры WTOOL, вызывающей процедуру SelectPerson, которая выполняет основной и завершающий этап работы.

Создание записи Person

Нам предстоит теперь разобраться с более сложными вопросами. В той части, которая связана с обработкой текстового документа, предстоит понять, как выделить из текста нужную информацию о персоне, для того чтобы создать формальный объект (переменную) созданного нами ранее пользовательского типа Person, и заполнить поля этого объекта. Другая часть работы связана с созданием объекта Outlook - элемента папки Contacts. Давайте посмотрим, как все это можно реализовать. Вот текст процедуры SelectPerson:

Public Sub SelectPerson()

 ' Выделение записи
Dim par As Paragraph, CountPar As Integer
Dim ibeg As Integer, ifin As Integer, nPerson As Integer
Dim PersonRange As Range
Dim i As Integer, Num As Variant
With ActiveDocument
Set PersonRange = .Paragraphs(1).Range
i = 0
'Цикл по записям, отобранных пользователем
For Each Num In Con
	i = i + 1
	'Выделение области документа, занятой записью
	'Номер абзаца, начинающего запись
	ibeg = Numbers(Num)
	'Номер абзаца, заканчивающего запись
	ifin = Numbers(Con(i) + 1) - 1
	PersonRange.Start = .Paragraphs(ibeg).Range.Start
	PersonRange.End = .Paragraphs(ifin).Range.End
	'Выделение записи
	PersonRange.Select
	'Обработать запись - объект Selection
	Call WorkWithSelected
 Next
End With
 myOl.Quit

End Sub
Листинг 2.31.

Внешний цикл организован по элементам коллекции Con, элементов у которой ровно столько, сколько персон выделил пользователь для преобразования информации о них в контакты папки Outlook. Первая возникающая задача для каждого элемента этого списка состоит в том, чтобы выделить область текстового документа, в которой записана информация о соответствующей персоне. Чтобы задать эту область - объект Range, достаточно знать параметры Start и End, определяющие местоположение начала и конца области. Вот как можно их определить. Я напомню, что текущий элемент Num коллекции Con задает порядковый номер персоны в списке, тогда по определению коллекции Number номер первого абзаца будет задаваться выражением Number (Num). Номер последнего абзаца записи можно определить, как номер первого абзаца следующей записи, уменьшенный на единицу. Этим алгоритмом я и пользуюсь в процедуре. Заметьте, если не принять дополнительных мер предосторожности, то он приведет к ошибке, когда выбрана последняя запись справочника. Чтобы избежать этого, я использовал стратегию, называемую введением "барьера", добавив в справочник специальную служебную запись (барьер) "Конец записей". Эта запись информирует пользователя об окончании списка персон и, естественно, никогда не будет входить в его выбор. Тем самым удается достаточно просто получить объект Range, задающий фрагмент текстового документа, описывающий информацию о нужной персоне. Выделение этой области задает объект Selection, с которым продолжает работу вызываемая процедура WorkWithSelected. Прежде, чем обсуждать ее работу, приведу ее текст:

Public Sub WorkWithSelected()
'Обработка с выбранной и отмеченной записью
Dim pers As Person, pars As Paragraphs, par As Paragraph
Dim i As Integer, n As Integer
Dim myR As Range
Dim FirstWord As String
Set pars = Selection.Paragraphs
With pers
	'Обработка первого абзаца - фамилии
	Set par = pars(1)
	Set myR = par.Range
	.FirstName = myR.Words(2).Text
	.MiddleName = myR.Words(3).Text
	.LastName = myR.Words(1).Text
	'Обработка должности - следующего непустого абзаца
	Set par = pars(2)
	If par.Range.Words.Count = 1 Then Set par = pars(3)
	Set myR = par.Range
	n = myR.Words.Count
	myR.End = par.Range.Words(n - 1).End
	.Post = myR.Text
	'Обработка оставшихся абзацев
	For Each par In pars
		Set myR = par.Range
		n = myR.Words.Count
		FirstWord = myR.Words(1).Text
		Select Case FirstWord
		 Case "Родился ", "Родилась "
			.DOB = SelectDate(par.Range)
		 Case "Тел"
			myR.Start = par.Range.Words(3).Start
			myR.End = par.Range.Words(n - 1).End
			.Tel = myR.Text
		 Case "Факс"
			myR.Start = par.Range.Words(3).Start
			myR.End = par.Range.Words(n - 1).End
			.Fax = myR.Text
		 Case "Адрес"
			myR.Start = par.Range.Words(3).Start
			myR.End = par.Range.Words(n - 1).End
			.Address = myR.Text
		 Case "e"
			myR.Start = par.Range.Words(5).Start
			myR.End = par.Range.Words(n - 1).End
			.Email = myR.Text
		 Case Else
		 .Other = .Other + myR.Text
		End Select
	Next par
	If Not IsDate(.DOB) Then .DOB = "1 января "
 'Debug.Print Selection.Range.Text
 'Debug.Print .FIO, .Post, .Address, .DOB, .Tel, .Fax, .Email, .Other
End With
'Запись создана - теперь создается контакт в Outlook
Call WriteToContact(pers)
End Sub
Листинг 2.32.

В этой процедуре заполняются поля переменной pers пользовательского типа Person. При этом существенно используются принятые соглашения о структуре справочника. Так по предположению первый абзац содержит только информацию о фамилии, имени и отчестве персоны, так что анализ первых трех слов этого абзаца позволяет заполнить поля LastName, FirstName и SecondName записи pers. Следующий абзац, который может следовать сразу за первым или быть отделенным пустым абзацем, содержит информацию о должности персоны. Запоминается весь текст этого абзаца, который и определяет описание должности персоны. Заметьте техническую деталь, при формировании данного поля из коллекции Words, задающей слова абзаца, удаляется последнее слово, в котором записан символ конца абзаца. Это же правило применяется и при работе с другими полями. При заполнении других полей не предполагается жесткий порядок их следования в тексте документа. Распознавание идет по ключевым словам, начинающим абзац, и программно осуществляется разбором случаев. Так заполнялись поля, определяющие телефон, факс, адрес и другие, подобные им. Все абзацы, не содержащие заданных ключевых слов, составляли поле Other. Пожалуй, наибольшую трудность вызывает распознавание даты рождения персоны. Дело в том, что для записи даты используются различные форматы, сокращения и прочие особенности. Более того, некоторые из персон, в особенности женщины, предпочитали не указывать год рождения, а мужчины не считали необходимым указывать число и месяц рождения. Чтобы справиться, хотя бы частично, с возникающими проблемами, я написал отдельную процедуру, занимающуюся разбором даты рождения. Вот ее текст:

Public Function SelectDate(ran As Range) As String
Dim Dat As String
With ran
If .Words(3) = "февраля " Then .Words(3) = "фев "
Dat = .Words(2) & .Words(3) & .Words(4)
If IsDate(Dat) Then
	SelectDate = Dat
 Else
 Dat = .Words(2) & .Words(3)
 If IsDate(Dat) Then
	SelectDate = Dat
 Else
	Dat = "1 января " & .Words(3)
	If IsDate(Dat) Then
	SelectDate = Dat
 Else
	Dat = "1 января "
	End If
 End If
End If

End With
End Function
Листинг 2.33.

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

?IsDate("13 февраля 1961")
False
?IsDate("13 фев. 1961")
True
Листинг 2.34.

В этой процедуре я не старался исправить все возможные ошибки, скорее я проверял корректность той или иной комбинации, используя для проверки функцию IsData, возвращающую истину, когда ее аргумент является правильной датой с точки зрения Office 2000. Если же установить дату рождения не удавалось, то в качестве даты принималась некоторая условная дата (1 января текущего года), что позволяло позже при работе с контактом понимать, что точная дата рождения контакта не известна.

По завершении формирования записи pers эта запись в качестве аргумента передавалась при вызове процедуры WriteContact, в которой и реализована работа с объектами Outlook.