Андрей Колесов

Для чтения этой статьи желательны некоторые познания в разработке COM-объектов и языке Visual Basic. Полный вариант рассмотренного здесь программного проекта (файл AndyTest.ZIP, 16 Кбайт) можно найти на сайте http://www.bytemag.ru. Для работы с исходными кодами понадобится инструментарий Visual Basic версии 5.0 или старше и библиотека Microsoft Smart Tags 1.0 Type Library, а для тестирования - Word 2002.

В предыдущей статье серии ("Smart Tags в офисных приложениях Microsoft", "BYTE/Россия" №10) мы начали изучать новую технологию "интеллектуальной обработки текстов" Smart Tags, впервые реализованную в Office XP. Мы посмотрели, что она представляет собой с точки зрения пользователя, какие готовые средства есть в составе пакета, вкратце рассмотрели принципы программного управления смарт-тегами в среде VBA (в приложениях Word 2002 и Excel 2002) и создание простейших обработчиков смарт-тегов в виде XML-описателей.

Хотелось бы сразу подчеркнуть, что мы показали только некоторые основные моменты разработки XML-распознавателей - инструментарий предоставляет целый ряд дополнительных возможностей, связанных в основном с динамическим обновлением списков терминов, в том числе с применением ссылок на удаленные Web-ресурсы. Тем не менее понятно, что функциональность XML-обработчиков (создание которых по силам любому пользователю, имеющему под рукой простейший редактор типа NotePad) весьма ограниченна. Процесс распознавания слов там сводится к тривиальному сравнению со списками терминов, а реализуемые действия - к заданию строки "Address" при обращении к Internet Explorer (ссылка на Web-ресурс, инициализация написания нового письма и т. п.).

Более широкие функциональные возможности предоставляет второй вариант реализации обработчиков смарт-тегов - в виде библиотек ActiveX DLL. Именно его мы и рассмотрим в данной статье. Хотелось бы подчеркнуть, что на примере создания DLL-распознавателей очень хорошо видна внутренняя логика работы самой технологии Smart Tags.

Терминология и логика работы

Прежде чем перейти к разработке DLL-обработчиков смарт-тегов, нужно определиться с терминологией. Это очень важно, так как сейчас, на начальном этапе освоения технологии (и при не очень точном переводе на русский язык английской исходной документации, которая тоже не самого лучшего качества), уже начинается определенная путаница в понятиях -- например, не всегда до конца ясно, что же такое собственно "смарт-тег".

В рассматриваемой технологии имеются два основных типа компонентов:

Обработчик смарт-тегов (его иногда - даже в документации SDK - называют собственно "смарт-тегом", что только запутывает ситуацию), реализуемый в виде XML-описателей или ActiveX DLL. Каждый обработчик характеризуется набором уникальных идентификаторов, которые почему-то в данном случае называются "типом" (чаще всего набор состоит из одного типа). Каждый обработчик имеет два функциональных блока - распознаватель (recognizer) и исполнитель действия (action). Первый выполняет анализ текста и привязку найденных фрагментов к данному распознавателю (точнее, к конкретному идентификатору распознавателя, типу смарт-тега). Второй выполняет операции по запросу пользователя (при выборе меню "Действия для смарт-тегов"), опять же устанавливая нужные связи с помощью идентификатора типа.

Смарт-тег. Это фрагмент текста документа (например, отдельное слово), распознанный одним из обработчиков, с присоединенной к нему (фрагменту) дополнительной метаинформацией. Обязательный элемент этой информации - идентификатор распознавателя (тип смарт-тега); собственно, именно это отличает смарт-тег от обычного текста. Кроме того, в метаданные может входить набор дополнительных свойств (об этом немного говорилось в предыдущей статье).

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

Поясним сказанное на примере. Для этого введем в документе Word 2002 фразу из двух слов:

Cough, Бейсик

В прошлый раз мы создали два XML-обработчика, в первом из которых (medical.xml) в списке терминов описано только слово "cough", а во втором (andy.xml) - оба слова. Соответственно, оба слова в документе будут выделены подчеркиванием, но за первым будут закреплены два распознавателя, т. е. фрагмент "cough" будет входить в состав двух разных смарт-тегов (рис. 1). Чтобы убедиться в этом, напишем и выполним такую макрокоманду:

Fig.1
Рис. 1. Один фрагмент текста может входить в состав двух разных смарт-тегов.

