// =================================================================================================
// Copyright 2002-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================

#include "XMP_Environment.h"	// ! This must be the first include!
#include "XMPCore_Impl.hpp"
#include "XMPIterator.hpp"

#include <string>
#include <stdio.h>	// For snprintf.

#if XMP_WinBuild
    #pragma warning ( disable : 4702 )	// unreachable code
    #pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
    #pragma warning ( disable : 4996 )	// '...' was declared deprecated
#endif

// =================================================================================================
// Support Routines
// =================================================================================================

namespace DngXmpSdk {

#ifndef TraceIterators
    #define TraceIterators 0
#endif

#if TraceIterators
    static const char * sStageNames[] = { "before", "self", "qualifiers", "children" };
#endif

static XMP_Node * sDummySchema = 0;	// ! Used for some ugliness with aliases.

// -------------------------------------------------------------------------------------------------
// AddSchemaProps
// --------------
//
// Add the top level properties to the IterNode for a schema.

static void
AddSchemaProps ( IterInfo & info, IterNode & iterSchema, const XMP_Node * xmpSchema )
{
    info = info;	// Avoid unused parameter warning.
    #if TraceIterators
        printf ( "    Adding properties of %s\n", xmpSchema->name.c_str() );
    #endif

    for ( size_t propNum = 0, propLim = xmpSchema->children.size(); propNum != propLim; ++propNum ) {
        const XMP_Node * xmpProp = xmpSchema->children[propNum];
        // *** set the has-aliases bit when appropriate
        iterSchema.children.push_back ( IterNode ( xmpProp->options, xmpProp->name, 0 ) );
        #if TraceIterators
            printf ( "        %s\n", xmpProp->name.c_str() );
        #endif
    }

}	// AddSchemaProps

// -------------------------------------------------------------------------------------------------
// AddSchemaAliases
// ----------------
//
// Add the aliases to the IterNode for a schema, if the corresponding actual exists.

static void
AddSchemaAliases ( IterInfo & info, IterNode & iterSchema, XMP_StringPtr schemaURI )
{

    // We're showing the aliases also. Look them up by their namespace prefix. Yes, the alias map is
    // sorted so we could process just that portion. But that takes more code and the extra speed
    // isn't worth it. (Plus this way we avoid a dependence on the map implementation.) Lookup the
    // XMP node from the alias, to make sure the actual exists.

    #if TraceIterators
        printf ( "    Adding aliases\n", schemaURI );
    #endif

    XMP_StringPtr nsPrefix;
    XMP_StringLen nsLen;
    bool found = XMPMeta::GetNamespacePrefix ( schemaURI, &nsPrefix, &nsLen );
    if ( ! found ) XMP_Throw ( "Unknown iteration namespace", kXMPErr_BadSchema );

    XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin();
    XMP_AliasMapPos endAlias  = sRegisteredAliasMap->end();

    for ( ; currAlias != endAlias; ++currAlias ) {
        if ( XMP_LitNMatch ( currAlias->first.c_str(), nsPrefix, nsLen ) ) {
            const XMP_Node * actualProp = FindConstNode ( &info.xmpObj->tree, currAlias->second );
            if ( actualProp != 0 ) {
                iterSchema.children.push_back ( IterNode ( (actualProp->options | kXMP_PropIsAlias), currAlias->first, 0 ) );
                #if TraceIterators
                    printf ( "        %s  =>  %s\n", currAlias->first.c_str(), actualProp->name.c_str() );
                #endif
            }
        }
    }

}	// AddSchemaAliases

// -------------------------------------------------------------------------------------------------
// AddNodeOffspring
// ----------------
//
// Add the immediate children and qualifiers to an IterNode.

static void
AddNodeOffspring ( IterInfo & info, IterNode & iterParent, const XMP_Node * xmpParent )
{
    XMP_VarString currPath ( iterParent.fullPath );
    size_t        leafOffset = iterParent.fullPath.size();

    if ( (! xmpParent->qualifiers.empty()) && (! (info.options & kXMP_IterOmitQualifiers)) ) {

        #if TraceIterators
            printf ( "    Adding qualifiers of %s\n", currPath.c_str() );
        #endif

        currPath += "/?";	// All qualifiers are named and use paths like "Prop/?Qual".
        leafOffset += 2;

        for ( size_t qualNum = 0, qualLim = xmpParent->qualifiers.size(); qualNum != qualLim; ++qualNum ) {
            const XMP_Node * xmpQual = xmpParent->qualifiers[qualNum];
            currPath += xmpQual->name;
            iterParent.qualifiers.push_back ( IterNode ( xmpQual->options, currPath, leafOffset ) );
            currPath.erase ( leafOffset );
            #if TraceIterators
                printf ( "        %s\n", xmpQual->name.c_str() );
            #endif
        }

        leafOffset -= 2;
        currPath.erase ( leafOffset );

    }

    if ( ! xmpParent->children.empty() ) {

        #if TraceIterators
            printf ( "    Adding children of %s\n", currPath.c_str() );
        #endif

        XMP_Assert ( xmpParent->options & kXMP_PropCompositeMask );

        if ( xmpParent->options & kXMP_PropValueIsStruct ) {
            currPath += '/';
            leafOffset += 1;
        }

        for ( size_t childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) {
            const XMP_Node * xmpChild = xmpParent->children[childNum];
            if ( ! (xmpParent->options & kXMP_PropValueIsArray) ) {
                currPath += xmpChild->name;
            } else {
                char buffer [32];	// AUDIT: Using sizeof(buffer) below for snprintf length is safe.
                snprintf ( buffer, sizeof(buffer), "[%d]", childNum+1 );	// ! XPath indices are one-based.
                currPath += buffer;
            }
            iterParent.children.push_back ( IterNode ( xmpChild->options, currPath, leafOffset ) );
            currPath.erase ( leafOffset );
            #if TraceIterators
                printf ( "        %s\n", (iterParent.children.back().fullPath.c_str() + leafOffset) );
            #endif
        }

    }

}	// AddNodeOffspring

// -------------------------------------------------------------------------------------------------
// SetCurrSchema
// -------------

static inline void
SetCurrSchema ( IterInfo & info, XMP_StringPtr schemaName )
{

    info.currSchema = schemaName;
    #if 0	// *** XMP_DebugBuild
        info._schemaPtr = info.currSchema.c_str();
    #endif

}	// SetCurrSchema

static inline void
SetCurrSchema ( IterInfo & info, XMP_VarString & schemaName )
{

    info.currSchema = schemaName;
    #if 0	// *** XMP_DebugBuild
        info._schemaPtr = info.currSchema.c_str();
    #endif

}	// SetCurrSchema

// -------------------------------------------------------------------------------------------------
// AdvanceIterPos
// --------------
//
// Adjust currPos and possibly endPos for the next step in a pre-order depth-first traversal. The
// current node has just been visited, move on to its qualifiers, children, then siblings, or back
// up to an ancestor. AdvanceIterPos either moves to a property or qualifier node that can be
// visited, or to the end of the entire iteration.

static void
AdvanceIterPos ( IterInfo & info )
{
    // -------------------------------------------------------------------------------------------
    // Keep looking until we find a node to visit or the end of everything. The first time through
    // the current node will exist, we just visited it. But we have to keep looking if the current
    // node was the last of its siblings or is an empty schema.

    // ! It is possible that info.currPos == info.endPos on entry. Don't dereference info.currPos yet!

    while ( true ) {

        if ( info.currPos == info.endPos ) {

            // ------------------------------------------------------------------------------------
            // At the end of a set of siblings, move up to an ancestor. We've either just finished
            // the qualifiers and will move to the children, or have just finished the children and
            // will move on to the next sibling.

            if ( info.ancestors.empty() ) break;	// We're at the end of the schema list.

            IterPosPair & parent = info.ancestors.back();
            info.currPos = parent.first;
            info.endPos  = parent.second;
            info.ancestors.pop_back();

            #if TraceIterators
                printf ( "    Moved up to %s, stage = %s\n",
                         info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] );
            #endif

        } else {

            // -------------------------------------------------------------------------------------------
            // Decide what to do with this iteration node based on its state. Don't use a switch statement,
            // some of the cases want to break from the loop. A break in a switch just exits the case.

            #if TraceIterators
                printf ( "    Moving from %s, stage = %s\n",
                         info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] );
            #endif

            if ( info.currPos->visitStage == kIter_BeforeVisit ) {		// Visit this node now.
                if ( info.currPos->options & kXMP_SchemaNode ) SetCurrSchema ( info, info.currPos->fullPath );
                break;
            }

            if ( info.currPos->visitStage == kIter_VisitSelf ) {		// Just finished visiting the value portion.
                info.currPos->visitStage = kIter_VisitQualifiers;		// Start visiting the qualifiers.
                if ( ! info.currPos->qualifiers.empty() ) {
                    info.ancestors.push_back ( IterPosPair ( info.currPos, info.endPos ) );
                    info.endPos  = info.currPos->qualifiers.end();		// ! Set the parent's endPos before changing currPos!
                    info.currPos = info.currPos->qualifiers.begin();
                    break;
                }
            }

            if ( info.currPos->visitStage == kIter_VisitQualifiers ) {	// Just finished visiting the qualifiers.
                info.currPos->qualifiers.clear();
                info.currPos->visitStage = kIter_VisitChildren;			// Start visiting the children.
                if ( ! info.currPos->children.empty() ) {
                    info.ancestors.push_back ( IterPosPair ( info.currPos, info.endPos ) );
                    info.endPos  = info.currPos->children.end();		// ! Set the parent's endPos before changing currPos!
                    info.currPos = info.currPos->children.begin();
                    break;
                }
            }

            if ( info.currPos->visitStage == kIter_VisitChildren ) {	// Just finished visiting the children.
                info.currPos->children.clear();
                ++info.currPos;											// Move to the next sibling.
                continue;
            }

            #if TraceIterators
                if ( info.currPos != info.endPos ) {
                    printf ( "    Moved to %s, stage = %s\n",
                             info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] );
                }
            #endif

        }

    }	// Loop to find the next node.

    XMP_Assert ( (info.currPos == info.endPos) || (info.currPos->visitStage == kIter_BeforeVisit) );

}	// AdvanceIterPos

