// =================================================================================================
// Copyright 2009 Adobe
// 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. If you have received this file from a source other
// than Adobe, then your use, modification, or distribution of it requires the prior written permission
// of Adobe.
// =================================================================================================

#if AdobePrivate
// =================================================================================================
// Change history
// ==============
//
// Writers:
//  AWL Alan Lillich
//  ADC Amandeep Chawla
//  IJS Inder Jeet Singh
//  AB  Amit Bhatti
//
// mm-dd-yy who M.m-bbb Description of changes, most recent on top.
//
// 01-05-15 AB  5.6-f122 Provide more functionalities to Plugin( Existing XMP packet, PacketInfo, OpenFlags, Error Callback and progress notification),
//                       more standard handler access API getFileModDate,IsMetadataWritable,putXMP,getAssociatedResources.
//                       New plugin handler for MPEG4 with Exif support.
// 03-19-14 IJS 5.6-f098 [3709896] P2 Handler Modified to support spanned clip.
// 02-13-13 ADC 5.6-f035 [3498686, 3497304] XMP GetFileModDate() is extremely slow.
//
// 10-10-12 ADC 5.5-c012 Changed internal implementation of common error notification infrastructure.
// 08-08-12 AWL 5.5-c007 XMPCore error notifications for one case of XML parsing, no existing test failures.
//
// 06-23-09 AWL 5.0-c040-f049 Add hack to revert to global DLL locking.
// 06-23-09 AWL 5.0-c039-f048 Fix atomic increment and decrement to be optional, not on Solaris.
// 06-22-09 AWL 5.0-c038-f047 Fix locking code to use atomic increment and decrement.
// 06-18-09 AWL 5.0-c037-f046 Minor tweaks from code review.
// 06-12-09 AWL 5.0-c035-f045 Simplify the multiple lock permutations.
// 06-11-09 AWL 5.0-c034-f043 Finish threading revamp, implement friendly reader/writer locking.
//
// =================================================================================================
#endif // AdobePrivate

#include "public/include/XMP_Environment.h"

#include "source/XMP_LibUtils.hpp"

#include "source/UnicodeInlines.incl_cpp"

#include <cstdio>
#include <cstring>

// =================================================================================================

#ifndef TraceThreadLocks
    #define TraceThreadLocks 0
#endif

// -------------------------------------------------------------------------------------------------

extern "C" bool Initialize_LibUtils()
{
    return true;
}

// -------------------------------------------------------------------------------------------------

extern "C" void Terminate_LibUtils(){
    // Nothing to do.
}

// =================================================================================================
// Thread synchronization locks
// =================================================================================================

XMP_ReadWriteLock::XMP_ReadWriteLock() : beingWritten(false)
{
    #if XMP_DebugBuild && HaveAtomicIncrDecr
        this->lockCount = 0;

#if 0 //changing type of XMP_AtomicCounter from int32_t to std::atomic<int32_t>

        // Atomic counter must be 32 or 64 bits and naturally aligned.
        size_t counterSize = sizeof ( XMP_AtomicCounter );
        size_t counterOffset = XMP_OffsetOf ( XMP_ReadWriteLock, lockCount );
        XMP_Assert ( (counterSize == 4) || (counterSize == 8) );    // Counter must be 32 or 64 bits.
        XMP_Assert ( (counterOffset & (counterSize-1)) == 0 );      // Counter must be naturally aligned.
#endif

    #endif
    XMP_BasicRWLock_Initialize ( this->lock );
    #if TraceThreadLocks
        fprintf ( stderr, "Created lock %.8X\n", this );
    #endif
}

// ---------------------------------------------------------------------------------------------

XMP_ReadWriteLock::~XMP_ReadWriteLock()
{
    #if TraceThreadLocks
        fprintf ( stderr, "Deleting lock %.8X\n", this );
    #endif
    #if XMP_DebugBuild && HaveAtomicIncrDecr
        XMP_Assert ( this->lockCount == 0 );
    #endif
    XMP_BasicRWLock_Terminate ( this->lock );
}

// ---------------------------------------------------------------------------------------------

