Андрей Колесов
kolesov@bytemag.ru


Первые две статьи на эту тему - "Smart Tags в офисных приложениях Microsoft" и "Разработка Smart Tags в виде ActiveX DLL" - опубликованы соответственно в "BYTE/Россия" № 10, 11'2001.

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

Смарт-теги без Smart Tags

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

В той статье речь шла о реализации обработчика в виде XML-описателя. Теперь я предлагаю реализовать подобный режим обработки средствами Word версий 2000 или 97, не имеющих инструментария Smart Tags.

Рассмотрим макрокоманду VBASmartTags (листинг 1 в конце статьи). Как видим, она обращается к форме UserForm1, которая содержит метку Label1 (для вывода найденного термина) и два списка. Первый, ListBox1, используется в качестве меню при выборе операции, а во втором хранится описание выполняемых действий (URL), и поэтому он сделан "невидимым" (ListBox2.Visible=False). Напишем код для обработки событий ListBox1_DblClick:

Private Sub ListBox1_DblClick _
  (ByVal Cancel As MSForms.ReturnBoolean)
  '
  ' Выбор операции по обработке термина
  Dim ieObject As Object
  Dim strAction$
  If ListBox1.ListIndex >= 0 Then 
    ' выполнение операции
    ' строка операции
    strAction$ = _
      ListBox2.List(ListBox1.ListIndex)
    ' вставка термина вместо {TEXT}
    ' FindWord - глобальная переменная _
      для передачи параметра
    strAction$ = Replace(strAction$, _
                  "{TEXT}", FindWord$)
    ' Инициализация Internet Explorer
    Set ieObject = CreateObject _
       ("InternetExplorer.Application")
    ieObject.Navigate strAction$
    ieObject.Visible = True
  End If
End Sub

Для обращения к макрокоманде поместим с помощью окна Customize кнопку ее вызова на панель меню.

Теперь посмотрим, как это будет работать. Введем некий текст, выделим в нем двойным щелчком мыши слово "Бейсиком", затем щелкнем команду VBASmartTags. Появится форма со списком предлагаемых действий (рис. 1). Дважды щелкните по нужной позиции и убедитесь, что соответствующая операция начала выполняться (рис. 2). Обратите внимание - вся эта обработка выполняется на основе данных, которые хранятся в сформированных ранее XML-описателях в рамках технологии Smart Tags (пример для Word 2002). Но есть и заметные отличия, которые требуют дополнительного обсуждения.

Fig.1 Рис. 1. Выбор типа обработки терминов документа с помощью макроса.

Fig.2 Рис. 2. Выполнение заданной обработки.

Конечно же, макрокоманда VBASmartTags представляет собой очень простой пример реализации обработки текста, призванный лишь проиллюстрировать основные подходы к этой методике. Например, поиск терминов выполняется до первого описателя, где этот термин встречается. Понятно, что совсем нетрудно написать код с созданием комбинированного меню, если найдено несколько распознавателей для одного термина.

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

Естественно, при прочих равных условиях макрокоманды работают медленнее машинного кода, но никто не мешает реализовать эту же функциональность в виде двоичных ActiveX DLL, в том числе с применением модели COM Add-Ins. Очевидно также, что приведенный здесь вариант не есть точная копия технологии Smart Tags. Есть одно качественное отличие - при работе со Smart Tags распознавание терминов выполняется автоматически, по ходу ввода текста. В случае же макрокоманд это делается по запросу пользователя в явном виде. И вот тут мы подходим к одному из главных вопросов - а насколько в реальной жизни нужно это автоматическое распознавание? Может быть, гораздо чаще требуется именно выполнение подобных операций по специальной команде? Поясню это на простом примере.

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

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

В этой связи выскажу сожаление, что до сих пор в VBA не появились события, которые обеспечивали бы автоматический запуск неких макросов по ходу обработки документа (при редактировании ячейки Excel, переходе на новую страницу и пр.). А если бы еще среда VBA появилась в составе Internet Explorer...

И все же что-то в этом есть

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

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

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

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

Пример приложения в Smart Tags SDK 1.1

Мы уже говорили о существовании такого набора для разработчиков. В нем в числе прочего есть несколько примеров создания обработчиков Smart Tags. Один из них, Metric Conversion Sample, выполняет преобразование числовых данных из метрической формы в английскую (литры в галлоны и т. п.). В распознавателе основной код связан с поиском в тексте этих фрагментов данных, а фиксация нужных данных выполняется в процедуре ISmartTagRecognizer_Recognize с помощью описания дополнительных свойств объекта Property Bag:

Set propbag = _
  RecognizerSite.GetNewPropertyBag
 ' название единицы измерения
propbag.Write "unit", "liters"   
 ' конкретная величина данного смарт-тега
propbag.Write "quantity", quantity  

Соответственно, при обработке смарт-тега (при выборе меню "Действия смарт-тегов") можно преобразовать фрагмент текста в английские единицы:

quantity = Properties.Read("quantity")
unit = Properties.Read("unit")
If (InStr(unit, "liters")) Then
      newquantity = quantity * 0.2642
      newunit = "gal."
End If
If (InStr(ApplicationName, _
     "Word.Application")) Then
   Target.Text = newquantity & newunit
ElseIf (InStr(ApplicationName, _
     "Excel.Application")) Then
   Target.Value = newquantity & newunit
Else
   MsgBox (ApplicationName & _
     " не поддерживается")
End If

Я привел этот код только для того, чтобы показать, как работать со свойствами смарт-тегов. Но при этом хотел бы обратить внимание на два момента. Первое: в этом простом примере видно, что совершенно одинаковые операции для Word и Excel почему-то реализуются неодинаково - для объекта Target в первом случае используется имя свойства Text, а во втором - Value. Если вы посмотрите на полный код данного примера в SDK, то увидите, что в действительности различий гораздо больше. Второе: полный код DLL-обработчика в примере занимает не менее 200 строк кода. Макрос с выполнением подобной операции преобразования данных (щелчок по выбранной величине в документе и вызов макроса) уложится в десяток строк со всеми нужными проверками.

Работа с Word 2002

Но все же хочется привести пример полезного применения смарт-тегов. Создайте новый документ в среде Word 2002 и напишите для него два таких макроса:

Sub NewSmartTag()
'
' Вставка текста 
' и объявление его объектом SmartTag
'
  Selection.Text = "Андрей Колесов"
  Selection.SmartTags.Add _
    ("MySmartTag#Andytest")
   ActiveDocument.SmartTags(1).Properties.Add _
      Name:="Email", _
      Value:="kolesov@bytemag.ru"
End Sub

Sub TestSmartTag()
'
'  Выборка информации о смарт-теге
  If ActiveDocument.SmartTags.Count > 0 _
      Then ' есть смарт-теги
    With ActiveDocument.SmartTags(1)
      MsgBox .XML & vbCrLf & vbCrLf & _
      "Email = " & .Properties("Email")
    End With
  End If
End Sub

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

Fig.3 Рис. 3. Формирование и обработка смарт-тегов методами VBA в Word 2002.

Но самое главное - вся дополнительная информация о персоне сохраняется при передаче документа с компьютера на компьютер. Закройте Word 2002, загрузите его снова (лучше на другом компьютере), откройте сохраненный документ, запустите макрос TestSmartTag и убедитесь в этом сами!

Работа с Excel 2002

Кое-что в технологии Smart Tags настораживает. Так, очень заметны и совершенно непонятны различия в реализации одного и того же механизма в разных приложениях. К счастью, их пока всего два, но что же будет дальше?

Некоторые различия в объектной модели, действительно, могут объясняться "наследием прошлого". Но тут-то речь идет о новой технологии и новых объектах. В предыдущих статьях мы использовали в качестве примера Word 2002. Но оказывается, что многие вещи в Excel 2002 выглядят несколько (или совсем) иначе. Самый простой пример - окно "Сервис | Автозамена | Смарт-теги", в котором сосредоточено управление режимами использования смарт-тегов. В Excel 2002 присутствует другой набор функций (рис. 4). Не говоря уже о том, что стандартные распознаватели (имена, телефоны и т. п.) в нем не используются вовсе.

Fig.4 Рис. 4. Окно "Автозамена | Смарт-теги" в Excel 2002 не такое, как в Word 2002.

Функциональные возможности работы со смарт-тегами в Excel заметно богаче. Например, в первой статье мы приводили несколько методов и свойств Word 2002, которые позволяют управлять режимами работы смарт-тегов, отмечая при этом, что тут нельзя управлять списком "Распознаватели" в окне "Смарт-теги". А в Excel 2002 это возможно. Напишите, например, такой макрос:

Sub RecognizersList()
  Dim i%
  With Application.SmartTagRecognizers
    If .Count > 0 Then
      For i = 1 To .Count
       MsgBox i & " " & .Item(i).progID & _
        " " & .Item(i).Enabled
      Next
    End If
  End With
End Sub 

При его выполнении вы получите описание всех распознавателей. С помощью свойства Enabled можно программно выполнять подключение или отключение соответствующего распознавателя.

Попробуем теперь выполнить операции по программному формированию смарт-тега в Excel 2002. Это будет выглядеть несколько иначе, чем в Word:

Sub NewSmartTagExcel()

  ' Создание смарт-тега
  
  'Включение режима внедрения смарт-тегов
  ActiveWorkbook.SmartTagOptions. _
     EmbedSmartTags = True
  ' Влючение режима распознавания
  Application.SmartTagRecognizers. _
     Recognize = True

  Range("A1").Formula = "Андрей Колесов"
  ' Создание смарт-тега
  Range("A1").SmartTags.Add _
    ("MySmartTag#Andytest") _
      .Properties.Add _
    Name:="Email", _
    Value:="kolesov@bytemag.ru"

End Sub

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

Fig.5 Рис. 5. Формирование смарт-тегов методами VBA в Excel 2002.