Sub SmartTagAllXML()
'
' Вывод XML-описаний всех смарт-тегов
' документа 
 Dim i%
 With ActiveDocument
    For i = 1 To .SmartTags.Count
      .ActiveWindow.Selection.TypeText _
         i & "  / " & _
        ActiveDocument.SmartTags(i).XML _
        & vbCrLf
    Next
End Sub

В результате мы получим распечатку:

1. <xml xmlns:st1=
     "urn:schemas-adatum-com:medical">
     <st1:Newcondition>Cough
     </st1:Newcondition>
   </xml>
2. <xml xmlns:st1=
     "urn:schemas-andy-com:basic">
     <st1:terms>Cough</st1:terms>
   </xml>
3. <xml xmlns:
     st1="urn:schemas-andy-com:basic">
     <st1:terms>Бейсик</st1:terms>
   </xml>

Но самое интересное нас ожидает далее. Запишем документ в режиме сохранения внедренных смарт-тегов, потом удалим файл Medical.XML, перезагрузим Word 2002 с этим же документом и запустим макрокоманду SmartTagAllXML. В распечатке мы увидим все те же три смарт-тега, хотя для первого из них обработчика уже не существует. Этот смарт-тег будет удален из документа только после того, как мы выполним обновление смарт-тегов с помощью кнопки "Повторная проверка" в диалоговом окне "Сервис | Автозамена | Смарт-теги".

Эта возможность сохранения смарт-тегов в документе (при его передаче с одного компьютера на другой, где могут быть совсем другие наборы распознавателей) представляется очень любопытной. В некоторых ее нюансах нужно еще разбираться, чтобы понять, какую практическую пользу может принести данное новшество. К этому вопросу мы еще вернемся в будущем, а пока начнем создавать обработчик смарт-тегов в виде ActiveX DLL.

Основные элементы интерфейса

Для создания DLL-обработчика нужна библиотека типов Microsoft Smart Tags 1.0, которая входит в состав Office XP. Она включает четыре интерфейса:

ISmartTagRecognizer - распознаватель, который связывает фрагменты текста с данным обработчиком. Эти фрагменты выделяются в документе как смарт-теги с помощью подчеркивания, при этом выводится кнопка "Действия для смарт-тегов".

ISmartTagAction - выполняет обработку смарт-тега. Обращение к этому интерфейсу происходит при нажатии кнопки "Действия для смарт-тегов" - сначала выдается меню с перечнем возможных команд обработки (рис. 1), а при выборе нужной команды выполняется сама операция.

ISmartTagRecognizerSite - обработка объекта Excel или Word, который показывает место фрагмента в документе, куда нужно добавить смарт-тег.

ISmartTagProperties - доступ к набору свойств смарт-тега, представленному парами ключей и значений.

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

Основные элементы программного интерфейса Smart Tags

Название элементов Тег-аналог XML-обработчика Описание
Интерфейс IsmartTagRecognizer
Desc (свойство)
<FL:description>
Описание распознавателя смарт-тегов
Name (свойство)
<FL:name>
диалогового окна "Сервис | Автозамена | Смарт-теги
ProgID (свойство) Нет аналога Уникальный идентификатор класса распознавателя. Используется для регистрации DLL
Recognize (метод) Нет аналога Операция распознавания фрагмента текста
SmartTagCount (свойство) Нет аналога Количество типов смарт-тегов в данном распознавателе (в XML-обработчике содержится только один тип)
SmartTagDownloadUR (свойство)
<FL:moreinfourl>
URL-ссылка на Web-ресурс, которая используется для выполнения дополнительной команды "Проверка новых действий
SmartTagName (свойство) Атрибут type тега
<FL:smartag>
Уникальные идентификаторы для каждого типа смарт-тегов в данном распознавателе. (Это то, что выше мы называли типом распознавателя для XML-обработчика, но сейчас их может быть несколько)
Интерфейс IsmartTagAction
Desc (свойство)
<FL:description>
Описание обработчика смарт-тегов
Name (свойство)
<FL:name>
Название обработчика. Это имя появляется в заголовке меню "Действия смарт-тегов
ProgID (свойство) Атрибут id тега
<FL:action>
Уникальный идентификатор класса обработчика
InvokeVerb (метод) Нет аналога Операция, которая выполняется при выборе команды меню "Действия смарт-тегов
SmartTagCount (свойство) Нет аналога Количество типов смарт-тегов, которые поддерживаются соответствующим распознавателем
SmartTagName (свойство) Атрибут Type тега
<FL:smartag>
Уникальные идентификаторы для каждого типа смарт-тегов в данном обработчике
VerbCaptionFromID (свойство)
<FL:action><FL:caption>
Название действия, которое прописывается в меню "Действия смарт-тегов
VerbCount (свойство) Считает автоматически Число операций обработки для данного типа смарт-тегов
VerbID (свойство) Нет аналога Свойство, которое возвращает уникальный ID (используемый внутри конкретного приложения) в пределах данного смарт-тега. Этот механизм реализован так, что DLL-обработчики могут смешивать разные виды обработки для различных типов смарт-тегов
VerbNameFromID (свойство) Нет аналога Свойство, которое возвращает имя (используемое внутри конкретного приложения) для представления данного вида обработки смарт-тегов. Например, для названия действия View Microsoft Web site это свойство вернет что-то вроде viewMSWebsite

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