void XMP_ReadWriteLock::Acquire ( bool forWriting )
{
    #if TraceThreadLocks
        fprintf ( stderr, "Acquiring lock %.8X for %s, count %d%s\n",
                  this, (forWriting ? "writing" : "reading"), this->lockCount, (this->beingWritten ? ", being written" : "") );
    #endif

    if ( forWriting ) {
        XMP_BasicRWLock_AcquireForWrite ( this->lock );
        this->beingWritten = forWriting;
        #if XMP_DebugBuild && HaveAtomicIncrDecr
            XMP_Assert ( this->lockCount == 0 );
        #endif
    } else {
        XMP_BasicRWLock_AcquireForRead ( this->lock );
        XMP_Assert ( ! this->beingWritten );
    }
    #if XMP_DebugBuild && HaveAtomicIncrDecr
        XMP_AtomicIncrement ( this->lockCount );
    #endif


    #if TraceThreadLocks
        fprintf ( stderr, "Acquired lock %.8X for %s, count %d%s\n",
                  this, (forWriting ? "writing" : "reading"), this->lockCount, (this->beingWritten ? ", being written" : "") );
    #endif
}

// ---------------------------------------------------------------------------------------------

void XMP_ReadWriteLock::Release()
{
    #if TraceThreadLocks
        fprintf ( stderr, "Releasing lock %.8X, count %d%s\n", this, this->lockCount, (this->beingWritten ? ", being written" : "") );
    #endif

    #if XMP_DebugBuild && HaveAtomicIncrDecr
        XMP_Assert ( this->lockCount > 0 );
        XMP_AtomicDecrement ( this->lockCount );    // ! Do these before unlocking, that might release a waiting thread.
    #endif
    bool forWriting = this->beingWritten;

    if ( forWriting ) {
        this->beingWritten = false;
        XMP_BasicRWLock_ReleaseFromWrite ( this->lock );
    } else {
        XMP_BasicRWLock_ReleaseFromRead ( this->lock );
    }

    #if TraceThreadLocks
        fprintf ( stderr, "Released lock %.8X, count %d%s\n", this, this->lockCount, (this->beingWritten ? ", being written" : "") );
    #endif
}

// =================================================================================================