// -------------------------------------------------------------------------------------------------
// GetNextXMPNode
// --------------
//
// Used by XMPIterator::Next to obtain the next XMP node, ignoring the kXMP_IterJustLeafNodes flag.
// This isolates some messy code, allowing a clean loop in Next if kXMP_IterJustLeafNodes is set.

static const XMP_Node *
GetNextXMPNode ( IterInfo & info )
{
    const XMP_Node * xmpNode = 0;

    // ----------------------------------------------------------------------------------------------
    // On entry currPos points to an iteration node whose state is either before-visit or visit-self.
    // If it is before-visit then we will return that node's value part now. If it is visit-self it
    // means the previous iteration returned the value portion of that node, so we can advance to the
    // next node in the iteration tree. Then we find the corresponding XMP node, allowing for the XMP
    // tree to have been modified since that part of the iteration tree was constructed.

    // ! NOTE: Supporting aliases throws in some nastiness with schemas. There might not be any XMP
    // ! node for the schema, but we still have to visit it because of possible aliases. The static
    // ! sDummySchema is returned if there is no real schema node.

    if ( info.currPos->visitStage != kIter_BeforeVisit ) AdvanceIterPos ( info );

    bool isSchemaNode = false;
    XMP_ExpandedXPath expPath;	// Keep outside the loop to avoid constant construct/destruct.

    while ( info.currPos != info.endPos ) {

        isSchemaNode = XMP_NodeIsSchema ( info.currPos->options );
        if ( isSchemaNode ) {
            SetCurrSchema ( info, info.currPos->fullPath );
            xmpNode = FindConstSchema ( &info.xmpObj->tree, info.currPos->fullPath.c_str() );
            if ( xmpNode == 0 ) xmpNode = sDummySchema;
        } else {
            ExpandXPath ( info.currSchema.c_str(), info.currPos->fullPath.c_str(), &expPath );
            xmpNode = FindConstNode ( &info.xmpObj->tree, expPath );
        }
        if ( xmpNode != 0 ) break;	// Exit the loop, we found a live XMP node.

        info.currPos->visitStage = kIter_VisitChildren;	// Make AdvanceIterPos move to the next sibling.
        info.currPos->children.clear();
        info.currPos->qualifiers.clear();
        AdvanceIterPos ( info );

    }

    if ( info.currPos == info.endPos ) return 0;

    // -------------------------------------------------------------------------------------------
    // Now we've got the iteration node and corresponding XMP node. Add the iteration children for
    // structs and arrays. The children of schema were added when the iterator was constructed.

    XMP_Assert ( info.currPos->visitStage == kIter_BeforeVisit );

    if ( info.currPos->visitStage == kIter_BeforeVisit ) {
        if ( (! isSchemaNode) && (! (info.options & kXMP_IterJustChildren)) ) {
            AddNodeOffspring ( info, *info.currPos, xmpNode );
        }
        info.currPos->visitStage = kIter_VisitSelf;
    }

    return xmpNode;

}	// GetNextXMPNode