Попробуем начать разработку реального DLL-обработчика - скорее всего, многое прояснится по ходу дела, хотя наверняка появятся и новые вопросы.

Создание DLL-обработчика

DLL-проект

В предыдущей статье, создавая свой XML-обработчик, мы обнаружили некоторые проблемы при обработке русских терминов (в частности, для различных склонений слов и разных регистров букв). Попробуем их решить с помощью DLL-обработчика.

Создать его можно с помощью любого инструмента, которые позволяет разрабатывать ActiveX DLL. Я буду использовать для этого Visual Basic 5.0 (при желании можно применить и VB 4.0). Для разработки потребуется также библиотека Microsoft Smart Tags 1.0 Type Library (MSTAG.TBL, объем 5 Кбайт), поставляемая в составе Office XP, в том числе в Standard Edition.

Для начала выполним следующие операции в среде VB:

  1. Создадим новый проект типа ActiveX DLL и назовем его AndySimpleTest.
  2. Установим ссылку на библиотеку Microsoft Smart Tags 1.0 Type Library (команда Project | References).
  3. Имя модуля класса Class1 (свойство Name) поменяем на SmartTagRecognizer и для подключения нужного программного интерфейса в разделе объявлений окна кода введем строку:
    Implements ISmartTagRecognizer
  4. Добавим к проекту еще один модуль класса (Project | Add Class Module), дадим ему имя SmartTagActionRecognizer и в разделе объявлений введем строку:
    Implements ISmartTagAction
  5. Добавим к проекту обычный BAS-модуль и назовем его PublicConst.
  6. Убедимся, что в окне Project | AndySimpleTest | Component установлен переключатель Project Compatibility.
  7. Сохраним проект.

Теперь приступим к программированию.

Определение глобальных констант

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

' В простейшем варианте у нас будет
' два типа смарт-тегов
Public Const SmartTagCountX As Long = 2

' Конкретное имя будет формироваться 
' путем прибавления к этой константе 
' уникального цифрового суффикса
Public Const SmartTagNameX As String = _
   "Andy-Simple-Test#SmartTags"

Программирование интерфейса распознавания

Начнем с интерфейса ISmartTagRecognizer, который реализуется в модуле класса SmartTagRecognizer. На рис. 2 хорошо видны процедуры, которые мы должны сформировать. Обратите внимание, что все свойства интерфейса используются в режиме "только для чтения".

Fig.2
Рис. 2. Все эти элементы интерфейса ISmartTagRecognizer нужно сформировать.

Сначала введем код для определения свойств ProgID, Name и Desc:

Private Property Get _
  ISmartTagRecognizer_ProgID() As String
  ' Идентификатор должен быть уникальным!
  ' Он должен точно совпадать 
  'с именем проекта и модуля класса!
  ISmartTagRecognizer_ProgID = _
    "AndySimpleTest.SmartTagRecognizer"
End Property

Private Property Get _
  ISmartTagRecognizer_Name  _
   (ByVal LocaleID As Long) As String
  ' Можем учитывать региональные 
  ' установки для выбора языка
  If LocaleID = 1049 Then ' Russian
    ISmartTagRecognizer_Name = _
      "Простой распознаватель смарт-тегов"
  Else
    ISmartTagRecognizer_Name = _
      "Simple Smart Tag Recognizer"
  End If
End Property

Private Property Get _
  ISmartTagRecognizer_Desc _
   (ByVal LocaleID As Long) As String
  ' Тут мы тоже можем учитывать региональные 
  ' установки, но сейчас не будем 
  ISmartTagRecognizer_Desc = _
   "Directs user to some resources"
End Property