#if UseHomeGrownLock

    #if XMP_MacBuild | XMP_UNIXBuild | XMP_iOSBuild | XMP_AndroidBuild

        // -----------------------------------------------------------------------------------------

        // About pthread mutexes and conditions:
        //
        // The mutex protecting the condition must be locked before waiting for the condition. A
        // thread can wait for a condition to be signaled by calling the pthread_cond_wait
        // subroutine. The subroutine atomically unlocks the mutex and blocks the calling thread
        // until the condition is signaled. When the call returns, the mutex is locked again.

        #define InitializeBasicMutex(mutex) { int err = pthread_mutex_init ( &mutex, 0 ); XMP_Enforce ( err == 0 ); }
        #define TerminateBasicMutex(mutex)  { int err = pthread_mutex_destroy ( &mutex ); XMP_Enforce_NoThrow ( err == 0 ); }

        #define AcquireBasicMutex(mutex)    { int err = pthread_mutex_lock ( &mutex ); XMP_Enforce ( err == 0 ); }
        #define ReleaseBasicMutex(mutex)    { int err = pthread_mutex_unlock ( &mutex ); XMP_Enforce ( err == 0 ); }

        #define InitializeBasicQueue(queue) { int err = pthread_cond_init ( &queue, 0 ); XMP_Enforce ( err == 0 ); }
        #define TerminateBasicQueue(queue)  { int err = pthread_cond_destroy ( &queue ); XMP_Enforce_NoThrow ( err == 0 ); }

        #define WaitOnBasicQueue(queue,mutex)   { int err = pthread_cond_wait ( &queue, &mutex ); XMP_Enforce ( err == 0 ); }
        #define ReleaseOneBasicQueue(queue)     { int err = pthread_cond_signal ( &queue ); XMP_Enforce ( err == 0 ); }
        #define ReleaseAllBasicQueue(queue)     { int err = pthread_cond_broadcast ( &queue ); XMP_Enforce ( err == 0 ); }

        // -----------------------------------------------------------------------------------------

    #elif XMP_WinBuild

        // -----------------------------------------------------------------------------------------

        #define InitializeBasicMutex(mutex) { InitializeCriticalSection ( &mutex ); }
        #define TerminateBasicMutex(mutex)  { DeleteCriticalSection ( &mutex ); }

        #define AcquireBasicMutex(mutex)    { EnterCriticalSection ( &mutex ); }
        #define ReleaseBasicMutex(mutex)    { LeaveCriticalSection ( &mutex ); }

        #if ! BuildLocksForWinXP

            // About Win32 condition variables (not on XP):
            //
            // Condition variables enable threads to atomically release a lock and enter the
            // sleeping state. They can be used with critical sections or slim reader/writer (SRW)
            // locks. Condition variables support operations that "wake one" or "wake all" waiting
            // threads. After a thread is woken, it re-acquires the lock it released when the thread
            // entered the sleeping state.

            #define InitializeBasicQueue(queue) { InitializeConditionVariable ( &queue ); }
            #define TerminateBasicQueue(queue)  /* Do nothing. */

            #define WaitOnBasicQueue(queue,mutex)   \
                { BOOL ok = SleepConditionVariableCS ( &queue, &mutex, INFINITE /* timeout */ ); XMP_Enforce ( ok ); }

            #define ReleaseOneBasicQueue(queue) { WakeConditionVariable ( &queue ); }
            #define ReleaseAllBasicQueue(queue) { WakeAllConditionVariable ( &queue ); }

        #else

            // Need to create our own queue for Windows XP. This is not a general queue, it depends
            // on the usage inside XMP_HomeGrownLock where the queueMutex guarantees that the
            // queueing operations are done single threaded.

            #define InitializeBasicQueue(queue) /* Do nothing. */
            #define TerminateBasicQueue(queue)  /* Do nothing. */

            #define WaitOnBasicQueue(queue,mutex)   { queue.Wait ( mutex ); }
            #define ReleaseOneBasicQueue(queue)     { queue.ReleaseOne(); }
            #define ReleaseAllBasicQueue(queue)     { queue.ReleaseAll(); }

            // -------------------------------------------------------------------------------------

            XMP_WinXP_HGQueue::XMP_WinXP_HGQueue() : queueEvent(0), waitCount(0), releaseAll(false)
            {
                this->queueEvent = CreateEvent ( NULL, FALSE, TRUE, NULL ); // Auto reset, initially clear.
                XMP_Enforce ( this->queueEvent != 0 );
            }

            // -------------------------------------------------------------------------------------

            XMP_WinXP_HGQueue::~XMP_WinXP_HGQueue()
            {
                CloseHandle ( this->queueEvent );
            }

            // -------------------------------------------------------------------------------------

            void XMP_WinXP_HGQueue::Wait ( XMP_BasicMutex & queueMutex )
            {
                ++this->waitCount;  // ! Does not need atomic increment, protected by queue mutex.
                ReleaseBasicMutex ( queueMutex );
                DWORD status = WaitForSingleObject ( this->queueEvent, INFINITE );
                if ( status != WAIT_OBJECT_0 ) XMP_Throw ( "Failure from WaitForSingleObject", kXMPErr_ExternalFailure );
                AcquireBasicMutex ( queueMutex );
                --this->waitCount;  // ! Does not need atomic decrement, protected by queue mutex.

                if ( this->releaseAll ) {
                    if ( this->waitCount == 0 ) {
                        this->releaseAll = false;
                    } else {
                        BOOL ok = SetEvent ( this->queueEvent );
                        if ( ! ok ) XMP_Throw ( "Failure from SetEvent", kXMPErr_ExternalFailure );
                    }
                }
            }

            // -------------------------------------------------------------------------------------

            void XMP_WinXP_HGQueue::ReleaseOne()
            {
                XMP_Assert ( ! this->releaseAll );
                BOOL ok = SetEvent ( this->queueEvent );
                if ( ! ok ) XMP_Throw ( "Failure from SetEvent", kXMPErr_ExternalFailure );
            }

            // -------------------------------------------------------------------------------------

            void XMP_WinXP_HGQueue::ReleaseAll()
            {
                this->releaseAll = true;
                BOOL ok = SetEvent ( this->queueEvent );
                if ( ! ok ) XMP_Throw ( "Failure from SetEvent", kXMPErr_ExternalFailure );
            }

        #endif

        // -----------------------------------------------------------------------------------------

    #endif

    // =============================================================================================

    XMP_HomeGrownLock::XMP_HomeGrownLock() : lockCount(0), readersWaiting(0), writersWaiting(0), beingWritten(false)
    {
        InitializeBasicMutex ( this->queueMutex );
        InitializeBasicQueue ( this->writerQueue );
        InitializeBasicQueue ( this->readerQueue );
    }

    // =============================================================================================

    XMP_HomeGrownLock::~XMP_HomeGrownLock()
    {
        TerminateBasicMutex ( this->queueMutex );
        TerminateBasicQueue ( this->writerQueue );
        TerminateBasicQueue ( this->readerQueue );
    }

    // =============================================================================================

    void XMP_HomeGrownLock::AcquireForRead()
    {
        XMP_AutoMutex autoMutex ( &this->queueMutex );

        ++this->readersWaiting; // ! Does not need atomic increment, protected by queue mutex.
        while ( (this->beingWritten) || (this->writersWaiting > 0) ) {
            // Don't allow more readers if writers are waiting.
            WaitOnBasicQueue ( this->readerQueue, this->queueMutex );
        }
        --this->readersWaiting; // ! Does not need atomic decrement, protected by queue mutex.
        XMP_Assert ( ! this->beingWritten );

        ++this->lockCount;  // ! Does not need atomic increment, protected by queue mutex.
    }

    // =============================================================================================

    void XMP_HomeGrownLock::AcquireForWrite()
    {
        XMP_AutoMutex autoMutex ( &this->queueMutex );

        ++this->writersWaiting; // ! Does not need atomic increment, protected by queue mutex.
        while ( this->lockCount > 0 ) {
            WaitOnBasicQueue ( this->writerQueue, this->queueMutex );
        }
        --this->writersWaiting; // ! Does not need atomic decrement, protected by queue mutex.
        XMP_Assert ( (! this->beingWritten) && (this->lockCount == 0) );

        ++this->lockCount;  // ! Does not need atomic increment, protected by queue mutex.
        this->beingWritten = true;
    }

    // =============================================================================================

    void XMP_HomeGrownLock::ReleaseFromRead()
    {
        XMP_AutoMutex autoMutex ( &this->queueMutex );

        XMP_Assert ( (! this->beingWritten) && (this->lockCount > 0) );
        --this->lockCount;  // ! Does not need atomic decrement, protected by queue mutex.

        if ( this->writersWaiting > 0 ) {
            ReleaseOneBasicQueue ( this->writerQueue );
        } else if ( this->readersWaiting > 0 ) {
            ReleaseAllBasicQueue ( this->readerQueue );
        }

    }

    // =============================================================================================

    void XMP_HomeGrownLock::ReleaseFromWrite()
    {
        XMP_AutoMutex autoMutex ( &this->queueMutex );

        XMP_Assert ( this->beingWritten && (this->lockCount == 1) );
        --this->lockCount;  // ! Does not need atomic decrement, protected by queue mutex.
        this->beingWritten = false;

        if ( this->writersWaiting > 0 ) {
            ReleaseOneBasicQueue ( this->writerQueue );
        } else if ( this->readersWaiting > 0 ) {
            ReleaseAllBasicQueue ( this->readerQueue );
        }
    }

    // =============================================================================================

