Main Page | Modules | Compound List | File List | Compound Members | File Members

# XMP API Transition Guide

This is a guide for XMP clients making the transition from the original C++ API to the revamped C++ API. The reader is assumed to be familiar with the old API, at least as far as used in the software being ported. The guide is presented as a series of old-to-new examples of typical usage. Reading the new API overview might provide background and motivation, but should not be necessary.

The new API is totally different in detail, the names of all include files, functions, types, and constants have changed. Many of the changes though are fairly cosmetic and trivial to port. In most other cases the new API should be more convenient to use. The iteration/enumeration functions are the only ones with significant visible semantic change.

This guide does not describe all of the old XMP functions, only those which have some non-trivial aspect for porting. If something is not described here, look at the old and new function signatures. They should be similar enough to make the transition obvious.

This is particularly true for the old UtilityXAP functions, they all have obvious mappings. Some of the UtilityXAP functions have been moved to TXMPMeta, mainly the binary get/set functions (UtilityXAP::GetBoolean has become TXMPMeta::GetProperty_Bool). Low value functions like UtilityXAP::AnalyzeStep or UtilityXAP::FilterPropPath have been removed.

Classes

The client API is defined by 3 C++ template classes: TXMPMeta, TXMPIterator, and TXMPUtils. These correspond to the old MetaXAP, XAPPaths, and UtilityXAP classes. The templates are instantiated with a string class which is used for the return of UTF-8 strings. The XMP headers will declare instantiated classes with the names SXMPMeta, SXMPIterator, and SXMPUtils.

The associated string class must provide these member functions, identical to those of std::string:

        assign ( const char * str, size_t len )
        const char * c_str()
        size_t size()

The result of assign does not matter, it is only used in a "void" manner.

Note:
  All of the code examples below assume the templates are instantiated using std::string.

Headers

Clients should only include XMP.hpp to access XMP, and XMP.incl_cpp to compile the template instantiations and client-side glue. The template string class is specified by defining TXMP_STRING_TYPE before the #include of either. A simple example is:

        #define TXMP_STRING_TYPE std::string
        #include "XMP.hpp"
        #include "XMP.incl_cpp"

Basic Property Access

The old functions MetaXAP::get and MetaXAP::set are replaced by TXMPMeta::GetProperty and TXMPMeta::SetProperty. The basic mapping for these is very simple.

The old way to set a simple property:

        meta.set ( XAP_NS_XAP, "CreatorTool", "Adobe Illustrator" );

The new way to set a simple property:

        meta.SetProperty ( kXMP_NS_XMP, "CreatorTool", "Adobe Illustrator" );

The old way to get a simple property:

        bool        found;
        std::string value;
        XAPFeatures features;

        found = meta.get ( XAP_NS_XAP, "CreatorTool", value, features );

The new way to get a simple property:

        bool           found;
        std::string    value;
        XMP_OptionBits options;

        found = meta.GetProperty ( kXMP_NS_XMP, "CreatorTool", &value, &options );

As before, you can pass XPath expressions for the second parameter to access elements of arrays or fields of structs. But there are new routines to make that easier. In addition, the new API allows you to eliminate the annoying '/*' for array items, reducing "Array/*[1]" to "Array[1]".

The options parameter returns considerably more information than the old features parameter. See the enum constants in XMP_Const.h for details.

Accessing Array Items

There are now special get/set functions for array items that take the index as an integer. This relieves you of the burden of formatting a path expression with the index as a decimal string. The examples below for the first element generalizes to any index in the obvious ways.

Note:
  The old get/set functions could only be used for existing array items. The new forms can be used to insert or append new items. See also the section on Creating Array Items.

The old way to access the first element of an array:

        meta.set ( XAP_NS_XAP, "Authors/*[1]", "Frank Zappa" );
        found = meta.get ( XAP_NS_XAP, "Authors/*[1]", value, features );

The new way to access the first element of an array:

        meta.SetArrayItem ( kXMP_NS_XMP, "Authors", 1, "Frank Zappa" );
        meta.GetArrayItem ( kXMP_NS_XMP, "Authors", 1, &value, &options );

The old way to access the last element of an array:

        meta.set ( XAP_NS_XAP, "Authors/*[last()]", "Frank Zappa" );
        found = meta.get ( XAP_NS_XAP, "Authors/*[last()]", value, features );

The new way to access the last element of an array:

        meta.SetArrayItem ( kXMP_NS_XMP, "Authors", kXMP_ArrayLastItem, "Frank Zappa" );
        meta.GetArrayItem ( kXMP_NS_XMP, "Authors", kXMP_ArrayLastItem, &value, &options );

Creating Array Items

The old API had rather cumbersome createFirstItem and append functions to use when adding an item to an array. The new API has a more convenient AppendArrayItem function.