В этом коде следует обратить внимание на следующие два момента. Во-первых, идентификатор ProgID должен быть не только уникальным, но и точно совпадать с именем проекта и модуля класса. С теоретической точки зрения эта конструкция не очень правильная, так как в ней заложена потенциальная опасность расхождения (из-за тривиальной опечатки при вводе или при коррекции имени проекта). Во-вторых, мы можем учитывать региональные установки конкретного компьютера для вывода служебных текстов на подходящем языке. Впрочем, не очень понятно, где задается LocaleId - то ли в системной установке OC, то ли еще где-то (например, Word может сам распознавать основной язык текста).

Запишем код для определения свойств SmartTagCount, SmartTagName и SmartTagDownLoadURL:

Private Property Get _
   ISmartTagRecognizer_SmartTagCount() _
     As Long
  ' Используем глобальную константу!
  ISmartTagRecognizer_SmartTagCount = _
    SmartTagCountX
End Property

Private Property Get _
  ISmartTagRecognizer_SmartTagName _
   (ByVal SmartTagID As Long) As String
  ' Формируем уникальное имя (тип) 
  ' смарт-тега с помощью глобальной 
  ' константы и порядкового номера 
  ' смарт-тега, например:
  '  "Andy-Simple-Test#SmartTags1"
  ISmartTagRecognizer_SmartTagName = _
    SmartTagName & SmartTagID
End Property

Private Property Get _
ISmartTagRecognizer_SmartTagDownloadURL _
   (ByVal SmartTagID As Long) As String
  ' Тут тоже можно задать разные ссылки 
  ' (за дополнительной информацией)
  '  для различных типов смарт-тегов
If SmartTagID = 1 Then
 ISmartTagRecognizer_SmartTagDownloadURL _
      = "http://visual.2000.ru/develop/"
Else
 ISmartTagRecognizer_SmartTagDownloadURL _
      = Null
End If
End Property

В этом коде нам пригодились глобальные константы, сформированные ранее. Здесь в качестве передаваемого параметра используется SmartTagID - он обозначает порядковый номер типа смарт-тега в данном обработчике и может изменяться от 1 до SmartTagCount. С его помощью формируется уникальное имя типа каждого смарт-тега, он же может использоваться для задания различных ссылок на дополнительную информацию.

Зададим список искомых терминов:

Dim MyTerms$(1 To 4)

Private Sub Class_Initialize()
  ' Формирование списка терминов
  MyTerms(1) = "фортран"
  MyTerms(2) = "fortran"
  MyTerms(3) = "си++"
  MyTerms(4) = "c++"
End Sub

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

4. Теперь напишем код для метода Reсognize (листинг 1). Изучим его внимательно, так как на его примере видна логика работы распознавателя смарт-тегов. Сначала разберемся с входными параметрами. Text - это текстовая строка с обрабатываемым фрагментом. LocateID - значение региональной установки (в данном случае мы ее не используем, но, наверное, она может пригодиться).

 

Листинг 1. Метод распознавания смарт-тегов в интерфейсе IsmartTagRecognizer

Private Sub _
  ISmartTagRecognizer_Recognize _
  (ByVal Text As String, _
  ByVal DataType As SmartTagLib.IF_TYPE, _
  ByVal LocaleID As Long, _
  ByVal RecognizerSite As _
  SmartTagLib.ISmartTagRecognizerSite)
  '
  ' Распознавание фрагмента текста
  ' Text - фрагмент текста
  ' DataType -- тип обрабатываемых данных
  
  Dim i As Integer, iConut As Integer
  Dim index As Integer
  Dim termlen As Integer
  Dim CurrentSmartTagName$
  Dim propbag As _
    SmartTagLib.ISmartTagProperties
  Text = LCase(Text)  ' убираем регистр
  '
  ' сравниваем для каждого распознавателя
    
  ' 1. Контекстный поиск заданных терминов
  CurrentSmartTagName$ = _
    ISmartTagRecognizer_SmartTagName(1)
  For i = LBound(MyTerms) _
      To UBound(MyTerms)
    termlen = Len(MyTerms(i))
    ' первый поиск
    index = InStr(Text, MyTerms(i))  
    While index > 0
      ' установка смарт-тега
      GoSub SetNewSmartTag  
      ' ищем дальше
      index = InStr(index + termlen, _
        Text, MyTerms(i))
    Wend
  Next i
    
  ' 2. Выборка первого термина в {}
  CurrentSmartTagName$ = _
    ISmartTagRecognizer_SmartTagName(2)
  index = InStr(Text, "{")
  If index > 0 Then
    termlen = InStr(index + 1, Text, "}")
    If termlen > 0 Then
      termlen = termlen - index + 1
      ' установка смарт-тега
      GoSub SetNewSmartTag  
    End If
  End If