#endif

// =================================================================================================
// Data structure dumping utilities
// ================================

void
DumpClearString ( const XMP_VarString & value, XMP_TextOutputProc outProc, void * refCon )
{

    char buffer [20];
    bool prevNormal;
    XMP_Status status = 0;

    XMP_StringPtr spanStart, spanEnd;
    XMP_StringPtr valueEnd = &value[0] + value.size();

    spanStart = &value[0];
    while ( spanStart < valueEnd ) {

        // Output the next span of regular characters.
        for ( spanEnd = spanStart; spanEnd < valueEnd; ++spanEnd ) {
            if ( *spanEnd > 0x7F ) break;
            if ( (*spanEnd < 0x20) && (*spanEnd != kTab) && (*spanEnd != kLF) ) break;
        }
        if ( spanStart != spanEnd ) status = (*outProc) ( refCon,  spanStart, (XMP_StringLen)(spanEnd-spanStart) );
        if ( status != 0 ) break;
        spanStart = spanEnd;

        // Output the next span of irregular characters.
        prevNormal = true;
        for ( spanEnd = spanStart; spanEnd < valueEnd; ++spanEnd ) {
            if ( ((0x20 <= *spanEnd) && (*spanEnd <= 0x7F)) || (*spanEnd == kTab) || (*spanEnd == kLF) ) break;
            char space = ' ';
            if ( prevNormal ) space = '<';
            status = (*outProc) ( refCon, &space, 1 );
            if ( status != 0 ) break;
            OutProcHexByte ( *spanEnd );
            prevNormal = false;
        }
        if ( ! prevNormal ) {
            status = (*outProc) ( refCon, ">", 1 );
            if ( status != 0 ) return;
        }
        spanStart = spanEnd;

    }

}   // DumpClearString