// =================================================================================================
// Init/Term
// =================================================================================================

// -------------------------------------------------------------------------------------------------
// Initialize
// ----------

/* class static */ bool
XMPIterator::Initialize()
{
    sDummySchema = new XMP_Node ( 0, "dummy:schema/", kXMP_SchemaNode);
    return true;

}	// Initialize

// -------------------------------------------------------------------------------------------------
// Terminate
// ----------

/* class static */ void
XMPIterator::Terminate() RELEASE_NO_THROW
{
    delete ( sDummySchema );
    sDummySchema = 0;
    return;

}	// Terminate

// -------------------------------------------------------------------------------------------------
// Unlock
// ------

void
XMPIterator::Unlock	( XMP_OptionBits options )
{
    options = options;	// Avoid unused parameter warning.

    XMPMeta::Unlock ( 0 );

}	// Unlock

// =================================================================================================
// Constructors
// =================================================================================================

// -------------------------------------------------------------------------------------------------
// XMPIterator
// -----------
//
// Constructor for iterations over the nodes in an XMPMeta object. This builds a tree of iteration
// nodes that caches the existing node names of the XMPMeta object. The iteration tree is a partial
// replica of the XMPMeta tree. The initial iteration tree normally has just the root node, all of
// the schema nodes for a full object iteration. Lower level nodes (children and qualifiers) are
// added when the parent is visited. If the kXMP_IterJustChildren option is passed then the initial
// iterator includes the children and the parent is marked as done. The iteration tree nodes are
// pruned when they are no longer needed.