Exit Sub
          
' формирование нового смарт-тега
SetNewSmartTag:  
  ' Запрашиваем у RecognizerSite 
  'объект для хранения свойств
  Set propbag = _ 
     RecognizerSite.GetNewPropertyBag
      ' подключаем смарт-тег к приложению
    RecognizerSite.CommitSmartTag _
      CurrentSmartTagName$, _
      index, termlen, propbag
  Return
End Sub

DataType - это числовой код, описывающий тип обрабатываемого текста, а точнее, указывающий, в какой момент ввода текста выполняется обращение к распознавателю:

  ' при вводе отдельного символа:
IF_TYPE_CHAR      =0x00000001  
  ' при вводе отдельного слова:
IF_TYPE_SINGLE_WD =0x00000002,
  ' после фильтрации текста по шаблону:
IF_TYPE_REGEXP    =0x00000004,
  ' при вводе абзаца:
IF_TYPE_PARA      =0x00000008,
  ' при вводе ячейки:
IF_TYPE_CELL      =0x00000010, 

В настоящее время задействованы только два последних режима, остальные зарезервированы на будущее. Очевидно, это даст возможность распознавателю самому определять, в каком режиме нужно обрабатывать текст. (Так написано в документации; мое тестирование показало, что обращение происходит после ввода каждого слова.)

RecognizerSite - это объект хост-приложения (Word, Excel), который выполняет нужные действия для внедрения смарт-тегов.

В методе Recognize мы реализовали два варианта анализа текста. В первом выполняется контекстный поиск заданных в массиве MyTerms терминов, а во втором -- в тексте ищется первый фрагмент, заключенный в фигурные скобки, например {Вася}.

Программирование интерфейса обработки ISmartTagAction

Код, определяющий свойства ProgID, Name, Desc, SmartTagCount, SmartTagName и SmartTagCaption, будет выглядеть примерно так же, как и для интерфейса ISmartTagRecognizer (листинг 2). Тут только стоит обратить внимание, что заголовок меню "Действия смарт-тегов" можно устанавливать в соответствии с типом смарт-тега.

 

Листинг 2. Код для определения статических свойств интерфейса IsmartTagAction

Private Property Get _ 
   ISmartTagAction_Desc _
   (ByVal LocaleID As Long) As String
  ISmartTagAction_Desc = _
    "Provides actions for some terms"
End Property

Private Property Get _
  ISmartTagAction_Name _
  (ByVal LocaleID As Long) As String
  If LocaleID = 1049 Then ' Russian
    ISmartTagRecognizer_Name = _
      "Простая обработка смарт-тегов"
  Else
    ISmartTagRecognizer_Name = _
      "Simple Smart Tag Action"
  End If
End Property

Private Property Get _
  ISmartTagAction_ProgId() As String
    ISmartTagAction_ProgId = _
      "AndySimpleTest.SmartTagAction"
End Property

Private Property Get _
  ISmartTagAction_SmartTagCaption _
   (ByVal SmartTagID As Long, _
   ByVal LocaleID As Long) As String
  ' То, что появится в заголовке меню:
  If SmartTagID = 1 Then
    If LocaleID = 1049 Then ' Russian
      ISmartTagAction_SmartTagCaption = _
        "Простая обработка терминов"
    Else
      ISmartTagAction_SmartTagCaption = _
        "Simple actions"
    End If
  Else
    ISmartTagAction_SmartTagCaption = _
      "Термин в фигурных скобках"
  End If
End Property

Private Property Get _
  ISmartTagAction_SmartTagCount() As Long
  ISmartTagAction_SmartTagCount = _
    SmartTagCountX
End Property

Private Property Get _
  ISmartTagAction_SmartTagName _
   (ByVal SmartTagID As Long) As String
   ISmartTagRecognizer_SmartTagName = _
     SmartTagNameX & SmartTagID
End Property

А вот с кодом для набора свойств, формирующих меню "Действия смарт-тегов" (листинг 3), нужно познакомиться внимательнее. Он выглядит довольно запутанно, но на самом деле разобраться можно.

 

Листинг 3. Формирование меню "Действия смарт-тегов"

