XMPProgrammers Guide
XMPProgrammers Guide
PROGRAMMER’S GUIDE
December 2023
Copyright © 2023 Adobe Inc. All rights reserved.
NOTICE: All information contained herein is the property of Adobe Inc. No part of this publication (whether in hard-copy
or electronic form) may be reproduced or transmitted, in any form or by any means, electronic, mechanical,
photocopying, recording, or otherwise, without the prior written consent of Adobe Inc.
Adobe, the Adobe logo, InDesign, Photoshop, PostScript, and the XMP logo are either registered trademarks or
trademarks of Adobe Inc. in the United States and/or other countries. MS-DOS, Windows, and Windows NT are either
registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. Apple,
Macintosh, and QuickTime are trademarks of Apple Computer, Inc., registered in the United States and other countries.
UNIX is a trademark in the United States and other countries, licensed exclusively through X/Open Company, Ltd. All
other trademarks are the property of their respective owners.
This publication and the information herein is furnished AS IS, is subject to change without notice, and should not be
construed as a commitment by Adobe Inc. Adobe Inc. assumes no responsibility or liability for any errors or inaccuracies,
makes no warranty of any kind (express, implied, or statutory) with respect to this publication, and expressly disclaims
any and all warranties of merchantability, fitness for particular purposes, and noninfringement of third party rights.
Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
About this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
How this document is organized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
New features and changes in this release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Conventions used in this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3
: 4
Toolkit configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
SXMPIterator class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Creating iterator objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Performing iterations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
SXMPUtils class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Path composition functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Type conversion functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
The Extensible Metadata Platform (XMP) provides a standard format for the creation, processing, and
interchange of metadata, for a wide variety of applications.
The public XMP Toolkit SDK contains two components, XMPCore and XMPFiles. The XMP Toolkit C++ SDK is
available at GitHub and Java SDK is available for download from Adobe Developer’s Center. This toolkit
also includes the XMPFiles Plug-in SDK, which allows you to create plug-ins that XMPFiles can load and use
to handle additional file formats. See the companion document XMPFiles Custom File-handler Plug-in SDK.
JAVASCRIPT: A JavaScript API for both components is available through the scripting interface of
applications such as Adobe Bridge. The JavaScript API, known as XMPScript, is documented in the
JavaScript Tools Guide. This document is available as part of the ExtendScript SDK, available at Adobe
Developer’s Center (Scripting), and also with the Adobe Bridge SDK, available at Adobe Developer’s
Center (Adobe Bridge).
Chapter 1, “XMP Toolkit SDK Overview," provides an introduction to the XMP Toolkit SDK components
and usage.
Chapter 2, “The XMPCore Component," describes the core API, which allows you to manipulate XMP
metadata programmatically.
Chapter 3, “The XMPFiles Component," describes the file-handler API, which allows you to read and
write XMP metadata that is embedded in files.
Chapter 4, “Using the XMP Toolkit SDK," explains how to get started using the XMP Toolkit SDK, and
provides hands-on examples of how to perform typical metadata-handling tasks.
The latest XMP Toolkit supports CMake version 3.23.2 for Windows, Mac, Linux, iOS, and 3.6 for
Android. It is recommended to use this version while creating XMP Toolkit projects using the CMake
scripts provided. The provided CMake scripts have also been restructured across different platforms
to ensure better modularity.
5
: Preface About this document 6
Compiler Upgrade
The latest XMP Toolkit supports Microsoft Visual Studio 2022 for Windows, XCode 14.2 compiler
for Mac platform, and gcc 4.8.2 for Linux.
This release supports development of dynamic libraries for XMPCore and XMPFiles, in addition to the
previously supported static libraries on iOS.
This release supports development of dynamic libraries for XMPCore and XMPFiles.
SVG Handler
XMPFiles now added support to read/write metadata into compressed SVG (Scalable Vector Graphics)
file also. See XMP Specification Part 3, Storage in Files (XMPSpecificationPart3.pdf ).
Several other fixes for bugs have also been made to this SDK release.
The XMP Toolkit SDK provides an API for handling XMP metadata. This document supplies an overview of
the API, together with hands-on example code and usage guidance. This is a companion document to the
API reference documentation, also included in the XMP Toolkit SDK.
Part 1, Data Model, Serialization, and Core Properties covers the basic metadata representation model
that is the foundation of the XMP standard format. The Data Model prescribes how XMP metadata can
be organized; it is independent of file format or specific usage. The Serialization Model prescribes how
the Data Model is represented in XML, specifically RDF.
This document provides all the details a programmer would need to implement a metadata
manipulation system such as the XMP Toolkit SDK (which is available from Adobe).
Part 2, Additional Properties, provides detailed property lists and descriptions for standard XMP
metadata schemas; these include general-purpose schemas such as Dublin Core, and special-purpose
schemas for Adobe applications such as Photoshop®. It also provides information on extending
existing schemas and creating new schemas.
Part 3, Storage in Files, provides information about how serialized XMP metadata is packaged into XMP
Packets and embedded in different file formats. It includes information about how XMP relates to and
incorporates other metadata formats, and how to reconcile values that are represented in multiple
metadata formats.
https://github.com/adobe/XMP-Toolkit-SDK
7
CHAPTER 1: XMP Toolkit SDK Overview About the XMP Toolkit SDK 8
Contains the file README.txt with compilation instructions for all supported
platforms.
docs/ The three-part XMP Specification, this document, the companion document
XMPFiles Custom File-handler Plug-in SDK, and the API reference documentation
(API/index.html).
public/include/ The header files and client-side glue code used by clients of the XMP Toolkit
SDK.
samples/ Sample and tutorial projects and the necessary resources to run the sample
code.
source/ The source code that implements the XMP Toolkit SDK. This folder contains
generic source code for XMPCore, XMPFiles, and the plug-ins.
third-party/ Place holders for third party source files which are needed for the XMP Toolkit
expat/ SDK, including ReadMe.txt files with information on how to obtain and install
zlib/ the tools. MD5 source code, needed by both components for MD5 hash
zuid/interfaces/
computation, is included.
XMPFilesPlugins/ The root folder for the XMPFiles plug-in SDK, which allows you to create
PDFHandler/ metadata-handler plug-ins that XMPFiles can load and use to handle additional
file formats. This includes compiled binaries for a plug-in that handles metadata
in PDF files. To use the plug-in, you must have the correct C run-time libraries
installed; see SDK components.
For complete details of using the SDK, see the companion document XMPFiles
Custom File-handler Plug-in SDK.
tools/cmake/ Placeholder for the CMake tool that manages the build process for the XMP
Toolkit SDK for all supported platforms and compilers other than Android.
Contains a ReadMe.txt file that provides information on how to obtain and
where to install this tool.
tools/android/ Placeholder for the all tools that manages android build process for the XMP
Toolkit SDK. All android architectures are built using these tools. This contains a
ReadMe.txt file that provides information on how to obtain and where to
install the required dependencies.
Ndk-bundle 19c
CHAPTER 1: XMP Toolkit SDK Overview The XMP libraries 9
SDK components
The XMP Toolkit SDK contains two libraries, XMPCore and XMPFiles. Compilation instructions for all
platforms are in the file <xmpsdk>/build/README.txt. See also “Building the XMP libraries” on page 57
and Appendix A, “XMP Toolkit Build Reference."
Both XMPCore and XMPFiles are provided as C++ implementations with CMake scripts that generate
project files for:
Windows 10 and above (64-bit and 64-bit), using Visual Studio 2022 (VC++ 17)
Mac OS 12.5 and above (64-bit), using Xcode 14.2, creating universal binaries.
Linux using makefiles for the GNU C Compile (gcc) version 4.8.2.
XMPCore and XMPFiles are also provided for iOS 16.2, as C++ implementations with CMake scripts
that generate project files for Mac OS X 12.5 and above (64-bit), using Xcode 14.2 and above,
creating binaries for armv7 and armv7arm64 (fat binary) architectures on devices.
XMPCore and XMPFiles are also provided for android for x86_64, x86, armabi-v7a and arm64-v8a
architectures.
Dependencies
These publicly available components and tools are also required to build the C/C++ libraries:
CMake build tool: You must provide CMake 3.23.2 for Windows, Mac, Linux, iOS, and 3.6 for Android.
Obtain the distribution for your platform from
http://www.cmake.org/cmake/resources/software.html.
Expat XML parser: You must provide the Expat XML parser 2.5.0. See
http://sourceforge.net/projects/expat/.
ZLIB: This public compression library is required by XMPFiles to work with compressed formats such as
UCF. You must provide ZLIB 1.2.13. See http://www.zlib.net.
XMPCore provides the fundamental API for manipulating, serializing, and deserializing XMP data,
along with support utilities for building particular structures and for iteration. It does not deal with
files.
XMPFiles provides an API for convenient I/O access to the main, or document level, XMP for a file. It
allows you to locate and retrieve existing metadata from a file, update file metadata, and add new
metadata to a file. It contains file handlers for individual file formats, and a packet scanner that can be
used for unknown file formats.
CHAPTER 1: XMP Toolkit SDK Overview The XMP libraries 10
Typically, you will use XMPFiles to read XMP metadata from a file into an XMP object (represented by the
concrete class SXMPMeta in the XMPCore component). You will use XMPCore functions to examine and
manipulate the metadata, then use XMPFiles again to write it back into the file.
The XMPFiles Plug-in SDK allows you to extend XMPFiles by creating plug-in metadata handlers for
additional file formats. For complete details, see the companion document XMPFiles Custom File-handler
Plug-in SDK.
The following chapters provide overviews and API summaries for the components. This section discusses
issues relevant to the Toolkit as a whole.
To allow your code to access the entire XMP API you must:
Provide access to XMPCore and XMPFiles by including the necessary defines and headers.
To do this, add the necessary define and includes directives to your source code so that all necessary code
is incorporated into the build:
#include <string>
#define XMP_INCLUDE_XMPFILES 1 //if using XMPFiles
#define TXMP_STRING_TYPE std::string
#include "XMP.hpp"
The SDK provides complete reference documentation for the template classes, but the templates must be
instantiated for use. You can read the header files (TXMPMeta.hpp and so on) for information, but do not
include them directly in your code. There is one overall header file, XMP.hpp, which is the only one that
C++ clients should include using the #include directive. Read the instructions in this file for instantiating
the template classes.
When you have done this, the API is available through the concrete classes named SXMP*; that is,
SXMPMeta, SXMPUtils, SXMPIterator, and SXMPFiles. This document refers to the SXMP* classes, which
you can instantiate and which provide static functions.
Clients must compile XMP.incl_cpp to ensure that all client-side glue code is generated. Do this by
including it in exactly one of your source files.
Read XMP_Const.h for detailed information about types and constants for namespace URIs and
option flags.
After initialization, several readers can access an object concurrently, but a writer locks the object for every
thread until the operation is complete. The locking occurs automatically when a write operation is
CHAPTER 1: XMP Toolkit SDK Overview The XMP libraries 11
attempted. For example, if you call SetProperty(), all other incoming read and write requests are queued
until that operation completes and the lock is released.
The SDK allows you to configure and change the XMPCore and XMPFiles locking mechanism through
conditional compilation. For example, you can choose to use a global lock instead of the multi-read/single
writer model. You can choose to use native Mac OS or Windows APIs, or even Boost for the lock
implementation. For details, see the file XMP_LibUtils.hpp in <sdkroot>/source/.
When using the toolkit in a multi-threaded environment, you should be aware that it is the hidden internal
library object that is locked, not the client object. Multiple client objects can refer to the same internal
object. For example, if you call SetProperty() on one client object, that change appears in all
GetProperty() calls on all other client objects that refer to the same internal object.
Multiple references to the same library object are created by assignments (a = b;) which perform a shallow
copy of the client object. The Clone() method creates a deep copy of the internal library object; see
“Copying metadata” on page 37.
If you are developing an XMPFiles custom file-handler plug-in, you must ensure that your custom
implementations of Plug-in API functions are thread safe. For complete details, see the XMPFiles Custom
File-handler Plug-in SDK.
Error handling
All of the API functions throw exceptions for serious errors. The XMP toolkit provides notification for errors
arising in calls to SXMPMeta and SXMPFiles. You can choose to register error-notification callback
functions for your client, which you use to suggest an intention for recovery from the error. See “Handling
error notifications” on page 43, and API documentation for the functions declared in TXMPMeta.hpp and
TXMPFiles.hpp.
The XMP Toolkit makes a best effort at recovery and continuation using the provided suggestion, and
throws an XMP_Error exception only if recovery is not possible. The XMP_Error exception object includes
a numeric code and diagnostic string; use XMP_Error::GetId() and XMPError::GetErrorMsg() to
retrieve them.
New numeric codes may be added at any time. There are typically many possible explanations for each
numeric code; the error explanations try to be precise about the specific circumstances causing the error.
The error explanation strings are intended for debugging use only; they are not localized, and should not
be displayed to users.
Data-dump utilities also provide debugging assistance; see “Examining XMP objects” on page 20.
Progress notifications
Write and update operations for very large files can be very time consuming; see “Updating and writing
file XMP” on page 51. The Toolkit provides periodic progress-report notifications for these operations. You
can register a progress-notification callback function for your client, which you can use to report on
progress to your user or abort the operation if needed.
You can specify a time interval between consecutive callbacks. Each notification reports an estimated
fraction of the operation completed and an estimated time to finish the operation, in seconds. See API
documentation of XMP_ProgressReportProc in XMP_Const.h.
CHAPTER 1: XMP Toolkit SDK Overview The XMP libraries 12
The folder <xmpsdk>/samples/build/ contains helper scripts that use the CMake tool to generate the
project files; execute the scripts only from this folder. Detailed instructions are in the readme.txt file. To
create your project, execute the appropriate batch file, script, or makefile:
In Windows, run GenerateSampleProjects_win.bat and choose from the option list. The project is
created in the VC17/ folder.
In Mac OS, run GenerateSampleProjects_mac.sh and choose from the option list. The project is
created in the xcode/ folder.
The source code for the samples is in <xmpsdk>/samples/source. When you build them, the compiled
code is written to <xmpsdk>/samples/target/, to a platform-specific folder with debug and release
subfolders.
ReadingXMP filename Demonstrates the basic use of the XMPFiles and XMPCore components,
obtaining read-only XMP from a file and examining it through the XMP
object.
Takes one parameter, the file name. Prints results to the screen, and to an
output file named XMPDump.txt.
Takes one parameter, the file name. Prints results to the screen, and to
two output files named XMP_RDF.txt and XMP_RDF_compact.txt
Takes no parameters.
XMPCoreCoverage These demonstrate syntax and usage by exercising most of the API
XMPFilesCoverage functions of each XMP Toolkit SDK component, using a sample XMP
Packet that contains all of the different property and attribute types.
DumpMainXMP filename Uses the XMPFiles component API to find the main XMP Packet for a data
file, serialize the XMP, and display it.
dumpfile
dumpfile [-help | -version | [-tree|-keys|-list] | -nocomments] path ]
Recursively parses the structure of the given file and prints a view of the file structure to standard output.
The tool identifies chunks, subchunks, fields, and properties (in a broad sense), and creates a tree structure
where each node contains a triple of <key, value, comment>; the comment portion is anything that is not a
value, but can still be useful for understanding or validating the content. If an XMP Packet is found, it is
identified.
This tool is not intended to extract metadata from files, or for any kind of use in production. It is a
development aid that can help you determine whether a file that cannot be read by XMPFiles is
malformed.
Switches
-help Prints usage information for the command.
-version Prints version information for this tool, and for the version of XMPCore to which it
is statically linked. This tool does not use XMPFiles.
-tree Shows the tree structure of the file. This is the default.
-keys Shows only the key-value nodes found in the file, as a list with no hierarchical
structure, in alphabetical order by key.
CHAPTER 1: XMP Toolkit SDK Overview The XMP libraries 14
Switches
-list Shows only the key-value nodes found in the file, as a list with no hierarchical
structure, in the order of parsing.
-nocomments Does not show the comment portion of key-value nodes.
xmpcommand
xmpcommand [-help | -out | -safe | -smart | -scan | -compact]
[info mediafile | put xmpfile mediafile | get mediafile |
dump mediafile]
A command-line tool for performing basic XMP actions such as get, put, and dump. Can be used for
testing and scripting automation.
Returns 0 if there are no errors. Warnings and errors are printed as part of the output, either to stdout or
the output file, not to stderr; you should check the return value before using the output.
Switches
-help Prints usage information for the command, with examples.
-out outfile Writes output and logs all warnings and errors both to standard output and to the
specified output file. If you specify the output file without this switch, stdout is
not used.
-safe Updates safely, writing to a temporary file then renaming it to the original file
name. See API documentation for safeSave(kXMPFiles_UpdateSafely).
-smart Requires the use of a smart file-format handler, does no packet scanning. Use of
smart handlers is the default, if one is available.
-scan Forces packet scanning, does not use a smart file-format handler.
-compact Writes extracted XMP Packet in compact RDF-style, rather than pretty-printing
attribute value for readability (which is the default).
Actions
info mediafile Prints basic information about the file.
put xmpfile Injects the XMP contained in the specified XMP file into the specified media file.
mediafile
get mediafile Retrieves the XMP Packet contained in the specified media file.
dump mediafile Prints the XMP Packet contained in the specified media file to standard output.
Preferred to get for testing output.
EXAMPLES:
xmpcommand info Sample.jpg
xmpcommand get ../Sample.jpg >onlyFileOut.txt
xmpcommand -out alsoFileOut.txt get Sample.eps
xmpcommand put xmp_mySnippet.xmp Sample.jpg
xmpcommand -smart put xmp_mySnippet.xmp Sample.jpg,exename
CHAPTER 1: XMP Toolkit SDK Overview The XMP libraries 15
(Note that in the last example, a smart handler would be used by default for a JPEG file, or any other
supported file type, even without the -smart switch.)
2 The XMPCore Component
The XMPCore component of the XMP Toolkit SDK provides the fundamental API for manipulating and
serializing XMP data, along with support utilities for building particular structures and for iteration. It does
not deal with files.
This chapter introduces the XMPCore component and describes how it relates to the XMP Data Model.
“Reading XMP properties” on page 16 provides a brief overview of the XMP Data Model and describes
the basic property-access and debugging capabilities of the XMPCore component.
“Modifying XMP data in the XMP object” on page 22 discusses how to use XMPCore to modify XMP by
creating and deleting properties and modifying property values.
“Working with schemas” on page 30 discusses how to create a new schema with a unique namespace,
and how to extend existing schemas by creating new properties.
“Iterating over metadata” on page 32 discusses how to use the iteration utility in XMPCore to traverse
a metadata tree in an XMP object.
“API summary: the XMPCore component” on page 35 summarizes the functions provided by the main
XMPCore classes.
For reference details of the C++ API, see the HTML documentation of the template classes, provided in the
SDK under <xmpsdk>/docs/API.
Arrays are sets of indexed items, with each item holding a value.
Structures are sets of named properties (fields), with each field holding a value.
Structures and arrays can contain other structures or arrays, nested to any depth. See the XMP
Specification for complete details on data types and properties.
In addition to these basic types, there is special handling for Property qualifiers and language alternatives,
and for Dates and times.
16
CHAPTER 2: The XMPCore Component Reading XMP properties 17
Because arrays and structures can be nested, the path to a particular item can be quite complex; the
SXMPUtils class provides utilities for constructing paths, which you can then pass to the accessors. These
helper functions are all static; call them directly from the SXMPUtils concrete class. There is no need to
create SXMPUtils objects.
Simple properties
The easiest way to access simple properties is with the function SXMPMeta::GetProperty(). Provide the
function with the namespace URI, property name and a string pointer to store the value. You can also pass
a pointer to an XMP_OptionBits structure in which to return option flags that describe the property; if you
do not want that information, the pointer can be null.
meta.GetProperty( kXMP_NS_XMP, "CreatorTool", &value, &opts );
The property name can be a general path expression to a property located somewhere other than at the
top level.
The GetProperty() function returns true if the desired property exists and the reference variable are set
accordingly. When the function returns false, indicating that the property does not exist, the reference
variables (&value and &option) are not guaranteed to contain any meaningful values. You should always
check the function’s return value to discover whether you can depend on the contents of the reference
variables:
bool result = meta.GetProperty( kXMP_NS_XMP, "CreatorTool", &value, &options );
if( result == false )
value.clear();
To access the elements of a top-level array, use SXMPMeta::GetArrayItem(). The function signature is
similar to that of GetProperty() except that you must pass the index of the desired element:
meta.GetArrayItem( kXMP_NS_DC, "creator", 1, &value, &opts );
This is a 1-based index; that is, the index for the first element is 1, not 0.
You can use CountArrayItems() to discover the number of elements in the array. Pass the namespace URI
for the array and the array name:
int numItems = meta.CountArrayItems( kXMP_NS_DC, "subject" );
A special index value, the constant kXMP_ArrayLastItem, allows you to access the last element, regardless
of how many elements are present.
Structures are analogous to associative arrays as they have named fields. Structures can have their own
namespaces; to access a structure’s field you must supply the structure’s namespace URI and the field
CHAPTER 2: The XMPCore Component Reading XMP properties 18
name. Field namespaces (unlike schema namespaces) cannot be referenced using prefixes. The URI and
field name are passed separately:
... fieldNamespaceURI, "MyFieldName"...
Because arrays and structures can contain nested arrays and structures, you may need a path to access an
item or field value below the top level. In the XMP Toolkit SDK, paths are similar to, but not identical to,
those defined by the XML path language, XPath. It is highly recommended that you use the provided
utility functions to construct complex paths, rather than constructing them by hand.
Use the Compose...Path() methods in the SXMPUtils class to compose paths. These are static methods,
called on class itself. For example, to compose the path to a field in a structure:
SXMPUtils::ComposeStructFieldPath( structNamepace, "MyStruct",
fieldNamespace, "MyFieldName", &path );
This composes a path to the field ‘MyFieldName’ within the ‘MyStruct’ structure, and stores the composed
path in the path variable. You can then use this composed path to get the property value with
GetProperty().
The composition utilities are very useful for complex, multi-step cases with nested structures or arrays, or
for use with the retrieve-by-type accessor functions such as GetProperty_Int(). For example the
following retrieves a Boolean value from the ‘Flash’ structure to determine whether the flash was fired:
bool value;
string path;
For additional detail, see “Composing paths to complex properties” on page 25.
Properties themselves may have their own properties attached to them. These "properties of properties"
are known as property qualifiers. Language alternatives, called alt-text items, are a special case of qualified
properties, indicated by the qualifier xml:lang. This is an example of a qualifier identified by a name and a
namespace prefix. To retrieve the value for a property qualifier, you specify the qualifier namespace URI
and the qualifier name:
meta::GetQualifier( schemaNamespaceURI, propertyName, qualNS, qualName,
&qualVal, opts )
A language alternative array allows a text property to be chosen based on a desired language, so that the
property value can be localized for several different languages. Each item in the array has a property
qualifier named xml:lang. The value of the xml:lang qualifier is used to determine which language
should be selected.
CHAPTER 2: The XMPCore Component Reading XMP properties 19
The easiest way to access alt-text items is with the SXMPMeta::GetLocalizedText() function, which has
this signature:
bool SXMPMeta::GetLocalizedText (
XMP_StringPtr schemaNS,
XMP_StringPtr altTextName
XMP_StringPtr genericLang,
XMP_StringPtr specificLang,
tStringObj * actualLang,
tStringObj * itemValue,
XMP_OptionBits * options )
The genericLang and specificLang parameters determine how an array item is selected. Both parameters
are RFC 3066 language tags; see http://www.w3.org/International/articles/language-tags. The specific
language tag is required and is used first to try and locate the desired element within the alt-text array. The
optional generic language tag is used if there is no match for a specific language tag.
The specific language tag should consist of both primary and secondary subtags, for example "en-US".
(Case is not considered, but the secondary subtag is uppercase by convention.) The generic language tag
is simply the first part of the specific language tag. For example if the specific language tag is ‘en-US’ then
the generic language tag is ‘en’. Although the specific tag can be supplied with just a primary tag it is
recommended that the specific language tag has both primary and secondary subtags, for example:
// Providing a generic language of ‘en’ and a specific language of ‘en-US’
meta.GetLocalizedText( kXMP_NS_DC, "title", "en", "en-US", NULL, &itemValue, NULL );
Ideally both the generic and specific tags should be supplied to GetLocalizedText(), as this gives the
greatest chance of success of finding a suitable item.
The actualLang output parameter is used to store the actual language of the item that was selected, if any.
The actual language is the value of the qualifier attached to the array item, for example ‘en-US’.
An alt-text array can have a default item, which has the property qualifier value of ‘x-default’ and is known
as the ‘x-default’ item. If present, it is always the first item of the array. The following shows a debugging
dump of an alt-text property:
dc: http://purl.org/dc/elements/1.1 (0x80000000 : schema)
dc:title (0x1E00 : isLangAlt isAlt isOrdered isArray)
[1] = "An English Title" (0x50 : hasLang hasQual)
? xml:lang = "x-default" (0x20 : isQual)
[2] = "An English Title" (0x50 : hasLang hasQual)
? xml:lang = "en-US" (0x20 : isQual)
[2] = "Un Titre Francais" (0x50 : hasLang hasQual)
? xml:lang = "fr-FR" (0x20 : isQual)
You can see that the x-default qualifier is attached to the first element within the array. When
GetLocalizedText() cannot match the specific language or the generic language, it returns the
x-default item if there is one. If no item can be found, the function returns false.
1. Attempt to find and return an item with a qualifier that is an exact match of the specific language.
2. If no match is found, attempt to find and return a partial match from the generic language.
3. If no match is found, locate and return the item with the ‘x-default’ qualifier.
4. If no x-default item is present, return false and do not modify the output parameter.
CHAPTER 2: The XMPCore Component Reading XMP properties 20
The XMP_DateTime class represents a date-time value. It can store times to nanoseconds, and provides
fields to handle UTC times and time-zone offsets. Utility functions help you to handle and manage dates
and times.
To retrieve a date value from metadata, use the accessor function SXMPMeta::GetProperty_Date(). This
has a similar signature to the generic accessor function GetProperty() except that you pass an explicit
XMP_DateTime pointer in which to store the retrieved date and time. If the property being accessed does
not exist, the function returns false. For example, to access the date-time value of a property named
‘MetadataDate’:
XMP_DateTime myDate;
meta.GetProperty_Date( kMP_NS_XMP, "MetadataDate", &myDate, NULL );
For additional detail, see “Modifying dates and times” on page 29.
To use SXMPMeta::DumpObject() you provide a callback function, which can output the contents of the
XMP object to the console or to a file. For example, the following shows some standard properties in the
Basic XMP schema:
xmp: http://ns.adobe.com/xap/1.0/ (0x80000000 : schema)
xmp:ModifyDate = "2007-07-16T11:33:20+01:00"
xmp:CreatorTool = "Adobe Photoshop CS3 Windows"
xmp:CreateDate = "2007-07-16T11:33:20+01:00"
xmp:MetadataDate = "2007-07-24T17:05:26+01:00"
dc:format = "image/jpeg"
dc:creator (0x600 : isOrdered isArray)
[1] = "Author Name"
CHAPTER 2: The XMPCore Component Reading XMP properties 21
The dump shows all schemas, prefixes and properties of an object, and the option bits that describe each
property. You can compare this format with the RDF for the same XMP, shown in “Parsing XMP” on
page 60. The following extract highlights the different data components in the data dump:
dc:format = "image/jpeg"
dc:creator (0x600 : isOrdered isArray)
[1] = "Author Name"
The callback function that you provide for DumpObject() must have the following signature:
XMP_Status MyCallBack(void *callerData, XMP_StringPtr buff, XMP_StringLen buffSize){
// Processing here
}
The second argument is a pointer to any data type you wish, for example a file or an output stream. The
data is passed as the first argument to your callback function. Your callback must cast it to the correct data
type. For example, the following code demonstrates how to dump an XMP object to a file:
XMP_Status MyCallBack( void *callerData, XMP_StringPtr buff, XMP_StringLen buffSize ){
XMP_Status status = 0;
ofstream * outFile = static_cast<ofstream*>(callerData);
(*outFile) write( buff, buffsize );
return status;
}
// ...
std::ofstream outFile;
outFile.open( "MyFile.txt", ios::out );
meta.DumpObject( MyCallBack, &outFile );
outFile.close();
This callback writes the XMP_StringPtr, passed as buff, to the file, passed in as *callerData. The callback
also accepts the length of the data buffer as an XMP_StringLen, buffSize. You should make use of this to
restrict that amount of data written each time the function is called.
Your callback must return a valid status; anything other than a no-error status causes output to be aborted.
CHAPTER 2: The XMPCore Component Modifying XMP data in the XMP object 22
NOTE ON HANDLING NEWLINES IN USER INTERFACES: The way a user interface handles newlines in text values is
important to the global and cross-platform portability of XMP. When displaying text, applications should
recognize common newline characters and sequences and ensure that they display as such. One
technique is to modify the displayed text, substituting appropriate local newlines. You must take care,
however, that the stored XMP value is not modified simply as a result of display.
Typical newlines are a single ASCII linefeed (LF, U+000A), a single ASCII carriage return (CR, U+000D), or
ASCII CR-LF. Section 2.11 of the XML 1.0 specification includes other sequences as recognized newlines for
normalization purposes: U+0085, U+2028, and the pair U+000D U+0085.
It is recommended that applications store all newlines in XMP text values as ASCII linefeed.
If the property does not exist, it is created and assigned the given value. The automatic creation of
properties may not always be desirable; to avoid it, use DoesPropertyExist() to determine if the
property is already available:
if(meta.DoesPropertyExist( kXMP_NS_XMP, "CreatorTool" ){
meta.SetProperty( kXMP_NS_XMP, "CreatorTool", "My Application Name", NULL )
}
Most of the functions in the XMP Toolkit SDK store retrieved values in buffers passed to the function
(call-by-reference). The actual return value of the function, in most cases, is simply a Boolean value that
describes the success of the operation.
When an operation is unsuccessful, the value of a variable passed in to store a retrieved value is not
guaranteed; it may be empty, or contain the prior value, or something else. Your code must not depend on
the value.
For example, this shows a common pattern for working with returned references:
SXMPMeta meta;
//some file operations here to get the metadata into the xmp object
std::string value;
XMP_OptionBits opts;
You can only use SetArrayItem() if the array already exists; if it does not, the function throws an
exception. To create a new array or add an item to an array, use SXMPMeta::AppendArrayItem().
To create a new array, you must supply options flags that describe the type of the array; ordered,
unordered, or language-alternative. If you do not supply the options flags, and the array does not already
exist, AppendArrayItem()throws an exception. For example:
// Will throw an exception if array does not exist
meta.AppendArrayItem( kXMP_NS_DC,"creator",0,"A Name",NULL );
If the XMP already has a property from the Dublin Core schema named ‘creator’, the array is modified; an
item is added to the array with value of ‘A Name’. However, because no options flag is supplied, if the array
does not exist, the function throws an exception and does not create a new array.
// Create the array if it does not already exist
meta.AppendArrayItem( kXMP_NS_DC,"creator",kXMP_PropArrayIsOrdered,"A Name",NULL );
Here again, if the XMP already has the property, the existing array is modified. However, because the
options flag is supplied, if the property does not exist, it is created as an ordered array.
Each property can have an arbitrary number of nested levels. For example, a structure can have arrays as its
field values; the array items themselves could also be arrays or structures, and so on.
CHAPTER 2: The XMPCore Component Modifying XMP data in the XMP object 24
Resource.pdf
sdk:MyProperty
sdk:F1
bag sdk:F3
seq
bag
sdk:F4 bag
sdk:F5
"A Value 3" sdk:F5
sdk:F4
"A Value 6"
"A Value 4"
"A Value 5"
In this example, Resource.pdf has a single property named "MyProperty". The property type is a
structure which has several fields, one of which is an ordered array. The array holds items which are
themselves structures.
<sdk:MyProperty rdf:parseType="Resource">
<sdk:F1>A Value1</sdk:F1>
CHAPTER 2: The XMPCore Component Modifying XMP data in the XMP object 25
<sdk:F2>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<sdk:F4>A Value3</sdk:F4>
<sdk:F5>A Value4</sdk:F5>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<sdk:F4>A Value5</sdk:F4>
<sdk:F5>A Value6</sdk:F5>
</rdf:li>
</rdf:Seq>
</sdk:F2>
<sdk:F3>A Value2</sdk:F3>
</sdk:MyProperty>
</rdf:Description>
</rdf:RDF>
In order to create properties like this, you must provide the correct name for the property when using any
of the property-setting functions of SXMPMeta. To access deeply nested properties, such as sdk:F5, the
name alone does not point to the correct property; you must provide a path. For example:
MyProperty2/sdk:F2[last()]/sdk:F5
—or—
MyProperty2/sdk:F2[2]/sdk:F5
You should not try to compose a complex path by hand. The XMP API provides utility functions, which you
should use to compose paths to deeply nested properties.
All of the utility functions are static; that is, they are called directly from the SXMPUtils class. You never
need to instantiate the SXMPUtils class.
You can create the complex properties by composing the paths using the utility function, then setting
property values incrementally.
For example, to add a value to sdk:F4 in the complex property structure shown in the diagram above, you
would first create the MyProperty structure, using the options flag kXMP_PropValueIsArray to signify
the value type of the property.
The structure sdk:F2 contains three fields, the second of which is an ordered array. To create the array, you
would follow these steps, illustrated by the following code extract:
1. Compose a path to the second field of the structure, by using the utility function
SXMPUtils::ComposeStructFieldPath().
3. Compose a path to the element that has just been added, using
SXMPUtils::ComposeArrayItemPath().
This illustrates the general technique; you can, of course, use different functions to achieve the same
effect. In this case, for instance, you could use the function SXMPMeta::SetStructField(), replacing the
last two lines of code with the following:
meta.SetStructField( ns.c_str(), path.c_str(),
ns.c_str(), "F4", "aValue3", NULL );
It is sometimes possible, depending on the property type, to create several nested properties at once, just
by providing a path expression. For example, to create a structure of structures, you could supply the path
and have the properties created directly. The following code adds several nested structures to a new
property:
SXMPUtils::ComposeStructFieldPath( ns.c_str(), "MyProperty", ns.c_str(),
"StructOne", &path );
SXMPUtils::ComposeStructFieldPath( ns.c_str(), path.c_str(), ns.c_str(), "
StructTwo", &path );
SXMPUtils::ComposeStructFieldPath( ns.c_str(), path.c_str(), ns.c_str(),
"StructThree", &path );
The structures are not created explicitly by the path composition functions, but are added when the path
is evaluated in the SetProperty() call.
Leaf properties of complex structs and arrays can have property qualifiers and text alternatives, regardless
of how deeply nested they may be. The arrays and structures themselves cannot have qualifiers; see the
XMP Specification Part 1, Data Model, Serialization, and Core Properties.
To add a qualifier to a property, you must provide the name of the qualifier and the name of the property
to which it is attached. In the case of a property within a nested structure, you must compose the path to
CHAPTER 2: The XMPCore Component Modifying XMP data in the XMP object 27
the field and then set the qualifier value. The following shows how to add a qualifier for sdk:F4 from the
example:
string path;
SXMPUtils::ComposeStructFieldPath( ns.c_str(), "MyProperty",
ns.c_str(), "F2", &path );
To set the qualifier with a binary value, compose the path using SXMPUtils::ComposeQualifierPath()
and use the appropriate mutator for setting the binary value:
SXMPUtils::ComposeStructFieldPath( ns.c_str(), "MyProperty",
ns.c_str(), "F2", &path );
The specificLang and genericLang arguments determine which item in the array is modified. The result
depends on whether the array is empty and if there is an x-default item present:
If the array is empty (that is, there are no items in the array, including an x-default item) then two items
are added to the array.
An x-default item is created at index 1 with the value of itemValue, and with an xml:lang property
qualifier with the value of ‘x-default’.
An item is added to the array at the first available index (in this case 2) with a value of itemValue
and with an xml:lang property qualifier with the value of specificLang.
If the array only contains an x-default item, the x-default item is modified and a new item added to the
array.
The x-default item value is set to itemValue and has an xml:lang qualifier with a value of
‘x-default’.
CHAPTER 2: The XMPCore Component Modifying XMP data in the XMP object 28
An item is added to the array at the first available index (in this case 2) with a value of itemValue
and with an xml:lang property qualifier with the value of specificLang.
If there are already several items in the array, including the x-default item:
If an item matches specificLang, that item’s value is modified. If the matching item’s existing value
matches that of the x-default item, the x-default item is also modified.
If an item matches the genericLang and there are no other items that match the genericLang, that
item’s value is modified. If the item’s existing value matches that of the x-default item then the
x-default item is also modified.
If an item matches the genericLang and there are other items that match the genericLang, a new
item is created. The new item value is set to itemValue and given an xml:lang qualifier with the
value of specificLang.
If you want to always add an item to the array if it does not already exist, then do not use both the
genericLang and the specificLang arguments. If you specify only a specific language, you guarantee that a
new item is added if an exact match is not made.
The rules for finding the language value to delete, based on the genericLang and specificLang values,
are similar to those for SetLocalizedText().
To create an alt-text array within a complex property, construct the correct path to the property, using the
SXMPUtils path composition functions. You can then use SetLocalizedText() to add items.
This code adds an alt-text array and several items to the complex structure in the example shown in
“Modifying and creating complex properties” on page 23. The new property has the name F6:
SXMPUtils::ComposeStructFieldPath(ns.c_str(), "MyProperty",
ns.c_str(), "F2", &path);
There is also a utility function for composing a path to a desired item within an alt-text array based on the
value of the property qualifier. SXMPUtils::ComposeLangSelector() composes a path to a specific item,
and only that item.
CHAPTER 2: The XMPCore Component Modifying XMP data in the XMP object 29
For example, the following shows XMP containing some alt-text items:
sdk: http://ns.adobe.com/xmp/sdk/ (0x80000000 : schema)
sdk:MyProperty (0x100 : isStruct)
sdk:F1 = "A Value1"
To access the GB English item and only that item, use this code:
string gbItemPath, gbItemVal;
SXMPUtils::ComposeLangSelector( ns.c_str(), path.c_str(), "en-GB", &gbItemPath );
// gbItemPath = MyProperty/sdk:F2[1]/sdk:F6[?xml:lang="en-GB"]
bool exists = meta.GetProperty( ns.c_str(), gbItemPath.c_str(), &gbItemVal, NULL );
ComposeLangSelector() returns a path that accesses a specific item, and only that item.
The Get/SetLocalizedText() functions access the best matching item for the language parameters,
and if all else fails, return or set the x-default item.
SXMPUtils provides a number of utility functions for examining, creating, and manipulating dates; for
example, to convert between time-zone and UTC times. The utility function
SXMPUtils::ConvertFrom_Date() converts an XMP_DateTime value into a string formatted according to
the ISO 8601 profile at http://www.w3.org/TR/NOTE-datetime. For example, 2006-04-10T16:35:34+0100.
CHAPTER 2: The XMPCore Component Working with schemas 30
You can use SXMPUtils::CompareDateTime() to compare date-time instances; for example, if you have
several dates and want to apply the latest time to a property. This shows how to compare dates, and also
how to construct a date-time value from an array:
XMP_DateTime myDate = { 2000, 4, 8, 18, 22, 57, 0, 0, 0, 0 };
XMP_DateTime timeNow;
SXMPUtils::CurrentDateTime( &timeNow );
It is recommended that you use local times, with a time zone designator of +hh:mm or -hh:mm, instead of Z.
This promotes human readability. For example, for a file saved in Los Angeles at 10 pm on July 23, 2005, a
timestamp of 2005-07-23T22:00:00-07:00 is understandable, while 2005-07-24T05:00:00Z is
confusing.
Although you can add new properties to extend an existing schema, the standard schemas are generally
intended for specific uses and should not be altered. Although you can, technically, add a property to, say,
the Dublin Core schema, it is not recommended. If you need a specific set of properties, you should create
a new schema. For example, you might create a schema com.companyName.xmp/1.0, and properties
within that schema for itemCode, orderNumber, and so on.
A schema must have a unique name, in order to avoid collisions with properties in other schemas. The
schema name is in the form of a URI and must comply with the XML 1.0 namespace rules, as defined at
http://www.w3.org/TR/2006/REC-xml-names-20060816/. You can also define a preferred prefix to use with
your namespace; defining a prefix is not mandatory, but it is recommended.
You will add properties to your namespace as you would to an existing schema. For your own schema, you
should also create a specification document that lists and describes all of the properties and data types. The
specification document is plain text, human readable, and does not need to be in any specialized format.
You should make it available to anyone wishing to work with your custom schema; however, it is not
necessary to publish it in the public domain.
CHAPTER 2: The XMPCore Component Working with schemas 31
Registering namespaces
To use your custom schema, you must register the namespace and prefix that you have chosen. There are
two ways to do this:
Create a new XMP object from valid RDF. Unknown namespaces are registered automatically.
To register a namespace explicitly, you provide the namespace URI and a preferred prefix:
string actualPrefix
SXMPMeta::RegisterNamespace( "http://ns.adobe.com/MyNamespace/",
"MyPrefix", &actualPrefix );
The prefix you pass in is a suggestion; it is not guaranteed to be registered as the prefix for the namespace.
The last argument, actualPrefix, returns the actual prefix that will be used for the registered namespace.
If you register a new namespace with a new prefix (that is, one not yet in use), the namespace is
registered with your suggested prefix, and the function returns it in the actualPrefix buffer.
If the prefix you supply for a new namespace is already in use, the function generates a new, unique
prefix based on the one you supplied. For example, if there is already a prefix "MyPrefix" then the
actual prefix returned is "MyPrefix_1_"; or, if "MyPrefix_1_" is already used, "MyPrefix_2_", and so on.
If you register an existing namespace with a new prefix, the function returns the prefix that is already
registered for that namespace.
The same rules apply if you create an XMP object from an RDF stream.
Prefix collisions can occasionally occur, both at run time and when XMP is serialized and stored. You should
never depend on the suggested prefix being used, but always check for and use the actual returned prefix
when registering a namespace.
To view all of the registered namespaces and their prefixes you can create a dump using a callback
function. See “Examining XMP objects” on page 20 for details on using callbacks and debugging dumps.
Extending schemas
There are no restrictions as to what properties you can add to a namespace; however, it is recommended
that you try to keep properties consistent with those already existing in a schema.
New properties that you add to a schema do not interfere with existing applications, as they have no
knowledge of your extensions. This also means, of course, that they cannot take advantage of them.
Your extensions are of use only to your own applications.
You should not change existing property definitions from other schemas. This may cause existing
applications to perform unexpectedly and produce incorrect results.
To add a new property to a schema you need only create that property and set a value, even if the value is
empty. For example if you wish to add a new text property to a schema you have created, the following is
sufficient:
meta.SetProperty( myNamepaceURI, "MyNewProperty", "The property value", NULL );
CHAPTER 2: The XMPCore Component Iterating over metadata 32
Any property type can be used when extending a schema and there are is no limitation to the number of
properties that can be added. When the XMP object is written back to the resource, the new property is
also written.
As long as you have provided a unique namespace URI for your schema, new properties that you define
are guaranteed to not interfere with existing schemas or their properties, even if your new properties have
the same local name as a property defined elsewhere.
You can also define new property value types; for example, your properties can define structures and
arrays. The specification document for your schema should document all schema properties and any new
property types.
Creating iterators
There are several constructors available for the iterator object that provide different levels of control over
how the iteration is done. The simplest iterator allows you to traverse an entire XMP object:
SXMPIterator iter( meta );
If you do not need to traverse the entire tree, you can provide the constructor with a schema name to
control where the iterator starts. To traverse a single schema, simply specify the schema name. For
example, this iterator only visits nodes from the Dublin Core schema:
SXMPIterator iter( meta, kXMP_NS_DC );
You can further specify a property within the schema at which to start the iteration. This is particularly
convenient for complex properties that may have many nested levels of arrays and structures. This code
creates an iterator for the single property ""Keywords" in the XMP Basic schema:
SXMPIterator iter( meta, kXMP_NS_XMP, "Keywords" );
Finally, you can provide option bits that control how the iteration progresses from the starting point. If, for
example, you do not want to traverse the entire subtree in a schema, you can limit the iteration to a single
level with the options kXMP_IterJustChildren:
SXMPIterator iter( meta, kXMP_NS_DC, kXMP_IterJustChildren );
This iterator visits only the immediate children of the root node, which in this case is the Dublin Core
schema. You can provide the options flag as the last parameter, following any other set of parameters (that
is, XMP object only; XMP object and schema; or XMP object, schema, and property).
A number of options flags (kXMP_Iter*) allow different kinds of control over which nodes are visited
during the iteration. You can, for example, create an iterator that visits only leaf nodes:
SXMPIterator iter( meta, kXMP_NS_DC, kXMP_IterJustLeafNodes );
This iterator visits only nodes that can have values. For example if you have a structured property with
several fields, the iterator does not visit the structure node itself, but does visit all of the structures fields.
CHAPTER 2: The XMPCore Component Iterating over metadata 33
You can combine multiple option bits with a logical OR to create the desired iterator. See “Creating iterator
objects” on page 43 for the list of available options.
Visiting nodes
To use your iterator instance, call its Next() method. The first time you call Next(), the method provides
information for the first node; each succeeding call visits the next node, until the iteration completes. The
method returns a Boolean value to indicate whether there are more nodes to visit; true if there are, and
false if the iteration is complete.
The parameters you pass to the Next() method store the details of the current node; that is, the schema
name, the property path, the property value, and the option bits that describe the property. All of the
parameters are optional; if you don’t need all of the information, you can omit or pass null for any of
them. For example, the following displays the schema name, path, and value of all properties in the Dublin
Core schema of the XMP object, but does not collect the property-type options flag:
SXMPIterator iter( meta, kXMP_NS_DC );
The default behavior of the iterator is to return a full path to the property; however, with complex
properties you may not be interested in the full path, but only in the property name. In this case, you can
create an iterator that uses the options flag kXMP_IterJustLeafName. This affects how the path is
returned for a visited node.
For example, the following shows an RDF extract representing a complex structure:
<xmpTest:MyTopStruct rdf:parseType="Resource">
<xmpTest:MySecondStruct rdf:parseType="Resource">
<xmpTest:MyThirdStruct rdf:parseType="Resource">
<xmpTest:MyThirdStructField>Field Value 3</xmpTest:MyThirdStructField>
</xmpTest:MyThirdStruct>
<xmpTest:MySecondStructField>Field Value 2</xmpTest:MySecondStructField>
</xmpTest:MySecondStruct>
<xmpTest:MyTopStructField>Field Value 1</xmpTest:MyTopStructField>
</xmpTest:MyTopStruct>
This code creates an iterator that returns only the name of the property and not the full path for this
structure:
XMP_OptionBits opts = ( kXMP_IterJustLeafNodes | kXMP_IterJustLeafName );
SXMPIterator iter( meta, kXMP_NS_SDK, "MyTopStruct", opts );
The following shows how the returned string can differ significantly depending on the options used to
create the iterator. This shows output from two different iterators for the same XMP object that visit the
same nodes, but use different output options:
CHAPTER 2: The XMPCore Component Iterating over metadata 34
In this example, only the fields with values have been visited, because the option flag
kXMP_IterJustLeafNodes was used to construct both iterators. The first set of output lines shows the
complete path for each field, which is the default behavior. The second set of output lines shows only the
property name because the option flag kXMP_IterJustLeafName was used.
Skipping nodes
The iterator object has a Skip() method that allows you to skip some or all of the remaining iterations.
The function accepts option bits that describe what should be skipped. In the following call, the option
kXMP_IterSkipSubtree skips a subtree below the current node:
iter.Skip( kXMP_IterSkipSubtree );
For example, if you want your iterator to visit each node in the XMP data tree except a particular schema
and then continue visiting other nodes in other schemas, use something like this:
SXMPIterator skipExifIter ( meta ); // Visit all nodes and properties
} else {
cout << schemaNS << " " << propPath << " = " << propVal << endl;
}
}
If the current node is a top-level node and you skip all of its siblings, the iteration is complete and no
more nodes will be visited.
If the current node is nested some number of levels down, then other nodes higher up the tree will still
be visited.
To illustrate this, consider the metadata structure in the following illustration. When the iteration reaches
"Item 4", we want to skip the rest of the array, but go on to visit remaining nodes at a higher level.
} else {
cout << schemaNS << " " << propPath << " = " << propVal << endl;
}
}
If "Item 4" were a complex property, rather than a simple one, this would skip any subtree below that node,
and any of that node’s remaining siblings (the rest of the items in "NestedArray").
TXMPMeta provides the core services of the XMP Toolkit SDK. Its methods allow you to create and
delete metadata properties, and to retrieve and modify property values.
In your code, you will work with the concrete classes SXMPMeta, SXMPUtils, and SXMPIterator.
Instantiate the SXMPMeta class to represent a set of metadata. You can create an empty object and fill it
with a string of serialized XMP data that you construct, or you can read the XMP metadata from a file into
the object. Use the SXMPMeta functions to work with namespaces and properties.
You can create your own private namespaces, but must register them before use, using
SXMPMeta::RegisterNamespace().
Property accessor functions of various kinds allow you to retrieve and set existing property values, and
to create new properties.
You can get and set property values as strings, or as binary types. SXMPUtils provides functions
for converting between types.
Use the path composition functions provided by SXMPUtils to construct complex paths with
nested structs or arrays. It is recommended that you not depend on specific namespace prefixes;
rather than hard-coding the prefixes for struct fields, use functions like GetStructField() and
SetStructField(), or ComposeStructFieldPath().
Create an SXMPIterator object to operate on all or a subset of the properties in the metadata tree
contained in an SXMPMeta object.
There is an additional constant class XMP_DateTime, which is not provided as a template, but is simply
defined. See the API documentation for details.
SXMPMeta class
This class defines functions for working with namespaces and with properties, and for serializing XMP data
into RDF text. In addition, static functions available through the SXMPMeta class allow you to initialize and
terminate the Toolkit cleanly, discover the Toolkit version, set up user notification for Toolkit failures, and
write out debugging information.
Another form of the constructor allows you to create a new SXMPMeta object and populate it with
metadata from a buffer containing serialized RDF text.
CHAPTER 2: The XMPCore Component SXMPMeta class 37
A buffer of RDF text that you pass directly to the constructor must contain a complete XMP packet. See
“Preparing metadata for I/O” on page 37 for additional options.
Copying metadata
You can pass an existing SXMPMeta object or reference to the constructor to do a shallow copy of the
object:
Create a new SXMPMeta object that refers to the same internal XMP object as an existing SXMPMeta
object.
Create a new SXMPMeta object that refers to the underlying reference for an existing XMP object,
which was obtained from some other XMP object by the GetInternalRef() method. This is used to
safely pass XMP objects across DLL boundaries.
These operations increment the reference count for the object, but do not perform a deep copy. They
return a reference to the same underlying object; they do not create a new object.
To reproduce an entire metadata tree contained in an XMP object, use SXMPMeta::Clone(). The clone
function returns a new object, not a pointer to the same object. The Clone() function is easy to misuse.
The following examples show correct and incorrect usage:
CORRECT USAGE: In this example, the clone2 line shows that you do not have to use an explicit pointer. This
is good for local usage, you do not have to worry about memory leaks.
SXMPMeta * clone1 = new SXMPMeta( sourceXMP.Clone() ); // This works
SXMPMeta clone2( sourceXMP.Clone() ); // This works also. (Not a pointer)
INCORRECT USAGE: In this code, the assignment to clone3 creates a temporary object, initializes it with the
clone, assigns the address of the temporary to clone3, then deletes the temporary.
SXMPMeta * clone3 = &sourceXMP.Clone(); // This does not work!
The function SXMPMeta::SerializeToBuffer() transforms the metadata tree contained in the XMP
object into a buffer of RDF text that conforms to the serialization model (see “Serializing for output” on
page 38).
The serialized buffer that results from the call to SXMPMeta::SerializeToBuffer(), or a serialized
buffer that you read from a file using XMPFiles::GetXMP() (see “Accessing metadata in files” on
page 54) can be reconstituted as an SXMPMeta object using SXMP::ParseFromBuffer(). You can also
parse multiple buffers of partial data into a single XMP object (see “Parsing serialized data into an XMP
object” on page 38).
CHAPTER 2: The XMPCore Component SXMPMeta class 38
These functions support serialization of metadata into RDF text that conforms to the serialization model
given in the XMP Specification Part 1, Data Model, Serialization, and Core Properties.
SerializeToBuffer() Serializes an XMP object into a buffer as RDF that conforms to the serialization
model.
You can provide option flags that control how serialization is performed.
ParseFromBuffer() Parses RDF from one or more input buffers into an SXMPMeta object. The input
for parsing can be any valid Unicode encoding.
The buffers can be any length. The buffer boundaries need not respect XML
tokens or even Unicode characters.
To specify options for serialization, use a logical OR of bit-flag constants such as these:
The options flags that you specify for serialization must be logically consistent; if they are not, an exception
is thrown. You cannot specify kXMP_OmitPacketWrapper along with kXMP_ReadOnlyPacket, or
kXMP_ExactPacketLength.
You can obtain a buffer of serialized metadata from an existing SXMPMeta object using
SXMPMeta::SerializeToBuffer(), or from a file, using SXMPFiles::GetXMP(), or you might obtain or
construct the buffer in some other way, as long as it conforms to the serialization model given in the XMP
Specification Part 1, Data Model, Serialization, and Core Properties.
A buffer that contains a complete XMP packet can be reconstituted as an SXMPMeta object simply by
passing it to the constructor:
SXMPMeta( ptrToXML, sizeOfXML );
This parses a full XMP packet. You will more typically need to parse multiple buffers containing partial data
into a single SXMPMeta object. To do this, you can make multiple calls to SXMPMeta::ParseFromBuffer(),
passing the RDF in a sequence of buffers, and setting the option flag kXMP_ParseMoreBuffers for all but
the last call.
http://www.w3.org/TR/2006/REC-xml-names-20060816/
For important details about constructing and referencing namespace names, refer to the discussion of
namespaces, prefixes, and XMP schemas in the XMP Specification Part 1, Data Model, Serialization, and Core
Properties.
XMPCore maintains a table of registered namespaces, which is initialized with a number of standard
namespaces. You can register custom namespaces, using the function
SXMPMeta::RegisterNamespace(). It is not an error if a specified namespace URI is already registered;
the function call does nothing in this case. If XMPCore encounters an unknown namespace when parsing
XML into an SXMPMeta object, it automatically registers the namespace.
The table of registered namespaces has one entry for each unique namespace URI, and one prefix for each
URI. When you explicitly register a new namespace, you can specify a suggested prefix. If the URI is not
already in the table, the suggested prefix is used only if that prefix is not already in use for some other URI.
If the suggested prefix is already in use, a derived prefix is constructed by appending a numeric suffix.
It is very important to understand that prefixes are local and scoped in the XML. The prefix is only a means
to look up the URI; the prefix itself is not considered in name comparison. Software must never depend on
the use of a specific prefix in stored XML. See “Creating custom schemas” on page 30.
When XMPCore parses XML, the stored prefix is used to look up the URI, then the URI is looked up in the
registered namespace table and added if not already there. This "active" prefix is unique within XMPCore.
XMPCore uses only the active prefix from the registered namespace table when it serializes data to XML; a
different prefix from parsed input is irrelevant.
Option constants describe the property type, which can be a simple type, a structure, or an array, as
described in the XMP Specification Part 1, Data Model, Serialization, and Core Properties.
Simple properties, simple array items, and simple struct fields have values. Arrays and structs do not
have values. Leaf items or fields can contain values, or can be empty arrays or structs.
There can be complex nesting of arrays, structs, and qualifiers. The SXMPUtils class offers functions
that help construct complex paths, which you can then pass to the property accessor functions.
For properties that can have values, the values can be set and retrieved either as strings or as binary
values. The SXMPUtils class offers functions to help convert between these types.
The property accessors always use Unicode strings with UTF-8 encoding. When you serialize the data,
you can specify other encodings; see “Serializing for output” on page 38.
You can create new properties using the property-setting functions. To create empty arrays and structs,
use the appropriate option flags. When you set a leaf value for a struct, all levels that are assigned implicitly
are created if necessary. Similarly, adding an array item with AppendArayItem() creates the named array if
necessary.
SetProperty() The simplest property setter, mainly for top level simple properties or after
using the path composition functions in XMPUtils.
SetArrayItem() Provides access to items within an array. The index is passed as an integer, you
need not worry about the path string syntax for array items, converting a loop
index to a string, and so on. The specified array must already exist; to create a
new array, use AppendArrayItem().
In normal usage the selected array item is modified. A new item is automatically
appended if the index is the array size plus 1. To insert a new item before or after
another item, use one of the option flags:
kXMP_InsertBeforeItem
kXMP_InsertAfterItem
Each call appends an item to the array. The corresponding parameters have the
same use as SetArrayItem(). You must specify the kind of array; if the array
exists, it must have the specified form.
CHAPTER 2: The XMPCore Component SXMPMeta class 41
SetStructField() Provides access to fields within a nested structure. The namespace for the field
is passed as a URI, you need not worry about the path string syntax.
SetQualifier() Provides access to a qualifier attached to a property. The namespace for the
qualifier is passed as a URI, you need not worry about the path string syntax.
SetLocalizedText() Modifies the value of a selected item in an alt-text array. Creates an appropriate
array item if necessary, and handles special cases for the x-default item. See
“Setting localized text” on page 41.
SetProperty_Float() Sets the value of a floating point property from a C double float.
If the selected item is from a match with the specific language, the value of that item is modified. If the
existing value of that item matches the existing value of the x-default item, the x-default item is
also modified. If the array only has one existing item (which is not x-default), an x-default item is
added with the given value.
If the selected item is from a match with the generic language and there are no other generic matches,
the value of that item is modified. If the existing value of that item matches the existing value of the
x-default item, the x-default item is also modified. If the array only has one existing item (which is
not x-default), an x-default item is added with the given value.
If the selected item is from a partial match with the generic language and there are other partial
matches, a new item is created for the specific language. The x-default item is not modified.
If the selected item is from the last two rules then a new item is created for the specific language. If the
array only had an x-default item, the x-default item is also modified. If the array was empty, items
are created for the specific language and x-default.
All of the retrieval functions return a Boolean result telling if the property exists, and, if it does, option flags
describing the property. If the property exists and has a value, the function returns the value.
CHAPTER 2: The XMPCore Component SXMPMeta class 42
GetProperty() The simplest property getter, mainly for top level simple properties or after
using the path composition functions in TXMPUtils.
GetArrayItem() Provides access to items within an array. The index is passed as an integer, you
need not worry about the path string syntax for array items, convert a loop index
to a string, and so on.
CountArrayItems() Useful for iteration within an array.
GetStructField() Provides access to fields within a nested structure. The namespace for the field is
passed as a URI, you need not worry about the path string syntax.
GetQualifier() Provides access to a qualifier attached to a property. The namespace for the
qualifier is passed as a URI, you need not worry about the path string syntax.
Note: Currently qualifiers are supported only for simple leaf properties.
GetLocalizedText() Returns information about a selected item in an alt-text array.
These functions delete properties. It is not an error if the specified property does not exist.
DeleteProperty() Deletes the given XMP subtree rooted at the given property.
DeleteArrayItem() Deletes the given XMP subtree rooted at the given array item.
DeleteStructField() Deletes the given XMP subtree rooted at the given struct field.
DeleteQualifier() Deletes the given XMP subtree rooted at the given qualifier.
DeleteLocalizedText() Deletes a specific language alternative value from an alt-text array.
Toolkit configuration
These globally-available static functions, called directly from the SXMPMeta class, provide utilities for
working with and configuring the XMP Toolkit SDK.
SXMPIterator class
This class provides iteration services for the SXMPMeta object. The SXMPIterator functions provide a
uniform means to iterate over the schema and properties within an XMP object.
You can pass an existing SXMPIterator object to the constructor to copy it. The copy constructor
creates a new client iterator that refers to the same underlying iterator.
Pass a specific SXMPMeta object to the constructor to create an iterator for the properties in that object.
You can specify an iteration root within the property tree, and can provide option flags that control how
the iteration is performed.
CHAPTER 2: The XMPCore Component SXMPUtils class 44
kXMP_IterJustChildren Visit just the immediate children of the root. Skip the root itself and all
nodes below the immediate children. This omits the qualifiers of the
immediate children, the qualifier nodes being below what they qualify.
kXMP_IterJustLeafNodes Visit just the leaf property nodes and their qualifiers.
kXMP_IterJustLeafName Return just the leaf component of the node names. The default is to return
the full path name.
kXMP_IterOmitQualifiers Do not visit the qualifiers of a node.
Performing iterations
Metadata within an XMP object is represented as a data tree with a single root node, which does not
explicitly appear in the data dump and is never visited by an iterator. Beneath the root are schema nodes,
which collect top-level properties in the same namespace. These are created and destroyed implicitly.
Beneath the schema nodes are the property nodes. The nodes below a property node depend on its type
(simple, struct, or array) and whether it has qualifiers.
An SXMPIterator constructor defines a starting point for the iteration and options that control how it
proceeds. By default, iteration starts at the root and visits all nodes beneath it in a depth-first manner. The
root node is not visited; the first visited node is a schema node.
You can provide a schema name or property path to select a different starting node. By default, this visits
the named root node first, then all nodes beneath it in a depth-first manner.
The SXMPIterator::Next() method delivers the schema URI, path, and option flags for the node being
visited. For a simple property node, it also delivers the value. Qualifiers for this node are visited next. The
fields of a struct or items of an array are visited after the qualifiers of the parent.
Next() Visits the next node in the iteration. Returns true if there was another node to visit, false if the
iteration is done.
Skip() Skips some portion of the remaining iterations. You can choose to skip the subtree below the
current node, or the subtree below and remaining siblings of the current node.
SXMPUtils class
This class provides utilities that support the use of the main SXMPMeta functions.
The Path composition functions provide support for composing path expressions to deeply nested
properties.
The Type conversion functions provide support for converting property values between binary types
and UTF-8 encoded strings.
CHAPTER 2: The XMPCore Component SXMPUtils class 45
You can use the utility functions provided by SXMPUtils to construct the paths required to access such
deeply nested properties. You can also use them to compose paths to top-level array items or struct fields
to use with binary accessors like GetProperty_Int().
You can use these functions to compose a complete path expression, or all but the last component. For
example, suppose you have a property that is an array of integers within a struct. You can access one of the
array items like this:
SXMPUtils::ComposeStructFieldPath( schemaNS, "Struct", fieldNS, "Array", &path );
SXMPUtils::ComposeArrayItemPath( schemaNS, path, index, &path );
exists = xmpObj.GetProperty_Int( schemaNS, path, &value, &options );
You could also use this code if you want the string form of the integer:
SXMPUtils::ComposeStructFieldPath( schemaNS, "Struct", fieldNS, "Array", &path );
xmpObj.GetArrayItem( schemaNS, path, index, &value, &options );
(In these examples, the schemaNS value refers to the top-level "schema" namespace, which the XMP
Toolkit SDK keeps separate from the rest of the path expression.)
The path syntax allows two forms of "content addressing" that you can use to select an item in an array of
alternatives. The form used in ComposeFieldSelector() lets you select an item in an array of structs
based on the value of one of the fields in the structs. The other form of content addressing is shown in
ComposeLangSelector().
For example, consider a simple struct that has two fields, the name of a city and the URI of an FTP site in
that city. Use ComposeFieldSelector() to create an array of download alternatives. You can show the
user a popup built from the values of the city fields. You can then get the corresponding URI as follows:
ComposeFieldSelector( schemaNS, "Downloads", fieldNS, "City", chosenCity, &path );
exists = GetStructField( schemaNS, path, fieldNS, "URI", &uri );
CHAPTER 2: The XMPCore Component SXMPUtils class 46
This chapter introduces the XMPFiles component of the XMP Toolkit SDK, which supports file input and
output for XMP, as opposed to the XMPCore API that supports manipulation of the XMP data itself.
“Using XMPFiles for metadata I/O” on page 47 introduces the XMPFiles component and how to use it
to access XMP metadata stored in files.
“API summary: SXMPFiles class” on page 53 summarizes the functions provided by SXMPFiles. For
reference details, see the online documentation of the template class, TXMPFiles, provided with the
XMP Toolkit SDK.
The XMPFiles API is a front end to a set of file handlers for various formats. The file handlers attempt to
provide smart, efficient support for all file formats for which the means to embed XMP is defined in the
XMP Specification, as documented in Part 3, Storage in Files. Adding XMP file-handling support for
individual formats is an ongoing effort. These formats currently have specific handlers:
47
CHAPTER 3: The XMPFiles Component Using XMPFiles for metadata I/O 48
You can extend XMPFiles with plug-ins that handle additional file formats; for complete details, see
XMPFiles Custom File-handler Plug-in SDK and the support files that are included with this toolkit.
You can still access metadata in file formats for which there is no specific handler; XMPFiles provides a
generic packet-scanning mechanism for files of any format, but its use is not recommended. In particular,
you should not use packet-scanning to look for XMP metadata in a file that is not known to have
document-level metadata. A Power Point file, for example, could have embedded metadata for an
included image, but no document-level metadata. Packet scanning would find the embedded metadata
and treat it as document metadata, with unpredictable results.
The XMPFiles API is designed for use by clients interested in the metadata and not in the primary file
content; the Adobe Bridge application is a typical example. The API is not intended to be particularly
appropriate for files authored by an application; that is, those files for which the application has explicit
knowledge of the file format.
The XMPFiles API is defined in the template class TXMPFiles. Use of the concrete class SXMPFiles (based
on std::string) is standard.
The class defines functions for opening and closing files, and for accessing the embedded metadata,
in addition to initialization and informational functions.
The SXMPFiles class provides static functions to initialize and terminate the XMPFiles component,
retrieve version information for it, and allow you to query the features available in individual format
handlers.
For a summary of API functions, see “API summary: SXMPFiles class” on page 53.
SXMPFiles::Initialize();
SXMPFiles::Initialize(pluginPath);
CHAPTER 3: The XMPFiles Component Using XMPFiles for metadata I/O 49
You can optionally pass the path of a folder that contains file-handler plug-ins which extend XMPFiles. For
information on building file-handler plug-ins, see the companion document XMPFiles Custom File-handler
Plug-in SDK.
The function returns true if the component has been initialized successfully; you can then use the
constructor to create SXMPFiles instance objects with which to access files and their contained XMP
metadata.
Once all processing is complete, you should explicitly terminate both components of the Toolkit, in order
to deallocate any global structures that were created on initialization. If you have initialized SXMPFiles,
terminate it explicitly before terminating SXMPMeta, which terminates the entire Toolkit:
SXMPMeta::Initialize();
SXMPFiles::Initialize();
SXMPFiles::Terminate();
SXMPMeta::Terminate();
Options that you provide with either the constructor or the OpenFile() function allow you to open a file
for read-only access, or for read-write access. (There are additional details; see “File formats and open
options” on page 50).
Once a file has been opened, you can access the metadata. To request the metadata from the SXMPFiles
instance, use the function GetXMP(). This function reports whether the file has metadata, as well as
retrieving the metadata if present. The function parses file metadata into an SXMPMeta object. Depending
on the file format, you can also choose to retrieve the raw packet data.
When calling GetXMP(), you must provide at least an SXMPMeta instance. The function can take additional
arguments to retrieve the raw XMP packet and the packet information. For example:
SXMPMeta meta;
std::string packet;
XMP_PacketInfo info;
myFile.GetXMP( &meta, &packet, &info );
The call to GetXMP() returns true if the file contains XMP data, false if it does not. If it does contain XMP, the
function parses the data into the provided SXMPMeta instance, which you can then use to access any of the
XMP properties, according to how the file was opened.
If you do not need to write out changed metadata, you can specify read-only access when you open
the file. In this case, the disk file is automatically closed in the file system after the data is read, and you
do not need to explicitly close it.
Typically, you will need to open a file, read and write the metadata, then close the file. To do this, you
specify read-write access when you open the file.
CHAPTER 3: The XMPFiles Component Using XMPFiles for metadata I/O 50
To modify the metadata in memory, use SXMPFiles::PutXMP(). You can do this as often as
necessary while the file is open.
When you close the file, SXMPFiles::CloseFile() writes out the metadata to the disk file.
When processing is complete, you should close the file explicitly, even if the file has been opened with
read-only access. Use the function CloseFile() on the SXMPFiles instance:
myFile.CloseFile();
If you opened the file as read-only, the disk file is actually closed in the file system immediately after
reading the XMP data, in order to avoid blocking the file. You can use CloseFile() to close the object
any time after calling GetXMP().
If you opened the file for writing, any changes that you make to the metadata with PutXMP() are not
actually written to the file until you call CloseFile(), which assigns the metadata packet back to the
file and closes it in the file system.
The file format that you specify on creation or with OpenFile() helps the XMPFiles component determine
which file handler to use for the file. If you provide a specific expected format, XMPFiles uses it as a hint
about what file handler to try first. If you want to open only a file of the given type, set the option flag
kXMPFiles_OpenStrictly (see “Open options” on page 51). This causes XMPFiles to throw an exception if
the format doesn’t match, rather than going on to check for other formats.
If you do not know the file format, use the default file format constant, kXMP_UnknownFile. In this case (or
if the format does not match the specified one and the open-strictly option is not set) XMPFiles chooses a
handler based on the actual content of the file. It checks each registered file handler in turn to see if it
understands the file format until it finds a suitable handler. It uses the file’s extension to choose the first
handler to try.
The call to OpenFile() returns true if the file was successfully opened. If the file could not be opened
successfully but no serious errors occurred, the function returns false. For serious errors, for example if the
specified file does not exist, XMPFiles attempts to recover, using suggestions provided by any error
handlers that your client has registered, and if the attempt fails, throws an exception.
You should not attempt to open files that are not known to contain document-level metadata. Power Point
files, for instance, might contain embedded metadata for included images, but no document-level
metadata.
The static function SXMPMeta::GetFormatInfo() allows you to determine the extent to which file
handling is supported for a particular file format. Depending on the format, the handler can provide these
capabilities:
Open options
In addition to the simple distinction of opening a file for read-only or read-write, you can combine option
bit flags to specify some additional control of the operation. Use the logical OR operator to combine the
bit-flag constants:
XMP_OptionBits opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUseSmartHandler
If the file did not previously contain XMP, or if the modified XMP is larger than before, you might not
actually be able to update the file. It depends on the format and the file handler’s capabilities, as well as on
how the file was opened. It is a good idea to check first, using SXMPFiles::CanPutXMP():
if( myFile.CanPutXMP( meta )){
myFile.PutXMP( meta );
}
CHAPTER 3: The XMPFiles Component Using XMPFiles for metadata I/O 52
CanPutXMP() returns true if the file can be updated. For example, if the file was opened as read-only, the
call returns false.
Remember that the XMP is not actually written back to the file on disk until you close the file object by
calling CloseFile(). You can use PutXMP() to update the XMP any number of times before closing the
file and writing out the data.
If you have specified the option bit kXMPFiles_OptimizeFileLayout with the open flags, then the XMP
toolkit will attempt to ensure certain optimizations while writing the file. Currently, this can be used only
for MPEG4/MOV files where the structure of the file needs to be optimized. For details see “Open options”
on page 51
Many applications check metadata when opening a file. Leaving a file open can cause conflicts with other
applications that might wish to access the file while you are working with the metadata. If you plan to
keep a file open for longer than a few minutes, it is better practice to open it for read-only, obtain the
metadata, and close the file. When you have finished processing the metadata, you can re-open the file
briefly for write access.
In such a case, XMPFiles uses an XMP_IO object to access the actual file through appropriate means. You
can call XMPFiles::OpenFile() on a client-defined XMP_IO object in place of the file’s path name string:
bool OpenFile (
XMP_IO * clientIO,
XMP_FileFormat format = kXMP_UnknownFile,
XMP_OptionBits openFlags = 0 );
The second and third parameters are the same as when you use a file path name string; see “File formats
and open options” on page 50.
When you use this form of OpenFile(), XMPFiles uses the XMP_IO object to read from and write to the
source, without directly touching the actual file through the file system.
Clients can provide their own implementations of XMP_IO to provide read-write access to a file that is
otherwise completely managed by the client; for complete details of this interface class, see the API
documentation in XMP_IO.hpp. Creating the XMP_IO object opens the source in a customized manner, and
destroying the object closes the source as needed.
CHAPTER 3: The XMPFiles Component API summary: SXMPFiles class 53
The general model for metadata access is to open a file, read and write the metadata, then close the file.
While open, portions of the file might be maintained in RAM data structures. Memory usage can vary
considerably depending on file format and access options. You can open a file for read-only or read-write
access, with typical exclusion for both modes.
Initialize() You must initialize the file handler before using SXMPFiles. You can optionally
pass the path of a folder containing file-handler plug-ins that extend
XMPFiles.
Terminate() You can terminate the file handler when done using SXMPFiles. This
deallocates global data structures created by initialization.
GetVersionInfo() Reports version information for the XMP file handler.
GetFormatInfo() Reports the supported features for a given file format. The supported features
can vary considerably among file formats, depending on both the general
capabilities of the format and the implementation of the handler for that
format.
CheckFileFormat() Reports the format of a file, as would be determined when attempting to
open that file.
CheckPackageFormat() Reports the format of a package, given the name of the top-level folder.
Examples of recognized packages include the video formats P2, XDCAM, or
Sony HDV. These packages contain collections of clips, stored as multiple files
in specific subfolders.
This is not the same path you would pass OpenFile(). For example, the
top-level path for a package might be ".../MyMovie", while the path to a file
you wish to open would be ".../MyMovie/SomeClip".
You can pass a file with which to initialize the object; this opens the specified file. The destructor
automatically closes the object’s associated file if necessary.
You can pass an existing file object to the constructor to copy it; this increments an internal reference
count, but does not perform a deep copy; see “Copying metadata” on page 37.
CHAPTER 3: The XMPFiles Component API summary: SXMPFiles class 54
OpenFile() Opens a file for metadata access. You can open it for reading, or for
updating. If you open a file for update, you must explicitly close it.
Options determine whether to reconcile other forms of metadata.
CloseFile() Closes an opened file.
GetFileInfo() Retrieves basic information about an opened file.
SetAbortProc() Allows you to define a callback function that checks for and responds
to a user-signaled abort.
GetFileModDate() Retrieves the most recent modification date for all of the files
associated with the metadata.
GetXMP() Obtains the parsed XMP from an open file. You can also choose to get the raw
XMP packet, and information about the raw XMP packet. Can report whether
XMP is present or not.
PutXMP() Updates the XMP in a file. This function supplies new XMP for the file; however,
the file is not actually written until closed. The options provided when the file
was opened determine whether the function reconciles other forms of
metadata.
CanPutXMP() Reports whether the XMP can be updated in the file, for a given XMP packet
size.
CHAPTER 3: The XMPFiles Component API summary: SXMPFiles class 55
Handling notifications
You can choose to provide handlers for error notifications and for progress notifications from XMPFiles
functions.
Error notification handlers should provide suggestions for attempted error recovery. A handler should
return true if recovery should be attempted, false if not. Use these SXMPFiles functions to register
callback functions to handle error notifications:
Progress notification handlers allow you to track the progress of long-running file-write operations.
Use your handler to report on progress to your user or abort the operation if needed. Use these
SXMPFiles functions to register callback functions to handle progress notifications:
This chapter provides hands-on examples and advice for using the XMP Toolkit to perform typical
metadata handling.
“Getting started” on page 56 describes how to build the XMP Toolkit from the projects included with
the SDK, and demonstrates how to build your own project using the XMP Toolkit libraries.
“Obtaining and creating XMP data” on page 60 discusses how to use the XMPFiles and XMPCore
components together to create, obtain, modify, and store XMP data.
Tutorial examples show how to work with the SDK. All projects and code to accompany the tutorials is
included in the SDK, in <xmpsdk>/samples; see “Sample code and tools” on page 12.
“Walkthrough 1: Opening files and reading XMP” on page 65 shows how to create and configure a
project in Windows or in Mac OS. It then demonstrates the basic use of the XMPFiles and XMPCore
components, obtaining read-only XMP from a file and examining it through the XMP object. It also
shows how to do a data-dump of the XMP object to get a more direct view of its contents.
“Walkthrough 3: Working with a custom schema” on page 77 demonstrates how to work with a
custom schema that has complex properties. It shows how to access and modify properties with
complex paths using the path composition utilities from the XMP API.
Getting started
This section describes how to build the Toolkit and where to locate the resulting libraries.
You must obtain the Expat XML Parser in order to create the XMP libraries. For a full list of files required to
install the Expat XML Parser see readme.txt in <xmpsdk>/third-party/expat.
1. Obtain a copy of the Expat distribution, minimum version 2.5.0. This can be downloaded
from: http://sourceforge.net/projects/expat
56
CHAPTER 4: Using the XMP Toolkit SDK Getting started 57
Installing ZLib
You must obtain and install the ZLib compression library in order to use the XMPFiles component with
compressed file formats such as UCF.
from: http://www.zlib.net/zlib.html
Installing CMake
You must obtain and install the CMake tool that manages the build process for the XMP Toolkit SDK.
1. Obtain a copy of the CMake distribution for your platform (version 3.23.2) from:
http://www.cmake.org/cmake/resources/software.html
3. Set execute permission for the installed CMake executable. In UNIX/Linux, for example:
$chmod u+x <cmake_root_path>
You must obtain and install Android tools that are required for the build process for Android binaries of the
XMP toolkit SDK.
1. CMake distribution can be installed from the Android Studio SDK Manager. Alternatively, obtain a
copy of the CMake distribution for your platform (version 3.6.4111459) from:
https://dl.google.com/android/repository/cmake-3.6.4111459-darwin-x86_64.zip
Ninja executable comes with the CMake distribution - <cmakeroot>/bin/ninja.
2. Install NDK (version 19c) from the Android Studio SDK Manager. Another option is to download
directly from: https://dl.google.com/android/repository/android-ndk-r19c-darwin-x86_64.zip
4. Set execute permission for the installed CMake and ninja executable. For example, On Mac:
$chmod u+x <cmake_root_path>
The following sections give instructions on how to build the libraries for each platform.
Before you can run any of the CMake helper scripts mentioned in these steps, you must make sure that
you have execute privileges for those file, as appropriate for your platform.
CHAPTER 4: Using the XMP Toolkit SDK Getting started 58
2. Follow the instruction to choose build options for projects; the Generate All option generates all
possible Visual Studio projects.
3. Open the generated solution file with MS Visual Studio 2022. For example, if you choose to generate a
static binary, you will see the file:
<xmpsdk>\build\vc17\static\windows_x64\XMPSDKToolkitSDK.sln
4. From the menu in MS Visual Studio, choose Build > Build Solution. This compiles the Toolkit to
produce two static libraries, XMPCoreStatic.lib and XMPFilesStatic.lib.
After a successful build for the 64-bit debug target, the libraries can be found in:
<xmpsdk>\public\libraries\windows_x64\debug\
The development environment for Mac OS is XCode 14.2. You can build the libraries in this
environment for deployment in Mac OS X or in iOS. XMPCore and XMPFiles, both are supported in iOS.
2. Follow the instruction to choose build options for projects; the Generate All option generates all
possible XCode projects.
If you choose to generate a static binary for Mac OS X, you will see the file:
<xmpsdk>/build/xcode/static/universal/XMPToolkitSDK64.xcodeproj
If you choose to generate a static binary for iOS, you will see the file:
<xmpsdk>/build/xcode/static/ios/XMPToolkitSDK.xcodeproj
4. From the menu in XCode, choose Build > Build. This compiles the Toolkit to produce the static
libraries, libXMPCoreStatic.a and libXMPFilesStatic.a.
After a successful build for the universal debug target for Mac OS X, both libraries can be found in:
<xmpsdk>/public/libraries/macintosh/universal/debug/
After a successful build for the armv7 arm64 iOS Device debug target, the XMPCore and XMPFiles
library can be found in:
<xmpsdk>/public/libraries/ios/armv7 armv64/Debug/
NOTE: If you have multiple versions of XCode installed on your development system, use the
xcode-select command to specify the one you wish to use:
$ xcode-select -print-path
b) Change the folder if necessary. For example, if the path to the version you want is
/Applications/Xcode.app, use:
Set the variable CMAKE_SYSTEM_LIBRARY_PATH to the root of the installed gcc compiler.
If SSP is enabled, set the variable XMP_GCC_LIBPATH to the path containing the static library
libssp.a, and set the variable the XMP_ENABLE_SECURE_SETTINGS to on.
3. You might need to adjust the environment variables for your distribution and installation. For
example, LD_LIBRARY_PATH must include folder paths that contain pthread and uuid shared libraries.
Look at the file <xmpsdk>/build/Makefile to see all of the build options that are available for each
target. Some of the available targets are:
all
clean
rebuild
DebugAll
3. Follow the instruction to choose the build options for projects. These intend to create android binaries
without creating intermediate projects/CMakeLists.
4. After a successful build for the ARM64 debug target, the libraries can be found in:
<xmpsdk>\public\libraries\android\arm64-v8a\debug\
x86_64
x86
armabi-v7a
arm64-v8a
Parse one or more buffers containing RDF XML into an XMP object, from which you can manipulate it
or write it to a file.
Serialize the XMP contained in an XMP object into a buffer containing an XMP packet as RDF XML.
Use RDF XML to create an XMP object and then apply the XMP to a file.
Parsing XMP
You can construct an SXMPMeta object directly from a buffer. A constructor is available that can accept a
single RDF buffer and create the XMP object. The buffer must contain a complete and valid RDF stream,
comprising an rdf:RDF element containing one or more rdf:Description elements.
For example, the following shows some valid RDF that represents several properties from the Dublin Core
schema. This RDF can be parsed and used to construct an XMP object.
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">An English Title</rdf:li>
<rdf:li xml:lang="en-US">An English Title</rdf:li>
<rdf:li xml:lang="fr-FR">Un Titre Francais</rdf:li>
</rdf:Alt>
</dc:title>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Green Bush</rdf:li>
</rdf:Alt>
<dc:format>image/jpeg</dc:format>
<dc:creator>
<rdf:Seq>
<rdf:li>Author Name</rdf:li>
</rdf:Seq>
</dc:creator>
</rdf:Description>
</rdf:RDF>
To parse this into an XMP object, you must supply the RDF as an XMP_StringPtr, together with the length
of the buffer:
string buffer = ""
// populate buffer with valid RDF
SXMPMeta meta( buffer.c_str(), strlen(buffer.c_str() );
CHAPTER 4: Using the XMP Toolkit SDK Obtaining and creating XMP data 61
The constructor can only parse a single valid RDF stream. If you have multiple buffers, you can either
combine them into a single buffer before constructing the object, or you can create an empty object, then
use SXMPMeta::ParseFromBuffer() to load multiple buffers of data. Each buffer can be any length and
does not have to end at a valid XML token or Unicode character. However, all buffers should ultimately
produce valid RDF.
The parsing function takes an options flag that specifies whether there are more input buffers to be
parsed. Use the option kXMP_ParseMoreBuffers if there are more buffers to process:
meta.ParseFromBuffer( buffer, buffersize, kXMP_ParseMoreBuffers );
When all buffers are processed, terminate input to the XMP object by calling the function again with
kXMP_NoOptions or 0 (which is the default). The following extract demonstrates using multiple buffers to
construct an XMP object:
// buffers created here
vector <string>::iterator i;
for(i = buffs->begin();i != buffs->end(); ++i){
meta.ParseFromBuffer( (*i).c_str(),strlen((*i).c_str()),kXMP_ParseMoreBuffers );
}
meta.ParseFromBuffer( NULL, NULL );
The last call to ParseFromBuffer() uses null as the input buffer, 0 as the length and the default option
flag, to signify that there are no more buffers for input.
Supply option flags for ApplyTemplate() to determine how the function should handle properties that
appear in both objects. You can choose to update, modify, or delete properties in the source.
For example the following affects all properties, both internal and external, and adds new properties from
the source to the destination:
SXMPUtils::ApplyTemplate( &destinationXMP, sourceXMP,
( kXMPTemplateAddNewProperties | kXMPTemplate_IncludeInternalProperties ));
CHAPTER 4: Using the XMP Toolkit SDK Obtaining and creating XMP data 62
This adds properties from the source only if they do not already exist in the destination. Arrays and
structures are merged. If a property exists in the destination and not in the source, it remains unmodified.
For example, suppose we have two XMP objects, represented by the RDF/XML shown here:
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">English US</rdf:li>
</rdf:Alt>
</dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>One</rdf:li>
</rdf:Bag>
</dc:subject>
</rdf:Description>
</rdf:RDF>
<dc:format>image/jpeg</dc:format>
<dc:subject>
<rdf:Bag>
<rdf:li>Two</rdf:li>
<rdf:li>Three</rdf:li>
</rdf:Bag>
</dc:subject>
</rdf:Description>
</rdf:RDF>
If you append properties from the source onto the destination using the given options, the result in the
destination object is as follows:
<dc:format>image/jpeg</dc:format>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">English US</rdf:li>
</rdf:Alt>
</dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>One</rdf:li>
<rdf:li>Two</rdf:li>
<rdf:li>Three</rdf:li>
</rdf:Bag>
</dc:subject>
</rdf:Description>
</rdf:RDF>
In this example:
Both the ‘creator’ property and the ‘subject’ property are present in both the source and destination,
so the arrays have been merged.
The ‘title’ property exists in the source, but does not exist in the destination, so it has been added.
The source does not have the ‘format’ property at all, so it remains unmodified in the destination.
You can use ApplyTemplate() to apply your own XMP templates to resources by providing a source XMP
object with the template properties.
Serializing XMP
You can use SXMPMeta::SerializeToBuffer() to create a serialized string of XML in valid RDF format
from the XMP data contained in the XMP object. The XMP Specification Part 1, Data Model, Serialization, and
Core Properties provides a complete overview of the storage model, describes how XMP is serialized and
discusses issues with RDF.
You can provide option flags that control the final format of the serialized RDF. For example, it can be a
complete XMP packet (which is the default), or you can specify kXMP_OmitPacketWrapper. If you want the
XMP data to use as little space as possible, use kXMP_UseCompactFormat to produce RDF with a very
compact syntax; this is the default option. If you have special formatting requirements, you can specify
which newline characters to use or how much indentation to apply to the RDF.
This simple call produces serialized XMP in the default compact format, without the packet wrapper:
string xmpBuffer;
meta.SerializeToBuffer( &xmpBuffer, kXMP_OmitPacketWrapper );
This is what the XMP looks like after it has been written to a file:
CHAPTER 4: Using the XMP Toolkit SDK Obtaining and creating XMP data 64
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">English US</rdf:li>
<rdf:li xml:lang="en-US">English US</rdf:li>
<rdf:li xml:lang="en-GB">English UK</rdf:li>
</rdf:Alt>
</dc:title>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Green Bush</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
In contrast, the following code segment serializes XMP data in canonical format, with the XMP packet
wrapper, using default values for indentation, newline characters and the base indent:
string xmpBuffer;
XMP_Options outOpts = kXMP_UseCanonicalFormat;
meta.SerializeToBuffer( &xmpBuffer, outOpts, NULL, "", "", NULL );
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<xmp:ModifyDate>2007-07-16T11:33:20+01:00</xmp:ModifyDate>
<xmp:CreateDate>2007-07-16T11:33:20+01:00</xmp:CreateDate>
<xmp:MetadataDate>2007-07-24T12:26:06+01:00</xmp:MetadataDate>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">English US</rdf:li>
<rdf:li xml:lang="en-US">English US</rdf:li>
<rdf:li xml:lang="en-GB">English UK</rdf:li>
</rdf:Alt>
</dc:title>
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 1: Opening files and reading XMP 65
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Green Bush</rdf:li>
</rdf:Alt>
</dc:description>
<dc:format>image/jpeg</dc:format>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
The first section of the walkthrough guides you through creating a CMake script that builds your
project for MS Visual Studio 2022 or for XCode 14.2
The second part of the walkthrough guides you through adding the necessary code to open a file and
read XMP properties.
Setting up a project
This section guides you through creating a console application which will be used to open a file and read
XMP properties.
Before you begin creating the project, make sure you have built the necessary libraries. Verify that the
library folder exists:
In Windows: <xmpsdk>\public\libraries\windows\debug
If not, check that you have completed all of the steps in “Getting started” on page 56.
The SDK provides a set of samples that illustrate coding techniques for various tasks. In addition to the
source code for each sample, there are CMake scripts that generate project files for use with a
platform-specific IDE.
In Windows, build the project files for MS Visual Studio 2022 by running the batch file:
<xmpsdk>\samples\build\GenerateSamples_win.bat
In Mac OS, build the project files for XCode 14.2 by running the shell script:
<xmpsdk>/samples/build/GenerateSamples_mac.sh
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 1: Opening files and reading XMP 66
The master script includes, at the end, all of individual CMake scripts for the samples that are provided
with the XMP Toolkit SDK. Each script generates the project for its corresponding sample. These scripts are
executed for the appropriate platform when you run GenerateSamples_win.bat or
GenerateSamples_mac.sh.
You can create a CMake script for your own sample project and add it to this list, so that the samples build
automatically generates the target-platform project for your new sample.
Here is a sample CMake script template, which you can modify with your own names and paths. In this
template, the project name is SampleTemplate. To customize this template for your own project, you must:
1. Define the minimum CMake version required for this script to generate projects on all supported
platforms (Windows, Mac OS, and Linux).
2. Add the name of your project. The project name is used as the base name of the executable project file
that will be created for each platform from the source files.
3. Add the source files that are to be compiled for your project.
4. Add include directories. This helps the build tool to search all the header files defined in the source
files. You can also specify preprocessor definitions such as the example one here,
TEMPLATE_EXAMPLE=1.
5. Add the paths to libraries that will be linked to the executable. This will always include at least the
XMPCore static library. Also, specify the output directory where the executable should be placed.
# ==============================================================================================
# Define minimum cmake version
# ==============================================================================================
cmake_minimum_required(VERSION 3.5.2)
# ==============================================================================================
# Replace "SampleTemplate" with your own project name
# ==============================================================================================
project (SampleTemplate)
# ==============================================================================================
# Add source files to the project
# Here: ${SAMPLE_SOURCE_ROOT} = <xmpsdk>/samples/source/
# ==============================================================================================
file (GLOB SOURCE_FILES ${SAMPLE_SOURCE_ROOT}/SampleTemplate.cpp)
source_group(SampleTemplate FILES ${SOURCE_FILES})
# ==============================================================================================
# Add include directories to the project
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 1: Opening files and reading XMP 67
# ==============================================================================================
# The base name of the executable to be created from the source files
# ==============================================================================================
add_executable(${PROJECT_NAME} ${SOURCE_FILES} )
# ==============================================================================================
# Set the XMP_BUILDMODE_DIR variable to Debug/Release
# ==============================================================================================
SetupInternalBuildDirectory()
# ==============================================================================================
# Add link paths for required libraries and set the output paths for all platforms
# Here we link all of the XMP libs, and set the output paths based on SAMPLE_SOURCE_ROOT
# ==============================================================================================
if(UNIX)
endif(UNIX)
# ==============================================================================================
# Add the Framework required for Mac OS (Cocoa here)
# ==============================================================================================
ADD_FRAMEWORK(Cocoa ${PROJECT_NAME})
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 1: Opening files and reading XMP 68
When you have modified the template to create a CMake script for your own project, add the new script to
the list in the master script, CMakeLists.txt, so that your project will automatically be generated when
you build the samples for your platform.
For example, if you place your new CMake script in this folder:
<xmpsdk>/samples/build/cmake/SampleTemplate
then this line in the master script, CMakeLists.txt, adds it to your script list:
# ${PROJECT_ROOT} ==>> <xmpsdk>/samples/build/cmake/
add_subdirectory(${PROJECT_ROOT}/SampleTemplate ${PROJECT_ROOT}/SampleTemplate/build${POSTFIX})
The application reads properties from three different schemas, the XMP Basic schema, the Dublin Core
schema and the EXIF schema. The properties demonstrate a variety of property and value types:
The following steps provide the major outline of creating the application; they do not show all code used
in the application. For the full code listing see <xmpsdk>/samples/source/ReadingXMP.cpp.
1. Provide the string class with which to instantiate the template classes:
#include <string>
#define TXMP_STRING_TYPE std::string
#include "XMP.incl_cpp"
6. Create a main method. Inside the main method, initialize both XMPCore and XMPFiles, using
conditional statements to verify initialization was successful:
if (!SXMPMeta::Initialize()) exit(1);
if (!SXMPFiles::Initialize()) exit(1);
7. Create the options to open the file for read-only access and request to use a format-specific handler:
XMP_OptionBits opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUseSmartHandler;
8. The file to be read is provided as a parameter on the command line. Create an SXMPFiles instance and
open the file, providing the filename, the file format and the options.
Depending on the file type, there may be no appropriate handler available. In this case, you would
have to open the file using packet scanning, providing a different set of option bits:
std::string status = "";
SXMPFiles myfile;
9. Create the SXMPMeta instance and retrieve the XMP from the file. We are not concerned with the raw
packet or the packet information, so you can leave out those output parameters:
if(ok){
SXMPMeta meta;
myFile.GetXMP( &meta );
}
10. Within the same logic block as the created SXMPMeta instance, add the code to display the simple
property "CreatorTool" by providing the namespace URI, the name of the property and a pointer to a
string in which to return the property value:
bool exists;
string simpleValue;
exists = meta.GetProperty( kXMP_NS_XMP, "CreatorTool", &simpleValue, NULL );
if( exists )
cout << "CreatorTool = " << simpleValue << endl;
else
simpleValue.clear();
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 1: Opening files and reading XMP 70
This checks that the property exists, so that the variable has been assigned a valid value, before
attempting to write that value to the console.
11. Display the first element of the ‘creator’ array. Provide the namespace URI, the name of the array, the
array index (1 for the first element) and a string in which to return the value:
string elementValue;
exists = meta.GetArrayItem( kXMP_NS_DC, "creator", 1, &elementValue, NULL );
if( exists )
cout << "dc:creator = " << elementValue << endl;
else
elementValue.clear();
12. Traverse the ‘subject’ property (an array) and display all elements. Use the number of items in the array
to control the traversal:
string propValue;
int arrSize = meta.CountArrayItems( kXMP_NS_DC, "subject");
for( int i = 1;i <= arrSize;i++ ){
meta.GetArrayItem( kXMP_NS_DC, "subject", i, &propValue, NULL );
cout << "dc:subject[" << i << "] = " << propValue << endl;
}
13. Get a localized text item; display the ‘title’ property in English:
string itemValue;
meta.GetLocalizedText( kXMP_NS_DC, "title", "en", "en-US", NULL,
&itemValue, NULL );
cout << "dc:title in English = " << itemValue << endl;
14. Get a localized text item; display the ‘title’ property in French:
meta.GetLocalizedText( kXMP_NS_DC, "title", "fr", "fr-FR", NULL,
&itemValue, NULL );
cout << "dc:title in French = " << itemValue << endl;
15. Get a date property; read the ‘MetadataDate’ property if it exists. If so convert the XMP_DateTime into
a string and display it:
XMP_DateTime myDate;
if( meta.GetProperty_Date( kXMP_NS_XMP, "MetadataDate", &myDate, NULL )){
string myDateStr;
SXMPUtils::ConvertFromDate( myDate, &myDateStr );
cout << "meta:MetadataDate = " << myDateStr << endl;
}
16. Discover if the EXIF Flash structure is available, if so display the flash status at the time the photograph
was taken:
bool exist;
string path, value;
exist = meta.DoesStructFieldExist( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF,"Fired" );
if( exist ){
bool flashFired;
SXMPUtils::ComposeStructFieldPath( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF,
"Fired", &path );
meta.GetProperty_Bool( kXMP_NS_EXIF, path.c_str(), &flashFired, NULL );
string flash = (flashFired) ? "True" : "False";
cout << "Flash Used = " << flash << endl;
}
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 1: Opening files and reading XMP 71
18. Outside of the logic block used to initialize XMPFiles, terminate both XMPCore and XMPFiles.
SXMPFiles::Terminate();
SXMPMeta::Terminate();
19. If you are going to run the application within your IDE, place a break point on the very last line of the
application. If you are going to run it from the command line, this is not necessary.
20. Compile and run the application. You should see output similar to this in the console window:
1. In MyReadingXMP.cpp, add a callback function just above the main function. The function must
conform to the signature for the typedef XMP_TextOutputProc. Name the function
DumpXMPToFile():
2. Inside the function, cast the *ref to an ofstream and direct the buffer to the file. Use the buffSize
parameter to control how much data is written to the file:
ofstream * outFile = static_cast<ofstream*>(ref);
(*outFile).write ( buff, buffSize );
4. Within the main function add a call to DumpObject(). Add this just before the XMPFiles instance is
closed:
ofstream dumpFile;
dumpFile.open("XMPDump.txt", ios::out);
meta.DumpObject(DumpXMPToFile, &dumpFile);
dumpFile.close();
cout << endl << "XMP dumped to XMPDump.txt" << endl;
6. Navigate to the appropriate target file of the compiled application and open the file XMPDump.txt,
which now contains a dump of the XMP data for the file provided as a parameter to the command.
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 2: Modifying XMP 73
The application you are about to create will accept a file path to a resource, open the file for update, and
read the XMP data from the file.
You will modify or add several properties to that XMP using XMPCore functions. You will then create an
XMP object from an RDF stream and append its properties to the first XMP object. Finally you will serialize
the resulting XMP and write it to a file.
Many of the initial steps are essentially the same as those in the previous tutorial; use the directions in
“Creating the MyReadXMP application” on page 68.
1. Add the necessary headers and macros to use both components of the XMP Toolkit SDK. See steps 1 to
5 of “Creating the MyReadXMP application” on page 68.
2. Initialize both XMPCore and XMPFiles. See step 6 of “Creating the MyReadXMP application” on
page 68.
3. Create the options to open the file for update, using a format-specific handler:
XMP_OptionBits opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUseSmartHandler;
4. Create an SXMPFiles instance and open the file providing the filename, the file format and the
options. See steps 7 to 9 of “Creating the MyReadXMP application” on page 68.
1. Add code that will write the current values for the properties used in this tutorial to the console
window. You can copy the utility function named displayPropertyValues(), provided in
<xmpsdk>/samples/source/ModifyingXMP.cpp. Place the copy in your source file just above the
main function.
2. In the main function, just after the call to GetXMP(), display the desired property values in the console
by calling displayPropertyValues():
displayPropertyValues( &meta );
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 2: Modifying XMP 74
3. Modify the ‘CreatorTool’ property. First check the property exists and then apply the edit.
if(meta.DoesPropertyExist( kXMP_NS_XMP, "CreatorTool" )){
meta.SetProperty( kXMP_NS_XMP, "CreatorTool", "Updated By XMP SDK", NULL );
}
5. Create a new ‘dc:creator’ property, an ordered array. Set the first item of the array to ‘Author Name’.
Note how you must supply the correct options flag to create an array of a particular type:
meta.AppendArrayItem( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered,
"Author Name", NULL );
6. Add a new item to the new ‘creator’ array. Give the item a value of ‘Another Author Name’:
meta.AppendArrayItem( kXMP_NS_DC, "creator",
kXMP_PropArrayIsOrdered, "Another Author Name", NULL );
7. Update the ‘dc:title’ property. Set two items, one for English and one for French. This also creates the
x-default item:
meta.SetLocalizedText( kXMP_NS_DC, "title", "en", "en-US", "An English Title" );
meta.SetLocalizedText( kXMP_NS_DC, "title", "fr", "fr-FR", "Un Titre Francais" );
8. Add code to write the current values for the properties used in this tutorial to the console. See step 1.
9. Close the SXMPFiles instance and terminate XMPFiles and XMPCore, then see steps 17 and 18 of
“Creating the MyReadXMP application” on page 68.
10. If you are running the application in an IDE, place a break point on the very last line of the application.
11. Compile and run the application. The output in the console looks like the following. Note, however,
that no data has yet been written back to the file.
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 2: Modifying XMP 75
To demonstrate creating an XMP object from multiple buffers you will create a string of RDF XML. The RDF
represents the ‘subject’ property from the Dublin Core schema. To simulate using multiple buffers, we pass
ten characters at a time to the XMP object using ParseFromBuffer().
1. Create a new function just above the main function, that takes no parameters and returns an
SXMPMeta instance. Name the function createXMPFromRDF(). You can copy the code from
<xmpsdk>/samples/source/ModifyingXMP.cpp.
2. Inside the body of the function create a string to hold the following RDF:
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about='' xmlns:dc='http://purl.org/dc/elements/1.1/'>
<dc:subject>
<rdf:Bag>
<rdf:li>XMP</rdf:li>
<rdf:li>SDK</rdf:li>
<rdf:li>Sample</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:format>image/jpeg</dc:format>
</rdf:Description>
</rdf:RDF>
4. Loop over the RDF string and pass ten characters at a time to the XMP object using
ParseFromBuffer(). This is demonstrates how to create an XMP object from multiple buffers. Note
the use of the option flag kXMP_ParseMoreBuffers:
SXMPMeta meta;
int i;
for(i = 0; i < (long)strlen( rdf ) - 10; i += 10)
{
meta.ParseFromBuffer( &rdf[i], 10, kXMP_ParseMoreBuffers);
}
5. Terminate the input to the XMP object by supplying the final piece of RDF. For this final buffer, allow
the options flag to default to 0:
meta.ParseFromBuffer( &rdf[i], strlen(rdf) - i );
7. Create a new XMP object by calling the createXMPFromRDF() in the main function, just before the call
to close the SXMPFiles instance:
SXMPMeta rdfMeta = createXMPFromRDF();
8. Append the properties of the XMP object created with the RDF to the XMP object retrieved from the
file. This adds the subject property from the Dublin Core schema:
SXMPUtils::ApplyTemplate( &meta, rdfMeta, ( kXMPTemplate_IncludeInternalProperties
| kXMPTemplate_AddNewProperties | kXMPTemplate_ReplaceExistingProperties ) );
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 2: Modifying XMP 76
9. Add code to write the current values for the properties used in this tutorial to the console. See step 1
from “Modifying XMP properties” on page 73.
11. Compile and run the application. The output in the console window looks like the following:
1. Create a new function just above the main function, that accepts two arguments, and returns nothing.
The first argument is a string pointer that will point to the RDF produced by the serialization. The
second argument is a string, the path to a file to which to write the RDF. Name the function
writeRDFToFile(). The function signature is:
2. Inside the function add code to open a file and write the RDF to it:
ofstream outFile;
outFile.open( filename.c_str(), ios::out );
outFile << *rdf;
outFile.close();
3. Serialize the XMP object, specifying the default values for space and indentation and setting no option
flags. Add this code inside the main function, just before the call to close the SXMPFiles instance:
string xmpBuffer;
meta.SerializeToBuffer( &xmpBuffer, NULL, NULL, "", "", NULL );
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 3: Working with a custom schema 77
5. Serialize the XMP object again, this time with options to omit the XMP packet wrapper and serialize
the RDF in canonical format. This call omits the newline, indent, and baseIndent parameters, allowing
them to default. Write the data to a different file:
XMP_OptionBits outOpts = kXMP_OmitPacketWrapper | kXMP_UseCanonicalFormat;
meta.SerializeToBuffer( &xmpBuffer, outOpts );
writeRDFToFile(&xmpBuffer, filename+"_XMP_RDF_Canonical.txt");
6. Both output files can be found in the same folder as the test file. Open each file and review the
contents. Note that the XMP properties are exactly same, although the RDF is different.
1. Associate the modified XMP with the SXMPFiles instance. Place this just before the call to close the
SXMPFiles instance:
2. Compile and run the application. The call to CloseFile() actually writes out the modified XMP back
to the resource file.
In the course of this walkthrough, you will add custom properties to an XMP object, with complex
structures and values, demonstrating how to access nested structs, arrays, and language alternatives. You
will then use that object to write the metadata to a file.
A schema is provided for the walkthrough and is used to demonstrate how to register you own schema
and add complex properties. This schema is purely illustrative, and not intended for actual use. It contains
some properties that are available in other schemas, but is not intended to replace those. The sample
schema is designed to track document changes; it provides properties to record the authors of a
document, along with contact details for each author, and the document changes each author has made.
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 3: Working with a custom schema 78
Many of the initial steps are essentially the same as those in the first tutorial; use the directions in “Creating
the MyReadXMP application” on page 68.
1. Add the necessary headers and macros. You do not need to include XMPFiles. See steps 1 to 5 of
“Creating the MyReadXMP application” on page 68.
2. Initialize the XMPCore component; see step 6 of “Creating the MyReadXMP application” on page 68.
You do not need to initialize XMPFiles.
1. Register the namespaces and the prefixes that will be used for the schema:
const XMP_StringPtr kXMP_NS_SDK_EDIT = "http://ns.adobe/xmp/sdk/Edit/";
const XMP_StringPtr kXMP_NS_SDK_USERS = "http://ns.adobe/xmp/sdk/User/";
// ...
string actualPrefix;
SXMPMeta::RegisterNamespace( kXMP_NS_SDK_EDIT, "xsdkEdit", &actualPrefix );
SXMPMeta::RegisterNamespace( kXMP_NS_SDK_USERS, "xsdkUser",&actualPrefix );
1. Create an XMP object and create the DocumentUsers property as an unordered array, using the
correct option bits for the array items. The array will store structures, so use
kXMP_PropValueIsStruct:
SXMPMeta meta;
meta.AppendArrayItem( kXMP_NS_SDK_EDIT, "DocumentUsers", kXMP_PropValueIsArray,
NULL, kXMP_PropValueIsStruct );
2. Compose the path to the last item, a UserDetails structure, in the DocumentUsers array:
string userItemPath;
SXMPUtils::ComposeArrayItemPath( kXMP_NS_SDK_EDIT, "DocumentUsers",
kXMP_ArrayLastItem, &userItemPath );
3. We need to add fields and their values to the UserDetails structure. First, set the User field for the
UserDetails structure, using the path that was composed in the previous step:
4. Add a qualifier to the new User field. Create a path to the field, then use this path to set the qualifier
value:
string userFieldPath;
SXMPUtils::ComposeStructFieldPath( kXMP_NS_SDK_EDIT, userItemPath.c_str(),
kXMP_NS_SDK_USERS, "User", &userFieldPath );
meta.SetQualifier( kXMP_NS_SDK_EDIT, userFieldPath.c_str(),
kXMP_NS_SDK_USERS, "Role", "Dev Engineer" );
5. To set the DUID field, compose a path to the field and set the binary value using SetProperty_Int():
string duidPath;
SXMPUtils::ComposeStructFieldPath( kXMP_NS_SDK_EDIT, userItemPath.c_str(),
kXMP_NS_SDK_USERS, "DUID", &duidPath );
meta.SetProperty_Int( kXMP_NS_SDK_EDIT, duidPath.c_str(), 2 );
6. Add the ContactDetails field, which is itself a struct of type Contact. The options bit
kXMP_PropValueIsStruct makes the new property a struct:
8. Use the composed path to the ContactDetails field to add the three fields to the structure; Email,
Telephone, and BaseLocation. In each case, we must provide the correct options bit to describe the
property:
meta.SetStructField( kXMP_NS_SDK_EDIT, contactStructPath.c_str(),
kXMP_NS_SDK_USERS, "Email", NULL, kXMP_PropArrayIsAlternate );
9. Compose a path to the Email field using the previously composed path to the ContactDetails field,
and add two items to the array:
string path;
SXMPUtils::ComposeStructFieldPath( kXMP_NS_SDK_EDIT,
contactStructPath.c_str(), kXMP_NS_SDK_USERS, "Email", &path );
You have now completed adding the DocumentUsers property to the XMP object, with values for one user.
Here is the debugging dump of this object:
xsdkEdit: http://ns.adobe/xmp/sdk/Edit/ (0x80000000 : schema)
xsdkEdit:DocumentUsers (0x200 : isArray)
[1] (0x100 : isStruct)
xsdkUser:User = "John Smith" (0x10 : hasQual)
? xsdkUser:Role = "Dev Engineer" (0x20 : isQual)
xsdkUser:DUID = "2"
xsdkUser:ContactDetails (0x100 : isStruct)
xsdkUser:Email (0xE00 : isAlt isOrdered isArray)
[1] = "js@adobe.xmp.com"
[2] = "js@adobe.home.com"
xsdkUser:Telephone (0x200 : isArray)
[1] = "89112"
[2] = "84432"
xsdkUser:BaseLocation = "London"
We will now add the second set of properties from the custom schema, to represent editing actions in the
described document and note what actions have taken place. For this sample we add data that shows
when the document was created.
1. Create a new property for the DocumentEdit ordered array, an array that will hold EditDetail
structures:
meta.AppendArrayItem( kXMP_NS_SDK_EDIT, "DocumentEdit",
kXMP_PropArrayIsOrdered, NULL, kXMP_PropValueIsStruct );
2. Compose the path to the last item of the new array. Because we have not created any values yet, this
points to an EditDetails structure at the first index of the array:
string lastItemPath;
SXMPUtils::ComposeArrayItemPath( kXMP_NS_SDK_EDIT, "DocumentEdit",
kXMP_ArrayLastItem, &lastItemPath );
3. Use the composed path to add the EditDate field, and set the date with the current time:
SXMPUtils::ComposeStructFieldPath( kXMP_NS_SDK_EDIT, lastItemPath.c_str(),
kXMP_NS_SDK_EDIT, "EditDate", &path );
XMP_DateTime dt;
SXMPUtils::CurrentDateTime( &dt );
meta.SetProperty_Date( kXMP_NS_SDK_EDIT, path.c_str(), dt );
CHAPTER 4: Using the XMP Toolkit SDK Walkthrough 3: Working with a custom schema 81
4. Add the EditComments field to store an alt-text array for any localized versions of the
document-update comments. Compose a path to the field and use SetLocalizedText() to set the
value:
SXMPUtils::ComposeStructFieldPath( kXMP_NS_SDK_EDIT, lastItemPath.c_str(),
kXMP_NS_SDK_EDIT, "EditComments", &path );
1. Add code to serialize the XMP to RDF. See “Serializing the updated XMP” on page 76.
2. Add code to dump the XMP object to a file; to this, you add a callback function to dump the registered
namespaces to a file. See “Adding a debugging callback” on page 71.
4. Review the output from the application. Notice that the namespaces for the custom schema have
been registered. Here is an extract of the namespace dump with both the custom schema namespaces
and the prefix used:
xmpT: => http://ns.adobe.com/xap/1.0/t/
xmpTPg: => http://ns.adobe.com/xap/1.0/t/pg/
xml: => http://www.w3.org/XML/1998/namespace
xmpDM: => http://ns.adobe.com/xmp/1.0/DynamicMedia/
xmpNote: => http://ns.adobe.com/xmp/note/
xmpidq: => http://ns.adobe.com/xmp/Identifier/qual/1.0/
xsdkEdit: => http://ns.adobe/xmp/sdk/Edit/
xsdkUser: => http://ns.adobe/xmp/sdk/User/
5. Examine the serialized RDF and dumped XMP and note how the properties are organized.
A XMP Toolkit Build Reference
These tables map the build targets to the generated XMP Toolkit libraries for the supported platforms and
architectures. To build for each platform, use the correct batch file, shell script, or makefile:
For Windows
Target OS Target Library Generated Project Location and Build Target Location under
Names Name under <xmpsdk>\build\vc17 <xmpsdk>\public\libraries\
Target OS Target Library Generated Project Location and Build Target Location under
Names Name under <xmpsdk>/build/xcode/ <xmpsdk>/public/libraries/
82
CHAPTER A: XMP Toolkit Build Reference 83
For Android
For Unix/Linux
Target OS Target Library Generated Project Location and Build Target Location under
Names Name under <xmpsdk>/build/gcc/ <xmpsdk>/public/libraries/