The old way to create an array item, appending to the end:

        size_t count;

        try {
            count = meta.count ( XAP_NS_XAP, "Authors/*" );
        } catch ( ... ) {
            count = 0
        }

        if ( count == 0 ) {
            meta.createFirstItem ( XAP_NS_XAP, "Authors", "Frank Zappa", xap_seq );
        } else {
            meta.append ( XAP_NS_XAP, "Authors/*[last()]", "Frank Zappa" )
        }

The new way to create an array item, appending to the end:

        meta.AppendArrayItem ( kXMP_NS_XMP, "Authors", kXMP_PropArrayIsOrdered, "Frank Zappa" );

Here's how to see if an array exists or how big it is:

        bool    exists  = meta.DoesPropertyExist ( kXMP_NS_XMP, "Authors" );
        size_t  count   = meta.CountArrayItems ( kXMP_NS_XMP, "Authors" );

There are also DoesArrayItemExist and DoesStructFieldExist functions.

Accessing Struct Fields

In the old API you accessed a field in a struct by writing an XPath expression of the general form "Struct/ns:Field". This has the subtle drawback that you must use the namespace prefix in the XPath expression. But prefixes are not guaranteed! The new API has functions that take the namespace URI.

The old way to access a field in a struct:

        meta.set ( XAP_NS_XAP_T_PG, "MaxPageSize/stDim:unit", "inch" );
        found = meta.get ( XAP_NS_XAP_T_PG, "MaxPageSize/stDim:unit", value, features );

The new way to access a field in a struct:

        meta.SetStructField ( XAP_NS_XAP_T_PG, "MaxPageSize", kXMP_NS_ST_Dim, "unit" "inch" );
        found = meta.GetStructField ( XAP_NS_XAP_T_PG, "MaxPageSize", kXMP_NS_ST_Dim, "unit", &value, &options );

Accessing Composite Data Structures

The old API forced you to write full XPath expressions for composite data structures with arrays or structs nested inside each other. This forced you to know the XPath syntax, and suffer the risks of using namespace prefixes for struct fields. The new API has path composition functions in XMPUtils to avoid both of these problems. You can compose the entire path then call Get/SetProperty, or compose all but the last portion then use Get/SetArrayItem or Get/SetStructField.

The examples here only show ComposeArrayItemPath, ComposeStructFieldPath is an obvious analog.

The old way to access a struct field within an array item:

        found = meta.get ( XAP_NS_XAP, "Thumbnails/*[1]/xapGImg:format", value, features );

The new way to access a struct field within an array item:

        std::string itemPath;

        XMPUtils::ComposeArrayItemPath ( kXMP_NS_XMP, "Thumbnails", 1, &itemPath );
        found = meta.GetStructField ( kXMP_NS_XMP, itemPath.c_str(),
                                      kXMP_NS_XMP_G_IMG, "format", &value, &options );

Note:
  As in the old API, the outermost, or "schema", namespace is the first parameter to all functions that manipulate properties. Even if the second parameter is a complex path expression.

Parsing and Serializing

The new API has the same construct-and-parse capability as the old API. This parses one buffer that must be a complete collection of XMP. Parsing multiple buffers where you do the I/O is similar to before, differing mainly in a reversal of the "I'm done" signal.

The old way to read and parse:

        MetaXAP meta;
        char    buffer [...];
        size_t  length;

        while ( true ) {
            -- read into buffer, setting length
            if ( length != 0 ) break;
            meta.parse ( buffer, length );
        }
        meta.parse ( buffer, 0, true );

The new way to read and parse:

        SXMPMeta    meta ( 0, 0 );
        char    buffer [...];
        size_t  length;

        while ( true ) {
            -- read into buffer, setting length
            if ( length != 0 ) break;
            meta.ParseFromBuffer ( buffer, length, kXMP_ParseMoreBuffers );
        }
        meta.ParseFromBuffer ( buffer, 0 );

Serialization in the old API was a multi-step process. You used MetaXAP::serialize to generate a hidden serialization, then used MetaXAP::extractSerialization to extract that, then used UtilityXAP::CreateXMLPacket to get the XMP packet wrapping. The New API combines all of this into TXMPMeta::SerializeToBuffer.

The old way to write a serialized packet:

        size_t      rdfLength = meta.serialize();
        std::string header, trailer;
        char        buffer [...];

        UtilityXAP::CreateXMLPacket ( "", true, 4096, "\n", header, trailer );
        -- write the header string
        while ( rdfLength > sizeof(buffer) ) {
            meta.extractSerialization ( buffer, sizeof(buffer) );
            -- write this buffer
            rdfLength -= sizeof(buffer);
        }
        meta.extractSerialization ( buffer, rdfLength );
        -- write this buffer (only rdfLength bytes)
        -- write the trailer string

The new way to write a serialized packet:

        std::string packet;

        meta.SerializeToBuffer ( &packet );
        -- write the packet string

Iteration

The functions to iterate over the XMP data tree have not changed a lot at first glance, but the way they operate has changed considerably.

The old API essentially offered two ways to do an iteration:

- One call to generate a list of all leaf nodes
- Program your own full traversal, recursively one level at a time

The new API performs a typical depth first traversal, visiting every node by default, with options to omit certain classes of nodes. Instead of mapping old patterns to new, it might make more sense to just use the new API and understand what it will return. It will also help to review the XMP data model, first documented in the November 2003 version of the XMP Specification. Pay attention to the option bits returned during iteration, they tell you what kind of node you're visiting.

Simple, default iteration code in the new API:

        std::string     str1, str2, str3;
        XMP_OptionBits  options;

        SXMPIterator iter ( meta );
        while ( iter.Next ( &str1, &str2, &str3, &options ) ) {
            fprintf ( log, "  %s %s = \"%s\", 0x%X\n",
                      str1.c_str(), str2.c_str(), str3.c_str(), options );
        }

For this RDF input:

        <rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
          <rdf:Description rdf:about='' xmlns:ns1='ns:test1/' xmlns:ns2='ns:test2/'>

            <ns1:SimpleProp1>Simple1 value</ns1:SimpleProp1>
            <ns1:SimpleProp2 xml:lang='x-default'>Simple2 value</ns1:SimpleProp2>

            <ns1:ArrayProp1>
              <rdf:Bag>
                <rdf:li>Item1.1 value</rdf:li>
                <rdf:li>Item1.2 value</rdf:li>
              </rdf:Bag>
            </ns1:ArrayProp1>

            <ns1:ArrayProp2>
              <rdf:Alt>
                <rdf:li xml:lang='x-one'>Item2.1 value</rdf:li>
                <rdf:li xml:lang='x-two'>Item2.2 value</rdf:li>
              </rdf:Alt>
            </ns1:ArrayProp2>

            <ns1:StructProp rdf:parseType='Resource'>
              <ns2:Field1>Field1 value</ns2:Field1>
              <ns2:Field2>Field2 value</ns2:Field2>
            </ns1:StructProp>

            <ns1:QualProp1 rdf:parseType='Resource'>
              <rdf:value>Prop value</rdf:value>
              <ns2:Qual>Qual value</ns2:Qual>
            </ns1:QualProp1>

            <ns1:QualProp2 rdf:parseType='Resource'>
              <rdf:value xml:lang='x-default'>Prop value</rdf:value>
              <ns2:Qual>Qual value</ns2:Qual>
            </ns1:QualProp2>

            <ns1:NestedStructProp rdf:parseType='Resource'>
              <ns2:Outer rdf:parseType='Resource'>
                <ns2:Middle rdf:parseType='Resource'>
                  <ns2:Inner rdf:parseType='Resource'>
                    <ns2:Field1>Field1 value</ns2:Field1>
                    <ns2:Field2>Field2 value</ns2:Field2>
                  </ns2:Inner>
                </ns2:Middle>
              </ns2:Outer>
            </ns1:NestedStructProp>

          </rdf:Description>
        </rdf:RDF>

The iteration shown above would print (spacing added by hand for legibility):

        ns:test1/  = "", 0x80000000

        ns:test1/ SimpleProp1 = "Simple1 value", 0x0

        ns:test1/ SimpleProp2 = "Simple2 value", 0x50
        ns:test1/ SimpleProp2/?xml:lang = "x-default", 0x20

        ns:test1/ ArrayProp1 = "", 0x200
        ns:test1/ ArrayProp1[1] = "Item1.1 value", 0x0
        ns:test1/ ArrayProp1[2] = "Item1.2 value", 0x0

        ns:test1/ ArrayProp2 = "", 0xE00
        ns:test1/ ArrayProp2[1] = "Item2.1 value", 0x50
        ns:test1/ ArrayProp2[1]/?xml:lang = "x-one", 0x20
        ns:test1/ ArrayProp2[2] = "Item2.2 value", 0x50
        ns:test1/ ArrayProp2[2]/?xml:lang = "x-two", 0x20

        ns:test1/ StructProp = "", 0x100
        ns:test1/ StructProp/ns2:Field1 = "Field1 value", 0x0
        ns:test1/ StructProp/ns2:Field2 = "Field2 value", 0x0

        ns:test1/ QualProp1 = "Prop value", 0x10
        ns:test1/ QualProp1/?ns2:Qual = "Qual value", 0x20

        ns:test1/ QualProp2 = "Prop value", 0x50
        ns:test1/ QualProp2/?xml:lang = "x-default", 0x20
        ns:test1/ QualProp2/?ns2:Qual = "Qual value", 0x20

        ns:test1/ NestedStructProp = "", 0x100
        ns:test1/ NestedStructProp/ns2:Outer = "", 0x100
        ns:test1/ NestedStructProp/ns2:Outer/ns2:Middle = "", 0x100
        ns:test1/ NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner = "", 0x100
        ns:test1/ NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner/ns2:Field1 = "Field1 value", 0x0
        ns:test1/ NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner/ns2:Field2 = "Field2 value", 0x0