Private Property Get _
  ISmartTagAction_VerbCount _
   (ByVal SmartTagName As String) As Long
  ' Сколько действий предлагается 
  ' для данного типа смарт-тега
  Select Case SmartTagName
    Case ISmartTagAction_SmartTagName(1)
      ISmartTagAction_VerbCount = 3
    Case ISmartTagAction_SmartTagName(2)
      ISmartTagAction_VerbCount = 2
  End Select
End Property

Private Property Get _
   ISmartTagAction_VerbID _
    (ByVal SmartTagName As String, _
    ByVal VerbIndex As Long) As Long
  ' Выбираем номер команды из общего
  ' списка команд
 If SmartTagName = _
   ISmartTagAction_SmartTagName(1) Then
    ' выбираются с первого номера
   ISmartTagAction_VerbID = VerbIndex 
 Else 'с третьего номера
   ISmartTagAction_VerbID = VerbIndex + 2  
 End If
End Property

Private Property Get _
   ISmartTagAction_VerbCaptionFromID _
   (ByVal VerbID As Long, _
   ByVal ApplicationName As String, _
   ByVal LocaleID As Long) As String
   
 ' Здесь формируются названия команд
 ' в меню "Действие смарт-тегов"
 Select Case VerbID
  Case 1
    ISmartTagAction_VerbCaptionFromID = _
      "1. www.microsoft.com"
  Case 2
    ISmartTagAction_VerbCaptionFromID = _
      "2. www.pcweek.ru"
  Case 3
    ISmartTagAction_VerbCaptionFromID = _
      "3. www.bytemag.ru"
  Case 4
    ISmartTagAction_VerbCaptionFromID = _
      "4. Выдача сообщения"
 End Select
End Property

Private Property Get _
  ISmartTagAction_VerbNameFromID _
  (ByVal VerbID As Long) As String
  ' Это свойство нужно только для 
  'работы с объектной моделью Excel
  ' с этим будем разбираться позднее...
  ISmartTagAction_VerbNameFromID = _
    "AndySimpleTest" & VerbID
End Property

Обращение ко всем четырем свойствам - IsmartTagAction_VerbCount, IsmartTagAction_VerbID, IsmartTagAction_VerbCaptionFromID и IsmartTagAction_VerbNameFromID - происходит в момент нажатия кнопки "Действие смарт-тегов" для формирования соответствующего меню. Тут дело в том, что обработчик имеет единый список команд выполнения действий для всех типов смарт-тегов (распознавателей), описанных в нем. В нашем примере имеются четыре такие команды (см. IsmartTagAction_VerbCaptionFromID) для обоих типов смарт-тегов. Один из них использует первые три команды, а второй - две последние, т.е. третья команда (строка "www.bytemag.ru") задействована в обоих вариантах.

Посмотрим, как это работает. При выборе кнопки "Действия смарт-тегов" происходит обращение к свойству IsmartTagAction_VerbCount, которое возвращает число команд, закрепленных за данным типом смарт-тега (SmartTagName). Далее формируется меню. Для каждого индекса меню (VerbIndex) определяется название команды, соответствующей данной позиции. Для этого сначала определяется номер команды (VerbID), а потом по этому номеру - само название (VerbCaptionFromID). Тут нужно отметить, что название может уточняться с учетом региональных установок (LocaleID) и конкретного приложения (ApplicationName). Одновременно идет формирование свойства VerbNameFromID, которое используется в объектной модели Excel, но с ним мы сейчас разбираться не будем.

И наконец, метод InvokeVerb реализует операции, выполняемые при выборе соответствующей позиции меню "Действия для смарт-тегов".

Тут основной параметр - опять же VerbID (еще раз - это не номер позиции меню, которую вы видите на экране, а некий номер-идентификатор списка команд, общего для всех распознавателей данного обработчика). Но на самом деле для уточнения команды или передаваемых в нее параметров можно использовать и другие сведения о данном смарт-теге - ApplicationName, Text (это сам фрагмент документа), Properties и XML (набор свойств и XML-описание смарт-тега).

Особо отметим параметр Target - ссылку на объект, содержащий данный смарт-тег. В документации говорится, что он обычно представляет собой объект документа типа Range (что вполне отвечает здравому смыслу), но может быть пустым, Null (что не очень понятно). В общем, с ним нужно разбираться отдельно.

Компиляция и регистрация DLL-обработчика

Для создания ActiveX DLL нужно просто выбрать команду "File | Make". А вот для регистрации обработчика смарт-тегов нужно немного поработать руками.

