Blame xmpsdk/src/XMPUtils-FileInfo.cpp

Packit Service 21b5d1
// =================================================================================================
Packit Service 21b5d1
// Copyright 2002-2008 Adobe Systems Incorporated
Packit Service 21b5d1
// All Rights Reserved.
Packit Service 21b5d1
//
Packit Service 21b5d1
// NOTICE:	Adobe permits you to use, modify, and distribute this file in accordance with the terms
Packit Service 21b5d1
// of the Adobe license agreement accompanying it.
Packit Service 21b5d1
// =================================================================================================
Packit Service 21b5d1
Packit Service 21b5d1
#include "XMP_Environment.h"	// ! This must be the first include!
Packit Service 21b5d1
#include "XMPCore_Impl.hpp"
Packit Service 21b5d1
Packit Service 21b5d1
#include "XMPUtils.hpp"
Packit Service 21b5d1
Packit Service 21b5d1
#include <time.h>
Packit Service 21b5d1
#include <string.h>
Packit Service 21b5d1
#include <cstdlib>
Packit Service 21b5d1
#include <locale.h>
Packit Service 21b5d1
#include <errno.h>
Packit Service 21b5d1
Packit Service 21b5d1
#include <stdio.h>	// For snprintf.
Packit Service 21b5d1
Packit Service 21b5d1
#if XMP_WinBuild
Packit Service 21b5d1
#ifdef _MSC_VER
Packit Service 21b5d1
	#pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
Packit Service 21b5d1
#endif
Packit Service 21b5d1
#endif
Packit Service 21b5d1
Packit Service 21b5d1
// =================================================================================================
Packit Service 21b5d1
// Local Types and Constants
Packit Service 21b5d1
// ========================= 
Packit Service 21b5d1
Packit Service 21b5d1
typedef unsigned long	UniCodePoint;
Packit Service 21b5d1
Packit Service 21b5d1
enum UniCharKind {
Packit Service 21b5d1
	UCK_normal,
Packit Service 21b5d1
	UCK_space,
Packit Service 21b5d1
	UCK_comma,
Packit Service 21b5d1
	UCK_semicolon,
Packit Service 21b5d1
	UCK_quote,
Packit Service 21b5d1
	UCK_control
Packit Service 21b5d1
};
Packit Service 21b5d1
typedef enum UniCharKind	UniCharKind;
Packit Service 21b5d1
Packit Service 21b5d1
#define UnsByte(c)	((unsigned char)(c))
Packit Service 21b5d1
#define UCP(u)		((UniCodePoint)(u))
Packit Service 21b5d1
	// ! Needed on Windows (& PC Linux?) for inequalities with literals ito avoid sign extension.
Packit Service 21b5d1
Packit Service 21b5d1
#ifndef TraceMultiFile
Packit Service 21b5d1
	#define TraceMultiFile	0
Packit Service 21b5d1
#endif
Packit Service 21b5d1
Packit Service 21b5d1
// =================================================================================================
Packit Service 21b5d1
// Static Variables
Packit Service 21b5d1
// ================
Packit Service 21b5d1
Packit Service 21b5d1
// =================================================================================================
Packit Service 21b5d1
// Local Utilities
Packit Service 21b5d1
// ===============
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// ClassifyCharacter
Packit Service 21b5d1
// -----------------
Packit Service 21b5d1
Packit Service 21b5d1
static void
Packit Service 21b5d1
ClassifyCharacter ( XMP_StringPtr fullString, size_t offset,
Packit Service 21b5d1
					UniCharKind * charKind, size_t * charSize, UniCodePoint * uniChar )
Packit Service 21b5d1
{
Packit Service 21b5d1
	*charKind = UCK_normal; // Assume typical case.
Packit Service 21b5d1
	
Packit Service 21b5d1
	unsigned char	currByte = UnsByte ( fullString[offset] );
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( currByte < UnsByte(0x80) ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// ----------------------------------------
Packit Service 21b5d1
		// We've got a single byte ASCII character.
Packit Service 21b5d1
Packit Service 21b5d1
		*charSize = 1;
Packit Service 21b5d1
		*uniChar = currByte;
Packit Service 21b5d1
Packit Service 21b5d1
		if ( currByte > UnsByte(0x22) ) {
Packit Service 21b5d1
Packit Service 21b5d1
			if ( currByte == UnsByte(0x2C) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;
Packit Service 21b5d1
			} else if ( currByte == UnsByte(0x3B) ) {
Packit Service 21b5d1
				*charKind = UCK_semicolon;
Packit Service 21b5d1
			} else if ( (currByte == UnsByte(0x5B)) || (currByte == UnsByte(0x5D)) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;	// ! ASCII '[' and ']' are used as quotes in Chinese and Korean.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else {	// currByte <= 0x22
Packit Service 21b5d1
Packit Service 21b5d1
			if ( currByte == UnsByte(0x22) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;
Packit Service 21b5d1
			} else if ( currByte == UnsByte(0x21) ) {
Packit Service 21b5d1
				*charKind = UCK_normal;
Packit Service 21b5d1
			} else if ( currByte == UnsByte(0x20) ) {
Packit Service 21b5d1
				*charKind = UCK_space;
Packit Service 21b5d1
			} else {
Packit Service 21b5d1
				*charKind = UCK_control;
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
Packit Service 21b5d1
	} else {	// currByte >= 0x80
Packit Service 21b5d1
	
Packit Service 21b5d1
		// ---------------------------------------------------------------------------------------
Packit Service 21b5d1
		// We've got a multibyte Unicode character. The first byte has the number of bytes and the
Packit Service 21b5d1
		// highest order bits. The other bytes each add 6 more bits. Compose the UTF-32 form so we
Packit Service 21b5d1
		// can classify directly with the Unicode code points. Order the upperBits tests to be
Packit Service 21b5d1
		// fastest for Japan, probably the most common non-ASCII usage.
Packit Service 21b5d1
		
Packit Service 21b5d1
		*charSize = 0;
Packit Service 21b5d1
		*uniChar = currByte;
Packit Service 21b5d1
		while ( (*uniChar & 0x80) != 0 ) {	// Count the leading 1 bits in the byte.
Packit Service 21b5d1
			++(*charSize);
Packit Service 21b5d1
			*uniChar = *uniChar << 1;
Packit Service 21b5d1
		}
Packit Service 21b5d1
		XMP_Assert ( (offset + *charSize) <= strlen(fullString) );
Packit Service 21b5d1
		
Packit Service 21b5d1
		*uniChar = *uniChar & 0x7F;			// Put the character bits in the bottom of uniChar.
Packit Service 21b5d1
		*uniChar = *uniChar >> *charSize;
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( size_t i = (offset + 1); i < (offset + *charSize); ++i ) {
Packit Service 21b5d1
			*uniChar = (*uniChar << 6) | (UnsByte(fullString[i]) & 0x3F);
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_Uns32 upperBits = *uniChar >> 8;	// First filter on just the high order 24 bits.
Packit Service 21b5d1
Packit Service 21b5d1
		if ( upperBits == 0xFF ) {			// U+FFxx
Packit Service 21b5d1
Packit Service 21b5d1
			if ( *uniChar == UCP(0xFF0C) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+FF0C, full width comma.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0xFF1B) ) {
Packit Service 21b5d1
				*charKind = UCK_semicolon;		// U+FF1B, full width semicolon.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0xFF64) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+FF64, half width ideographic comma.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0xFE ) {	// U+FE--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( *uniChar == UCP(0xFE50) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+FE50, small comma.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0xFE51) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+FE51, small ideographic comma.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0xFE54) ) {
Packit Service 21b5d1
				*charKind = UCK_semicolon;		// U+FE54, small semicolon.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0x30 ) {	// U+30--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( *uniChar == UCP(0x3000) ) {
Packit Service 21b5d1
				*charKind = UCK_space;			// U+3000, ideographic space.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0x3001) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+3001, ideographic comma.
Packit Service 21b5d1
			} else if ( (UCP(0x3008) <= *uniChar) && (*uniChar <= UCP(0x300F)) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;			// U+3008..U+300F, various quotes.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0x303F) ) {
Packit Service 21b5d1
				*charKind = UCK_space;			// U+303F, ideographic half fill space.
Packit Service 21b5d1
			} else if ( (UCP(0x301D) <= *uniChar) && (*uniChar <= UCP(0x301F)) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;			// U+301D..U+301F, double prime quotes.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0x20 ) {	// U+20--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( (UCP(0x2000) <= *uniChar) && (*uniChar <= UCP(0x200B)) ) {
Packit Service 21b5d1
				*charKind = UCK_space;			// U+2000..U+200B, en quad through zero width space.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0x2015) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;			// U+2015, dash quote.
Packit Service 21b5d1
			} else if ( (UCP(0x2018) <= *uniChar) && (*uniChar <= UCP(0x201F)) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;			// U+2018..U+201F, various quotes.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0x2028) ) {
Packit Service 21b5d1
				*charKind = UCK_control;			// U+2028, line separator.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0x2029) ) {
Packit Service 21b5d1
				*charKind = UCK_control;			// U+2029, paragraph separator.
Packit Service 21b5d1
			} else if ( (*uniChar == UCP(0x2039)) || (*uniChar == UCP(0x203A)) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;			// U+2039 and U+203A, guillemet quotes.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0x06 ) {	// U+06--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( *uniChar == UCP(0x060C) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+060C, Arabic comma.
Packit Service 21b5d1
			} else if ( *uniChar == UCP(0x061B) ) {
Packit Service 21b5d1
				*charKind = UCK_semicolon;		// U+061B, Arabic semicolon.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0x05 ) {	// U+05--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( *uniChar == UCP(0x055D) ) {
Packit Service 21b5d1
				*charKind = UCK_comma;			// U+055D, Armenian comma.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0x03 ) {	// U+03--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( *uniChar == UCP(0x037E) ) {
Packit Service 21b5d1
				*charKind = UCK_semicolon;		// U+037E, Greek "semicolon" (really a question mark).
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		} else if ( upperBits == 0x00 ) {	// U+00--
Packit Service 21b5d1
Packit Service 21b5d1
			if ( (*uniChar == UCP(0x00AB)) || (*uniChar == UCP(0x00BB)) ) {
Packit Service 21b5d1
				*charKind = UCK_quote;			// U+00AB and U+00BB, guillemet quotes.
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
				
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// ClassifyCharacter
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// IsClosingingQuote
Packit Service 21b5d1
// -----------------
Packit Service 21b5d1
Packit Service 21b5d1
static inline bool
Packit Service 21b5d1
IsClosingingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote )
Packit Service 21b5d1
{
Packit Service 21b5d1
Packit Service 21b5d1
	if ( (uniChar == closeQuote) ||
Packit Service 21b5d1
		 ( (openQuote == UCP(0x301D)) && ((uniChar == UCP(0x301E)) || (uniChar == UCP(0x301F))) ) ) {
Packit Service 21b5d1
		return true;
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
		return false;
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// IsClosingingQuote
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// IsSurroundingQuote
Packit Service 21b5d1
// ------------------
Packit Service 21b5d1
Packit Service 21b5d1
static inline bool
Packit Service 21b5d1
IsSurroundingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote )
Packit Service 21b5d1
{
Packit Service 21b5d1
Packit Service 21b5d1
	if ( (uniChar == openQuote) || IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
Packit Service 21b5d1
		return true;
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
		return false;
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// IsSurroundingQuote
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// GetClosingQuote
Packit Service 21b5d1
// ---------------
Packit Service 21b5d1
Packit Service 21b5d1
static UniCodePoint
Packit Service 21b5d1
GetClosingQuote ( UniCodePoint openQuote )
Packit Service 21b5d1
{
Packit Service 21b5d1
	UniCodePoint	closeQuote;
Packit Service 21b5d1
	
Packit Service 21b5d1
	switch ( openQuote ) {
Packit Service 21b5d1
Packit Service 21b5d1
		case UCP(0x0022) : closeQuote = UCP(0x0022);	// ! U+0022 is both opening and closing.
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x005B) : closeQuote = UCP(0x005D);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x00AB) : closeQuote = UCP(0x00BB);	// ! U+00AB and U+00BB are reversible.
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x00BB) : closeQuote = UCP(0x00AB);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x2015) : closeQuote = UCP(0x2015);	// ! U+2015 is both opening and closing.
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x2018) : closeQuote = UCP(0x2019);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x201A) : closeQuote = UCP(0x201B);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x201C) : closeQuote = UCP(0x201D);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x201E) : closeQuote = UCP(0x201F);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x2039) : closeQuote = UCP(0x203A);	// ! U+2039 and U+203A are reversible.
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x203A) : closeQuote = UCP(0x2039);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x3008) : closeQuote = UCP(0x3009);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x300A) : closeQuote = UCP(0x300B);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x300C) : closeQuote = UCP(0x300D);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x300E) : closeQuote = UCP(0x300F);
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		case UCP(0x301D) : closeQuote = UCP(0x301F);	// ! U+301E also closes U+301D.
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
		default			 : closeQuote = 0;