А аналог макроса TestSmartTag для программной выборки смарт-тегов будет выглядеть следующим образом:

Sub TestSmartTag()
'
  Dim i%
  With Application.ActiveSheet
    If .SmartTags.Count Then
      For i = 1 To .SmartTags.Count
        With .SmartTags(i)
          MsgBox "Name = " & .Name & _
            vbCrLf & vbCrLf & _
            "XML = " & .XML & _
            vbCrLf & vbCrLf & _
            "Имя первого свойства = " _
            & .Properties(1).Name
            ' тут подразумевается, что 
            ' есть одно свойство!
        End With
      Next
    End If
  End With
End Sub

(К сожалению, почему-то при вводе кода интеллектуальная подсказка для конструкции Application.ActiveSheet не работает.)

 

Листинг 1. Код макрокоманды VBAStartTags

Public Const ListFolder As String = _
  "C:\Program Files\Common Files\" & _ 
  "Microsoft Shared\Smart Tag\Lists\"
' то, что передается в Form
Public FindWord$  

Sub VBASmartTags()
'
'  Интеллектуальная обработка 
'  терминов кустарным способом
'
' ----------------------------------------
  Dim PathName$, FileName$
  Dim MyWord$
  MyWord$ = Trim$(Selection.Text)
  If Len(MyWord$) < 2 Then
    MsgBox "Не выделен термин!"
    Exit Sub
  End If
'
' Поиск XML-файлов
  PathName$ = ListFolder$ + "*.xml"
  FileName$ = Dir(PathName$)
  If FileName$ = "" Then
    MsgBox "Нет списка XML-описателей смарт-тегов"
    Exit Sub
  End If
   
  Do
    ' обращение на поиск термина в данном распознавателе
    If OneXMLFile(ListFolder$ & _
         FileName$, MyWord$) Then _
      Exit Sub  ' найден термин
    FileName$ = Dir  ' продолжаем поиск
  Loop While FileName$ <> ""
  MsgBox "Термин " & Chr$(34) & _
     MyWord & Chr$(34) & _
     " не найден в XML-распознавателях"
End Sub

Function OneXMLFile(FileName$, MyWord$)
'
' Поиск заданного слова (MyWord$) 
' в XML-описателе (FileName$)

' Выход: OneXMLFile = True - слово найдено
'
   Dim xmlDoc As DOMDocument
   
   Dim Flname$
   Dim FLnameNode As IXMLDOMElement
   Dim FLacts As IXMLDOMElement
   Dim FLaction As IXMLDOMElement
   Dim FLactList As IXMLDOMNodeList
   
   ' включение обработки ошибок
   On Error GoTo XMLError 
   Set xmlDoc = New DOMDocument
   xmlDoc.Load (FileName$)

   ' имя распознавателя
   Set FLnameNode = _
     xmlDoc.selectSingleNode _
     ("FL:smarttaglist/FL:name")
   FLname = FLnameNode.Text
   
   ' список терминов
   Set FLnameNode = _
     xmlDoc.selectSingleNode _
     ("FL:smarttaglist/FL:smarttag/FL:terms/FL:termlist")
   Dim TermList$()   ' массив терминов
   Dim i%, MyTerm$
   
   TermList$ = Split(FLnameNode.Text, ",")
   FindWord = ""
   For i = LBound(TermList) To _
     UBound(TermList)
     MyTerm$ = Trim$(TermList(i))
     ' Вариант с точным совпадением:
     'If MyWord$ = MyTerm$ Then ' найден
     
     ' Вариант поиска с учетом окончаний
     ' и без учета регистра:
     If LCase(MyWord$) Like _ 
       LCase(MyTerm) & "*" Then 
       ' найден
       FindWord = MyTerm$: Exit For
     End If
   Next
   ' FindWord - глобальная переменная 
   ' для  передачи термина в форму
   If FindWord = "" Then ' не найден
      OneXMLFile = False
      Exit Function
   End If
   '
   ' Выбираем узлы Action
   Set FLacts = _
     xmlDoc.selectSingleNode _
     "FL:smarttaglist/FL:smarttag/FL:actions")
   Set FLactList = _
     FLacts.selectNodes("//FL:action")
   
   ' Формируем списки имен команд и действий
   For Each FLaction In FLactList
      UserForm1.ListBox1.AddItem _
         FLaction.selectSingleNode _
         ("FL:caption").Text
      UserForm1.ListBox2.AddItem _
         FLaction.selectSingleNode _
         ("FL:url").Text
   Next
   
   UserForm1.Caption = _
     "Распознаватель: " & FLname
   UserForm1.Label1.Caption = _
     "Обработка термина: " & FindWord$
   ' Обращаемся к форме для выбора операции
   UserForm1.Show
   '
   Set xmlDoc = Nothing
   OneXMLFile = True

  Exit Function
   
XMLError:
  ' ошибка будет, только если 
  ' неверно сформирован XML-файл
  MsgBox "Ошибка при обработке файла " _
    & FileName$
  OneXMLFile = False
End Function