Introduction
This document contains information on the creation of Smart Tags for Microsoft Office. This focuses on the ISmartTagRecognizer
and ISmartTagAction
interfaces that work with Office XP and 2003. Their newer counterparts, ISmartTagRecognizer2
and ISmartTagAction2
, will not be discussed here because although Microsoft has published information about these interfaces and their new features (cascading menus, dynamic captions, backwards compatibility, etc), they have not yet published much information on their actual implementation***. As an example to go along with my explanation, I will use the creation of a simple smart tag (named SimpleTerm
) that recognizes different coffee flavors for a make believe company.
***EDIT: Refer to John Liu's message at the bottom for more info on Smart Tags 2
Notes on Smart Tags
If you try to create two smart tags that recognize the same text, the application will arbitrarily pick one of the two smart tags to run. You could also disable the smart tag that you do not wish to run in the application settings. One smart tag I have made was to recognize phone numbers and then give the user options of formatting the number. This smart tag was chosen over Microsoft Office�s smart tag, which probably looks for the phone number in Outlook or adds it into your address book (I couldn�t get Microsoft�s version to recognize any phone numbers).
If you wish to put smart tags into a document and send it to someone else, you can embed them in the file. This puts information into the document that the smart tag exists along with a URL where the user may download the smart tag (if the URL was provided by the smart tag�s programmer) The user you send the file to will then be notified that the smart tags exist and will be prompted to download the whole smart tag from that URL. If you receive a document and wish to scan it to see if the smart tags on your computer recognize any text, you can do so in the smart tag options of your application.
Smart Tags do not work in IE6.
Making the Smart Tag
Create the Resource Libraries and Install Them in the Global Assembly Cache
For security reasons, every library you use must have a strong name and must be registered in the global assembly cache (even your final .dll smart tag file). Since the Smart Tag Library lacks a strong name, we must create a strong-named wrapper library for it. First we�ll create a strong name for this library and then install it into the GAC.
- Open a Visual Studio Command Prompt (Start -> Programs -> Visual Studio .NET -> Visual Studio .NET Tools -> Visual Studio .NET 2003 Command Prompt)
- Create a strong name by issuing the command:
sn �k SmartTagLib.snk
- Here you could also create a strong name for your final .dll file:
sn �k SimpleTerm.snk
- Next move to the directory C:\Program Files\Common Files\Microsoft Shared\Smart Tag and type the following to create a strong named wrapper for the Strong Tag Library:
tlbimp mstag.tlb /keyfile:c:\SmartTagLib.snk /out:c:\SmartTagLib.dll
Notice that the library was originally mstag.tlb and now a new library named SmartTagLib.dll is located at the root directory of drive C. The path to SmartTagLib.snk may differ if you created it in another folder.
- Issue the following command to install this DLL into the GAC
gacutil /i c:\SmartTagLib.dll
- Create strong names and install other libraries that you will be using as resources
Create and Install the Smart Tag DLL
Open Visual Studio .NET and create a new Visual C# Class Library. I named my project
SimpleTerm
and created two classes,
SmartTagRecognizer
(to recognize the Smart Tag) and
SmartTagAction
(to perform an action on the Smart Tag). In the Solution Explorer, Right Click on References and choose to Add Reference. Click on Browse and locate all the strong named libraries that you created and will be using and add them.
Add using SmartTagLib
to the top of your classes along with the names of other libraries you are using Create your Recognizer
Class, implementing SmartTagLib.ISmartTagRecognizer
. ISmartTagRecognizer
has 6 properties (ProgID, Name, Desc, SmartTagCount, SmartTagDownloadURL, SmartTagName
) and one method (Recognize()
). Here is a description of the ISmartTagRecognizer
interface along with examples from my SimpleTerm project. The Smart Tag SDK v1.1 also has a description of the Smart Tag interfaces and is downloadable from Microsoft�s website
ProgID
is the programmatic identifier of the recognizer interface. It can be implemented like this:
public string ProgId
{
get
{
return "SimpleTerm.SmartTagRecognizer";
}
}
- Name is a short title that reflects what the recognizer does. This is what appears in the Smart Tag Menu
public string get_Name(int localeID)
{
return "My Simple Term Recognizer";
}
localeID
is the per-paragraph language identifier for the string being passed in. It is always equal to the locale identifier or language of the host application. So if a host application calls ISmartTagRecognizer::get_Name
with a localeID
that represents English, it is asking the ISmartTagRecognizer
to provide the recognizer name in English. Applications that do not have a notion of a per-item localeID
will pass in 0 (zero).
Desc
is a substantive description of what the recognizer does public string get_Desc(int localeID)
{
return "Simple Term Recognizer recognizes Fourth Coffee flavors"
+ " in documents";
}
localeID
is the per-paragraph language identifier for the string being passed in
SmartTagCount
specifies the number of smart tag types that the recognizer supports public int SmartTagCount
{
get
{
return 1;
}
}
Note that based on this count, implementing applications will call SmartTagDownloadURL
and SmartTagName
a corresponding number of times to find out more information about the recognizer's supported smart tags
SmartTagDownloadURL
is a URL that gets embedded in documents to allow users to download actions if they do not already have them installed for that particular smart tag. There is an option in the Smart Tag Actions button called "Check for New Actions�" to navigate to the download URL site so that recipients of documents with smart tags can easily download the actions for those smart tags. public string get_SmartTagDownloadURL(int smartTagID)
{
return "http://www.mydownloadurl.com";
}
smartTagID
is an enumerated integer that corresponds to SmartTagCount. The count will range from 1 to SmartTagCount
. Upon initialization, if a recognizer returns 3 from SmartTagCount
, it will subsequently receive 3 calls to SmartTagDownloadURL
, with SmartTagID
ranging from 1 to 3. If this recognizer does not want to supply a SmartTagDownloadURL
, it should return "" or Null.
This property is useful in the following example:
Person A creates an e-mail message with several CameraName smart tags in it and sends it to person B. Ordinarily, because person B does not have the CameraName DLL installed on their machine, the annotated data cannot be seen. Smart-tagging the e-mail message would have been a pretty useless exercise on the part of person A. But because the CameraName recognizer embeds a download URL, at the very least, person B can take advantage of the fact that there are CameraName smart tags in the documents by going to the Web page to download the actions and install the CameraName recognizer/action smart tag DLL.
- SmartTagName�s are the unique identifiers of smart tag types that the recognizer supports. The strings that are returned by this method must conform to the "
namespaceURI#tagname
" key style that defines a smart tag
public string get_SmartTagName(int smartTagID)
{
if(smartTagID == 1)
return ("schemas-fourth-com/fourthcoffee#flavor");
else
return null;
}
Recognize()
This is the main method where all the action happens. Text is recognized as a smart tag type and committed for the Action class to handle.
public void Recognize(string text, SmartTagLib.IF_TYPE dataType, int localeID, SmartTagLib.ISmartTagRecognizerSite recognizerSite)
text
is the string that is searched for smart tags. dataType an enumerator that describes the type of text being passed to the recognizer
typedef enum { IF_TYPE_CHAR = 0x000000001, IF_TYPE_SINGLE_WD = 0x00000002, IF_TYPE_REGEXP = 0x00000004, IF_TYPE_PARA = 0x00000008, IF_TYPE_CELL = 0x00000010} IF_TYPE
IF_TYPE_CHAR
applies to text being passed to the recognizer a single character at a time.
IF_TYPE_SINGLE_WD
applies to text being passed to the recognizer a single word at a time.
IF_TYPE_REGEXP
applies to text being passed to the recognizer after filtering using pattern matching.
IF_TYPE_PARA
applies to text being passed to the recognizer a paragraph at a time.
IF_TYPE_CELL
applies to text being passed to the recognizer a cell at a time (as in Excel, for example).
Note that presently onlyIF_TYPE_PARA
and IF_TYPE_CELL
are used (according to Smart Tag SDK 1.1, which will be outdated when the new Smart Tag SDK 2.0 is released). localeID is the per-paragraph language identifier for the string being passed in
RecognizerSite
is the interface that host applications expose so that recognizers can get property bags and commit smart tags. RecognizerSite's
GetNewPropertyBag()
method returns a property bag that can hold key/value pairs to be passed on to the action class once the smart tag has been committed. Its other method, CommitSmartTag()
, tells the ISmartRecognizerSite
interface to add a new smart tag based on the passed in text. Its syntax is as follows:
void CommitSmartTag(string smartTagName, int start, int length, ISmartTagProperties Properties)
smartTagName
is a smart tag type string, in the form of namespaceURI#localname
start
is an integer (1- based) which specifies the start position of the recognized smart tag in a recognized text
length
is a count of the number of characters that the smart tag will span
properties
is the ISmartTagProperties
object previously obtained from GetNewPropertyBag()
The ISmartTagProperties
interface is what is used as the property bag. Its properties are : -
Count
: an integer of how many properties are stored, KeyFromIndex
: returns a key string corresponding to a passed index integer, Read
: returns a value string corresponding to the passed key string, and ValueFromIndex
: returns a value string corresponding to a passed index integer. It has one method, Write(string key, string value)
to write a key/value pair to it
Now create your Action
Class, implementing SmartTagLib.ISmartTagAction
. ISmartTagAction
has 10 properties (ProgID, Name, Desc, SmartTagCount, SmartTagName, SmartTagCaption, VerbCount, VerbID, VerbCaptionFromID, VerbNameFromID
) and one method (Invokeverb()
).
-
ProgID
is the programmatic identifier of the action interface. Its implementation is just like ISmartTagRecognizer
�s
ProgID
public string ProgId
{
get
{
return "SimpleTerm.SmartTagAction";
}
}
Name
is a short title of what the action does (similar to ISmartTagRecognizer
�s)
Desc
is a substantive description of what the action does (similar to ISmartTagRecognizer
�s)
SmartTagCount
specifies the number of smart tag types that the recognizer supports (similar to ISmartTagRecognize
r�s)
- SmartTagName�s are the unique identifiers of smart tag types that the dll supports. The strings that are returned by this method must conform to the "
namespaceURI#tagname
" key style that defines a smart tag (similar to ISmartTagRecognizer
�s)
SmartTagCaption
is a caption for a smart tag type for use in the menu for the Smart Tag public string get_SmartTagCaption(int smartTagID,
int localeID)
{
return "Fourth Coffee";
}
smartTagID
is an enumerated integer that corresponds to smartTagCount
. Its value ranges from 1 to the value of smartTagCount
. This method is called SmartTagCount
times to retrieve names for each smart tag type. localeID
is the per-paragraph language identifier for the string being passed in
VerbCount
is the total number of verbs, or actions, that are supported by this DLL for a given smart tag type public int get_VerbCount(string smartTagName)
{
if (smartTagName == "schemas-fourth-" +
"com/fourthcoffee#flavor")
return 1;
else
return 0;
}
smartTagName
is a smart tag type string in the form of namespaceURI#tagname
VerbID
is a unique integer identifier for a verb public int get_VerbID(string smartTagName, int verbIndex)
{
return verbIndex;
}
smartTagName
is in the form of namespaceURI#tagname
. verbIndex
is a unique verb identifier. This mechanism is supplied so that DLLs can mix and match verbs for the various smart tag types they support. For example, a smart tag action DLL might support one verb for four smart tag types, or it might support four variants of one smart tag verb for a smart tag type.
VerbCaptionFromID
is a caption for a verb that appears on the Smart Tag Menu public string get_VerbCaptionFromID(int verbID,
string applicationName, int localeID)
{
if(verbID == 1)
return ("View Fourth Coffee product" +
" information");
else
return null;
}
verbID
is an unique integer identifier for the verb obtained earlier. applicationName
is the ProgID
of the calling application. A verb can customize its caption for the application. localeID
is the language identifier that corresponds to the UI language of the calling application. A verb can customize its caption for this localeID
, or return null or �� to specify that the verb does not work under the respective localeID
VerbNameFromID
is a language-agnostic identifier string for a verb that is used internally in the application to represent the VerbID
public string get_VerbNameFromID(int verbID)
{
switch(verbID)
{
case 1:
return "viewProductInfo";
default:
return null;
}
}
verbID
is an integer previously specified with the VerbID
method
InvokeVerb
()
invokes the appropriate verb when a user clicks on an action displayed in the smart tag UI that corresponds to the VerbCaptionFromID
value.
public void InvokeVerb(int verbID, string applicationName, Object target, SmartTagLib.ISmartTagProperties properties, string text, string xml)
VerbID
is the unique ID of the verb to invoke. applicationName
is a string that represents the name (ProgID
) of the calling application. This is an object-model-agnostic way of getting the name of an application. It is also used to identify applications that do not support the passing in of a Target. target
is a reference to the application-specific object that is responsible for calling the smart tag. In Excel, it is an Excel range object that points to the cell that the smart tag was attached to. In Word, it is a Word range object that wraps around the block of text that the smart tag refers to. However, note that this object may be any other kind of pointer to an object appropriate for the calling application. It could also be null
. properties
is the smart tag property bag. An action DLL can read, write, and modify these properties so that future action invocations can take advantage of this information. text
is a read-only string
that represents the text of the smart tag. For example, if a smart tag on a cell reads �MSFT�, text would equal �MSFT�. xml is a read only string that is an XML representation of the smart tag
After making these classes, a strong name key needs to be assigned to the project so the library will have strong name after its compiled. Open AssemblyInfo.cs and replace the line that reads
[assembly: AssemblyKeyFile(��)]
with
[assembly: AssemblyKeyFile(�C:\\SimpleTerm.snk�)]
This uses the key you created in earlier. The name and location of the key may be whatever you specified.
Now you need to tell Visual Studio .NET to register you DLL when you compile it. To do this go to your Project Properties, click on Configuration Properties -> Build and set Register for COM Interop to True.
Build the solution. Open a Visual Studio Command Prompt and go to the bin\Debug directory of your project. Install your DLL with the following command:
gacutil /i SimpleTerm.dll
(replace �SimpleTerm� with your library�s name)
If you compile your project multiple times, the classes� CLSIDs will keep changing, which could lead to errors. To prevent this, open regedit and go to HKEY_CLASSES_ROOT
. In this registry you should be able to find two subkeys by the names of your classes given in the format library.class. For my example it is SimpleTerm.SmartTagAction
and SimpleTerm.SmartTagRecognizer
. As subkeys for both of these, you will find each class�s CLSID. For each class, copy the CLSID and insert the following right above your class declaration:
[System.Runtime.InteropServices.GuidAttribute(<CLSID>),
System.Runtime.InteropServices.ProgIdAttribute()]
This will tell the compiler what to make your CLSID and your ProgID. For the
SmartTagRecognizer
class in our example the entry would look like:
[System.Runtime.InteropServices.GuidAttribute("69D56959-B1C2"
+ "-3987-80F2-845E73197626"),
System.Runtime.InteropServices.ProgIdAttribute( "SimpleTerm"
+ ".SmartTagRecognizer")]
Once the CLSIDs are set, you need to insert them into a place in the registry where applications look for smart tag DLLs. Locate the following key in the registry:
HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions
Here you will find many subkeys with CLSID values. Add the CLSID of your Smart Tag Action class here. Add the CLSID of your Smart Tag Recognizer class to the subkeys of
HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Recognizers
Note that with the newer Smart Tags for Office 2003, CLSIDs can be entered in
HKEY_LOCAL_MACHINE
to install the smart tag for all users of a computer
.NET also has a problem with referencing your DLL from certain applications. This is because the applications find your DLL through another DLL called mscoree.dll. This is done for security reasons in .Net. If an application is unable to load mscoree.dll when it starts, your DLL will not be referenced either. To ensure that mscoree.dll can be located by the application you must edit registry keys via regedit. Go to HKEY_CLASSES_ROOT\CLSID and locate the CLSIDs of both of your classes. Both of these keys will have a subkey called InprocServer32 and this will have a default value of mscoree.dll. Change this value to C:/Windows/System32/mscoree.dll for both of your classes. With this full pathname, your application should be able to correctly locate mscoree.dll. If you cannot find your CLSID in the registry, see if restarting regedit helps.
Note: You will have to change this value every time you compile
Your Smart Tag should now be working. If it doesn�t seem to be loading, check your security settings. For Word, you can adjust your security settings by going to Tools -> Macro -> Security. On the Trusted Publishers tab, make sure you have the Trust all installed add-ins and templates box checked. If your smart tag is loading but not functioning properly, you can debug it through Visual Studio .NET by going to Project Properties -> Configuration Properties -> Debugging. Change the Debug Mode to Program and select your Start Application to be the .exe file of the application you wish to test your smart tag with. For example, if you are trying to make a smart tag for Word, you would put C:\Program Files\Microsoft Office\OFFICE11\WINWORD.EXE as your Start Application. If you are still having problems with your smart tag, refer to the FAQ of the Smart Tag SDK.
Additional References
All these contain information about the new Smart Tags API. The last link contains the most detailed information I�ve found about the actual methods, but I still found it lacking.