// -------------------------------------------------------------------------------------------------

static void
DumpStringMap ( const XMP_StringMap & map, XMP_StringPtr label, XMP_TextOutputProc outProc, void * refCon )
{
    XMP_cStringMapPos currPos;
    XMP_cStringMapPos endPos = map.end();

    size_t maxLen = 0;
    for ( currPos = map.begin(); currPos != endPos; ++currPos ) {
        size_t currLen = currPos->first.size();
        if ( currLen > maxLen ) maxLen = currLen;
    }

    OutProcNewline();
    OutProcLiteral ( label );
    OutProcNewline();

    for ( currPos = map.begin(); currPos != endPos; ++currPos ) {
        OutProcNChars ( "  ", 2 );
        DumpClearString ( currPos->first, outProc, refCon );
        OutProcPadding ( maxLen - currPos->first.size() );
        OutProcNChars ( " => ", 4 );
        DumpClearString ( currPos->second, outProc, refCon );
        OutProcNewline();
    }

}   // DumpStringMap

// =================================================================================================
// Namespace Tables
// =================================================================================================

XMP_NamespaceTable::XMP_NamespaceTable ( const XMP_NamespaceTable & presets )
{
    XMP_AutoLock presetLock ( &presets.lock, kXMP_ReadLock );

    this->uriToPrefixMap = presets.uriToPrefixMap;
    this->prefixToURIMap = presets.prefixToURIMap;

}   // XMP_NamespaceTable::XMP_NamespaceTable

// =================================================================================================