Packit Service 21b5d1
						   break;
Packit Service 21b5d1
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	return closeQuote;
Packit Service 21b5d1
	
Packit Service 21b5d1
}	// GetClosingQuote
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// CodePointToUTF8
Packit Service 21b5d1
// ---------------
Packit Service 21b5d1
Packit Service 21b5d1
static void
Packit Service 21b5d1
CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str )
Packit Service 21b5d1
{
Packit Service 21b5d1
	size_t i, byteCount;
Packit Service 21b5d1
	XMP_Uns8 buffer [8];
Packit Service 21b5d1
	UniCodePoint cpTemp;
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( uniChar <= 0x7F ) {
Packit Service 21b5d1
Packit Service 21b5d1
		i = 7;
Packit Service 21b5d1
		byteCount = 1;
Packit Service 21b5d1
		buffer[7] = char(uniChar);
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
Packit Service 21b5d1
		// ---------------------------------------------------------------------------------------
Packit Service 21b5d1
		// Copy the data bits from the low order end to the high order end, include the 0x80 mask.
Packit Service 21b5d1
		
Packit Service 21b5d1
		i = 8;
Packit Service 21b5d1
		cpTemp = uniChar;
Packit Service 21b5d1
		while ( cpTemp != 0 ) {
Packit Service 21b5d1
			-- i;	// Exit with i pointing to the last byte stored.
Packit Service 21b5d1
			buffer[i] = UnsByte(0x80) | (UnsByte(cpTemp) & 0x3F);
Packit Service 21b5d1
			cpTemp = cpTemp >> 6;
Packit Service 21b5d1
		}
Packit Service 21b5d1
		byteCount = 8 - i;	// The total number of bytes needed.
Packit Service 21b5d1
		XMP_Assert ( (2 <= byteCount) && (byteCount <= 6) );
Packit Service 21b5d1
Packit Service 21b5d1
		// -------------------------------------------------------------------------------------
Packit Service 21b5d1
		// Make sure the high order byte can hold the byte count mask, compute and set the mask.
Packit Service 21b5d1
		
Packit Service 21b5d1
		size_t bitCount = 0;	// The number of data bits in the first byte.
Packit Service 21b5d1
		for ( cpTemp = (buffer[i] & UnsByte(0x3F)); cpTemp != 0; cpTemp = cpTemp >> 1 ) bitCount += 1;
Packit Service 21b5d1
		if ( bitCount > (8 - (byteCount + 1)) ) byteCount += 1;
Packit Service 21b5d1
		
Packit Service 21b5d1
		i = 8 - byteCount;	// First byte index and mask shift count.
Packit Service 21b5d1
		XMP_Assert ( (0 <= i) && (i <= 6) );
Packit Service 21b5d1
		buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF);	// AUDIT: Safe, i is between 0 and 6.
Packit Service 21b5d1
	
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	utf8Str.assign ( (char*)(&buffer[i]), byteCount );
Packit Service 21b5d1
	
Packit Service 21b5d1
}	// CodePointToUTF8
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// ApplyQuotes
Packit Service 21b5d1
// -----------
Packit Service 21b5d1
Packit Service 21b5d1
static void
Packit Service 21b5d1
ApplyQuotes ( XMP_VarString * item, UniCodePoint openQuote, UniCodePoint closeQuote, bool allowCommas )
Packit Service 21b5d1
{
Packit Service 21b5d1
	bool	prevSpace	= false;
Packit Service 21b5d1
	size_t	charOffset, charLen;
Packit Service 21b5d1
	UniCharKind		charKind;
Packit Service 21b5d1
	UniCodePoint	uniChar;
Packit Service 21b5d1
	
Packit Service 21b5d1
	// -----------------------------------------------------------------------------------------
Packit Service 21b5d1
	// See if there are any separators in the value. Stop at the first occurrance. This is a bit
Packit Service 21b5d1
	// tricky in order to make typical typing work conveniently. The purpose of applying quotes
Packit Service 21b5d1
	// is to preserve the values when splitting them back apart. That is CatenateContainerItems
Packit Service 21b5d1
	// and SeparateContainerItems must round trip properly. For the most part we only look for
Packit Service 21b5d1
	// separators here. Internal quotes, as in -- Irving "Bud" Jones -- won't cause problems in
Packit Service 21b5d1
	// the separation. An initial quote will though, it will make the value look quoted.
Packit Service 21b5d1
Packit Service 21b5d1
	charOffset = 0;
Packit Service 21b5d1
	ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( charKind != UCK_quote ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
	for ( charOffset = 0; size_t(charOffset) < item->size(); charOffset += charLen ) {
Packit Service 21b5d1
Packit Service 21b5d1
			ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
Packit Service 21b5d1
Packit Service 21b5d1
			if ( charKind == UCK_space ) {
Packit Service 21b5d1
				if ( prevSpace ) break; // Multiple spaces are a separator.
Packit Service 21b5d1
				prevSpace = true;
Packit Service 21b5d1
			} else {
Packit Service 21b5d1
				prevSpace = false;
Packit Service 21b5d1
				if ( (charKind == UCK_semicolon) || (charKind == UCK_control) ) break;
Packit Service 21b5d1
				if ( (charKind == UCK_comma) && (! allowCommas) ) break;
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( size_t(charOffset) < item->size() ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// --------------------------------------------------------------------------------------
Packit Service 21b5d1
		// Create a quoted copy, doubling any internal quotes that match the outer ones. Internal
Packit Service 21b5d1
		// quotes did not stop the "needs quoting" search, but they do need doubling. So we have
Packit Service 21b5d1
		// to rescan the front of the string for quotes. Handle the special case of U+301D being
Packit Service 21b5d1
		// closed by either U+301E or U+301F.
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_VarString	newItem;
Packit Service 21b5d1
		size_t			splitPoint;
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( splitPoint = 0; splitPoint <= charOffset; ++splitPoint ) {
Packit Service 21b5d1
			ClassifyCharacter ( item->c_str(), splitPoint, &charKind, &charLen, &uniChar );
Packit Service 21b5d1
			if ( charKind == UCK_quote ) break;
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		CodePointToUTF8 ( openQuote, newItem );
Packit Service 21b5d1
		newItem.append ( *item, 0, splitPoint );	// Copy the leading "normal" portion.
Packit Service 21b5d1
Packit Service 21b5d1
		for ( charOffset = splitPoint; size_t(charOffset) < item->size(); charOffset += charLen ) {
Packit Service 21b5d1
			ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
Packit Service 21b5d1
			newItem.append ( *item, charOffset, charLen );
Packit Service 21b5d1
			if ( (charKind == UCK_quote) && IsSurroundingQuote ( uniChar, openQuote, closeQuote ) ) {
Packit Service 21b5d1
				newItem.append ( *item, charOffset, charLen );
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_VarString closeStr;
Packit Service 21b5d1
		CodePointToUTF8 ( closeQuote, closeStr );
Packit Service 21b5d1
		newItem.append ( closeStr );
Packit Service 21b5d1
		
Packit Service 21b5d1
		*item = newItem;
Packit Service 21b5d1
	
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
}	// ApplyQuotes
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// IsInternalProperty
Packit Service 21b5d1
// ------------------
Packit Service 21b5d1
Packit Service 21b5d1
// *** Need static checks of the schema prefixes!
Packit Service 21b5d1
Packit Service 21b5d1
#define IsExternalProperty(s,p) (! IsInternalProperty ( s, p ))
Packit Service 21b5d1
Packit Service 21b5d1
static bool
Packit Service 21b5d1
IsInternalProperty ( const XMP_VarString & schema, const XMP_VarString & prop )
Packit Service 21b5d1
{
Packit Service 21b5d1
	bool isInternal = false;
Packit Service 21b5d1
Packit Service 21b5d1
	if ( schema == kXMP_NS_DC ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		if ( (prop == "dc:format")	||
Packit Service 21b5d1
			 (prop == "dc:language") ) {
Packit Service 21b5d1
			isInternal = true;
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		if ( (prop == "xmp:BaseURL")		||
Packit Service 21b5d1
			 (prop == "xmp:CreatorTool")	||
Packit Service 21b5d1
			 (prop == "xmp:Format")			||
Packit Service 21b5d1
			 (prop == "xmp:Locale")			||
Packit Service 21b5d1
			 (prop == "xmp:MetadataDate")	||
Packit Service 21b5d1
			 (prop == "xmp:ModifyDate") ) {
Packit Service 21b5d1
			isInternal = true;
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_PDF ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		if ( (prop == "pdf:BaseURL")	||
Packit Service 21b5d1
			 (prop == "pdf:Creator")	||
Packit Service 21b5d1
			 (prop == "pdf:ModDate")	||
Packit Service 21b5d1
			 (prop == "pdf:PDFVersion") ||
Packit Service 21b5d1
			 (prop == "pdf:Producer") ) {
Packit Service 21b5d1
			isInternal = true;
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_TIFF ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
		isInternal = true;	// ! The TIFF properties are internal by default.
Packit Service 21b5d1
		if ( (prop == "tiff:ImageDescription")	||	// ! ImageDescription, Artist, and Copyright are aliased.
Packit Service 21b5d1
			 (prop == "tiff:Artist")			||
Packit Service 21b5d1
			 (prop == "tiff:Copyright") ) {
Packit Service 21b5d1
			isInternal = false;
Packit Service 21b5d1
		}
Packit Service 21b5d1
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_EXIF ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The EXIF properties are internal by default.
Packit Service 21b5d1
		if ( prop == "exif:UserComment" ) isInternal = false;
Packit Service 21b5d1
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_EXIF_Aux ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The EXIF Aux properties are internal by default.
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_Photoshop ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		if ( prop == "photoshop:ICCProfile" ) isInternal = true;
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_CameraRaw ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		if ( (prop == "crs:Version")		||
Packit Service 21b5d1
			 (prop == "crs:RawFileName")	||
Packit Service 21b5d1
			 (prop == "crs:ToneCurveName") ) {
Packit Service 21b5d1
			isInternal = true;
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_AdobeStockPhoto ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The bmsp schema has only internal properties.
Packit Service 21b5d1
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP_MM ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The xmpMM schema has only internal properties.
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP_Text ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The xmpT schema has only internal properties.
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP_PagedFile ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The xmpTPg schema has only internal properties.
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP_Graphics ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The xmpG schema has only internal properties.
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP_Image ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The xmpGImg schema has only internal properties.
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( schema == kXMP_NS_XMP_Font ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		isInternal = true;	// ! The stFNT schema has only internal properties.
Packit Service 21b5d1
	
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	return isInternal;
Packit Service 21b5d1
Packit Service 21b5d1
}	// IsInternalProperty
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// RemoveSchemaChildren
Packit Service 21b5d1
// --------------------
Packit Service 21b5d1
Packit Service 21b5d1
static void
Packit Service 21b5d1
RemoveSchemaChildren ( XMP_NodePtrPos schemaPos, bool doAll )
Packit Service 21b5d1
{
Packit Service 21b5d1
	XMP_Node * schemaNode = *schemaPos;
Packit Service 21b5d1
	XMP_Assert ( XMP_NodeIsSchema ( schemaNode->options ) );
Packit Service 21b5d1
		
Packit Service 21b5d1
	// ! Iterate backwards to reduce shuffling as children are erased and to simplify the logic for
Packit Service 21b5d1
	// ! denoting the current child. (Erasing child n makes the old n+1 now be n.)
Packit Service 21b5d1
Packit Service 21b5d1
	size_t		   propCount = schemaNode->children.size();
Packit Service 21b5d1
	XMP_NodePtrPos beginPos	 = schemaNode->children.begin();
Packit Service 21b5d1
	
Packit Service 21b5d1
	for ( size_t propNum = propCount-1, propLim = (size_t)(-1); propNum != propLim; --propNum ) {
Packit Service 21b5d1
		XMP_NodePtrPos currProp = beginPos + propNum;
Packit Service 21b5d1
		if ( doAll || IsExternalProperty ( schemaNode->name, (*currProp)->name ) ) {
Packit Service 21b5d1
			delete *currProp;	// ! Both delete the node and erase the pointer from the parent.
Packit Service 21b5d1
			schemaNode->children.erase ( currProp );
Packit Service 21b5d1
		}
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( schemaNode->children.empty() ) {
Packit Service 21b5d1
		XMP_Node * tree = schemaNode->parent;
Packit Service 21b5d1
		tree->children.erase ( schemaPos );
Packit Service 21b5d1
		delete schemaNode;
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// RemoveSchemaChildren
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// ItemValuesMatch
Packit Service 21b5d1
// ---------------
Packit Service 21b5d1
//
Packit Service 21b5d1
// Does the value comparisons for array merging as part of XMPUtils::AppendProperties.
Packit Service 21b5d1
Packit Service 21b5d1
static bool
Packit Service 21b5d1
ItemValuesMatch ( const XMP_Node * leftNode, const XMP_Node * rightNode )
Packit Service 21b5d1
{
Packit Service 21b5d1
	const XMP_OptionBits leftForm  = leftNode->options & kXMP_PropCompositeMask;
Packit Service 21b5d1
	const XMP_OptionBits rightForm = leftNode->options & kXMP_PropCompositeMask;
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( leftForm != rightForm ) return false;
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( leftForm == 0 ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// Simple nodes, check the values and xml:lang qualifiers.
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( leftNode->value != rightNode->value ) return false;
Packit Service 21b5d1
		if ( (leftNode->options & kXMP_PropHasLang) != (rightNode->options & kXMP_PropHasLang) ) return false;
Packit Service 21b5d1
		if ( leftNode->options & kXMP_PropHasLang ) {
Packit Service 21b5d1
			if ( leftNode->qualifiers[0]->value != rightNode->qualifiers[0]->value ) return false;
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( leftForm == kXMP_PropValueIsStruct ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// Struct nodes, see if all fields match, ignoring order.
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( leftNode->children.size() != rightNode->children.size() ) return false;
Packit Service 21b5d1
Packit Service 21b5d1
		for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) {
Packit Service 21b5d1
			const XMP_Node * leftField	= leftNode->children[leftNum];
Packit Service 21b5d1
			const XMP_Node * rightField = FindConstChild ( rightNode, leftField->name.c_str() );
Packit Service 21b5d1
			if ( (rightField == 0) || (! ItemValuesMatch ( leftField, rightField )) ) return false;
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// Array nodes, see if the "leftNode" values are present in the "rightNode", ignoring order, duplicates,
Packit Service 21b5d1
		// and extra values in the rightNode-> The rightNode is the destination for AppendProperties.
Packit Service 21b5d1
Packit Service 21b5d1
		XMP_Assert ( leftForm & kXMP_PropValueIsArray );
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) {
Packit Service 21b5d1
Packit Service 21b5d1
			const XMP_Node * leftItem = leftNode->children[leftNum];
Packit Service 21b5d1
Packit Service 21b5d1
			size_t rightNum, rightLim;
Packit Service 21b5d1
			for ( rightNum = 0, rightLim = rightNode->children.size(); rightNum != rightLim; ++rightNum ) {
Packit Service 21b5d1
				const XMP_Node * rightItem = rightNode->children[rightNum];
Packit Service 21b5d1
				if ( ItemValuesMatch ( leftItem, rightItem ) ) break;
Packit Service 21b5d1
			}
Packit Service 21b5d1
			if ( rightNum == rightLim ) return false;
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
	return true;	// All of the checks passed.
Packit Service 21b5d1
	
Packit Service 21b5d1
}	// ItemValuesMatch
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// AppendSubtree
Packit Service 21b5d1
// -------------
Packit Service 21b5d1
//
Packit Service 21b5d1
// The main implementation of XMPUtils::AppendProperties. See the description in TXMPMeta.hpp.
Packit Service 21b5d1
Packit Service 21b5d1
static void
Packit Service 21b5d1
AppendSubtree ( const XMP_Node * sourceNode, XMP_Node * destParent, const bool replaceOld, const bool deleteEmpty )
Packit Service 21b5d1
{
Packit Service 21b5d1
	XMP_NodePtrPos destPos;
Packit Service 21b5d1
	XMP_Node * destNode = FindChildNode ( destParent, sourceNode->name.c_str(), kXMP_ExistingOnly, &destPos );
Packit Service 21b5d1
	
Packit Service 21b5d1
	bool valueIsEmpty = false;
Packit Service 21b5d1
	if ( deleteEmpty ) {
Packit Service 21b5d1
		if ( XMP_PropIsSimple ( sourceNode->options ) ) {
Packit Service 21b5d1
			valueIsEmpty = sourceNode->value.empty();
Packit Service 21b5d1
		} else {
Packit Service 21b5d1
			valueIsEmpty = sourceNode->children.empty();
Packit Service 21b5d1
		} 
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( deleteEmpty & valueIsEmpty ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		if ( destNode != 0 ) {
Packit Service 21b5d1
			delete ( destNode );
Packit Service 21b5d1
			destParent->children.erase ( destPos );
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( destNode == 0 ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// The one easy case, the destination does not exist.
Packit Service 21b5d1
		CloneSubtree ( sourceNode, destParent );
Packit Service 21b5d1
Packit Service 21b5d1
	} else if ( replaceOld ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// The destination exists and should be replaced.
Packit Service 21b5d1
Packit Service 21b5d1
		destNode->value	  = sourceNode->value;	// *** Should use SetNode.
Packit Service 21b5d1
		destNode->options = sourceNode->options;
Packit Service 21b5d1
		destNode->RemoveChildren();
Packit Service 21b5d1
		destNode->RemoveQualifiers();
Packit Service 21b5d1
		CloneOffspring ( sourceNode, destNode );
Packit Service 21b5d1
Packit Service 21b5d1
		#if 0	// *** XMP_DebugBuild
Packit Service 21b5d1
			destNode->_valuePtr = destNode->value.c_str();
Packit Service 21b5d1
		#endif
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
Packit Service 21b5d1
		// The destination exists and is not totally replaced. Structs and arrays are merged.
Packit Service 21b5d1
Packit Service 21b5d1
		XMP_OptionBits sourceForm = sourceNode->options & kXMP_PropCompositeMask;
Packit Service 21b5d1
		XMP_OptionBits destForm	  = destNode->options & kXMP_PropCompositeMask;
Packit Service 21b5d1
		if ( sourceForm != destForm ) return;
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( sourceForm == kXMP_PropValueIsStruct ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
			// To merge a struct process the fields recursively. E.g. add simple missing fields. The
Packit Service 21b5d1
			// recursive call to AppendSubtree will handle deletion for fields with empty values.
Packit Service 21b5d1
Packit Service 21b5d1
			for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
Packit Service 21b5d1
				const XMP_Node * sourceField = sourceNode->children[sourceNum];
Packit Service 21b5d1
				AppendSubtree ( sourceField, destNode, replaceOld, deleteEmpty );
Packit Service 21b5d1
				if ( deleteEmpty && destNode->children.empty() ) {
Packit Service 21b5d1
					delete ( destNode );
Packit Service 21b5d1
					destParent->children.erase ( destPos );
Packit Service 21b5d1
				}
Packit Service 21b5d1
			}
Packit Service 21b5d1
			
Packit Service 21b5d1
		} else if ( sourceForm & kXMP_PropArrayIsAltText ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
			// Merge AltText arrays by the xml:lang qualifiers. Make sure x-default is first. Make a
Packit Service 21b5d1
			// special check for deletion of empty values. Meaningful in AltText arrays because the
Packit Service 21b5d1
			// xml:lang qualifier provides unambiguous source/dest correspondence.
Packit Service 21b5d1
Packit Service 21b5d1
			for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
Packit Service 21b5d1
Packit Service 21b5d1
				const XMP_Node * sourceItem = sourceNode->children[sourceNum];
Packit Service 21b5d1
				if ( sourceItem->qualifiers.empty() || (sourceItem->qualifiers[0]->name != "xml:lang") ) continue;
Packit Service 21b5d1
				
Packit Service 21b5d1
				XMP_Index  destIndex = LookupLangItem ( destNode, sourceItem->qualifiers[0]->value );
Packit Service 21b5d1
				
Packit Service 21b5d1
				if ( deleteEmpty && sourceItem->value.empty() ) {
Packit Service 21b5d1
Packit Service 21b5d1
					if ( destIndex != -1 ) {
Packit Service 21b5d1
						delete ( destNode->children[destIndex] );
Packit Service 21b5d1
						destNode->children.erase ( destNode->children.begin() + destIndex );
Packit Service 21b5d1
						if ( destNode->children.empty() ) {
Packit Service 21b5d1
							delete ( destNode );
Packit Service 21b5d1
							destParent->children.erase ( destPos );
Packit Service 21b5d1
						}
Packit Service 21b5d1
					}
Packit Service 21b5d1
Packit Service 21b5d1
				} else {
Packit Service 21b5d1
				
Packit Service 21b5d1
					if (  destIndex != -1 ) continue;	// Not replacing, keep the existing item.
Packit Service 21b5d1
				
Packit Service 21b5d1
					if ( (sourceItem->qualifiers[0]->value != "x-default") || destNode->children.empty() ) {
Packit Service 21b5d1
						CloneSubtree ( sourceItem, destNode );
Packit Service 21b5d1
					} else {
Packit Service 21b5d1
						XMP_Node * destItem = new XMP_Node ( destNode, sourceItem->name, sourceItem->value, sourceItem->options );
Packit Service 21b5d1
						CloneOffspring ( sourceItem, destItem );
Packit Service 21b5d1
						destNode->children.insert ( destNode->children.begin(), destItem );
Packit Service 21b5d1
				}
Packit Service 21b5d1
				
Packit Service 21b5d1
				}
Packit Service 21b5d1
Packit Service 21b5d1
			}
Packit Service 21b5d1
		
Packit Service 21b5d1
		} else if ( sourceForm & kXMP_PropValueIsArray ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
			// Merge other arrays by item values. Don't worry about order or duplicates. Source 
Packit Service 21b5d1
			// items with empty values do not cause deletion, that conflicts horribly with merging.
Packit Service 21b5d1
Packit Service 21b5d1
			for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
Packit Service 21b5d1
				const XMP_Node * sourceItem = sourceNode->children[sourceNum];
Packit Service 21b5d1
Packit Service 21b5d1
				size_t	destNum, destLim;
Packit Service 21b5d1
				for ( destNum = 0, destLim = destNode->children.size(); destNum != destLim; ++destNum ) {
Packit Service 21b5d1
					const XMP_Node * destItem = destNode->children[destNum];
Packit Service 21b5d1
					if ( ItemValuesMatch ( sourceItem, destItem ) ) break;
Packit Service 21b5d1
				}
Packit Service 21b5d1
				if ( destNum == destLim ) CloneSubtree ( sourceItem, destNode );
Packit Service 21b5d1
Packit Service 21b5d1
			}
Packit Service 21b5d1
			
Packit Service 21b5d1
		}
Packit Service 21b5d1
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// AppendSubtree
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// =================================================================================================
Packit Service 21b5d1
// Class Static Functions
Packit Service 21b5d1
// ======================
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// CatenateArrayItems
Packit Service 21b5d1
// ------------------
Packit Service 21b5d1
Packit Service 21b5d1
/* class static */ void
Packit Service 21b5d1
XMPUtils::CatenateArrayItems ( const XMPMeta & xmpObj,
Packit Service 21b5d1
							   XMP_StringPtr   schemaNS,
Packit Service 21b5d1
							   XMP_StringPtr   arrayName,
Packit Service 21b5d1
							   XMP_StringPtr   separator,
Packit Service 21b5d1
							   XMP_StringPtr   quotes,
Packit Service 21b5d1
							   XMP_OptionBits  options,
Packit Service 21b5d1
							   XMP_StringPtr * catedStr,
Packit Service 21b5d1
							   XMP_StringLen * catedLen )
Packit Service 21b5d1
{
Packit Service 21b5d1
	XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // ! Enforced by wrapper.
Packit Service 21b5d1
	XMP_Assert ( (separator != 0) && (quotes != 0) && (catedStr != 0) && (catedLen != 0) ); // ! Enforced by wrapper.
Packit Service 21b5d1
	
Packit Service 21b5d1
	size_t		 strLen=0, strPos=0, charLen=0;
Packit Service 21b5d1
	UniCharKind	 charKind;
Packit Service 21b5d1
	UniCodePoint currUCP, openQuote, closeQuote;
Packit Service 21b5d1
	
Packit Service 21b5d1
	const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0);
Packit Service 21b5d1
	
Packit Service 21b5d1
	const XMP_Node * arrayNode = 0; // ! Move up to avoid gcc complaints.
Packit Service 21b5d1
	XMP_OptionBits	 arrayForm = 0;
Packit Service 21b5d1
	const XMP_Node * currItem  = 0;
Packit Service 21b5d1
Packit Service 21b5d1
	// Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces.
Packit Service 21b5d1
	// Any of the recognized semicolons or spaces are allowed.
Packit Service 21b5d1
	
Packit Service 21b5d1
	strPos = 0;
Packit Service 21b5d1
	strLen = strlen ( separator );
Packit Service 21b5d1
	bool haveSemicolon = false;
Packit Service 21b5d1
	
Packit Service 21b5d1
	while ( strPos < strLen ) {
Packit Service 21b5d1
		ClassifyCharacter ( separator, strPos, &charKind, &charLen, &currUCP );
Packit Service 21b5d1
		strPos += charLen;
Packit Service 21b5d1
		if ( charKind == UCK_semicolon ) {
Packit Service 21b5d1
			if ( haveSemicolon ) XMP_Throw ( "Separator can have only one semicolon",  kXMPErr_BadParam );
Packit Service 21b5d1
			haveSemicolon = true;
Packit Service 21b5d1
		} else if ( charKind != UCK_space ) {
Packit Service 21b5d1
			XMP_Throw ( "Separator can have only spaces and one semicolon",	 kXMPErr_BadParam );
Packit Service 21b5d1
		}
Packit Service 21b5d1
	};
Packit Service 21b5d1
	if ( ! haveSemicolon ) XMP_Throw ( "Separator must have one semicolon",	 kXMPErr_BadParam );
Packit Service 21b5d1
	
Packit Service 21b5d1
	// Make sure the open and close quotes are a legitimate pair.
Packit Service 21b5d1
Packit Service 21b5d1
	strLen = strlen ( quotes );
Packit Service 21b5d1
	ClassifyCharacter ( quotes, 0, &charKind, &charLen, &openQuote );
Packit Service 21b5d1
	if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
Packit Service 21b5d1
Packit Service 21b5d1
	if ( charLen == strLen ) {
Packit Service 21b5d1
		closeQuote = openQuote;
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
		strPos = charLen;
Packit Service 21b5d1
		ClassifyCharacter ( quotes, strPos, &charKind, &charLen, &closeQuote );
Packit Service 21b5d1
		if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
Packit Service 21b5d1
		if ( (strPos + charLen) != strLen ) XMP_Throw ( "Quoting string too long", kXMPErr_BadParam );
Packit Service 21b5d1
	}
Packit Service 21b5d1
	if ( closeQuote != GetClosingQuote ( openQuote ) ) XMP_Throw ( "Mismatched quote pair", kXMPErr_BadParam );
Packit Service 21b5d1
Packit Service 21b5d1
	// Return an empty result if the array does not exist, hurl if it isn't the right form.
Packit Service 21b5d1
	
Packit Service 21b5d1
	sCatenatedItems->erase();
Packit Service 21b5d1
Packit Service 21b5d1
	XMP_ExpandedXPath arrayPath;
Packit Service 21b5d1
	ExpandXPath ( schemaNS, arrayName, &arrayPath );
Packit Service 21b5d1
Packit Service 21b5d1
	arrayNode = FindConstNode ( &xmpObj.tree, arrayPath );
Packit Service 21b5d1
	if ( arrayNode == 0 ) goto EXIT;	// ! Need to set the output pointer and length.
Packit Service 21b5d1
Packit Service 21b5d1
	arrayForm = arrayNode->options & kXMP_PropCompositeMask;
Packit Service 21b5d1
	if ( (! (arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
Packit Service 21b5d1
		XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadParam );
Packit Service 21b5d1
	}
Packit Service 21b5d1
	if ( arrayNode->children.empty() ) goto EXIT;	// ! Need to set the output pointer and length.
Packit Service 21b5d1
	
Packit Service 21b5d1
	// Build the result, quoting the array items, adding separators. Hurl if any item isn't simple.
Packit Service 21b5d1
	// Start the result with the first value, then add the rest with a preceding separator.
Packit Service 21b5d1
	
Packit Service 21b5d1
	currItem = arrayNode->children[0];
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
Packit Service 21b5d1
	*sCatenatedItems = currItem->value;
Packit Service 21b5d1
	ApplyQuotes ( sCatenatedItems, openQuote, closeQuote, allowCommas );
Packit Service 21b5d1
	
Packit Service 21b5d1
	for ( size_t itemNum = 1, itemLim = arrayNode->children.size(); itemNum != itemLim; ++itemNum ) {
Packit Service 21b5d1
		const XMP_Node * currItem = arrayNode->children[itemNum];
Packit Service 21b5d1
		if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
Packit Service 21b5d1
		XMP_VarString tempStr ( currItem->value );
Packit Service 21b5d1
		ApplyQuotes ( &tempStr, openQuote, closeQuote, allowCommas );
Packit Service 21b5d1
		*sCatenatedItems += separator;
Packit Service 21b5d1
		*sCatenatedItems += tempStr;
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
EXIT:
Packit Service 21b5d1
	*catedStr = sCatenatedItems->c_str();
Packit Service 21b5d1
	*catedLen = sCatenatedItems->size();
Packit Service 21b5d1
Packit Service 21b5d1
}	// CatenateArrayItems
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// SeparateArrayItems
Packit Service 21b5d1
// ------------------
Packit Service 21b5d1
Packit Service 21b5d1
/* class static */ void
Packit Service 21b5d1
XMPUtils::SeparateArrayItems ( XMPMeta *	  xmpObj,
Packit Service 21b5d1
							   XMP_StringPtr  schemaNS,
Packit Service 21b5d1
							   XMP_StringPtr  arrayName,
Packit Service 21b5d1
							   XMP_OptionBits options,
Packit Service 21b5d1
							   XMP_StringPtr  catedStr )
Packit Service 21b5d1
{
Packit Service 21b5d1
	XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (catedStr != 0) );	// ! Enforced by wrapper.
Packit Service 21b5d1
	
Packit Service 21b5d1
	XMP_VarString itemValue;
Packit Service 21b5d1
	size_t itemStart, itemEnd;
Packit Service 21b5d1
	size_t nextSize, charSize = 0;	// Avoid VS uninit var warnings.
Packit Service 21b5d1
	UniCharKind	  nextKind, charKind = UCK_normal;
Packit Service 21b5d1
	UniCodePoint  nextChar, uniChar = 0;
Packit Service 21b5d1
	
Packit Service 21b5d1
	// Extract "special" option bits, verify and normalize the others.
Packit Service 21b5d1
	
Packit Service 21b5d1
	bool preserveCommas = false;
Packit Service 21b5d1
	if ( options & kXMPUtil_AllowCommas ) {
Packit Service 21b5d1
		preserveCommas = true;
Packit Service 21b5d1
		options ^= kXMPUtil_AllowCommas;
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
	options = VerifySetOptions ( options, 0 );	// Keep a zero value, has special meaning below.
Packit Service 21b5d1
	if ( options & ~kXMP_PropArrayFormMask ) XMP_Throw ( "Options can only provide array form", kXMPErr_BadOptions );
Packit Service 21b5d1
	
Packit Service 21b5d1
	// Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept.
Packit Service 21b5d1
	
Packit Service 21b5d1
	XMP_ExpandedXPath arrayPath;
Packit Service 21b5d1
	ExpandXPath ( schemaNS, arrayName, &arrayPath );
Packit Service 21b5d1
	XMP_Node * arrayNode = FindNode ( &xmpObj->tree, arrayPath, kXMP_ExistingOnly );
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( arrayNode != 0 ) {
Packit Service 21b5d1
		// The array exists, make sure the form is compatible. Zero arrayForm means take what exists.
Packit Service 21b5d1
		XMP_OptionBits arrayForm = arrayNode->options & kXMP_PropArrayFormMask;
Packit Service 21b5d1
		if ( (arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
Packit Service 21b5d1
			XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadXPath );
Packit Service 21b5d1
		}
Packit Service 21b5d1
		if ( (options != 0) && (options != arrayForm) ) XMP_Throw ( "Mismatch of specified and existing array form", kXMPErr_BadXPath );	// *** Right error?
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
		// The array does not exist, try to create it.
Packit Service 21b5d1
		arrayNode = FindNode ( &xmpObj->tree, arrayPath, kXMP_CreateNodes, (options | kXMP_PropValueIsArray) );
Packit Service 21b5d1
		if ( arrayNode == 0 ) XMP_Throw ( "Failed to create named array", kXMPErr_BadXPath );
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
	XMP_NodeOffspring oldChildren ( arrayNode->children );
Packit Service 21b5d1
	size_t oldChildCount = oldChildren.size();
Packit Service 21b5d1
	arrayNode->children.clear();
Packit Service 21b5d1
	
Packit Service 21b5d1
	// Extract the item values one at a time, until the whole input string is done. Be very careful
Packit Service 21b5d1
	// in the extraction about the string positions. They are essentially byte pointers, while the
Packit Service 21b5d1
	// contents are UTF-8. Adding or subtracting 1 does not necessarily move 1 Unicode character!
Packit Service 21b5d1
	
Packit Service 21b5d1
	size_t endPos = strlen ( catedStr );
Packit Service 21b5d1
	
Packit Service 21b5d1
	itemEnd = 0;
Packit Service 21b5d1
	while ( itemEnd < endPos ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
		// Skip any leading spaces and separation characters. Always skip commas here. They can be
Packit Service 21b5d1
		// kept when within a value, but not when alone between values.
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( itemStart = itemEnd; itemStart < endPos; itemStart += charSize ) {
Packit Service 21b5d1
			ClassifyCharacter ( catedStr, itemStart, &charKind, &charSize, &uniChar );
Packit Service 21b5d1
			if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) break;
Packit Service 21b5d1
		}
Packit Service 21b5d1
		if ( itemStart >= endPos ) break;
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( charKind != UCK_quote ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
			// This is not a quoted value. Scan for the end, create an array item from the substring.
Packit Service 21b5d1
Packit Service 21b5d1
			for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {
Packit Service 21b5d1
Packit Service 21b5d1
				ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );
Packit Service 21b5d1
Packit Service 21b5d1
				if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) continue;
Packit Service 21b5d1
				if ( (charKind == UCK_comma) && preserveCommas ) continue;
Packit Service 21b5d1
				if ( charKind != UCK_space ) break;
Packit Service 21b5d1
Packit Service 21b5d1
				if ( (itemEnd + charSize) >= endPos ) break;	// Anything left?
Packit Service 21b5d1
				ClassifyCharacter ( catedStr, (itemEnd+charSize), &nextKind, &nextSize, &nextChar );
Packit Service 21b5d1
				if ( (nextKind == UCK_normal) || (nextKind == UCK_quote) ) continue;
Packit Service 21b5d1
				if ( (nextKind == UCK_comma) && preserveCommas ) continue;
Packit Service 21b5d1
				break;	// Have multiple spaces, or a space followed by a separator.
Packit Service 21b5d1
Packit Service 21b5d1
			}		
Packit Service 21b5d1
Packit Service 21b5d1
			itemValue.assign ( catedStr, itemStart, (itemEnd - itemStart) );
Packit Service 21b5d1
		
Packit Service 21b5d1
		} else {
Packit Service 21b5d1
		
Packit Service 21b5d1
			// Accumulate quoted values into a local string, undoubling internal quotes that
Packit Service 21b5d1
			// match the surrounding quotes. Do not undouble "unmatching" quotes.
Packit Service 21b5d1
		
Packit Service 21b5d1
			UniCodePoint openQuote = uniChar;
Packit Service 21b5d1
			UniCodePoint closeQuote = GetClosingQuote ( openQuote );
Packit Service 21b5d1
Packit Service 21b5d1
			itemStart += charSize;	// Skip the opening quote;
Packit Service 21b5d1
			itemValue.erase();
Packit Service 21b5d1
			
Packit Service 21b5d1
			for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {
Packit Service 21b5d1
Packit Service 21b5d1
				ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );
Packit Service 21b5d1
Packit Service 21b5d1
				if ( (charKind != UCK_quote) || (! IsSurroundingQuote ( uniChar, openQuote, closeQuote)) ) {
Packit Service 21b5d1
				
Packit Service 21b5d1
					// This is not a matching quote, just append it to the item value.
Packit Service 21b5d1
					itemValue.append ( catedStr, itemEnd, charSize );
Packit Service 21b5d1
					
Packit Service 21b5d1
				} else {
Packit Service 21b5d1
				
Packit Service 21b5d1
					// This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate
Packit Service 21b5d1
					// various edge cases like undoubled opening (non-closing) quotes, or end of input.
Packit Service 21b5d1
					
Packit Service 21b5d1
					if ( (itemEnd + charSize) < endPos ) {
Packit Service 21b5d1
						ClassifyCharacter ( catedStr, itemEnd+charSize, &nextKind, &nextSize, &nextChar );
Packit Service 21b5d1
					} else {
Packit Service 21b5d1
						nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B;
Packit Service 21b5d1
					}
Packit Service 21b5d1
					
Packit Service 21b5d1
					if ( uniChar == nextChar ) {
Packit Service 21b5d1
						// This is doubled, copy it and skip the double.
Packit Service 21b5d1
						itemValue.append ( catedStr, itemEnd, charSize );
Packit Service 21b5d1
						itemEnd += nextSize;	// Loop will add in charSize.
Packit Service 21b5d1
					} else if ( ! IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
Packit Service 21b5d1
						// This is an undoubled, non-closing quote, copy it.
Packit Service 21b5d1
						itemValue.append ( catedStr, itemEnd, charSize );
Packit Service 21b5d1
					} else {
Packit Service 21b5d1
						// This is an undoubled closing quote, skip it and exit the loop.
Packit Service 21b5d1
						itemEnd += charSize;
Packit Service 21b5d1
						break;
Packit Service 21b5d1
					}
Packit Service 21b5d1
Packit Service 21b5d1
				}
Packit Service 21b5d1
Packit Service 21b5d1
			}	// Loop to accumulate the quoted value.
Packit Service 21b5d1
		
Packit Service 21b5d1
		}
Packit Service 21b5d1
Packit Service 21b5d1
		// Add the separated item to the array. Keep a matching old value in case it had separators.
Packit Service 21b5d1
		
Packit Service 21b5d1
		size_t oldChild;
Packit Service 21b5d1
		for ( oldChild = 0; oldChild < oldChildCount; ++oldChild ) {
Packit Service 21b5d1
			if ( (oldChildren[oldChild] != 0) && (itemValue == oldChildren[oldChild]->value) ) break;
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_Node * newItem = 0;
Packit Service 21b5d1
		if ( oldChild == oldChildCount ) {
Packit Service 21b5d1
			newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 );
Packit Service 21b5d1
		} else {
Packit Service 21b5d1
			newItem = oldChildren[oldChild];
Packit Service 21b5d1
			oldChildren[oldChild] = 0;	// ! Don't match again, let duplicates be seen.
Packit Service 21b5d1
		}
Packit Service 21b5d1
		arrayNode->children.push_back ( newItem );
Packit Service 21b5d1
		
Packit Service 21b5d1
	}	// Loop through all of the returned items.
Packit Service 21b5d1
Packit Service 21b5d1
	// Delete any of the old children that were not kept.
Packit Service 21b5d1
	for ( size_t i = 0; i < oldChildCount; ++i ) {
Packit Service 21b5d1
		if ( oldChildren[i] != 0 ) delete oldChildren[i];
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
}	// SeparateArrayItems
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// RemoveProperties
Packit Service 21b5d1
// ----------------
Packit Service 21b5d1
Packit Service 21b5d1
/* class static */ void
Packit Service 21b5d1
XMPUtils::RemoveProperties ( XMPMeta *		xmpObj,
Packit Service 21b5d1
							 XMP_StringPtr	schemaNS,
Packit Service 21b5d1
							 XMP_StringPtr	propName,
Packit Service 21b5d1
							 XMP_OptionBits options )
Packit Service 21b5d1
{
Packit Service 21b5d1
	XMP_Assert ( (schemaNS != 0) && (propName != 0) );	// ! Enforced by wrapper.
Packit Service 21b5d1
	
Packit Service 21b5d1
	const bool doAll = XMP_TestOption (options, kXMPUtil_DoAllProperties );
Packit Service 21b5d1
	const bool includeAliases = XMP_TestOption ( options, kXMPUtil_IncludeAliases );
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( *propName != 0 ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// Remove just the one indicated property. This might be an alias, the named schema might
Packit Service 21b5d1
		// not actually exist. So don't lookup the schema node.
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( *schemaNS == 0 ) XMP_Throw ( "Property name requires schema namespace", kXMPErr_BadParam );
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_ExpandedXPath expPath;
Packit Service 21b5d1
		ExpandXPath ( schemaNS, propName, &expPath );
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_NodePtrPos propPos;
Packit Service 21b5d1
		XMP_Node * propNode = FindNode ( &(xmpObj->tree), expPath, kXMP_ExistingOnly, kXMP_NoOptions, &propPos );
Packit Service 21b5d1
		if ( propNode != 0 ) {
Packit Service 21b5d1
			if ( doAll || IsExternalProperty ( expPath[kSchemaStep].step, expPath[kRootPropStep].step ) ) {
Packit Service 21b5d1
				XMP_Node * parent = propNode->parent;	// *** Should have XMP_Node::RemoveChild(pos).
Packit Service 21b5d1
				delete propNode;	// ! Both delete the node and erase the pointer from the parent.
Packit Service 21b5d1
				parent->children.erase ( propPos );
Packit Service 21b5d1
				DeleteEmptySchema ( parent );
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( *schemaNS != 0 ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// Remove all properties from the named schema. Optionally include aliases, in which case
Packit Service 21b5d1
		// there might not be an actual schema node. 
Packit Service 21b5d1
Packit Service 21b5d1
		XMP_NodePtrPos schemaPos;
Packit Service 21b5d1
		XMP_Node * schemaNode = FindSchemaNode ( &xmpObj->tree, schemaNS, kXMP_ExistingOnly, &schemaPos );
Packit Service 21b5d1
		if ( schemaNode != 0 ) RemoveSchemaChildren ( schemaPos, doAll );
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( includeAliases ) {
Packit Service 21b5d1
		
Packit Service 21b5d1
			// We're removing the aliases also. Look them up by their namespace prefix. Yes, the
Packit Service 21b5d1
			// alias map is sorted so we could process just that portion. But that takes more code
Packit Service 21b5d1
			// and the extra speed isn't worth it. (Plus this way we avoid a dependence on the map
Packit Service 21b5d1
			// implementation.) Lookup the XMP node from the alias, to make sure the actual exists.
Packit Service 21b5d1
Packit Service 21b5d1
			XMP_StringPtr nsPrefix;
Packit Service 21b5d1
			XMP_StringLen nsLen;
Packit Service 21b5d1
			(void) XMPMeta::GetNamespacePrefix ( schemaNS, &nsPrefix, &nsLen );
Packit Service 21b5d1
			
Packit Service 21b5d1
			XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin();
Packit Service 21b5d1
			XMP_AliasMapPos endAlias  = sRegisteredAliasMap->end();
Packit Service 21b5d1
			
Packit Service 21b5d1
			for ( ; currAlias != endAlias; ++currAlias ) {
Packit Service 21b5d1
				if ( strncmp ( currAlias->first.c_str(), nsPrefix, nsLen ) == 0 ) {
Packit Service 21b5d1
					XMP_NodePtrPos actualPos;
Packit Service 21b5d1
					XMP_Node * actualProp = FindNode ( &xmpObj->tree, currAlias->second, kXMP_ExistingOnly, kXMP_NoOptions, &actualPos );
Packit Service 21b5d1
					if ( actualProp != 0 ) {
Packit Service 21b5d1
						XMP_Node * rootProp = actualProp;
Packit Service 21b5d1
						while ( ! XMP_NodeIsSchema ( rootProp->parent->options ) ) rootProp = rootProp->parent;
Packit Service 21b5d1
						if ( doAll || IsExternalProperty ( rootProp->parent->name, rootProp->name ) ) {
Packit Service 21b5d1
							XMP_Node * parent = actualProp->parent;
Packit Service 21b5d1
							delete actualProp;	// ! Both delete the node and erase the pointer from the parent.
Packit Service 21b5d1
							parent->children.erase ( actualPos );
Packit Service 21b5d1
							DeleteEmptySchema ( parent );
Packit Service 21b5d1
						}
Packit Service 21b5d1
					}
Packit Service 21b5d1
				}
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
		
Packit Service 21b5d1
		// Remove all appropriate properties from all schema. In this case we don't have to be
Packit Service 21b5d1
		// concerned with aliases, they are handled implicitly from the actual properties.
Packit Service 21b5d1
Packit Service 21b5d1
		// ! Iterate backwards to reduce shuffling if schema are erased and to simplify the logic
Packit Service 21b5d1
		// ! for denoting the current schema. (Erasing schema n makes the old n+1 now be n.)
Packit Service 21b5d1
Packit Service 21b5d1
		size_t		   schemaCount = xmpObj->tree.children.size();
Packit Service 21b5d1
		XMP_NodePtrPos beginPos	   = xmpObj->tree.children.begin();
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( size_t schemaNum = schemaCount-1, schemaLim = (size_t)(-1); schemaNum != schemaLim; --schemaNum ) {
Packit Service 21b5d1
			XMP_NodePtrPos currSchema = beginPos + schemaNum;
Packit Service 21b5d1
			RemoveSchemaChildren ( currSchema, doAll );
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// RemoveProperties
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// AppendProperties
Packit Service 21b5d1
// ----------------
Packit Service 21b5d1
Packit Service 21b5d1
/* class static */ void
Packit Service 21b5d1
XMPUtils::AppendProperties ( const XMPMeta & source,
Packit Service 21b5d1
							 XMPMeta *		 dest,
Packit Service 21b5d1
							 XMP_OptionBits	 options )
Packit Service 21b5d1
{
Packit Service 21b5d1
	XMP_Assert ( dest != 0 );	// ! Enforced by wrapper.
Packit Service 21b5d1
Packit Service 21b5d1
	const bool doAll	   = ((options & kXMPUtil_DoAllProperties) != 0);
Packit Service 21b5d1
	const bool replaceOld  = ((options & kXMPUtil_ReplaceOldValues) != 0);
Packit Service 21b5d1
	const bool deleteEmpty = ((options & kXMPUtil_DeleteEmptyValues) != 0);
Packit Service 21b5d1
Packit Service 21b5d1
	for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum != schemaLim; ++schemaNum ) {
Packit Service 21b5d1
Packit Service 21b5d1
		const XMP_Node * sourceSchema = source.tree.children[schemaNum];
Packit Service 21b5d1
Packit Service 21b5d1
		// Make sure we have a destination schema node. Remember if it is newly created.
Packit Service 21b5d1
		
Packit Service 21b5d1
		XMP_Node * destSchema = FindSchemaNode ( &dest->tree, sourceSchema->name.c_str(), kXMP_ExistingOnly );
Packit Service 21b5d1
		const bool newDestSchema = (destSchema == 0);
Packit Service 21b5d1
		if ( newDestSchema ) {
Packit Service 21b5d1
			destSchema = new XMP_Node ( &dest->tree, sourceSchema->name, sourceSchema->value, kXMP_SchemaNode );
Packit Service 21b5d1
			dest->tree.children.push_back ( destSchema );
Packit Service 21b5d1
		}
Packit Service 21b5d1
Packit Service 21b5d1
		// Process the source schema's children. Do this backwards in case deleteEmpty is set.
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( long propNum = ((long)sourceSchema->children.size() - 1); propNum >= 0; --propNum ) {
Packit Service 21b5d1
			const XMP_Node * sourceProp = sourceSchema->children[propNum];
Packit Service 21b5d1
			if ( doAll || IsExternalProperty ( sourceSchema->name, sourceProp->name ) ) {
Packit Service 21b5d1
				AppendSubtree ( sourceProp, destSchema, replaceOld, deleteEmpty );
Packit Service 21b5d1
// ***				RemoveMultiValueInfo ( dest, sourceSchema->name.c_str(), sourceProp->name.c_str() );
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( destSchema->children.empty() ) {
Packit Service 21b5d1
			if ( newDestSchema ) {
Packit Service 21b5d1
				delete ( destSchema );
Packit Service 21b5d1
				dest->tree.children.pop_back();
Packit Service 21b5d1
			} else if ( deleteEmpty ) {
Packit Service 21b5d1
				DeleteEmptySchema ( destSchema );
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// AppendProperties
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// -------------------------------------------------------------------------------------------------
Packit Service 21b5d1
// DuplicateSubtree
Packit Service 21b5d1
// ----------------
Packit Service 21b5d1
Packit Service 21b5d1
/* class static */ void
Packit Service 21b5d1
XMPUtils::DuplicateSubtree ( const XMPMeta & source,
Packit Service 21b5d1
							 XMPMeta *		 dest,
Packit Service 21b5d1
							 XMP_StringPtr	 sourceNS,
Packit Service 21b5d1
							 XMP_StringPtr	 sourceRoot,
Packit Service 21b5d1
							 XMP_StringPtr	 destNS,
Packit Service 21b5d1
							 XMP_StringPtr	 destRoot,
Packit Service 21b5d1
							 XMP_OptionBits	 options )
Packit Service 21b5d1
{
Packit Service 21b5d1
	UNUSED(options);
Packit Service 21b5d1
	
Packit Service 21b5d1
	bool fullSourceTree = false;
Packit Service 21b5d1
	bool fullDestTree   = false;
Packit Service 21b5d1
	
Packit Service 21b5d1
	XMP_ExpandedXPath sourcePath, destPath; 
Packit Service 21b5d1
Packit Service 21b5d1
	const XMP_Node * sourceNode = 0;
Packit Service 21b5d1
	XMP_Node * destNode = 0;
Packit Service 21b5d1
	
Packit Service 21b5d1
	XMP_Assert ( (sourceNS != 0) && (*sourceNS != 0) );
Packit Service 21b5d1
	XMP_Assert ( (sourceRoot != 0) && (*sourceRoot != 0) );
Packit Service 21b5d1
	XMP_Assert ( (dest != 0) && (destNS != 0) && (destRoot != 0) );
Packit Service 21b5d1
Packit Service 21b5d1
	if ( *destNS == 0 )	  destNS   = sourceNS;
Packit Service 21b5d1
	if ( *destRoot == 0 ) destRoot = sourceRoot;
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( XMP_LitMatch ( sourceNS, "*" ) ) fullSourceTree = true;
Packit Service 21b5d1
	if ( XMP_LitMatch ( destNS, "*" ) )   fullDestTree   = true;
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( (&source == dest) && (fullSourceTree | fullDestTree) ) {
Packit Service 21b5d1
		XMP_Throw ( "Can't duplicate tree onto itself", kXMPErr_BadParam );
Packit Service 21b5d1
	}
Packit Service 21b5d1
	
Packit Service 21b5d1
	if ( fullSourceTree & fullDestTree ) XMP_Throw ( "Use Clone for full tree to full tree", kXMPErr_BadParam );
Packit Service 21b5d1
Packit Service 21b5d1
	if ( fullSourceTree ) {
Packit Service 21b5d1
	
Packit Service 21b5d1
		// The destination must be an existing empty struct, copy all of the source top level as fields.
Packit Service 21b5d1
Packit Service 21b5d1
		ExpandXPath ( destNS, destRoot, &destPath );
Packit Service 21b5d1
		destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );
Packit Service 21b5d1
Packit Service 21b5d1
		if ( (destNode == 0) || (! XMP_PropIsStruct ( destNode->options )) ) {
Packit Service 21b5d1
			XMP_Throw ( "Destination must be an existing struct", kXMPErr_BadXPath );
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( ! destNode->children.empty() ) {
Packit Service 21b5d1
			if ( options & kXMP_DeleteExisting ) {
Packit Service 21b5d1
				destNode->RemoveChildren();
Packit Service 21b5d1
			} else {
Packit Service 21b5d1
				XMP_Throw ( "Destination must be an empty struct", kXMPErr_BadXPath );
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) {
Packit Service 21b5d1
Packit Service 21b5d1
			const XMP_Node * currSchema = source.tree.children[schemaNum];
Packit Service 21b5d1
Packit Service 21b5d1
			for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) {
Packit Service 21b5d1
				sourceNode = currSchema->children[propNum];
Packit Service 21b5d1
				XMP_Node * copyNode = new XMP_Node ( destNode, sourceNode->name, sourceNode->value, sourceNode->options );
Packit Service 21b5d1
				destNode->children.push_back ( copyNode );
Packit Service 21b5d1
				CloneOffspring ( sourceNode, copyNode );
Packit Service 21b5d1
			}
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
	} else if ( fullDestTree ) {
Packit Service 21b5d1
Packit Service 21b5d1
		// The source node must be an existing struct, copy all of the fields to the dest top level.
Packit Service 21b5d1
Packit Service 21b5d1
		XMP_ExpandedXPath sourcePath; 
Packit Service 21b5d1
		ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
Packit Service 21b5d1
		sourceNode = FindConstNode ( &source.tree, sourcePath );
Packit Service 21b5d1
Packit Service 21b5d1
		if ( (sourceNode == 0) || (! XMP_PropIsStruct ( sourceNode->options )) ) {
Packit Service 21b5d1
			XMP_Throw ( "Source must be an existing struct", kXMPErr_BadXPath );
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		destNode = &dest->tree;
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( ! destNode->children.empty() ) {
Packit Service 21b5d1
			if ( options & kXMP_DeleteExisting ) {
Packit Service 21b5d1
				destNode->RemoveChildren();
Packit Service 21b5d1
			} else {
Packit Service 21b5d1
				XMP_Throw ( "Destination tree must be empty", kXMPErr_BadXPath );
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
		std::string   nsPrefix;
Packit Service 21b5d1
		XMP_StringPtr nsURI;
Packit Service 21b5d1
		XMP_StringLen nsLen;
Packit Service 21b5d1
		
Packit Service 21b5d1
		for ( size_t fieldNum = 0, fieldLim = sourceNode->children.size(); fieldNum < fieldLim; ++fieldNum ) {
Packit Service 21b5d1
Packit Service 21b5d1
			const XMP_Node * currField = sourceNode->children[fieldNum];
Packit Service 21b5d1
Packit Service 21b5d1
			size_t colonPos = currField->name.find ( ':' );
Packit Service 21b5d1
			nsPrefix.assign ( currField->name.c_str(), colonPos );
Packit Service 21b5d1
			bool nsOK = XMPMeta::GetNamespaceURI ( nsPrefix.c_str(), &nsURI, &nsLen );
Packit Service 21b5d1
			if ( ! nsOK ) XMP_Throw ( "Source field namespace is not global", kXMPErr_BadSchema );
Packit Service 21b5d1
			
Packit Service 21b5d1
			XMP_Node * destSchema = FindSchemaNode ( &dest->tree, nsURI, kXMP_CreateNodes );
Packit Service 21b5d1
			if ( destSchema == 0 ) XMP_Throw ( "Failed to find destination schema", kXMPErr_BadSchema );
Packit Service 21b5d1
Packit Service 21b5d1
			XMP_Node * copyNode = new XMP_Node ( destSchema, currField->name, currField->value, currField->options );
Packit Service 21b5d1
			destSchema->children.push_back ( copyNode );
Packit Service 21b5d1
			CloneOffspring ( currField, copyNode );
Packit Service 21b5d1
Packit Service 21b5d1
		}
Packit Service 21b5d1
		
Packit Service 21b5d1
	} else {
Packit Service 21b5d1
Packit Service 21b5d1
		// Find the root nodes for the source and destination subtrees.
Packit Service 21b5d1
		
Packit Service 21b5d1
		ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
Packit Service 21b5d1
		ExpandXPath ( destNS, destRoot, &destPath );
Packit Service 21b5d1
	
Packit Service 21b5d1
		sourceNode = FindConstNode ( &source.tree, sourcePath );
Packit Service 21b5d1
		if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath );
Packit Service 21b5d1
		
Packit Service 21b5d1
		destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );	// Dest must not yet exist.
Packit Service 21b5d1
		if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath );
Packit Service 21b5d1
		
Packit Service 21b5d1
		destNode = FindNode ( &dest->tree, destPath, kXMP_CreateNodes );	// Now create the dest.
Packit Service 21b5d1
		if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath );
Packit Service 21b5d1
		
Packit Service 21b5d1
		// Make sure the destination is not within the source! The source can't be inside the destination
Packit Service 21b5d1
		// because the source already existed and the destination was just created.
Packit Service 21b5d1
		
Packit Service 21b5d1
		if ( &source == dest ) {
Packit Service 21b5d1
			for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) {
Packit Service 21b5d1
				if ( testNode == sourceNode ) {
Packit Service 21b5d1
					// *** delete the just-created dest root node
Packit Service 21b5d1
					XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath );
Packit Service 21b5d1
				}
Packit Service 21b5d1
			}
Packit Service 21b5d1
		}
Packit Service 21b5d1
	
Packit Service 21b5d1
		// *** Could use a CloneTree util here and maybe elsewhere.
Packit Service 21b5d1
		
Packit Service 21b5d1
		destNode->value	  = sourceNode->value;	// *** Should use SetNode.
Packit Service 21b5d1
		destNode->options = sourceNode->options;
Packit Service 21b5d1
		CloneOffspring ( sourceNode, destNode );
Packit Service 21b5d1
Packit Service 21b5d1
	}
Packit Service 21b5d1
Packit Service 21b5d1
}	// DuplicateSubtree
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
// =================================================================================================