Spotlight importers should be provided by all applications that support custom document formats. A Spotlight importer parses your document format for relevant information and assigning that information to the appropriate metadata keys.
An example metadata importer that extracts metadata from a custom document is included in the /Developer/Examples/Metadata/ImporterExample
. This example is referred to throughout this article.
Xcode provides a project template, Metadata Importer, that provides the functionality commonly shared by importers.
This template creates a project with the required frameworks, a template for the Info.plist
, a template for the schema file, a template for the localizable schema.strings
file, a template for the main.c
file that contains the necessary CFPlugin implementation and GetMetadataForFile.c
, a skeleton implementation of the required callback function. The target creates a CFPlugin bundle with an mdimporter
extension.
In addition to writing the extraction code, you’ll need to modify the templates to specify the document types your importer handles and list the keys your importer provides.
Each plug-in factory that can import metadata must have a unique identification number associated with it. Typically, there is only a single plug-in factory for each metadata importer, as a single function can handle many document types.
When you create a new metadata importer project, Xcode creates a UUID for your importer. Here is the UUID xCode generated for the sample metadata importer.
8AED83B3-C412-11D8-85A3-000393D59866 |
This value is used in the importer’s Info.plist
, as well as the main.c
file. Listing 1 shows the Info.plist template that was generated by Xcode.
Listing 1 Metadata importer Info.plist template
<?xml version="1.0" encoding="UTF-8"?> |
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" |
"http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<plist version="1.0"> |
<dict> |
<key>CFBundleDevelopmentRegion</key> |
<string>English</string> |
<key>CFBundleDocumentTypes</key> |
<array> |
<dict> |
<key>CFBundleTypeRole</key> |
<string>MDImporter</string> |
<key>LSItemContentTypes</key> |
<array> |
<string>SUPPORTED_UTI_TYPE</string> |
</array> |
</dict> |
</array> |
<key>CFBundleExecutable</key> |
<string>MyCustomImporter</string> |
<key>CFBundleIconFile</key> |
<string></string> |
<key>CFBundleIdentifier</key> |
<string>com.apple.yourcfbundle</string> |
<key>CFBundleInfoDictionaryVersion</key> |
<string>6.0</string> |
<key>CFBundlePackageType</key> |
<string>BNDL</string> |
<key>CFBundleSignature</key> |
<string>????</string> |
<key>CFBundleVersion</key> |
<string>1.0</string> |
<key>CFPlugInDynamicRegisterFunction</key> |
<string></string> |
<key>CFPlugInDynamicRegistration</key> |
<string>NO</string> |
<key>CFPlugInFactories</key> |
<dict> |
<key>8AED83B3-C412-11D8-85A3-000393D59866</key> |
<string>MetadataImporterPluginFactory</string> |
</dict> |
<key>CFPlugInTypes</key> |
<dict> |
<key>8B08C4BF-415B-11D8-B3F9-0003936726FC</key> |
<array> |
<string>8AED83B3-C412-11D8-85A3-000393D59866</string> |
</array> |
</dict> |
<key>CFPlugInUnloadFunction</key> |
<string></string> |
</dict> |
</plist> |
The CFPlugInFactories
entry is a dictionary that associates the metadata importer host ID to the UUIDs of the plug-in factory function an importer requires. The CFPluginInTypes
dictionary contains keys that associate the UUID of the factory function to the function. In both locations, Xcode inserted the newly generated UUID.
Here is the relevant line from the main.c
template that Xcode created.
Listing 2 Setting the importer ID in main.c
#define PLUGIN_ID "8AED83B3-C412-11D8-85A3-000393D59866" |
An importer must be associated with the document types that it can import. You do this by specifying the Uniform Type Identifiers (UTIs) that correspond to the supported documents.
The supported UTIs are specified in the LSItemContentTypes
array in the importer’s Info.plist
. The template in Listing 1 includes a placeholder, SUPPORTED_UTI_TYPE
, that you should replace with the UTI that your importer handles. If more than one document type is supported you can add additional string entries to the LSItemContentTypes
array in the Info.plist
. In the example importer, the SUPPORTED_UTI_Type
is com.apple.mycocoadocumentapp.mycustomdocument
.
Note: If an importer reads metadata from a document package you must add com.apple.package
to the array of UTIs declared in the UTTypeConformsTo
entry.
If your application does not define a UTI for its document types, you can declare one in your importer's Info.plist
by adding the UTExportedTypeDeclarations
key. Standalone importers that don't correspond to an application should declare the UTIs that they support by specifying a UTImportedTypeDeclarations
key. The UTImportedTypeDeclarations
format is the same as the UTExportedDeclarations
format shown in Listing 3. See “Uniform Type Identifier Concepts” for more information on declaring UTIs.
Listing 3 UTExportedTypeDeclarations format
<key>UTExportedTypeDeclarations</key> |
<array> |
<dict> |
<key>UTTypeIdentifier</key> |
<string>com.yourcompany.yourUTI</string> |
<key>UTTypeReferenceURL</key> |
<string>http://www.company.com/yourproduct</string> |
<key>UTTypeDescription</key> |
<string>Your Document Kind String</string> |
<key>UTTypeConformsTo</key> |
<array> |
<string>public.data</string> |
<string>public.content</string> |
</array> |
<key>UTTypeTagSpecification</key> |
<dict> |
<key>com.apple.ostype</key> |
<string>XXXX</string> |
<key>public.filename-extension</key> |
<array> |
<string>xxxx</string> |
</array> |
</dict> |
</dict> |
</array> |
You need to specify the metadata attributes that your metadata importer returns by modifying the project’s schema.xml
file. This is an XML Schema document that provides details on the returned attributes and allows you to specify custom metadata keys as well.
Listing 4 shows the schema.xml
template generated by Xcode.
Listing 4 Metadata Importer schema.xml template
<?xml version="1.0" encoding="UTF-8"?> |
<schema version="1.0" xmlns="http://www.apple.com/metadata" |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
xsi:schemaLocation="http://www.apple.com/metadata |
file:///System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/MetadataSchema.xsd"> |
<note> |
Custom attributes that this metadata importer supports. Below |
is an example of a multivalued string attribute. Other types |
are CFNumber, CFDate, CFBoolean and CFData. |
</note> |
<attributes> |
<attribute name="com_Foo_YourAttrName" multivalued="true" type="CFString"/> |
</attributes> |
<types> |
<type name="SUPPORTED_UTI_TYPE"> |
<note> |
The keys that this metadata importer handles. |
</note> |
<allattrs> |
com_Foo_YourAttrName |
</allattrs> |
<displayattrs> |
com_Foo_YourAttrName |
</displayattrs> |
</type> |
</types> |
</schema> |
You must edit this template to suit your metadata importer.
Replace the SUPPORTED_UTI_TYPE
placeholder with the appropriate UTI type for your document.
Edit the attributes
element, editing or removing the attribute
elements as required.
The metadata keys are prefixed with the reverse DNS naming schema, replacing “_” with “.” for key-value coding compatibility. Each of these custom metadata values return a single CFString as specified by the type
attribute of the attribute
element.
Metadata importers can only return the following CF types: CFString, CFNumber, CFBoolean, and CFDate. If a key returns an array of values, the type
attribute specifies the CF type and the attribute
element must include a multivalued
attribute with a value of true
.
If your importer does not require custom metadata keys, you can remove the attributes
element entirely.
Edit the allattrs
element so that it contains all your metadata keys.
Edit the displayattrs
element so that it contains a subset of your metadata keys that are recommended for previewing.
Edit the schema.strings
file to provide display name and description strings for your custom metadata keys.
Listing 5 shows the schema.xml
file that is included with the sample metadata importer project.
Listing 5 schema.xml file for the sample metadata importer
<?xml version="1.0" encoding="UTF-8"?> |
<schema version="1.0" xmlns="http://www.apple.com/metadata" |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
xsi:schemaLocation="http://www.apple.com/metadata |
file:///System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/MetadataSchema.xsd"> |
<note> |
Custom attributes that this metadata importer supports. Below |
is an example of a multivalued string attribute. Other types |
are CFNumber, CFDate, CFBoolean and CFData. |
</note> |
<attributes> |
<attribute name="com_apple_myCocoaDocumentApp_myCustomDocument_notes" multivalued="false" type="CFString"/> |
</attributes> |
<types> |
<type name="com.apple.mycocoadocumentapp.mycustomdocument"> |
<note> |
The keys that this metadata importer handles. |
</note> |
<allattrs> |
com_apple_myCocoaDocumentApp_myCustomDocument_notes |
</allattrs> |
<displayattrs> |
com_apple_myCocoaDocumentApp_myCustomDocument_notes |
</displayattrs> |
</type> |
</types> |
</schema> |
The sample metadata importer declares one new attribute key, com_apple_myCocoaDocumentApp_myCustomDocument_notes
. The key name is prefixed with the reverse DNS naming schema, replacing “_” with “.” for key-value coding compatibility. Each of these custom metadata values return a single CFString as specified by the type
attribute of the attribute
element.
Listing 6 shows the schema.strings file for the sample metadata importer.
Listing 6 Sample importer’s schema.strings file
"com_apple_myCocoaDocumentApp_myCustomDocument_notes" = "Notes"; |
"com_apple_myCocoaDocumentApp_myCustomDocument_notes.Description" = "What it is you're supposed to remember."; |
The command-line tool mdcheckschema
performs a simple validation on a schema and is useful when testing your own importer schema for validity.
When metadata is extracted for a file, the GetMetadataForFile
function is called. The function is passed the plug-in interface, a mutable dictionary that you’ll add the metadata attribute keys and values to, the UTI type of the target file, and the full path to the target file.
Listing 7 shows the GetMetadataForFile
skeleton implementation provided by Xcode in GetMetadataForFile.c
.
Listing 7 GetMetadataForFile template implementation
Boolean GetMetadataForFile(void* thisInterface, |
CFMutableDictionaryRef attributes, |
CFStringRef contentTypeUTI, |
CFStringRef pathToFile) |
{ |
/* Pull any available metadata from the file at the specified path */ |
/* Return the attribute keys and attribute values in the dict */ |
/* Return true if successful, false if there was no data provided */ |
#warning To complete your importer please implement the function GetMetadataForFile in GetMetadataForFile.c |
return false; |
} |
Your implementation of this function should extract the metadata from the file and insert it into the dictionary with the appropriate keys and values. If it successfully returns metadata, the function should return with a value of true
. If no metadata was extracted, you should return false
.
The example’s custom document format is a simple property list containing the author, title and reminder notes. Note that the example makes use of Objective-C and the Foundation class NSDictionary to read the dictionary from the file. In order to use Objective-C in your GetMetadataForFile
implementation you must rename GetMetadataForFile.c
to GetMetadataForFile.m
. Listing 8 shows the GetMetadataForFile
implementation of the example metadata importer.
Listing 8 Objective-C implementation of GetMetadataForFile for the sample metadata importer
Boolean GetMetadataForFile(void* thisInterface, |
CFMutableDictionaryRef attributes, |
CFStringRef contentTypeUTI, |
CFStringRef pathToFile) |
{ |
/* Pull any available metadata from the file at the specified path */ |
/* Return the attribute keys and attribute values in the dict */ |
/* Return true if successful, false if there was no data provided */ |
Boolean success=NO; |
NSDictionary *tempDict; |
NSAutoreleasePool *pool; |
// Don't assume that there is an autorelease pool around the calling of this function. |
pool = [[NSAutoreleasePool alloc] init]; |
// load the document at the specified location |
tempDict=[[NSDictionary alloc] initWithContentsOfFile:(NSString *)pathToFile]; |
if (tempDict) |
{ |
// set the kMDItemTitle attribute to the Title |
[(NSMutableDictionary *)attributes setObject:[tempDict objectForKey:@"title"] |
forKey:(NSString *)kMDItemTitle]; |
// set the kMDItemAuthors attribute to an array containing the single Author |
// value |
[(NSMutableDictionary *)attributes setObject:[NSArray arrayWithObject:[tempDict objectForKey:@"author"]] |
forKey:(NSString *)kMDItemAuthors]; |
// set our custom document notes attribute to the Notes value |
// (in the real world, you'd likely use the kMDItemTextContent attribute, however that |
// would make it hard to demonstrate using a custom key!) |
[(NSMutableDictionary *)attributes setObject:[tempDict objectForKey:@"notes"] |
forKey:@"com_apple_myCocoaDocumentApp_myCustomDocument_notes"]; |
// return YES so that the attributes are imported |
success=YES; |
// release the loaded document |
[tempDict release]; |
} |
[pool release]; |
return success; |
} |
Last updated: 2009-10-11