bool XMP_NamespaceTable::Define ( XMP_StringPtr _uri, XMP_StringPtr _suggPrefix,
                                  XMP_StringPtr * prefixPtr, XMP_StringLen * prefixLen )
{
    XMP_AutoLock tableLock ( &this->lock, kXMP_WriteLock );
    bool prefixMatches = false;

    XMP_Assert ( (_uri != 0) && (*_uri != 0) && (_suggPrefix != 0) && (*_suggPrefix != 0) );

    XMP_VarString   uri ( _uri );
    XMP_VarString   suggPrefix ( _suggPrefix );
    if ( suggPrefix[suggPrefix.size()-1] != ':' ) suggPrefix += ':';
    VerifySimpleXMLName ( _suggPrefix, _suggPrefix+suggPrefix.size()-1 );   // Exclude the colon.

    XMP_StringMapPos uriPos = this->uriToPrefixMap.find ( uri );

    if ( uriPos == this->uriToPrefixMap.end() ) {

        // The URI is not yet registered, make sure we use a unique prefix.

        XMP_VarString uniqPrefix ( suggPrefix );
        int  suffix = 0;
        char buffer [32];   // AUDIT: Plenty of room for the "_%d_" suffix.

        while ( true ) {
            if ( this->prefixToURIMap.find ( uniqPrefix ) == this->prefixToURIMap.end() ) break;
            ++suffix;
            snprintf ( buffer, sizeof(buffer), "_%d_:", suffix );   // AUDIT: Using sizeof for snprintf length is safe.
            uniqPrefix = suggPrefix;
            uniqPrefix.erase ( uniqPrefix.size()-1 );   // ! Remove the trailing ':'.
            uniqPrefix += buffer;
        }

        // Add the new namespace to both maps.

        XMP_StringPair newNS ( uri, uniqPrefix );
        uriPos = this->uriToPrefixMap.insert ( this->uriToPrefixMap.end(), newNS );

        newNS.first.swap ( newNS.second );
        (void) this->prefixToURIMap.insert ( this->prefixToURIMap.end(), newNS );

    }

    // Return the actual prefix and see if it matches the suggested prefix.

    if ( prefixPtr != 0 ) *prefixPtr = uriPos->second.c_str();
    if ( prefixLen != 0 ) *prefixLen = (XMP_StringLen)uriPos->second.size();

    prefixMatches = ( uriPos->second == suggPrefix );
    return prefixMatches;

}   // XMP_NamespaceTable::Define

// =================================================================================================

bool XMP_NamespaceTable::GetPrefix ( XMP_StringPtr _uri, XMP_StringPtr * prefixPtr, XMP_StringLen * prefixLen ) const
{
    XMP_AutoLock tableLock ( &this->lock, kXMP_ReadLock );
    bool found = false;

    XMP_Assert ( (_uri != 0) && (*_uri != 0) );

    XMP_VarString uri ( _uri );
    XMP_cStringMapPos uriPos = this->uriToPrefixMap.find ( uri );

    if ( uriPos != this->uriToPrefixMap.end() ) {
        if ( prefixPtr != 0 ) *prefixPtr = uriPos->second.c_str();
        if ( prefixLen != 0 ) *prefixLen = (XMP_StringLen)uriPos->second.size();
        found = true;
    }

    return found;

}   // XMP_NamespaceTable::GetPrefix

// =================================================================================================

bool XMP_NamespaceTable::GetURI ( XMP_StringPtr _prefix, XMP_StringPtr * uriPtr, XMP_StringLen * uriLen ) const
{
    XMP_AutoLock tableLock ( &this->lock, kXMP_ReadLock );

    bool found = false;

    XMP_Assert ( (_prefix != 0) && (*_prefix != 0) );

    XMP_VarString prefix ( _prefix );
    if ( prefix[prefix.size()-1] != ':' ) prefix += ':';
    XMP_cStringMapPos prefixPos = this->prefixToURIMap.find ( prefix );

    if ( prefixPos != this->prefixToURIMap.end() ) {
        if ( uriPtr != 0 ) *uriPtr = prefixPos->second.c_str();
        if ( uriLen != 0 ) *uriLen = (XMP_StringLen)prefixPos->second.size();
        found = true;
    }

    return found;

}   // XMP_NamespaceTable::GetURI

// =================================================================================================