В первую очередь нашу библиотеку AndySimpleTest.DLL нужно зарегистрировать на компьютере в качестве ActiveX-сервера. Сейчас это не нужно делать специально, так как такая регистрация выполняется автоматически при создании исполняемого модуля. Если же вы запишете полученный файл на другой ПК, то нужно будет выполнить команду:

Regsvr32.exe AndySimpleTest.DLL

Но чтобы DLL могла работать в качестве обработчика смарт-тегов, необходимо с помощью утилиты Regedit.exe прописать в системный Реестр еще два ключа - для распознавателя и исполнителя действий. Откройте в Реестре раздел HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag, который содержит две папки - Recognizers и Actions. В каждой из них создайте по одному новому ключу, в которые введите полные идентификаторы ProgID двух созданных нами классов - соответственно AndySimpleTest.SmartTagRecognizer и AndySimpleTest.SmartTagAction (рис. 3).

Fig.3
Рис. 3. Регистрация DLL-обработчиков смарт-тегов в Реестре.

Тестирование

А теперь загрузим Word 2002 и посмотрим, как будет работать наш DLL-обработчик смарт-тегов.

Для начала откроем окно "Сервис | Автозамена | Смарт-теги" и убедимся, что в списке "Распознаватели" появилась строка "Простой распознаватель смарт-тегов" (рис. 4). Отметим, что это соответствует свойству SmartTagRecognizer_Name, которое правильно среагировало на региональную установку компьютера (русский, 1049).

Fig.4
Рис. 4. Список распознавателей пополнился созданной нами библиотекой DLL.

Введем в Word 2002 такой текст "На Фортране {Фортран} я написал довольно много программ" и убедимся, что в нем подчеркиванием выделены два фрагмента - "Фортран" (без окончания) и "{Фортран}". В первом случае сработал наш распознаватель 1 - тут проводился простой контекстный поиск, без учета регистра букв. Подведем к этому слову курсор и раскроем меню "Действия смарт-тегов" (рис. 5). Как видно, меню сформировано в соответствии с нашим кодом (см. заголовок и первые три команды). Обратите внимание на новую команду меню, которой в предыдущих примерах не было, - "Проверка новых действий...". Это встроенная команда, которая автоматически ссылается на Web-ресурс, прописанный в свойстве ISmartTagRecognizer_SmartTagDownloadURL (строка появляется в меню, если этот адрес определен). Выберите разные команды меню и убедитесь, что вызываемый Internet Explorer обращается по заданным адресам.

Fig.5
Рис. 5. Сформированное нами меню для обработки смарт-тега.

Теперь подведем курсор ко второму найденному смарт-тегу "{Фортран}" и раскроем меню (рис. 6). Тут видно, что на него, как и было задумано, среагировали оба созданных нами распознавателя (если курсор установить точно на фигурную скобку, то будет работать только второй распознаватель). Выберем второй вариант обработки, а далее команду "Выдача сообщения". Посмотрим на содержимое параметров, передаваемых в метод InvokeVerb и представленных в окне сообщений (рис. 7).

Fig.6
Рис. 6. Этот фрагмент соответствует двум разным смарт-тегам.

Fig.7
Рис. 7. Содержимое параметров, передаваемых в метод InvokeVerb.

В результате проведенного тестирования прояснились некоторые детали работы технологии. Так, можно высказать некоторые предположения относительно назначения свойств SmartTagCount и SmartTagName в интерфейсах Recognizer и Action. Судя по всему, офисное приложение при загрузке сначала опрашивает все обнаруженные им обработчики и формирует таблицу соответствия между типами смарт-тегов и обработчиками. Это делается именно с помощью чтения свойств SmartTagCount и SmartTagName. Поэтому, например, если в нашем программном коде установить константу SmartTagCountX = 1, то в методе Recognize не будет выполняться установка смарт-тегов для второго варианта, так как соответствующее имя типа не было прописано в этой таблице.

О нюансах регистрации DLL-обработчиков

В завершение этого обзора отметим еще один важный нюанс, касающийся управления обработчиками смарт-тегов. Как мы уже говорили, в списке "Распознаватели" окна "Сервис | Автозамена | Смарт-теги" можно указывать обработчики, действующие для данного приложения (рис. 4). Проведем эксперимент - откроем это окно и сбросим флажки у всех распознавателей. После этого перезагрузим Word 2002 и опять посмотрим на содержимое данного диалогового окна (рис. 8) - теперь все флажки сброшены, а последний установлен! Почему это происходит?

Fig.8
Рис. 8. Для нашего смарт-тега флажок почему-то установлен.