XMPIterator::XMPIterator ( const XMPMeta & xmpObj,
                           XMP_StringPtr   schemaNS,
                           XMP_StringPtr   propName,
                           XMP_OptionBits  options ) : info(IterInfo(options,&xmpObj)), clientRefs(0)
{
    if ( (options & kXMP_IterClassMask) != kXMP_IterProperties ) {
        XMP_Throw ( "Unsupported iteration kind", kXMPErr_BadOptions );
    }

    // *** Lock the XMPMeta object if we ever stop using a full DLL lock.

    if ( *propName != 0 ) {

        // An iterator rooted at a specific node.

        #if TraceIterators
            printf ( "\nNew XMP property iterator for \"%s\", options = %X\n    Schema = %s, root = %s\n",
                     xmpObj.tree.name.c_str(), options, schemaNS, propName );
        #endif

        XMP_ExpandedXPath propPath;
        ExpandXPath ( schemaNS, propName, &propPath );
        XMP_Node * propNode = FindConstNode ( &xmpObj.tree, propPath );	// If not found get empty iteration.

        if ( propNode != 0 ) {

            XMP_VarString rootName ( propPath[1].step );	// The schema is [0].
            for ( size_t i = 2; i < propPath.size(); ++i ) {
                XMP_OptionBits stepKind = GetStepKind ( propPath[i].options );
                if ( stepKind <= kXMP_QualifierStep ) rootName += '/';
                rootName += propPath[i].step;
            }

            propName = rootName.c_str();
            size_t leafOffset = rootName.size();
            while ( (leafOffset > 0) && (propName[leafOffset] != '/') && (propName[leafOffset] != '[') ) --leafOffset;
            if ( propName[leafOffset] == '/' ) ++leafOffset;

            info.tree.children.push_back ( IterNode ( propNode->options, propName, leafOffset ) );
            SetCurrSchema ( info, propPath[kSchemaStep].step.c_str() );
            if ( info.options & kXMP_IterJustChildren ) {
                AddNodeOffspring ( info, info.tree.children.back(), propNode );
            }

        }

    } else if ( *schemaNS != 0 ) {

        // An iterator for all properties in one schema.

        #if TraceIterators
            printf ( "\nNew XMP schema iterator for \"%s\", options = %X\n    Schema = %s\n",
                     xmpObj.tree.name.c_str(), options, schemaNS );
        #endif

        info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, schemaNS, 0 ) );
        IterNode & iterSchema = info.tree.children.back();

        XMP_Node * xmpSchema = FindConstSchema ( &xmpObj.tree, schemaNS );
        if ( xmpSchema != 0 ) AddSchemaProps ( info, iterSchema, xmpSchema );

        if ( info.options & kXMP_IterIncludeAliases ) AddSchemaAliases ( info, iterSchema, schemaNS );

        if ( iterSchema.children.empty() ) {
            info.tree.children.pop_back();	// No properties, remove the schema node.
        } else {
            SetCurrSchema ( info, schemaNS );
        }

    } else {

        // An iterator for all properties in all schema. First add schema that exist (have children),
        // adding aliases from them if appropriate. Then add schema that have no actual properties
        // but do have aliases to existing properties, if we're including aliases in the iteration.

        #if TraceIterators
            printf ( "\nNew XMP tree iterator for \"%s\", options = %X\n",
                     xmpObj.tree.name.c_str(), options );
        #endif

        // First pick up the schema that exist.

        for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum != schemaLim; ++schemaNum ) {

            const XMP_Node * xmpSchema = xmpObj.tree.children[schemaNum];
            info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, xmpSchema->name, 0 ) );
            IterNode & iterSchema = info.tree.children.back();

            if ( ! (info.options & kXMP_IterJustChildren) ) {
                AddSchemaProps ( info, iterSchema, xmpSchema );
                if ( info.options & kXMP_IterIncludeAliases ) AddSchemaAliases ( info, iterSchema, xmpSchema->name.c_str() );
                if ( iterSchema.children.empty() ) info.tree.children.pop_back();	// No properties, remove the schema node.
            }

        }

        if ( info.options & kXMP_IterIncludeAliases ) {

            // Add the schema that only have aliases. The most convenient, and safest way, is to go
            // through the registered namespaces, see if it exists, and let AddSchemaAliases do its
            // thing if not. Don't combine with the above loop, it is nicer to have the "real" stuff
            // be in storage order (not subject to the namespace map order).

            // ! We don't do the kXMP_IterJustChildren handing in the same way here as above. The
            // ! existing schema (presumably) have actual children. We need to call AddSchemaAliases
            // ! here to determine if the namespace has any aliases to existing properties. We then
            // ! strip the children if necessary.

            XMP_cStringMapPos currNS = sNamespaceURIToPrefixMap->begin();
            XMP_cStringMapPos endNS  = sNamespaceURIToPrefixMap->end();
            for ( ; currNS != endNS; ++currNS ) {
                XMP_StringPtr schemaName = currNS->first.c_str();
                if ( FindConstSchema ( &xmpObj.tree, schemaName ) != 0 ) continue;
                info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, schemaName, 0 ) );
                IterNode & iterSchema = info.tree.children.back();
                AddSchemaAliases ( info, iterSchema, schemaName );
                if ( iterSchema.children.empty() ) {
                    info.tree.children.pop_back();	// No aliases, remove the schema node.
                } else if ( info.options & kXMP_IterJustChildren ) {
                    iterSchema.children.clear();	// Get rid of the children.
                }
            }

        }

    }

    // Set the current iteration position to the first node to be visited.

    info.currPos = info.tree.children.begin();
    info.endPos  = info.tree.children.end();

    if ( (info.options & kXMP_IterJustChildren) && (info.currPos != info.endPos) && (*schemaNS != 0) ) {
        info.currPos->visitStage = kIter_VisitSelf;
    }

    #if TraceIterators
        if ( info.currPos == info.endPos ) {
            printf ( "    ** Empty iteration **\n" );
        } else {
            printf ( "    Initial node %s, stage = %s, iterator @ %.8X\n",
                     info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this );
        }
    #endif

}	// XMPIterator for XMPMeta objects