void XMP_NamespaceTable::Dump ( XMP_TextOutputProc outProc, void * refCon ) const
{
    XMP_AutoLock tableLock ( &this->lock, kXMP_ReadLock );

    XMP_cStringMapPos p2uEnd = this->prefixToURIMap.end();  // ! Move up to avoid gcc complaints.
    XMP_cStringMapPos u2pEnd = this->uriToPrefixMap.end();

    DumpStringMap ( this->prefixToURIMap, "Dumping namespace prefix to URI map", outProc, refCon );

    if ( this->prefixToURIMap.size() != this->uriToPrefixMap.size() ) {
        OutProcLiteral ( "** bad namespace map sizes **" );
        XMP_Throw ( "Fatal namespace map problem", kXMPErr_InternalFailure );
    }

    for ( XMP_cStringMapPos nsLeft = this->prefixToURIMap.begin(); nsLeft != p2uEnd; ++nsLeft ) {

        XMP_cStringMapPos nsOther = this->uriToPrefixMap.find ( nsLeft->second );
        if ( (nsOther == u2pEnd) || (nsLeft != this->prefixToURIMap.find ( nsOther->second )) ) {
            OutProcLiteral ( "  ** bad namespace URI **  " );
            DumpClearString ( nsLeft->second, outProc, refCon );
            break;
        }

        for ( XMP_cStringMapPos nsRight = nsLeft; nsRight != p2uEnd; ++nsRight ) {
            if ( nsRight == nsLeft ) continue;  // ! Can't start at nsLeft+1, no operator+!
            if ( nsLeft->second == nsRight->second ) {
                OutProcLiteral ( "  ** duplicate namespace URI **  " );
                DumpClearString ( nsLeft->second, outProc, refCon );
                break;
            }
        }

    }

    for ( XMP_cStringMapPos nsLeft = this->uriToPrefixMap.begin(); nsLeft != u2pEnd; ++nsLeft ) {

        XMP_cStringMapPos nsOther = this->prefixToURIMap.find ( nsLeft->second );
        if ( (nsOther == p2uEnd) || (nsLeft != this->uriToPrefixMap.find ( nsOther->second )) ) {
            OutProcLiteral ( "  ** bad namespace prefix **  " );
            DumpClearString ( nsLeft->second, outProc, refCon );
            break;
        }

        for ( XMP_cStringMapPos nsRight = nsLeft; nsRight != u2pEnd; ++nsRight ) {
            if ( nsRight == nsLeft ) continue;  // ! Can't start at nsLeft+1, no operator+!
            if ( nsLeft->second == nsRight->second ) {
                OutProcLiteral ( "  ** duplicate namespace prefix **  " );
                DumpClearString ( nsLeft->second, outProc, refCon );
                break;
            }
        }

    }

}   // XMP_NamespaceTable::Dump

// =================================================================================================
static XMP_Bool matchdigit ( XMP_StringPtr text ) {
    if ( *text >= '0' && *text <= '9' )
        return true;
    return false;
}

static XMP_Bool matchUpperCase ( XMP_StringPtr text ) {
    if ( *text >= 'A' && *text <= 'Z' )
        return true;
    return false;
}

static XMP_Bool matchLowerCase ( XMP_StringPtr text ) {
    if ( *text >= 'a' && *text <= 'z' )
        return true;
    return false;
}

/* matchhere: search for regexp at beginning of text */
static XMP_Bool matchhere ( XMP_StringPtr regexp, XMP_StringPtr text ) {
    if ( regexp[0] == '\0' )
        return true;
    if ( regexp[0] == '\\' ) {
        if ( regexp[1] == 'd' ) {
            if ( matchdigit(text) )
                return matchhere ( regexp+2, text+1 );
            else
                return false;
        }
        else if ( regexp[1] == 'W' ) {
            if ( matchUpperCase(text) )
                return matchhere ( regexp+2, text+1 );
            else
                return false;
        }
        else if ( regexp[1] == 'w' ) {
            if ( matchLowerCase(text) )
                return matchhere ( regexp+2, text+1 );
            else
                return false;
        }
    }

    if ( regexp[0] == '$' && regexp[1] == '\0' )
        return *text == '\0';

    if ( *text != '\0' && regexp[0] == *text )
        return matchhere ( regexp+1, text+1 );
    return 0;
}

/* match: search for regexp anywhere in text */
static XMP_Bool match ( XMP_StringPtr regexp, XMP_StringPtr text ) {
    if ( regexp[0] == '^' )
        return matchhere ( regexp+1, text );
    do {    /* must look even if string is empty */
        if ( matchhere ( regexp, text ) )
            return true;
    } while ( *text++ != '\0' );
    return false;
}

XMP_Bool XMP_RegExp::Match ( XMP_StringPtr s )
{
    if ( regExpStr.size() == 0 )
        return true;
    if ( s == NULL )
        return false;
    return match ( this->regExpStr.c_str(), s );
}
// =================================================================================================