Чтобы ответить на этот вопрос, откроем в системном Реестре папку HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag (рис. 3). Обратите внимание, что наша библиотека DLL зарегистрирована с помощью некоего вполне осмысленного идентификатора, а остальные ключи (это обработчики, поставляемые в составе Office XP) - с использованием замысловатого шестнадцатеричного кода. (Кстати, на рисунке хорошо видно, что число классов Actions не совпадает с числом Recognizers. Дело в том, что необязательно совмещать несколько распознавателей или обработчиков в один класс, их можно смело разделить между разными модулями. Ведь их соответствие определяется по уникальным именам SmartTagName.)

Различие это объясняется тем, что для созданного обработчика мы использовали его внутренний идентификатор ProgID, а для других применен CLSID - уникальный идентификатор класса, который был присвоен модулю в момент его регистрации на данном компьютере в качестве ActiveX-сервера. Механизм Smart Tags не поддерживает сохранение статуса "подключен/отключен" для DLL, зарегистрированных с помощью ProgID. Если вы хотите, чтобы ваши установки сохранялись, их нужно регистрировать с помощью CLSID.

Это делается следующим образом. Найдите в разделе HKEY_CLASSES_ROOT системного Реестра папку с именем нужного класса, в данном случае AndySimpleTest.SmartTagAction\Clsid. В правой части окна выделите правой кнопкой мыши строку Default, а затем выберите команду Modify в появившемся контекстном меню. В открывшемся диалоговом окне щелкните правой кнопкой мыши поле Value data, а затем команду Copy (рис. 9). Далее откройте папку HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions и замените в нем ключ со значением AndySimpleTest.SmartTagAction на содержимое буфера обмена (если вы регистрируете обработчик первый раз, то создайте новый ключ). Далее такую же операцию выполните для распознавателя AndySimpleTest.SmartTagRecognizer.

Fig.9
Рис. 9. Копируем уникальный идентификатор класса CLSID.

Однако при использовании CLSID нужно иметь в виду, что в случае повторной регистрации ActiveX-сервера для него будет выделяться новый уникальный идентификатор, т. е. значение, записанное в раздел Smart Tag, утратит силу, его нужно будет корректировать заново. Отсюда совет - на этапе отладки DLL используйте регистрацию с помощь ProgID (как это мы и делали) и лишь после получения окончательного варианта замените код на CLSID.

Это еще не окончание...

Можно считать, что мы разобрались с созданием DLL-обработчиков, по крайней мере с основными возможностями этой технологии. Конечно, многие нюансы не были рассмотрены, тут есть еще что изучать. Подводя промежуточный итог тестирования, хотелось бы отметить, что процедуры регистрации обработчиков (и XML, и DLL) кажутся пока довольно запутанными и работают не очень надежно. Например, заметны проблемы с подключением XML-обработчиков (похоже, это каким-то образом связано с проверкой грамматики). Мои эксперименты с регистрацией DLL показали, что тут тоже используется довольно хрупкий механизм...

И все же теперь пора попытаться ответить на главный вопрос - для каких реальных практических задач можно применить технологию Smart Tags? Этим мы и займемся в следующий раз.

Листинг 4. Выполнение операций по обработке смарт-тегов
Private Sub ISmartTagAction_InvokeVerb _
  (ByVal VerbID As Long, _
   ByVal ApplicationName As String, _
   ByVal Target As Object, _
   ByVal Properties As _
     SmartTagLib.ISmartTagProperties, _
   ByVal Text As String, _
   ByVal Xml As String)
   
  'Выполнение каких-то действий 
  ' при выборе команды меню
 
  ' Для выбора конкретной операции
  ' можно использовать анализ
  ' всех входных параметров, но мы 
  ' сейчас используем только VerbID
  If VerbID <= 3 Then  
   ' обращение к Internet Explorer
   Dim ieObject As Object
   Set ieObject = CreateObject _
    ("InternetExplorer.Application")
   With ieObject
     Select Case VerbID
      Case 1
        .Navigate2 "www.microsoft.com"
      Case 2
        .Navigate2 "www.pcweek.ru"
      Case 3
        .Navigate2 "www.bytemag.ru"
     End Select
    .Visible = True
   End With
  Else   ' вывод такого сообщения
    MsgBox "ApplicationName = " _
     & ApplicationName & vbCrLf & _
     "Число свойств смарт-тега = " & _
     Properties.Count & vbCrLf & _
     "Text = " & Text & vbCrLf & _
     "XML = " & Xml
  End If
End Sub