// -------------------------------------------------------------------------------------------------
// XMPIterator
// -----------
//
// Constructor for iterations over global tables such as registered namespaces or aliases.

XMPIterator::XMPIterator ( XMP_StringPtr  schemaNS,
                           XMP_StringPtr  propName,
                           XMP_OptionBits options ) : info(IterInfo(options,0)), clientRefs(0)
{

    XMP_Throw ( "Unimplemented XMPIterator constructor for global tables", kXMPErr_Unimplemented );
        void * p; p = &schemaNS; p = &propName; p = &options;	// Avoid unused param warnings.

}	// XMPIterator for global tables

// -------------------------------------------------------------------------------------------------
// ~XMPIterator
// -----------

XMPIterator::~XMPIterator() RELEASE_NO_THROW
{
    XMP_Assert ( this->clientRefs <= 0 );
    // Let everything else default.

}	// ~XMPIterator

// =================================================================================================
// Iteration Methods
// =================================================================================================

// -------------------------------------------------------------------------------------------------
// Next
// ----
//
// Do a preorder traversal of the cached nodes.

// *** Need to document the relationships between currPos, endPos, and visitStage.

bool
XMPIterator::Next ( XMP_StringPtr *	 schemaNS,
                    XMP_StringLen *	 nsSize,
                    XMP_StringPtr *	 propPath,
                    XMP_StringLen *	 pathSize,
                    XMP_StringPtr *	 propValue,
                    XMP_StringLen *	 valueSize,
                    XMP_OptionBits * propOptions )
{
    // *** Lock the XMPMeta object if we ever stop using a full DLL lock.

    // ! NOTE: Supporting aliases throws in some nastiness with schemas. There might not be any XMP
    // ! node for the schema, but we still have to visit it because of possible aliases.

    if ( info.currPos == info.endPos ) return false;	// Happens at the start of an empty iteration.

    #if TraceIterators
        printf ( "Next iteration from %s, stage = %s, iterator @ %.8X\n",
                 info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this );
    #endif

    const XMP_Node * xmpNode = GetNextXMPNode ( info );
    if ( xmpNode == 0 ) return false;
    bool isSchemaNode = XMP_NodeIsSchema ( info.currPos->options );

    if ( info.options & kXMP_IterJustLeafNodes ) {
        while ( isSchemaNode || (! xmpNode->children.empty()) ) {
            info.currPos->visitStage = kIter_VisitQualifiers;	// Skip to this node's children.
            xmpNode = GetNextXMPNode ( info );
            if ( xmpNode == 0 ) return false;
            isSchemaNode = XMP_NodeIsSchema ( info.currPos->options );
        }
    }

    *schemaNS = info.currSchema.c_str();
    *nsSize   = info.currSchema.size();

    *propOptions = info.currPos->options;

    *propPath  = "";
    *pathSize  = 0;
    *propValue = "";
    *valueSize = 0;

    if ( ! (*propOptions & kXMP_SchemaNode) ) {

        *propPath = info.currPos->fullPath.c_str();
        *pathSize = info.currPos->fullPath.size();
        if ( info.options & kXMP_IterJustLeafName ) {
            *propPath += info.currPos->leafOffset;
            *pathSize -= info.currPos->leafOffset;
        }

        if ( ! (*propOptions & kXMP_PropCompositeMask) ) {
            *propValue = xmpNode->value.c_str();
            *valueSize = xmpNode->value.size();
        }

    }

    #if TraceIterators
        printf ( "    Next node %s, stage = %s\n",
                 info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] );
    #endif

    return true;

}	// Next

// -------------------------------------------------------------------------------------------------
// Skip
// ----
//
// Skip some portion of the traversal related to the last visited node. We skip either that node's
// children, or those children and the previous node's siblings. The implementation might look a bit
// awkward because info.currNode always points to the next node to be visited. We might already have
// moved past the things to skip, e.g. if the previous node was simple and the last of its siblings.

enum {
    kXMP_ValidIterSkipOptions	= kXMP_IterSkipSubtree | kXMP_IterSkipSiblings
};

void
XMPIterator::Skip ( XMP_OptionBits iterOptions )
{
//	if ( (info.currPos == kIter_NullPos) )  XMP_Throw ( "No prior position to skip from", kXMPErr_BadIterPosition );
    if ( iterOptions == 0 ) XMP_Throw ( "Must specify what to skip", kXMPErr_BadOptions );
    if ( (iterOptions & ~kXMP_ValidIterSkipOptions) != 0 ) XMP_Throw ( "Undefined options", kXMPErr_BadOptions );

    #if TraceIterators
        printf ( "Skipping from %s, stage = %s, iterator @ %.8X",
                 info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this );
    #endif

    if ( iterOptions & kXMP_IterSkipSubtree ) {
        #if TraceIterators
            printf ( ", mode = subtree\n" );
        #endif
        info.currPos->visitStage = kIter_VisitChildren;
    } else if ( iterOptions & kXMP_IterSkipSiblings ) {
        #if TraceIterators
            printf ( ", mode = siblings\n" );
        #endif
        info.currPos = info.endPos;
        AdvanceIterPos ( info );
    }
    #if TraceIterators
        printf ( "    Skipped to %s, stage = %s\n",
                 info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] );
    #endif


}	// Skip

// -------------------------------------------------------------------------------------------------
// UnlockIter
// ----------

void
XMPIterator::UnlockIter	( XMP_OptionBits options )
{
    options = options;	// Avoid unused parameter warning.

    XMPMeta::Unlock ( 0 );

}	// UnlockIter

} // namespace DngXmpSdk
// =================================================================================================
