1 // =================================================================================================
2 // Copyright 2003 Adobe Systems Incorporated
3 // All Rights Reserved.
4 //
5 // NOTICE:	Adobe permits you to use, modify, and distribute this file in accordance with the terms
6 // of the Adobe license agreement accompanying it.
7 // =================================================================================================
8 
9 #include "public/include/XMP_Environment.h"	// ! This must be the first include!
10 #include "XMPCore/source/XMPCore_Impl.hpp"
11 
12 #include "XMPCore/XMPCoreDefines.h"
13 #include "XMPCore/source/XMPUtils.hpp"
14 #if ENABLE_CPP_DOM_MODEL
15 	#include "source/UnicodeInlines.incl_cpp"
16 	#include "source/UnicodeConversions.hpp"
17 	#include "source/ExpatAdapter.hpp"
18 	#include "third-party/zuid/interfaces/MD5.h"
19 	#include "XMPCore/Interfaces/IMetadata_I.h"
20 	#include "XMPCore/Interfaces/IArrayNode_I.h"
21 	#include "XMPCore/Interfaces/ISimpleNode_I.h"
22 	#include "XMPCore/Interfaces/INodeIterator_I.h"
23 	#include "XMPCore/Interfaces/IPathSegment_I.h"
24 	#include "XMPCore/Interfaces/IPath_I.h"
25 	#include "XMPCore/Interfaces/INameSpacePrefixMap_I.h"
26 	#include "XMPCore/Interfaces/IDOMImplementationRegistry_I.h"
27 	#include "XMPCommon/Interfaces/IUTF8String_I.h"
28 #endif
29 #include <algorithm>	// For binary_search.
30 
31 #include <time.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <locale.h>
35 #include <errno.h>
36 
37 #include <stdio.h>	// For snprintf.
38 
39 #if XMP_WinBuild
40 	#pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
41 #endif
42 
43 // =================================================================================================
44 // Local Types and Constants
45 // =========================
46 
47 typedef unsigned long	UniCodePoint;
48 
49 enum UniCharKind {
50 	UCK_normal,
51 	UCK_space,
52 	UCK_comma,
53 	UCK_semicolon,
54 	UCK_quote,
55 	UCK_control
56 };
57 typedef enum UniCharKind	UniCharKind;
58 
59 #define UnsByte(c)	((unsigned char)(c))
60 #define UCP(u)		((UniCodePoint)(u))
61 	// ! Needed on Windows (& PC Linux?) for inequalities with literals ito avoid sign extension.
62 
63 #ifndef TraceMultiFile
64 	#define TraceMultiFile	0
65 #endif
66 
67 // =================================================================================================
68 // Static Variables
69 // ================
70 
71 // =================================================================================================
72 // Local Utilities
73 // ===============
74 
75 // -------------------------------------------------------------------------------------------------
76 // ClassifyCharacter
77 // -----------------
78 
79 static void
ClassifyCharacter(XMP_StringPtr fullString,size_t offset,UniCharKind * charKind,size_t * charSize,UniCodePoint * uniChar)80 ClassifyCharacter ( XMP_StringPtr fullString, size_t offset,
81 					UniCharKind * charKind, size_t * charSize, UniCodePoint * uniChar )
82 {
83 	*charKind = UCK_normal; // Assume typical case.
84 
85 	unsigned char	currByte = UnsByte ( fullString[offset] );
86 
87 	if ( currByte < UnsByte(0x80) ) {
88 
89 		// ----------------------------------------
90 		// We've got a single byte ASCII character.
91 
92 		*charSize = 1;
93 		*uniChar = currByte;
94 
95 		if ( currByte > UnsByte(0x22) ) {
96 
97 			if ( currByte == UnsByte(0x2C) ) {
98 				*charKind = UCK_comma;
99 			} else if ( currByte == UnsByte(0x3B) ) {
100 				*charKind = UCK_semicolon;
101 			}
102 			// [2674672] Discontinue to interpret square brackets
103 			//           as Asian quotes in XMPUtils::SeparateArrayItems(..))
104 			// *** else if ( (currByte == UnsByte(0x5B)) || (currByte == UnsByte(0x5D)) ) {
105 			// ***	*charKind = UCK_quote;	// ! ASCII '[' and ']' are used as quotes in Chinese and Korean.
106 			// *** }
107 
108 		} else {	// currByte <= 0x22
109 
110 			if ( currByte == UnsByte(0x22) ) {
111 				*charKind = UCK_quote;
112 			} else if ( currByte == UnsByte(0x21) ) {
113 				*charKind = UCK_normal;
114 			} else if ( currByte == UnsByte(0x20) ) {
115 				*charKind = UCK_space;
116 			} else {
117 				*charKind = UCK_control;
118 			}
119 
120 		}
121 
122 	} else {	// currByte >= 0x80
123 
124 		// ---------------------------------------------------------------------------------------
125 		// We've got a multibyte Unicode character. The first byte has the number of bytes and the
126 		// highest order bits. The other bytes each add 6 more bits. Compose the UTF-32 form so we
127 		// can classify directly with the Unicode code points. Order the upperBits tests to be
128 		// fastest for Japan, probably the most common non-ASCII usage.
129 
130 		*charSize = 0;
131 		*uniChar = currByte;
132 		while ( (*uniChar & 0x80) != 0 ) {	// Count the leading 1 bits in the byte.
133 			++(*charSize);
134 			*uniChar = *uniChar << 1;
135 		}
136 		XMP_Assert ( (offset + *charSize) <= strlen(fullString) );
137 
138 		*uniChar = *uniChar & 0x7F;			// Put the character bits in the bottom of uniChar.
139 		*uniChar = *uniChar >> *charSize;
140 
141 		for ( size_t i = (offset + 1); i < (offset + *charSize); ++i ) {
142 			*uniChar = (*uniChar << 6) | (UnsByte(fullString[i]) & 0x3F);
143 		}
144 
145 		XMP_Uns32 upperBits = static_cast<XMP_Uns32>(*uniChar >> 8);	// First filter on just the high order 24 bits.
146 
147 		if ( upperBits == 0xFF ) {			// U+FFxx
148 
149 			if ( *uniChar == UCP(0xFF0C) ) {
150 				*charKind = UCK_comma;			// U+FF0C, full width comma.
151 			} else if ( *uniChar == UCP(0xFF1B) ) {
152 				*charKind = UCK_semicolon;		// U+FF1B, full width semicolon.
153 			} else if ( *uniChar == UCP(0xFF64) ) {
154 				*charKind = UCK_comma;			// U+FF64, half width ideographic comma.
155 			}
156 
157 		} else if ( upperBits == 0xFE ) {	// U+FE--
158 
159 			if ( *uniChar == UCP(0xFE50) ) {
160 				*charKind = UCK_comma;			// U+FE50, small comma.
161 			} else if ( *uniChar == UCP(0xFE51) ) {
162 				*charKind = UCK_comma;			// U+FE51, small ideographic comma.
163 			} else if ( *uniChar == UCP(0xFE54) ) {
164 				*charKind = UCK_semicolon;		// U+FE54, small semicolon.
165 			}
166 
167 		} else if ( upperBits == 0x30 ) {	// U+30--
168 
169 			if ( *uniChar == UCP(0x3000) ) {
170 				*charKind = UCK_space;			// U+3000, ideographic space.
171 			} else if ( *uniChar == UCP(0x3001) ) {
172 				*charKind = UCK_comma;			// U+3001, ideographic comma.
173 			} else if ( (UCP(0x3008) <= *uniChar) && (*uniChar <= UCP(0x300F)) ) {
174 				*charKind = UCK_quote;			// U+3008..U+300F, various quotes.
175 			} else if ( *uniChar == UCP(0x303F) ) {
176 				*charKind = UCK_space;			// U+303F, ideographic half fill space.
177 			} else if ( (UCP(0x301D) <= *uniChar) && (*uniChar <= UCP(0x301F)) ) {
178 				*charKind = UCK_quote;			// U+301D..U+301F, double prime quotes.
179 			}
180 
181 		} else if ( upperBits == 0x20 ) {	// U+20--
182 
183 			if ( (UCP(0x2000) <= *uniChar) && (*uniChar <= UCP(0x200B)) ) {
184 				*charKind = UCK_space;			// U+2000..U+200B, en quad through zero width space.
185 			} else if ( *uniChar == UCP(0x2015) ) {
186 				*charKind = UCK_quote;			// U+2015, dash quote.
187 			} else if ( (UCP(0x2018) <= *uniChar) && (*uniChar <= UCP(0x201F)) ) {
188 				*charKind = UCK_quote;			// U+2018..U+201F, various quotes.
189 			} else if ( *uniChar == UCP(0x2028) ) {
190 				*charKind = UCK_control;			// U+2028, line separator.
191 			} else if ( *uniChar == UCP(0x2029) ) {
192 				*charKind = UCK_control;			// U+2029, paragraph separator.
193 			} else if ( (*uniChar == UCP(0x2039)) || (*uniChar == UCP(0x203A)) ) {
194 				*charKind = UCK_quote;			// U+2039 and U+203A, guillemet quotes.
195 			}
196 
197 		} else if ( upperBits == 0x06 ) {	// U+06--
198 
199 			if ( *uniChar == UCP(0x060C) ) {
200 				*charKind = UCK_comma;			// U+060C, Arabic comma.
201 			} else if ( *uniChar == UCP(0x061B) ) {
202 				*charKind = UCK_semicolon;		// U+061B, Arabic semicolon.
203 			}
204 
205 		} else if ( upperBits == 0x05 ) {	// U+05--
206 
207 			if ( *uniChar == UCP(0x055D) ) {
208 				*charKind = UCK_comma;			// U+055D, Armenian comma.
209 			}
210 
211 		} else if ( upperBits == 0x03 ) {	// U+03--
212 
213 			if ( *uniChar == UCP(0x037E) ) {
214 				*charKind = UCK_semicolon;		// U+037E, Greek "semicolon" (really a question mark).
215 			}
216 
217 		} else if ( upperBits == 0x00 ) {	// U+00--
218 
219 			if ( (*uniChar == UCP(0x00AB)) || (*uniChar == UCP(0x00BB)) ) {
220 				*charKind = UCK_quote;			// U+00AB and U+00BB, guillemet quotes.
221 			}
222 
223 		}
224 
225 	}
226 
227 }	// ClassifyCharacter
228 
229 
230 // -------------------------------------------------------------------------------------------------
231 // IsClosingingQuote
232 // -----------------
233 
234 static inline bool
IsClosingingQuote(UniCodePoint uniChar,UniCodePoint openQuote,UniCodePoint closeQuote)235 IsClosingingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote )
236 {
237 
238 	if ( (uniChar == closeQuote) ||
239 		 ( (openQuote == UCP(0x301D)) && ((uniChar == UCP(0x301E)) || (uniChar == UCP(0x301F))) ) ) {
240 		return true;
241 	} else {
242 		return false;
243 	}
244 
245 }	// IsClosingingQuote
246 
247 
248 // -------------------------------------------------------------------------------------------------
249 // IsSurroundingQuote
250 // ------------------
251 
252 static inline bool
IsSurroundingQuote(UniCodePoint uniChar,UniCodePoint openQuote,UniCodePoint closeQuote)253 IsSurroundingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote )
254 {
255 
256 	if ( (uniChar == openQuote) || IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
257 		return true;
258 	} else {
259 		return false;
260 	}
261 
262 }	// IsSurroundingQuote
263 
264 
265 // -------------------------------------------------------------------------------------------------
266 // GetClosingQuote
267 // ---------------
268 
269 static UniCodePoint
GetClosingQuote(UniCodePoint openQuote)270 GetClosingQuote ( UniCodePoint openQuote )
271 {
272 	UniCodePoint	closeQuote;
273 
274 	switch ( openQuote ) {
275 
276 		case UCP(0x0022) : closeQuote = UCP(0x0022);	// ! U+0022 is both opening and closing.
277 						   break;
278 		// *** [2674672] Discontinue to interpret square brackets
279 		// *** as Asian quotes in XMPUtils::SeparateArrayItems(..))
280 		// *** case UCP(0x005B) : closeQuote = UCP(0x005D);
281 		// ***				   break;
282 		case UCP(0x00AB) : closeQuote = UCP(0x00BB);	// ! U+00AB and U+00BB are reversible.
283 						   break;
284 		case UCP(0x00BB) : closeQuote = UCP(0x00AB);
285 						   break;
286 		case UCP(0x2015) : closeQuote = UCP(0x2015);	// ! U+2015 is both opening and closing.
287 						   break;
288 		case UCP(0x2018) : closeQuote = UCP(0x2019);
289 						   break;
290 		case UCP(0x201A) : closeQuote = UCP(0x201B);
291 						   break;
292 		case UCP(0x201C) : closeQuote = UCP(0x201D);
293 						   break;
294 		case UCP(0x201E) : closeQuote = UCP(0x201F);
295 						   break;
296 		case UCP(0x2039) : closeQuote = UCP(0x203A);	// ! U+2039 and U+203A are reversible.
297 						   break;
298 		case UCP(0x203A) : closeQuote = UCP(0x2039);
299 						   break;
300 		case UCP(0x3008) : closeQuote = UCP(0x3009);
301 						   break;
302 		case UCP(0x300A) : closeQuote = UCP(0x300B);
303 						   break;
304 		case UCP(0x300C) : closeQuote = UCP(0x300D);
305 						   break;
306 		case UCP(0x300E) : closeQuote = UCP(0x300F);
307 						   break;
308 		case UCP(0x301D) : closeQuote = UCP(0x301F);	// ! U+301E also closes U+301D.
309 						   break;
310 		default			 : closeQuote = 0;
311 						   break;
312 
313 	}
314 
315 	return closeQuote;
316 
317 }	// GetClosingQuote
318 
319 
320 // -------------------------------------------------------------------------------------------------
321 // CodePointToUTF8
322 // ---------------
323 
324 static void
CodePointToUTF8(UniCodePoint uniChar,XMP_VarString & utf8Str)325 CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str )
326 {
327 	size_t i, byteCount;
328 	XMP_Uns8 buffer [8];
329 	UniCodePoint cpTemp;
330 
331 	if ( uniChar <= 0x7F ) {
332 
333 		i = 7;
334 		byteCount = 1;
335 		buffer[7] = char(uniChar);
336 
337 	} else {
338 
339 		// ---------------------------------------------------------------------------------------
340 		// Copy the data bits from the low order end to the high order end, include the 0x80 mask.
341 
342 		i = 8;
343 		cpTemp = uniChar;
344 		while ( cpTemp != 0 ) {
345 			-- i;	// Exit with i pointing to the last byte stored.
346 			buffer[i] = UnsByte(0x80) | (UnsByte(cpTemp) & 0x3F);
347 			cpTemp = cpTemp >> 6;
348 		}
349 		byteCount = 8 - i;	// The total number of bytes needed.
350 		XMP_Assert ( (2 <= byteCount) && (byteCount <= 6) );
351 
352 		// -------------------------------------------------------------------------------------
353 		// Make sure the high order byte can hold the byte count mask, compute and set the mask.
354 
355 		size_t bitCount = 0;	// The number of data bits in the first byte.
356 		for ( cpTemp = (buffer[i] & UnsByte(0x3F)); cpTemp != 0; cpTemp = cpTemp >> 1 ) bitCount += 1;
357 		if ( bitCount > (8 - (byteCount + 1)) ) byteCount += 1;
358 
359 		i = 8 - byteCount;	// First byte index and mask shift count.
360 		XMP_Assert ( (0 <= i) && (i <= 6) );
361 		buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF);	// AUDIT: Safe, i is between 0 and 6.
362 
363 	}
364 
365 	utf8Str.assign ( (char*)(&buffer[i]), byteCount );
366 
367 }	// CodePointToUTF8
368 
369 
370 // -------------------------------------------------------------------------------------------------
371 // ApplyQuotes
372 // -----------
373 
374 static void
ApplyQuotes(XMP_VarString * item,UniCodePoint openQuote,UniCodePoint closeQuote,bool allowCommas)375 ApplyQuotes ( XMP_VarString * item, UniCodePoint openQuote, UniCodePoint closeQuote, bool allowCommas )
376 {
377 	bool	prevSpace	= false;
378 	size_t	charOffset, charLen;
379 	UniCharKind		charKind;
380 	UniCodePoint	uniChar;
381 
382 	// -----------------------------------------------------------------------------------------
383 	// See if there are any separators in the value. Stop at the first occurrance. This is a bit
384 	// tricky in order to make typical typing work conveniently. The purpose of applying quotes
385 	// is to preserve the values when splitting them back apart. That is CatenateContainerItems
386 	// and SeparateContainerItems must round trip properly. For the most part we only look for
387 	// separators here. Internal quotes, as in -- Irving "Bud" Jones -- won't cause problems in
388 	// the separation. An initial quote will though, it will make the value look quoted.
389 
390 	charOffset = 0;
391 	ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
392 
393 	if ( charKind != UCK_quote ) {
394 
395 	for ( charOffset = 0; size_t(charOffset) < item->size(); charOffset += charLen ) {
396 
397 			ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
398 
399 			if ( charKind == UCK_space ) {
400 				if ( prevSpace ) break; // Multiple spaces are a separator.
401 				prevSpace = true;
402 			} else {
403 				prevSpace = false;
404 				if ( (charKind == UCK_semicolon) || (charKind == UCK_control) ) break;
405 				if ( (charKind == UCK_comma) && (! allowCommas) ) break;
406 			}
407 
408 		}
409 
410 	}
411 
412 	if ( size_t(charOffset) < item->size() ) {
413 
414 		// --------------------------------------------------------------------------------------
415 		// Create a quoted copy, doubling any internal quotes that match the outer ones. Internal
416 		// quotes did not stop the "needs quoting" search, but they do need doubling. So we have
417 		// to rescan the front of the string for quotes. Handle the special case of U+301D being
418 		// closed by either U+301E or U+301F.
419 
420 		XMP_VarString	newItem;
421 		size_t			splitPoint;
422 
423 		for ( splitPoint = 0; splitPoint <= charOffset; ++splitPoint ) {
424 			ClassifyCharacter ( item->c_str(), splitPoint, &charKind, &charLen, &uniChar );
425 			if ( charKind == UCK_quote ) break;
426 		}
427 
428 		CodePointToUTF8 ( openQuote, newItem );
429 		newItem.append ( *item, 0, splitPoint );	// Copy the leading "normal" portion.
430 
431 		for ( charOffset = splitPoint; size_t(charOffset) < item->size(); charOffset += charLen ) {
432 			ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
433 			newItem.append ( *item, charOffset, charLen );
434 			if ( (charKind == UCK_quote) && IsSurroundingQuote ( uniChar, openQuote, closeQuote ) ) {
435 				newItem.append ( *item, charOffset, charLen );
436 			}
437 		}
438 
439 		XMP_VarString closeStr;
440 		CodePointToUTF8 ( closeQuote, closeStr );
441 		newItem.append ( closeStr );
442 
443 		*item = newItem;
444 
445 	}
446 
447 }	// ApplyQuotes
448 
449 
450 // -------------------------------------------------------------------------------------------------
451 // IsInternalProperty
452 // ------------------
453 
454 // *** Need static checks of the schema prefixes!
455 
456 static const char * kExternalxmpDM[] =
457 	{ "xmpDM:album",
458 	  "xmpDM:altTapeName",
459 	  "xmpDM:altTimecode",
460 	  "xmpDM:artist",
461 	  "xmpDM:cameraAngle",
462 	  "xmpDM:cameraLabel",
463 	  "xmpDM:cameraModel",
464 	  "xmpDM:cameraMove",
465 	  "xmpDM:client",
466 	  "xmpDM:comment",
467 	  "xmpDM:composer",
468 	  "xmpDM:director",
469 	  "xmpDM:directorPhotography",
470 	  "xmpDM:engineer",
471 	  "xmpDM:genre",
472 	  "xmpDM:good",
473 	  "xmpDM:instrument",
474 	  "xmpDM:logComment",
475 	  "xmpDM:projectName",
476 	  "xmpDM:releaseDate",
477 	  "xmpDM:scene",
478 	  "xmpDM:shotDate",
479 	  "xmpDM:shotDay",
480 	  "xmpDM:shotLocation",
481 	  "xmpDM:shotName",
482 	  "xmpDM:shotNumber",
483 	  "xmpDM:shotSize",
484 	  "xmpDM:speakerPlacement",
485 	  "xmpDM:takeNumber",
486 	  "xmpDM:tapeName",
487 	  "xmpDM:trackNumber",
488 	  "xmpDM:videoAlphaMode",
489 	  "xmpDM:videoAlphaPremultipleColor",
490 	  0 };	// ! Must have zero sentinel!
491 
492 typedef const char ** CharStarIterator;	// Used for binary search of kExternalxmpDM;
493 static const char ** kLastExternalxmpDM = 0;	// Set on first use.
CharStarLess(const char * left,const char * right)494 static int CharStarLess (const char * left, const char * right )
495 	{ return (strcmp ( left, right ) < 0); }
496 
497 #define IsExternalProperty(s,p) (! IsInternalProperty ( s, p ))
498 
499 bool
IsInternalProperty(const XMP_VarString & schema,const XMP_VarString & prop)500 IsInternalProperty ( const XMP_VarString & schema, const XMP_VarString & prop )
501 {
502 	bool isInternal = false;
503 
504 	if ( schema == kXMP_NS_DC ) {
505 
506 		if ( (prop == "dc:format")	||
507 			 (prop == "dc:language") ) {
508 			isInternal = true;
509 		}
510 
511 	} else if ( schema == kXMP_NS_XMP ) {
512 
513 		if ( (prop == "xmp:BaseURL")		||
514 			 (prop == "xmp:CreatorTool")	||
515 			 (prop == "xmp:Format")			||
516 			 (prop == "xmp:Locale")			||
517 			 (prop == "xmp:MetadataDate")	||
518 			 (prop == "xmp:ModifyDate") ) {
519 			isInternal = true;
520 		}
521 
522 	} else if ( schema == kXMP_NS_PDF ) {
523 
524 		if ( (prop == "pdf:BaseURL")	||
525 			 (prop == "pdf:Creator")	||
526 			 (prop == "pdf:ModDate")	||
527 			 (prop == "pdf:PDFVersion") ||
528 			 (prop == "pdf:Producer") ) {
529 			isInternal = true;
530 		}
531 
532 	} else if ( schema == kXMP_NS_TIFF ) {
533 
534 		isInternal = true;	// ! The TIFF properties are internal by default.
535 		if ( (prop == "tiff:ImageDescription")	||	// ! ImageDescription, Artist, and Copyright are aliased.
536 			 (prop == "tiff:Artist")			||
537 			 (prop == "tiff:Copyright") ) {
538 			isInternal = false;
539 		}
540 
541 	} else if ( schema == kXMP_NS_EXIF ) {
542 
543 		isInternal = true;	// ! The EXIF properties are internal by default.
544 		if ( prop == "exif:UserComment" ) isInternal = false;
545 
546 	} else if ( schema == kXMP_NS_EXIF_Aux ) {
547 
548 		isInternal = true;	// ! The EXIF Aux properties are internal by default.
549 
550 	} else if ( schema == kXMP_NS_Photoshop ) {
551 
552 		if ( (prop == "photoshop:ICCProfile")  ||
553 			 (prop == "photoshop:TextLayers") ) isInternal = true;
554 
555 	} else if ( schema == kXMP_NS_CameraRaw ) {
556 
557 		isInternal = true;	// All of crs: is internal, they are processing settings.
558 
559 	} else if ( schema == kXMP_NS_DM ) {
560 
561 		// ! Most of the xmpDM schema is internal, and unknown properties default to internal.
562 		if ( kLastExternalxmpDM == 0 ) {
563 			for ( kLastExternalxmpDM = &kExternalxmpDM[0]; *kLastExternalxmpDM != 0; ++kLastExternalxmpDM ) {}
564 		}
565 		isInternal = (! std::binary_search ( &kExternalxmpDM[0], kLastExternalxmpDM, prop.c_str(), CharStarLess ));
566 
567 	} else if ( schema == kXMP_NS_Script ) {
568 
569 		isInternal = true;	// ! Most of the xmpScript schema is internal, and unknown properties default to internal.
570 		if ( (prop == "xmpScript:action") || (prop == "xmpScript:character") || (prop == "xmpScript:dialog") ||
571 			 (prop == "xmpScript:sceneSetting") || (prop == "xmpScript:sceneTimeOfDay") ) {
572 			isInternal = false;
573 		}
574 
575 	} else if ( schema == kXMP_NS_BWF ) {
576 
577 		if ( prop == "bext:version" ) isInternal = true;
578 
579 	} else if ( schema == kXMP_NS_AdobeStockPhoto ) {
580 
581 		isInternal = true;	// ! The bmsp schema has only internal properties.
582 
583 	} else if ( schema == kXMP_NS_XMP_MM ) {
584 
585 		isInternal = true;	// ! The xmpMM schema has only internal properties.
586 
587 	} else if ( schema == kXMP_NS_XMP_Text ) {
588 
589 		isInternal = true;	// ! The xmpT schema has only internal properties.
590 
591 	} else if ( schema == kXMP_NS_XMP_PagedFile ) {
592 
593 		isInternal = true;	// ! The xmpTPg schema has only internal properties.
594 
595 	} else if ( schema == kXMP_NS_XMP_Graphics ) {
596 
597 		isInternal = true;	// ! The xmpG schema has only internal properties.
598 
599 	} else if ( schema == kXMP_NS_XMP_Image ) {
600 
601 		isInternal = true;	// ! The xmpGImg schema has only internal properties.
602 
603 	} else if ( schema == kXMP_NS_XMP_Font ) {
604 
605 		isInternal = true;	// ! The stFNT schema has only internal properties.
606 
607 	}
608 
609 	return isInternal;
610 
611 }	// IsInternalProperty
612 
613 
614 // -------------------------------------------------------------------------------------------------
615 // RemoveSchemaChildren
616 // --------------------
617 
618 static void
RemoveSchemaChildren(XMP_NodePtrPos schemaPos,bool doAll)619 RemoveSchemaChildren ( XMP_NodePtrPos schemaPos, bool doAll )
620 {
621 	XMP_Node * schemaNode = *schemaPos;
622 	XMP_Assert ( XMP_NodeIsSchema ( schemaNode->options ) );
623 
624 	// ! Iterate backwards to reduce shuffling as children are erased and to simplify the logic for
625 	// ! denoting the current child. (Erasing child n makes the old n+1 now be n.)
626 
627 	size_t		   propCount = schemaNode->children.size();
628 	XMP_NodePtrPos beginPos	 = schemaNode->children.begin();
629 
630 	for ( size_t propNum = propCount-1, propLim = (size_t)(-1); propNum != propLim; --propNum ) {
631 		XMP_NodePtrPos currProp = beginPos + propNum;
632 		if ( doAll || IsExternalProperty ( schemaNode->name, (*currProp)->name ) ) {
633 			delete *currProp;	// ! Both delete the node and erase the pointer from the parent.
634 			schemaNode->children.erase ( currProp );
635 		}
636 	}
637 
638 	if ( schemaNode->children.empty() ) {
639 		XMP_Node * tree = schemaNode->parent;
640 		tree->children.erase ( schemaPos );
641 		delete schemaNode;
642 	}
643 
644 }	// RemoveSchemaChildren
645 
646 
647 // -------------------------------------------------------------------------------------------------
648 // ItemValuesMatch
649 // ---------------
650 //
651 // Does the value comparisons for array merging as part of XMPUtils::AppendProperties.
652 
653 static bool
ItemValuesMatch(const XMP_Node * leftNode,const XMP_Node * rightNode)654 ItemValuesMatch ( const XMP_Node * leftNode, const XMP_Node * rightNode )
655 {
656 	const XMP_OptionBits leftForm  = leftNode->options & kXMP_PropCompositeMask;
657 	const XMP_OptionBits rightForm = leftNode->options & kXMP_PropCompositeMask;
658 
659 	if ( leftForm != rightForm ) return false;
660 
661 	if ( leftForm == 0 ) {
662 
663 		// Simple nodes, check the values and xml:lang qualifiers.
664 
665 		if ( leftNode->value != rightNode->value ) return false;
666 		if ( (leftNode->options & kXMP_PropHasLang) != (rightNode->options & kXMP_PropHasLang) ) return false;
667 		if ( leftNode->options & kXMP_PropHasLang ) {
668 			if ( leftNode->qualifiers[0]->value != rightNode->qualifiers[0]->value ) return false;
669 		}
670 
671 	} else if ( leftForm == kXMP_PropValueIsStruct ) {
672 
673 		// Struct nodes, see if all fields match, ignoring order.
674 
675 		if ( leftNode->children.size() != rightNode->children.size() ) return false;
676 
677 		for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) {
678 			const XMP_Node * leftField	= leftNode->children[leftNum];
679 			const XMP_Node * rightField = FindConstChild ( rightNode, leftField->name.c_str() );
680 			if ( (rightField == 0) || (! ItemValuesMatch ( leftField, rightField )) ) return false;
681 		}
682 
683 	} else {
684 
685 		// Array nodes, see if the "leftNode" values are present in the "rightNode", ignoring order, duplicates,
686 		// and extra values in the rightNode-> The rightNode is the destination for AppendProperties.
687 
688 		XMP_Assert ( leftForm & kXMP_PropValueIsArray );
689 
690 		for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) {
691 
692 			const XMP_Node * leftItem = leftNode->children[leftNum];
693 
694 			size_t rightNum, rightLim;
695 			for ( rightNum = 0, rightLim = rightNode->children.size(); rightNum != rightLim; ++rightNum ) {
696 				const XMP_Node * rightItem = rightNode->children[rightNum];
697 				if ( ItemValuesMatch ( leftItem, rightItem ) ) break;
698 			}
699 			if ( rightNum == rightLim ) return false;
700 
701 		}
702 
703 	}
704 
705 	return true;	// All of the checks passed.
706 
707 }	// ItemValuesMatch
708 
709 
710 // -------------------------------------------------------------------------------------------------
711 // AppendSubtree
712 // -------------
713 //
714 // The main implementation of XMPUtils::AppendProperties. See the description in TXMPMeta.hpp.
715 
716 static void
AppendSubtree(const XMP_Node * sourceNode,XMP_Node * destParent,const bool mergeCompound,const bool replaceOld,const bool deleteEmpty)717 AppendSubtree ( const XMP_Node * sourceNode, XMP_Node * destParent,
718 				const bool mergeCompound, const bool replaceOld, const bool deleteEmpty )
719 {
720 	XMP_NodePtrPos destPos;
721 	XMP_Node * destNode = FindChildNode ( destParent, sourceNode->name.c_str(), kXMP_ExistingOnly, &destPos );
722 
723 	bool valueIsEmpty = false;
724 	if ( XMP_PropIsSimple ( sourceNode->options ) ) {
725 		valueIsEmpty = sourceNode->value.empty();
726 	} else {
727 		valueIsEmpty = sourceNode->children.empty();
728 	}
729 
730 	if ( valueIsEmpty ) {
731 		if ( deleteEmpty && (destNode != 0) ) {
732 			delete ( destNode );
733 			destParent->children.erase ( destPos );
734 		}
735 		return;	// ! Done, empty values are either ignored or cause deletions.
736 	}
737 
738 	if ( destNode == 0 ) {
739 		// The one easy case, the destination does not exist.
740 		destNode = CloneSubtree ( sourceNode, destParent, true /* skipEmpty */ );
741 		XMP_Assert ( (destNode == 0) || (! destNode->value.empty()) || (! destNode->children.empty()) );
742 		return;
743 	}
744 
745 	// If we get here we're going to modify an existing property, either replacing or merging.
746 
747 	XMP_Assert ( (! valueIsEmpty) && (destNode != 0) );
748 
749 	XMP_OptionBits sourceForm = sourceNode->options & kXMP_PropCompositeMask;
750 	XMP_OptionBits destForm	  = destNode->options & kXMP_PropCompositeMask;
751 
752 	bool replaceThis = replaceOld;	// ! Don't modify replaceOld, it gets passed to inner calls.
753 	if ( mergeCompound && (! XMP_PropIsSimple ( sourceForm )) ) replaceThis = false;
754 
755 	if ( replaceThis ) {
756 
757 		destNode->value	  = sourceNode->value;	// *** Should use SetNode.
758 		destNode->options = sourceNode->options;
759 		destNode->RemoveChildren();
760 		destNode->RemoveQualifiers();
761 		CloneOffspring ( sourceNode, destNode, true /* skipEmpty */ );
762 
763 		if ( (! XMP_PropIsSimple ( destNode->options )) && destNode->children.empty() ) {
764 			// Don't keep an empty array or struct. The source might be implicitly empty due to
765 			// all children being empty. In this case CloneOffspring should skip them.
766 			DeleteSubtree ( destPos );
767 		}
768 
769 		return;
770 
771 	}
772 
773 	// From here on are cases for merging arrays or structs.
774 
775 	if ( XMP_PropIsSimple ( sourceForm ) || (sourceForm != destForm) ) return;
776 
777 	if ( sourceForm == kXMP_PropValueIsStruct ) {
778 
779 		// To merge a struct process the fields recursively. E.g. add simple missing fields. The
780 		// recursive call to AppendSubtree will handle deletion for fields with empty values.
781 
782 		for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim && destNode!= NULL; ++sourceNum ) {
783 			const XMP_Node * sourceField = sourceNode->children[sourceNum];
784 			AppendSubtree ( sourceField, destNode, mergeCompound, replaceOld, deleteEmpty );
785 			if ( deleteEmpty && destNode->children.empty() ) {
786 				delete ( destNode );
787 				destParent->children.erase ( destPos );
788 			}
789 		}
790 
791 	} else if ( sourceForm & kXMP_PropArrayIsAltText ) {
792 
793 		// Merge AltText arrays by the xml:lang qualifiers. Make sure x-default is first. Make a
794 		// special check for deletion of empty values. Meaningful in AltText arrays because the
795 		// xml:lang qualifier provides unambiguous source/dest correspondence.
796 
797 		XMP_Assert ( mergeCompound );
798 
799 		for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim && destNode!= NULL; ++sourceNum ) {
800 
801 			const XMP_Node * sourceItem = sourceNode->children[sourceNum];
802 			if ( sourceItem->qualifiers.empty() || (sourceItem->qualifiers[0]->name != "xml:lang") ) continue;
803 
804 			XMP_Index destIndex = LookupLangItem ( destNode, sourceItem->qualifiers[0]->value );
805 
806 			if ( sourceItem->value.empty() ) {
807 
808 				if ( deleteEmpty && (destIndex != -1) ) {
809 					delete ( destNode->children[destIndex] );
810 					destNode->children.erase ( destNode->children.begin() + destIndex );
811 					if ( destNode->children.empty() ) {
812 						delete ( destNode );
813 						destParent->children.erase ( destPos );
814 					}
815 				}
816 
817 			} else {
818 
819 				if ( destIndex != -1 ) {
820 
821 					// The source and dest arrays both have this language item.
822 
823 					if ( replaceOld ) {	// ! Yes, check replaceOld not replaceThis!
824 						destNode->children[destIndex]->value = sourceItem->value;
825 					}
826 
827 				} else {
828 
829 					// The dest array does not have this language item, add it.
830 
831 					if ( (sourceItem->qualifiers[0]->value != "x-default") || destNode->children.empty() ) {
832 						// Typical case, empty dest array or not "x-default". Non-empty should always have "x-default".
833 						CloneSubtree ( sourceItem, destNode, true /* skipEmpty */ );
834 					} else {
835 						// Edge case, non-empty dest array had no "x-default", insert that at the beginning.
836 						XMP_Node * destItem = new XMP_Node ( destNode, sourceItem->name, sourceItem->value, sourceItem->options );
837 						CloneOffspring ( sourceItem, destItem, true /* skipEmpty */ );
838 						destNode->children.insert ( destNode->children.begin(), destItem );
839 					}
840 
841 				}
842 
843 			}
844 
845 		}
846 
847 	} else if ( sourceForm & kXMP_PropValueIsArray ) {
848 
849 		// Merge other arrays by item values. Don't worry about order or duplicates. Source
850 		// items with empty values do not cause deletion, that conflicts horribly with merging.
851 
852 		for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
853 			const XMP_Node * sourceItem = sourceNode->children[sourceNum];
854 
855 			size_t	destNum, destLim;
856 			for ( destNum = 0, destLim = destNode->children.size(); destNum != destLim; ++destNum ) {
857 				const XMP_Node * destItem = destNode->children[destNum];
858 				if ( ItemValuesMatch ( sourceItem, destItem ) ) break;
859 			}
860 			if ( destNum == destLim ) CloneSubtree ( sourceItem, destNode, true /* skipEmpty */ );
861 
862 		}
863 
864 	}
865 
866 }	// AppendSubtree
867 
868 
869 // =================================================================================================
870 // Class Static Functions
871 // ======================
872 
873 // -------------------------------------------------------------------------------------------------
874 // CatenateArrayItems
875 // ------------------
876 
877 
878 #if ENABLE_CPP_DOM_MODEL
879 // -------------------------------------------------------------------------------------------------
880 // CatenateArrayItems_v2
881 // ------------------
882 
883 /* class static */ void
CatenateArrayItems_v2(const XMPMeta & ptr,XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_StringPtr separator,XMP_StringPtr quotes,XMP_OptionBits options,XMP_VarString * catedStr)884 XMPUtils::CatenateArrayItems_v2(const XMPMeta & ptr,
885 								XMP_StringPtr   schemaNS,
886 								XMP_StringPtr   arrayName,
887 								XMP_StringPtr   separator,
888 								XMP_StringPtr   quotes,
889 								XMP_OptionBits  options,
890 								XMP_VarString * catedStr)
891 {
892 	using namespace AdobeXMPCore;
893 	using namespace AdobeXMPCommon;
894 
895 	if(sUseNewCoreAPIs) {
896 		const XMPMeta2 & xmpObj = dynamic_cast<const XMPMeta2 &>(ptr);
897 		XMP_Assert((schemaNS != 0) && (arrayName != 0)); // ! Enforced by wrapper.
898 		XMP_Assert((separator != 0) && (quotes != 0) && (catedStr != 0)); // ! Enforced by wrapper.
899 
900 		size_t		 strLen, strPos, charLen;
901 		UniCharKind	 charKind;
902 		UniCodePoint currUCP, openQuote, closeQuote;
903 
904 		const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0);
905 
906 		spINode arrayNode; // ! Move up to avoid gcc complaints.
907 		XMP_OptionBits	 arrayForm = 0, arrayOptions = 0;
908 		spcINode  currItem;
909 
910 		// Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces.
911 		// Any of the recognized semicolons or spaces are allowed.
912 
913 		strPos = 0;
914 		strLen = strlen(separator);
915 		bool haveSemicolon = false;
916 
917 		while (strPos < strLen) {
918 			ClassifyCharacter(separator, strPos, &charKind, &charLen, &currUCP);
919 			strPos += charLen;
920 			if (charKind == UCK_semicolon) {
921 				if (haveSemicolon) XMP_Throw("Separator can have only one semicolon", kXMPErr_BadParam);
922 				haveSemicolon = true;
923 			}
924 			else if (charKind != UCK_space) {
925 				XMP_Throw("Separator can have only spaces and one semicolon", kXMPErr_BadParam);
926 			}
927 		};
928 		if (!haveSemicolon) XMP_Throw("Separator must have one semicolon", kXMPErr_BadParam);
929 
930 		// Make sure the open and close quotes are a legitimate pair.
931 
932 		strLen = strlen(quotes);
933 		ClassifyCharacter(quotes, 0, &charKind, &charLen, &openQuote);
934 		if (charKind != UCK_quote) XMP_Throw("Invalid quoting character", kXMPErr_BadParam);
935 
936 		if (charLen == strLen) {
937 			closeQuote = openQuote;
938 		}
939 		else {
940 			strPos = charLen;
941 			ClassifyCharacter(quotes, strPos, &charKind, &charLen, &closeQuote);
942 			if (charKind != UCK_quote) XMP_Throw("Invalid quoting character", kXMPErr_BadParam);
943 			if ((strPos + charLen) != strLen) XMP_Throw("Quoting string too long", kXMPErr_BadParam);
944 		}
945 		if (closeQuote != GetClosingQuote(openQuote)) XMP_Throw("Mismatched quote pair", kXMPErr_BadParam);
946 
947 		// Return an empty result if the array does not exist, hurl if it isn't the right form.
948 
949 		catedStr->erase();
950 
951 		XMP_ExpandedXPath arrayPath;
952 		ExpandXPath(schemaNS, arrayName, &arrayPath);
953 
954 		XMPUtils::FindCnstNode((xmpObj.mDOM), arrayPath, arrayNode, &arrayOptions);
955 
956 		if (!arrayNode) return;
957 
958 		arrayForm = arrayOptions & kXMP_PropCompositeMask;
959 		if ((!(arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate)) {
960 			XMP_Throw("Named property must be non-alternate array", kXMPErr_BadParam);
961 		}
962 		size_t arrayChildCount = XMPUtils::GetNodeChildCount(arrayNode);
963 		if (!arrayChildCount) return;
964 
965 		// Build the result, quoting the array items, adding separators. Hurl if any item isn't simple.
966 		// Start the result with the first value, then add the rest with a preceeding separator.
967 
968 		spcINodeIterator arrayIter = XMPUtils::GetNodeChildIterator(arrayNode);
969 
970 		if ((XMPUtils::GetIXMPOptions(currItem) & kXMP_PropCompositeMask) != 0) XMP_Throw("Array items must be simple", kXMPErr_BadParam);
971 
972 		*catedStr = arrayIter->GetNode()->ConvertToSimpleNode()->GetValue()->c_str();
973 		ApplyQuotes(catedStr, openQuote, closeQuote, allowCommas);
974 
975 		//ArrayNodes in the new DOM are homogeneous so need to check types of other items in the arary if the first one is Simple
976 		for (arrayIter = arrayIter->Next(); arrayIter; arrayIter = arrayIter->Next()) {
977 
978 			XMP_VarString tempStr( arrayIter->GetNode()->ConvertToSimpleNode()->GetValue()->c_str());
979 			ApplyQuotes(&tempStr, openQuote, closeQuote, allowCommas);
980 			*catedStr += separator;
981 			*catedStr += tempStr;
982 		}
983 	}
984 	else {
985 		return;
986 	}
987 
988 
989 }	// CatenateArrayItems_v2
990 
991 #endif
992 // -------------------------------------------------------------------------------------------------
993 /* class static */ void
CatenateArrayItems(const XMPMeta & xmpObj,XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_StringPtr separator,XMP_StringPtr quotes,XMP_OptionBits options,XMP_VarString * catedStr)994 XMPUtils::CatenateArrayItems ( const XMPMeta & xmpObj,
995 							   XMP_StringPtr   schemaNS,
996 							   XMP_StringPtr   arrayName,
997 							   XMP_StringPtr   separator,
998 							   XMP_StringPtr   quotes,
999 							   XMP_OptionBits  options,
1000 							   XMP_VarString * catedStr )
1001 {
1002 
1003 #if ENABLE_CPP_DOM_MODEL
1004 
1005 	if(sUseNewCoreAPIs) {
1006 
1007 		dynamic_cast<const XMPMeta2 &>(xmpObj);
1008 		CatenateArrayItems_v2(xmpObj, schemaNS, arrayName, separator, quotes, options, catedStr);
1009 		return;
1010 
1011 	}
1012 
1013 #endif
1014 
1015 	XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // ! Enforced by wrapper.
1016 	XMP_Assert ( (separator != 0) && (quotes != 0) && (catedStr != 0) ); // ! Enforced by wrapper.
1017 
1018 	size_t		 strLen, strPos, charLen;
1019 	UniCharKind	 charKind;
1020 	UniCodePoint currUCP, openQuote, closeQuote;
1021 
1022 	const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0);
1023 
1024 	const XMP_Node * arrayNode = 0; // ! Move up to avoid gcc complaints.
1025 	XMP_OptionBits	 arrayForm = 0;
1026 	const XMP_Node * currItem  = 0;
1027 
1028 	// Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces.
1029 	// Any of the recognized semicolons or spaces are allowed.
1030 
1031 	strPos = 0;
1032 	strLen = strlen ( separator );
1033 	bool haveSemicolon = false;
1034 
1035 	while ( strPos < strLen ) {
1036 		ClassifyCharacter ( separator, strPos, &charKind, &charLen, &currUCP );
1037 		strPos += charLen;
1038 		if ( charKind == UCK_semicolon ) {
1039 			if ( haveSemicolon ) XMP_Throw ( "Separator can have only one semicolon",  kXMPErr_BadParam );
1040 			haveSemicolon = true;
1041 		} else if ( charKind != UCK_space ) {
1042 			XMP_Throw ( "Separator can have only spaces and one semicolon",	 kXMPErr_BadParam );
1043 		}
1044 	};
1045 	if ( ! haveSemicolon ) XMP_Throw ( "Separator must have one semicolon",	 kXMPErr_BadParam );
1046 
1047 	// Make sure the open and close quotes are a legitimate pair.
1048 
1049 	strLen = strlen ( quotes );
1050 	ClassifyCharacter ( quotes, 0, &charKind, &charLen, &openQuote );
1051 	if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
1052 
1053 	if ( charLen == strLen ) {
1054 		closeQuote = openQuote;
1055 	} else {
1056 		strPos = charLen;
1057 		ClassifyCharacter ( quotes, strPos, &charKind, &charLen, &closeQuote );
1058 		if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
1059 		if ( (strPos + charLen) != strLen ) XMP_Throw ( "Quoting string too long", kXMPErr_BadParam );
1060 	}
1061 	if ( closeQuote != GetClosingQuote ( openQuote ) ) XMP_Throw ( "Mismatched quote pair", kXMPErr_BadParam );
1062 
1063 	// Return an empty result if the array does not exist, hurl if it isn't the right form.
1064 
1065 	catedStr->erase();
1066 
1067 	XMP_ExpandedXPath arrayPath;
1068 	ExpandXPath ( schemaNS, arrayName, &arrayPath );
1069 
1070 	arrayNode = FindConstNode ( &xmpObj.tree, arrayPath );
1071 	if ( arrayNode == 0 ) return;
1072 
1073 	arrayForm = arrayNode->options & kXMP_PropCompositeMask;
1074 	if ( (! (arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
1075 		XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadParam );
1076 	}
1077 	if ( arrayNode->children.empty() ) return;
1078 
1079 	// Build the result, quoting the array items, adding separators. Hurl if any item isn't simple.
1080 	// Start the result with the first value, then add the rest with a preceeding separator.
1081 
1082 	currItem = arrayNode->children[0];
1083 
1084 	if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
1085 	*catedStr = currItem->value;
1086 	ApplyQuotes ( catedStr, openQuote, closeQuote, allowCommas );
1087 
1088 	for ( size_t itemNum = 1, itemLim = arrayNode->children.size(); itemNum != itemLim; ++itemNum ) {
1089 		const XMP_Node * item = arrayNode->children[itemNum];
1090 		if ( (item->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
1091 		XMP_VarString tempStr ( item->value );
1092 		ApplyQuotes ( &tempStr, openQuote, closeQuote, allowCommas );
1093 		*catedStr += separator;
1094 		*catedStr += tempStr;
1095 	}
1096 
1097 }	// CatenateArrayItems
1098 
1099 
1100 // -------------------------------------------------------------------------------------------------
1101 // SeparateArrayItems_v2
1102 // ------------------
1103 #if ENABLE_CPP_DOM_MODEL
1104 /* class static */ void
SeparateArrayItems_v2(XMPMeta * xmpObj2,XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_OptionBits options,XMP_StringPtr catedStr)1105 XMPUtils::SeparateArrayItems_v2(XMPMeta *	  xmpObj2,
1106 XMP_StringPtr  schemaNS,
1107 XMP_StringPtr  arrayName,
1108 XMP_OptionBits options,
1109 XMP_StringPtr  catedStr)
1110 {
1111 
1112 #if ENABLE_CPP_DOM_MODEL
1113 	using namespace AdobeXMPCore;
1114 	using namespace AdobeXMPCommon;
1115 	XMPMeta2 * xmpObj = NULL;
1116 	if(sUseNewCoreAPIs) {
1117 		xmpObj = dynamic_cast<XMPMeta2 *> (xmpObj2);
1118 	}
1119 
1120 #endif
1121 	XMP_Assert((schemaNS != 0) && (arrayName != 0) && (catedStr != 0));	// ! Enforced by wrapper.
1122 	// TODO - check if the array item name should be arrayname one or karrayitem
1123 	// TODO - check the find array in case array doesn't already exist
1124 	XMP_VarString itemValue;
1125 	size_t itemStart, itemEnd;
1126 	size_t nextSize, charSize = 0;
1127 	UniCharKind	  nextKind, charKind = UCK_normal;
1128 	UniCodePoint  nextChar, uniChar = 0;
1129 	XMP_OptionBits arrayOptions = 0;
1130 
1131 
1132 	bool preserveCommas = false;
1133 	if (options & kXMPUtil_AllowCommas) {
1134 		preserveCommas = true;
1135 		options ^= kXMPUtil_AllowCommas;
1136 	}
1137 
1138 	options = VerifySetOptions(options, 0);
1139 	if (options & ~kXMP_PropArrayFormMask) XMP_Throw("Options can only provide array form", kXMPErr_BadOptions);
1140 
1141 	// Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept.
1142 
1143 	XMP_ExpandedXPath arrayPath;
1144 	ExpandXPath(schemaNS, arrayName, &arrayPath);
1145 	spINode arrayNode;
1146 	if (XMPUtils::FindCnstNode(xmpObj->mDOM, arrayPath, arrayNode, &arrayOptions)){
1147 
1148 		XMP_OptionBits arrayForm = arrayOptions & kXMP_PropArrayFormMask;
1149 		if ((arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate)) {
1150 			XMP_Throw("Named property must be non-alternate array", kXMPErr_BadXPath);
1151 		}
1152 
1153 		if ((options != 0) && (options != arrayForm)) XMP_Throw("Mismatch of specified and existing array form", kXMPErr_BadXPath);	// *** Right error?
1154 	}
1155 	else {
1156 		// The array does not exist, try to create it.
1157 
1158 		XPathStepInfo  lastPathSegment(arrayPath.back());
1159 		XMP_VarString arrayStep = lastPathSegment.step;
1160 		//arrayPath.pop_back();
1161 		spINode destNode;
1162 		XMP_Index insertIndex = 0;
1163 		if (!XMPUtils::FindNode(xmpObj->mDOM, arrayPath, kXMP_CreateNodes, options, destNode, &insertIndex, true)) {
1164 			XMP_Throw("Failure creating array node", kXMPErr_BadXPath);
1165 		}
1166 		std::string arrayNameSpace, arrayName;
1167 		auto defaultMap = INameSpacePrefixMap::GetDefaultNameSpacePrefixMap();
1168 		arrayOptions = options;
1169 		XMPUtils::GetNameSpaceAndNameFromStepValue(lastPathSegment.step, defaultMap, arrayNameSpace, arrayName);
1170 		//  Need to check Alternate first
1171 		if (arrayOptions & kXMP_PropArrayIsAlternate) {
1172 			arrayNode = IArrayNode::CreateAlternativeArrayNode( arrayNameSpace.c_str(), arrayNameSpace.size(), arrayName.c_str(), arrayName.size());
1173 		}
1174 		else if (arrayOptions & kXMP_PropArrayIsOrdered) {
1175 			arrayNode = IArrayNode::CreateOrderedArrayNode( arrayNameSpace.c_str(), arrayNameSpace.size(), arrayName.c_str(), arrayName.size() );
1176 		}
1177 		else if (arrayOptions & kXMP_PropArrayIsUnordered) {
1178 			arrayNode = IArrayNode::CreateUnorderedArrayNode( arrayNameSpace.c_str(), arrayNameSpace.size(), arrayName.c_str(), arrayName.size() );
1179 		}
1180 
1181 		else {
1182 			XMP_Throw("Failure creating array node", kXMPErr_BadXPath);
1183 		}
1184 		if (destNode->GetNodeType() == INode::kNTStructure) {
1185 
1186 			destNode->ConvertToStructureNode()->InsertNode(arrayNode);
1187 		}
1188 		else if (destNode->GetNodeType() == INode::kNTArray) {
1189 
1190 			destNode->ConvertToArrayNode()->AppendNode(arrayNode);
1191 		}
1192 		else {
1193 
1194 			XMP_Throw("Failure creating array node", kXMPErr_BadXPath);
1195 		}
1196 
1197 		if (!arrayNode) XMP_Throw("Failed to create named array", kXMPErr_BadXPath);
1198 	}
1199 
1200 
1201 	size_t oldChildCount = XMPUtils::GetNodeChildCount(arrayNode);
1202 	std::vector<XMP_VarString> oldArrayNodes;
1203 	std::vector<spINode> qualifiers;
1204 
1205 	// used to handle duplicates
1206 	std::vector<bool> oldArrayNodeSeen(oldChildCount, false);
1207 	spcINodeIterator oldArrayChildIter = XMPUtils::GetNodeChildIterator(arrayNode);
1208 
1209 	for (; oldArrayChildIter; oldArrayChildIter = oldArrayChildIter->Next()) {
1210 
1211 		oldArrayNodes.push_back( oldArrayChildIter->GetNode()->ConvertToSimpleNode()->GetValue()->c_str());
1212 		if (oldArrayChildIter->GetNode()->HasQualifiers()) {
1213 
1214 			qualifiers.push_back(oldArrayChildIter->GetNode()->Clone());
1215 			/*for ( auto it = oldArrayChildIter->GetNode()->QualifiersIterator(); it; it = it->Next() ) {
1216 				qualifiers.push_back( it->GetNode()->Clone() );
1217 				}*/
1218 		}
1219 		else {
1220 			qualifiers.push_back(spINode());
1221 		}
1222 
1223 	}
1224 
1225 	arrayNode->Clear(true, false);
1226 	// used to avoid typecasting repeatedly!
1227 	spIArrayNode tempArrayNode = arrayNode->ConvertToArrayNode();
1228 
1229 	size_t endPos = strlen(catedStr);
1230 
1231 	itemEnd = 0;
1232 	while (itemEnd < endPos) {
1233 
1234 
1235 
1236 		for (itemStart = itemEnd; itemStart < endPos; itemStart += charSize) {
1237 			ClassifyCharacter(catedStr, itemStart, &charKind, &charSize, &uniChar);
1238 			if ((charKind == UCK_normal) || (charKind == UCK_quote)) break;
1239 		}
1240 		if (itemStart >= endPos) break;
1241 
1242 		if (charKind != UCK_quote) {
1243 
1244 
1245 
1246 			for (itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize) {
1247 
1248 				ClassifyCharacter(catedStr, itemEnd, &charKind, &charSize, &uniChar);
1249 
1250 				if ((charKind == UCK_normal) || (charKind == UCK_quote)) continue;
1251 				if ((charKind == UCK_comma) && preserveCommas) continue;
1252 				if (charKind != UCK_space) break;
1253 
1254 				if ((itemEnd + charSize) >= endPos) break;	// Anything left?
1255 				ClassifyCharacter(catedStr, (itemEnd + charSize), &nextKind, &nextSize, &nextChar);
1256 				if ((nextKind == UCK_normal) || (nextKind == UCK_quote)) continue;
1257 				if ((nextKind == UCK_comma) && preserveCommas) continue;
1258 				break;	// Have multiple spaces, or a space followed by a separator.
1259 
1260 			}
1261 
1262 			itemValue.assign(catedStr, itemStart, (itemEnd - itemStart));
1263 
1264 		}
1265 		else {
1266 
1267 			// Accumulate quoted values into a local string, undoubling internal quotes that
1268 			// match the surrounding quotes. Do not undouble "unmatching" quotes.
1269 
1270 			UniCodePoint openQuote = uniChar;
1271 			UniCodePoint closeQuote = GetClosingQuote(openQuote);
1272 
1273 			itemStart += charSize;	// Skip the opening quote;
1274 			itemValue.erase();
1275 
1276 			for (itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize) {
1277 
1278 				ClassifyCharacter(catedStr, itemEnd, &charKind, &charSize, &uniChar);
1279 
1280 				if ((charKind != UCK_quote) || (!IsSurroundingQuote(uniChar, openQuote, closeQuote))) {
1281 
1282 					// This is not a matching quote, just append it to the item value.
1283 					itemValue.append(catedStr, itemEnd, charSize);
1284 
1285 				}
1286 				else {
1287 
1288 					// This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate
1289 					// various edge cases like undoubled opening (non-closing) quotes, or end of input.
1290 
1291 					if ((itemEnd + charSize) < endPos) {
1292 						ClassifyCharacter(catedStr, itemEnd + charSize, &nextKind, &nextSize, &nextChar);
1293 					}
1294 					else {
1295 						nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B;
1296 					}
1297 
1298 					if (uniChar == nextChar) {
1299 						// This is doubled, copy it and skip the double.
1300 						itemValue.append(catedStr, itemEnd, charSize);
1301 						itemEnd += nextSize;	// Loop will add in charSize.
1302 					}
1303 					else if (!IsClosingingQuote(uniChar, openQuote, closeQuote)) {
1304 						// This is an undoubled, non-closing quote, copy it.
1305 						itemValue.append(catedStr, itemEnd, charSize);
1306 					}
1307 					else {
1308 						// This is an undoubled closing quote, skip it and exit the loop.
1309 						itemEnd += charSize;
1310 						break;
1311 					}
1312 
1313 				}
1314 
1315 			}	// Loop to accumulate the quoted value.
1316 
1317 		}
1318 
1319 		// Add the separated item to the array. Keep a matching old value in case it had separators.
1320 
1321 		size_t oldChild;
1322 
1323 		spISimpleNode newItem;
1324 		for (oldChild = 1; oldChild <= oldChildCount; ++oldChild) {
1325 			if (!oldArrayNodeSeen[oldChild - 1] && itemValue == oldArrayNodes[oldChild - 1]) break;
1326 		}
1327 
1328 
1329 		if (oldChild == oldChildCount + 1) {
1330 			//	newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 );
1331 			newItem = ISimpleNode::CreateSimpleNode(arrayNode->GetNameSpace()->c_str(), arrayNode->GetNameSpace()->size(),
1332 				kXMP_ArrayItemName, AdobeXMPCommon::npos, itemValue.c_str());
1333 		}
1334 		else {
1335 			newItem = ISimpleNode::CreateSimpleNode(arrayNode->GetNameSpace()->c_str(), arrayNode->GetNameSpace()->size(),
1336 				kXMP_ArrayItemName, AdobeXMPCommon::npos, oldArrayNodes[oldChild - 1].c_str());
1337 			if (qualifiers[ oldChild - 1] && qualifiers[ oldChild - 1] ->HasQualifiers() ) {
1338 
1339 				for (auto it = qualifiers[oldChild - 1] ->QualifiersIterator(); it; it = it->Next()) {
1340 
1341 					newItem->InsertQualifier(it->GetNode()->Clone());
1342 				}
1343 			}
1344 			oldArrayNodeSeen[oldChild - 1] = true;	// ! Don't match again, let duplicates be seen.
1345 		}
1346 
1347 		tempArrayNode->AppendNode(newItem);
1348 
1349 	}	// Loop through all of the returned items.
1350 
1351 	// Delete any of the old children that were not kept.
1352 
1353 
1354 }	// SeparateArrayItems_v2
1355 #endif
1356 
1357 // -------------------------------------------------------------------------------------------------
1358 // SeparateArrayItems
1359 // ------------------
1360 
1361 /* class static */ void
SeparateArrayItems(XMPMeta * xmpObj,XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_OptionBits options,XMP_StringPtr catedStr)1362 XMPUtils::SeparateArrayItems ( XMPMeta *	  xmpObj,
1363 							   XMP_StringPtr  schemaNS,
1364 							   XMP_StringPtr  arrayName,
1365 							   XMP_OptionBits options,
1366 							   XMP_StringPtr  catedStr )
1367 {
1368 
1369 #if ENABLE_CPP_DOM_MODEL
1370 	if (sUseNewCoreAPIs) {
1371 		SeparateArrayItems_v2(xmpObj, schemaNS, arrayName, options, catedStr);
1372 		return;
1373 	}
1374 #endif
1375 	XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (catedStr != 0) );	// ! Enforced by wrapper.
1376 
1377 	XMP_VarString itemValue;
1378 	size_t itemStart, itemEnd;
1379 	size_t nextSize, charSize = 0;	// Avoid VS uninit var warnings.
1380 	UniCharKind	  nextKind, charKind = UCK_normal;
1381 	UniCodePoint  nextChar, uniChar = 0;
1382 
1383 	// Extract "special" option bits, verify and normalize the others.
1384 
1385 	bool preserveCommas = false;
1386 	if ( options & kXMPUtil_AllowCommas ) {
1387 		preserveCommas = true;
1388 		options ^= kXMPUtil_AllowCommas;
1389 	}
1390 
1391 	options = VerifySetOptions ( options, 0 );	// Keep a zero value, has special meaning below.
1392 	if ( options & ~kXMP_PropArrayFormMask ) XMP_Throw ( "Options can only provide array form", kXMPErr_BadOptions );
1393 
1394 	// Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept.
1395 
1396 	XMP_ExpandedXPath arrayPath;
1397 	ExpandXPath ( schemaNS, arrayName, &arrayPath );
1398 	XMP_Node * arrayNode = ::FindNode( &xmpObj->tree, arrayPath, kXMP_ExistingOnly );
1399 
1400 	if ( arrayNode != 0 ) {
1401 		// The array exists, make sure the form is compatible. Zero arrayForm means take what exists.
1402 		XMP_OptionBits arrayForm = arrayNode->options & kXMP_PropArrayFormMask;
1403 		if ( (arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
1404 			XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadXPath );
1405 		}
1406 		if ( (options != 0) && (options != arrayForm) ) XMP_Throw ( "Mismatch of specified and existing array form", kXMPErr_BadXPath );	// *** Right error?
1407 	} else {
1408 		// The array does not exist, try to create it.
1409 		arrayNode = ::FindNode( &xmpObj->tree, arrayPath, kXMP_CreateNodes, (options | kXMP_PropValueIsArray) );
1410 		if ( arrayNode == 0 ) XMP_Throw ( "Failed to create named array", kXMPErr_BadXPath );
1411 	}
1412 
1413 	XMP_NodeOffspring oldChildren ( arrayNode->children );
1414 	size_t oldChildCount = oldChildren.size();
1415 	arrayNode->children.clear();
1416 
1417 	// Extract the item values one at a time, until the whole input string is done. Be very careful
1418 	// in the extraction about the string positions. They are essentially byte pointers, while the
1419 	// contents are UTF-8. Adding or subtracting 1 does not necessarily move 1 Unicode character!
1420 
1421 	size_t endPos = strlen ( catedStr );
1422 
1423 	itemEnd = 0;
1424 	while ( itemEnd < endPos ) {
1425 
1426 		// Skip any leading spaces and separation characters. Always skip commas here. They can be
1427 		// kept when within a value, but not when alone between values.
1428 
1429 		for ( itemStart = itemEnd; itemStart < endPos; itemStart += charSize ) {
1430 			ClassifyCharacter ( catedStr, itemStart, &charKind, &charSize, &uniChar );
1431 			if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) break;
1432 		}
1433 		if ( itemStart >= endPos ) break;
1434 
1435 		if ( charKind != UCK_quote ) {
1436 
1437 			// This is not a quoted value. Scan for the end, create an array item from the substring.
1438 
1439 			for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {
1440 
1441 				ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );
1442 
1443 				if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) continue;
1444 				if ( (charKind == UCK_comma) && preserveCommas ) continue;
1445 				if ( charKind != UCK_space ) break;
1446 
1447 				if ( (itemEnd + charSize) >= endPos ) break;	// Anything left?
1448 				ClassifyCharacter ( catedStr, (itemEnd+charSize), &nextKind, &nextSize, &nextChar );
1449 				if ( (nextKind == UCK_normal) || (nextKind == UCK_quote) ) continue;
1450 				if ( (nextKind == UCK_comma) && preserveCommas ) continue;
1451 				break;	// Have multiple spaces, or a space followed by a separator.
1452 
1453 			}
1454 
1455 			itemValue.assign ( catedStr, itemStart, (itemEnd - itemStart) );
1456 
1457 		} else {
1458 
1459 			// Accumulate quoted values into a local string, undoubling internal quotes that
1460 			// match the surrounding quotes. Do not undouble "unmatching" quotes.
1461 
1462 			UniCodePoint openQuote = uniChar;
1463 			UniCodePoint closeQuote = GetClosingQuote ( openQuote );
1464 
1465 			itemStart += charSize;	// Skip the opening quote;
1466 			itemValue.erase();
1467 
1468 			for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {
1469 
1470 				ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );
1471 
1472 				if ( (charKind != UCK_quote) || (! IsSurroundingQuote ( uniChar, openQuote, closeQuote)) ) {
1473 
1474 					// This is not a matching quote, just append it to the item value.
1475 					itemValue.append ( catedStr, itemEnd, charSize );
1476 
1477 				} else {
1478 
1479 					// This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate
1480 					// various edge cases like undoubled opening (non-closing) quotes, or end of input.
1481 
1482 					if ( (itemEnd + charSize) < endPos ) {
1483 						ClassifyCharacter ( catedStr, itemEnd+charSize, &nextKind, &nextSize, &nextChar );
1484 					} else {
1485 						nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B;
1486 					}
1487 
1488 					if ( uniChar == nextChar ) {
1489 						// This is doubled, copy it and skip the double.
1490 						itemValue.append ( catedStr, itemEnd, charSize );
1491 						itemEnd += nextSize;	// Loop will add in charSize.
1492 					} else if ( ! IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
1493 						// This is an undoubled, non-closing quote, copy it.
1494 						itemValue.append ( catedStr, itemEnd, charSize );
1495 					} else {
1496 						// This is an undoubled closing quote, skip it and exit the loop.
1497 						itemEnd += charSize;
1498 						break;
1499 					}
1500 
1501 				}
1502 
1503 			}	// Loop to accumulate the quoted value.
1504 
1505 		}
1506 
1507 		// Add the separated item to the array. Keep a matching old value in case it had separators.
1508 
1509 		size_t oldChild;
1510 		for ( oldChild = 0; oldChild < oldChildCount; ++oldChild ) {
1511 			if ( (oldChildren[oldChild] != 0) && (itemValue == oldChildren[oldChild]->value) ) break;
1512 		}
1513 
1514 		XMP_Node * newItem = 0;
1515 		if ( oldChild == oldChildCount ) {
1516 			newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 );
1517 		} else {
1518 			newItem = oldChildren[oldChild];
1519 			oldChildren[oldChild] = 0;	// ! Don't match again, let duplicates be seen.
1520 		}
1521 		arrayNode->children.push_back ( newItem );
1522 
1523 	}	// Loop through all of the returned items.
1524 
1525 	// Delete any of the old children that were not kept.
1526 	for ( size_t i = 0; i < oldChildCount; ++i ) {
1527 		if ( oldChildren[i] != 0 ) delete oldChildren[i];
1528 	}
1529 
1530 }	// SeparateArrayItems
1531 
1532 
1533 // -------------------------------------------------------------------------------------------------
1534 // ApplyTemplate
1535 // -------------
1536 
1537 /* class static */ void
ApplyTemplate(XMPMeta * workingXMP,const XMPMeta & templateXMP,XMP_OptionBits actions)1538 XMPUtils::ApplyTemplate ( XMPMeta *	      workingXMP,
1539 						  const XMPMeta & templateXMP,
1540 						  XMP_OptionBits  actions )
1541 {
1542 
1543 #if ENABLE_CPP_DOM_MODEL
1544 	if (sUseNewCoreAPIs) {
1545 		ApplyTemplate_v2(workingXMP, templateXMP, actions);
1546 		return;
1547 	}
1548 #endif
1549 
1550 	bool doClear   = XMP_OptionIsSet ( actions, kXMPTemplate_ClearUnnamedProperties );
1551 	bool doAdd     = XMP_OptionIsSet ( actions, kXMPTemplate_AddNewProperties );
1552 	bool doReplace = XMP_OptionIsSet ( actions, kXMPTemplate_ReplaceExistingProperties );
1553 
1554 	bool deleteEmpty = XMP_OptionIsSet ( actions, kXMPTemplate_ReplaceWithDeleteEmpty );
1555 	doReplace |= deleteEmpty;	// Delete-empty implies Replace.
1556 	deleteEmpty &= (! doClear);	// Clear implies not delete-empty, but keep the implicit Replace.
1557 
1558 	bool doAll = XMP_OptionIsSet ( actions, kXMPTemplate_IncludeInternalProperties );
1559 
1560 	// ! In several places we do loops backwards so that deletions do not perturb the remaining indices.
1561 	// ! These loops use ordinals (size .. 1), we must use a zero based index inside the loop.
1562 
1563 	if ( doClear ) {
1564 
1565 		// Visit the top level working properties, delete if not in the template.
1566 
1567 		for ( size_t schemaOrdinal = workingXMP->tree.children.size(); schemaOrdinal > 0; --schemaOrdinal ) {
1568 
1569 			size_t schemaNum = schemaOrdinal-1;	// ! Convert ordinal to index!
1570 			XMP_Node * workingSchema = workingXMP->tree.children[schemaNum];
1571 			const XMP_Node * templateSchema = FindConstSchema ( &templateXMP.tree, workingSchema->name.c_str() );
1572 
1573 			if ( templateSchema == 0 ) {
1574 
1575 				// The schema is not in the template, delete all properties or just all external ones.
1576 
1577 				if ( doAll ) {
1578 
1579 					workingSchema->RemoveChildren();	// Remove the properties here, delete the schema below.
1580 
1581 				} else {
1582 
1583 					for ( size_t propOrdinal = workingSchema->children.size(); propOrdinal > 0; --propOrdinal ) {
1584 						size_t propNum = propOrdinal-1;	// ! Convert ordinal to index!
1585 						XMP_Node * workingProp = workingSchema->children[propNum];
1586 						if ( IsExternalProperty ( workingSchema->name, workingProp->name ) ) {
1587 							delete ( workingProp );
1588 							workingSchema->children.erase ( workingSchema->children.begin() + propNum );
1589 						}
1590 					}
1591 
1592 				}
1593 
1594 			} else {
1595 
1596 				// Check each of the working XMP's properties to see if it is in the template.
1597 
1598 				for ( size_t propOrdinal = workingSchema->children.size(); propOrdinal > 0; --propOrdinal ) {
1599 					size_t propNum = propOrdinal-1;	// ! Convert ordinal to index!
1600 					XMP_Node * workingProp = workingSchema->children[propNum];
1601 					if ( (doAll || IsExternalProperty ( workingSchema->name, workingProp->name )) &&
1602 						 (FindConstChild ( templateSchema, workingProp->name.c_str() ) == 0) ) {
1603 						delete ( workingProp );
1604 						workingSchema->children.erase ( workingSchema->children.begin() + propNum );
1605 					}
1606 				}
1607 
1608 			}
1609 
1610 			if ( workingSchema->children.empty() ) {
1611 				delete ( workingSchema );
1612 				workingXMP->tree.children.erase ( workingXMP->tree.children.begin() + schemaNum );
1613 			}
1614 
1615 		}
1616 
1617 	}
1618 
1619 	if ( doAdd | doReplace ) {
1620 
1621 		for ( size_t schemaNum = 0, schemaLim = templateXMP.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) {
1622 
1623 			const XMP_Node * templateSchema = templateXMP.tree.children[schemaNum];
1624 
1625 			// Make sure we have an output schema node, then process the top level template properties.
1626 
1627 			XMP_NodePtrPos workingSchemaPos;
1628 			XMP_Node * workingSchema = FindSchemaNode ( &workingXMP->tree, templateSchema->name.c_str(),
1629 														kXMP_ExistingOnly, &workingSchemaPos );
1630 			if ( workingSchema == 0 ) {
1631 				workingSchema = new XMP_Node ( &workingXMP->tree, templateSchema->name, templateSchema->value, kXMP_SchemaNode );
1632 				workingXMP->tree.children.push_back ( workingSchema );
1633 				workingSchemaPos = workingXMP->tree.children.end() - 1;
1634 			}
1635 
1636 			for ( size_t propNum = 0, propLim = templateSchema->children.size(); propNum < propLim; ++propNum ) {
1637 				const XMP_Node * templateProp = templateSchema->children[propNum];
1638 				if ( doAll || IsExternalProperty ( templateSchema->name, templateProp->name ) ) {
1639 					AppendSubtree ( templateProp, workingSchema, doAdd, doReplace, deleteEmpty );
1640 				}
1641 			}
1642 
1643 			if ( workingSchema->children.empty() ) {
1644 				delete ( workingSchema );
1645 				workingXMP->tree.children.erase ( workingSchemaPos );
1646 			}
1647 
1648 		}
1649 
1650 	}
1651 
1652 }	// ApplyTemplate
1653 
1654 
1655 // -------------------------------------------------------------------------------------------------
1656 // RemoveProperties
1657 // ----------------
1658 
1659 /* class static */ void
RemoveProperties(XMPMeta * xmpObj,XMP_StringPtr schemaNS,XMP_StringPtr propName,XMP_OptionBits options)1660 XMPUtils::RemoveProperties ( XMPMeta *		xmpObj,
1661 							 XMP_StringPtr	schemaNS,
1662 							 XMP_StringPtr	propName,
1663 							 XMP_OptionBits options )
1664 {
1665 
1666 #if ENABLE_CPP_DOM_MODEL
1667 	if (sUseNewCoreAPIs) {
1668 
1669 		RemoveProperties_v2(xmpObj, schemaNS, propName, options);
1670 		return;
1671 	}
1672 #endif
1673 
1674 	XMP_Assert ( (schemaNS != 0) && (propName != 0) );	// ! Enforced by wrapper.
1675 
1676 	const bool doAll = XMP_TestOption (options, kXMPUtil_DoAllProperties );
1677 	const bool includeAliases = XMP_TestOption ( options, kXMPUtil_IncludeAliases );
1678 
1679 	if ( *propName != 0 ) {
1680 
1681 		// Remove just the one indicated property. This might be an alias, the named schema might
1682 		// not actually exist. So don't lookup the schema node.
1683 
1684 		if ( *schemaNS == 0 ) XMP_Throw ( "Property name requires schema namespace", kXMPErr_BadParam );
1685 
1686 		XMP_ExpandedXPath expPath;
1687 		ExpandXPath ( schemaNS, propName, &expPath );
1688 
1689 		XMP_NodePtrPos propPos;
1690 		XMP_Node * propNode = ::FindNode( &(xmpObj->tree), expPath, kXMP_ExistingOnly, kXMP_NoOptions, &propPos );
1691 		if ( propNode != 0 ) {
1692 			if ( doAll || IsExternalProperty ( expPath[kSchemaStep].step, expPath[kRootPropStep].step ) ) {
1693 				XMP_Node * parent = propNode->parent;	// *** Should have XMP_Node::RemoveChild(pos).
1694 				delete propNode;	// ! Both delete the node and erase the pointer from the parent.
1695 				parent->children.erase ( propPos );
1696 				DeleteEmptySchema ( parent );
1697 			}
1698 		}
1699 
1700 	} else if ( *schemaNS != 0 ) {
1701 
1702 		// Remove all properties from the named schema. Optionally include aliases, in which case
1703 		// there might not be an actual schema node.
1704 
1705 		XMP_NodePtrPos schemaPos;
1706 		XMP_Node * schemaNode = FindSchemaNode ( &xmpObj->tree, schemaNS, kXMP_ExistingOnly, &schemaPos );
1707 		if ( schemaNode != 0 ) RemoveSchemaChildren ( schemaPos, doAll );
1708 
1709 		if ( includeAliases ) {
1710 
1711 			// We're removing the aliases also. Look them up by their namespace prefix. Yes, the
1712 			// alias map is sorted so we could process just that portion. But that takes more code
1713 			// and the extra speed isn't worth it. (Plus this way we avoid a dependence on the map
1714 			// implementation.) Lookup the XMP node from the alias, to make sure the actual exists.
1715 
1716 			XMP_StringPtr nsPrefix;
1717 			XMP_StringLen nsLen;
1718 			(void) XMPMeta::GetNamespacePrefix ( schemaNS, &nsPrefix, &nsLen );
1719 
1720 			XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin();
1721 			XMP_AliasMapPos endAlias  = sRegisteredAliasMap->end();
1722 
1723 			for ( ; currAlias != endAlias; ++currAlias ) {
1724 				if ( strncmp ( currAlias->first.c_str(), nsPrefix, nsLen ) == 0 ) {
1725 					XMP_NodePtrPos actualPos;
1726 					XMP_Node * actualProp = ::FindNode( &xmpObj->tree, currAlias->second, kXMP_ExistingOnly, kXMP_NoOptions, &actualPos );
1727 					if ( actualProp != 0 ) {
1728 						XMP_Node * rootProp = actualProp;
1729 						while ( ! XMP_NodeIsSchema ( rootProp->parent->options ) ) rootProp = rootProp->parent;
1730 						if ( doAll || IsExternalProperty ( rootProp->parent->name, rootProp->name ) ) {
1731 							XMP_Node * parent = actualProp->parent;
1732 							delete actualProp;	// ! Both delete the node and erase the pointer from the parent.
1733 							parent->children.erase ( actualPos );
1734 							DeleteEmptySchema ( parent );
1735 						}
1736 					}
1737 				}
1738 			}
1739 
1740 		}
1741 
1742 	} else {
1743 
1744 		// Remove all appropriate properties from all schema. In this case we don't have to be
1745 		// concerned with aliases, they are handled implicitly from the actual properties.
1746 
1747 		// ! Iterate backwards to reduce shuffling if schema are erased and to simplify the logic
1748 		// ! for denoting the current schema. (Erasing schema n makes the old n+1 now be n.)
1749 
1750 		size_t		   schemaCount = xmpObj->tree.children.size();
1751 		XMP_NodePtrPos beginPos	   = xmpObj->tree.children.begin();
1752 
1753 		for ( size_t schemaNum = schemaCount-1, schemaLim = (size_t)(-1); schemaNum != schemaLim; --schemaNum ) {
1754 			XMP_NodePtrPos currSchema = beginPos + schemaNum;
1755 			RemoveSchemaChildren ( currSchema, doAll );
1756 		}
1757 
1758 	}
1759 
1760 }	// RemoveProperties
1761 
1762 
1763 // -------------------------------------------------------------------------------------------------
1764 // DuplicateSubtree
1765 // ----------------
1766 
1767 /* class static */ void
DuplicateSubtree(const XMPMeta & source,XMPMeta * dest,XMP_StringPtr sourceNS,XMP_StringPtr sourceRoot,XMP_StringPtr destNS,XMP_StringPtr destRoot,XMP_OptionBits options)1768 XMPUtils::DuplicateSubtree ( const XMPMeta & source,
1769 							 XMPMeta *		 dest,
1770 							 XMP_StringPtr	 sourceNS,
1771 							 XMP_StringPtr	 sourceRoot,
1772 							 XMP_StringPtr	 destNS,
1773 							 XMP_StringPtr	 destRoot,
1774 							 XMP_OptionBits	 options )
1775 {
1776 
1777 #if ENABLE_CPP_DOM_MODEL
1778 	if(sUseNewCoreAPIs) {
1779 		(void)dynamic_cast<const XMPMeta2 &>(source);
1780 		return XMPUtils::DuplicateSubtree_v2(source, dest, sourceNS, sourceRoot, destNS, destRoot, options);
1781 	}
1782 
1783 #endif
1784 
1785 	IgnoreParam(options);
1786 
1787 	bool fullSourceTree = false;
1788 	bool fullDestTree   = false;
1789 
1790 	XMP_ExpandedXPath sourcePath, destPath;
1791 
1792 	const XMP_Node * sourceNode = 0;
1793 	XMP_Node * destNode = 0;
1794 
1795 	XMP_Assert ( (sourceNS != 0) && (*sourceNS != 0) );
1796 	XMP_Assert ( (sourceRoot != 0) && (*sourceRoot != 0) );
1797 	XMP_Assert ( (dest != 0) && (destNS != 0) && (destRoot != 0) );
1798 
1799 	if ( *destNS == 0 )	  destNS   = sourceNS;
1800 	if ( *destRoot == 0 ) destRoot = sourceRoot;
1801 
1802 	if ( XMP_LitMatch ( sourceNS, "*" ) ) fullSourceTree = true;
1803 	if ( XMP_LitMatch ( destNS, "*" ) )   fullDestTree   = true;
1804 
1805 	if ( (&source == dest) && (fullSourceTree | fullDestTree) ) {
1806 		XMP_Throw ( "Can't duplicate tree onto itself", kXMPErr_BadParam );
1807 	}
1808 
1809 	if ( fullSourceTree & fullDestTree ) XMP_Throw ( "Use Clone for full tree to full tree", kXMPErr_BadParam );
1810 
1811 	if ( fullSourceTree ) {
1812 
1813 		// The destination must be an existing empty struct, copy all of the source top level as fields.
1814 
1815 		ExpandXPath ( destNS, destRoot, &destPath );
1816 		destNode = ::FindNode( &dest->tree, destPath, kXMP_ExistingOnly );
1817 
1818 		if ( (destNode == 0) || (! XMP_PropIsStruct ( destNode->options )) ) {
1819 			XMP_Throw ( "Destination must be an existing struct", kXMPErr_BadXPath );
1820 		}
1821 
1822 		if ( ! destNode->children.empty() ) {
1823 			if ( options & kXMP_DeleteExisting ) {
1824 				destNode->RemoveChildren();
1825 			} else {
1826 				XMP_Throw ( "Destination must be an empty struct", kXMPErr_BadXPath );
1827 			}
1828 		}
1829 
1830 		for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) {
1831 
1832 			const XMP_Node * currSchema = source.tree.children[schemaNum];
1833 
1834 			for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) {
1835 				sourceNode = currSchema->children[propNum];
1836 				XMP_Node * copyNode = new XMP_Node ( destNode, sourceNode->name, sourceNode->value, sourceNode->options );
1837 				destNode->children.push_back ( copyNode );
1838 				CloneOffspring ( sourceNode, copyNode );
1839 			}
1840 
1841 		}
1842 
1843 	} else if ( fullDestTree ) {
1844 
1845 		// The source node must be an existing struct, copy all of the fields to the dest top level.
1846 
1847 		XMP_ExpandedXPath srcPath;
1848 		ExpandXPath ( sourceNS, sourceRoot, &srcPath );
1849 		sourceNode = FindConstNode ( &source.tree, srcPath );
1850 
1851 		if ( (sourceNode == 0) || (! XMP_PropIsStruct ( sourceNode->options )) ) {
1852 			XMP_Throw ( "Source must be an existing struct", kXMPErr_BadXPath );
1853 		}
1854 
1855 		destNode = &dest->tree;
1856 
1857 		if ( ! destNode->children.empty() ) {
1858 			if ( options & kXMP_DeleteExisting ) {
1859 				destNode->RemoveChildren();
1860 			} else {
1861 				XMP_Throw ( "Destination tree must be empty", kXMPErr_BadXPath );
1862 			}
1863 		}
1864 
1865 		std::string   nsPrefix;
1866 		XMP_StringPtr nsURI;
1867 		XMP_StringLen nsLen;
1868 
1869 		for ( size_t fieldNum = 0, fieldLim = sourceNode->children.size(); fieldNum < fieldLim; ++fieldNum ) {
1870 
1871 			const XMP_Node * currField = sourceNode->children[fieldNum];
1872 
1873 			size_t colonPos = currField->name.find ( ':' );
1874 			if (  colonPos == std::string::npos ) continue;
1875 			nsPrefix.assign ( currField->name.c_str(), colonPos );
1876 			bool nsOK = XMPMeta::GetNamespaceURI ( nsPrefix.c_str(), &nsURI, &nsLen );
1877 			if ( ! nsOK ) XMP_Throw ( "Source field namespace is not global", kXMPErr_BadSchema );
1878 
1879 			XMP_Node * destSchema = FindSchemaNode ( &dest->tree, nsURI, kXMP_CreateNodes );
1880 			if ( destSchema == 0 ) XMP_Throw ( "Failed to find destination schema", kXMPErr_BadSchema );
1881 
1882 			XMP_Node * copyNode = new XMP_Node ( destSchema, currField->name, currField->value, currField->options );
1883 			destSchema->children.push_back ( copyNode );
1884 			CloneOffspring ( currField, copyNode );
1885 
1886 		}
1887 
1888 	} else {
1889 
1890 		// Find the root nodes for the source and destination subtrees.
1891 
1892 		ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
1893 		ExpandXPath ( destNS, destRoot, &destPath );
1894 
1895 		sourceNode = FindConstNode ( &source.tree, sourcePath );
1896 		if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath );
1897 
1898 		destNode = ::FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );	// Dest must not yet exist.
1899 		if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath );
1900 
1901 		destNode = ::FindNode ( &dest->tree, destPath, kXMP_CreateNodes );	// Now create the dest.
1902 		if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath );
1903 
1904 		// Make sure the destination is not within the source! The source can't be inside the destination
1905 		// because the source already existed and the destination was just created.
1906 
1907 		if ( &source == dest ) {
1908 			for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) {
1909 				if ( testNode == sourceNode ) {
1910 					// *** delete the just-created dest root node
1911 					XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath );
1912 				}
1913 			}
1914 		}
1915 
1916 		// *** Could use a CloneTree util here and maybe elsewhere.
1917 
1918 		destNode->value	  = sourceNode->value;	// *** Should use SetNode.
1919 		destNode->options = sourceNode->options;
1920 		CloneOffspring ( sourceNode, destNode );
1921 
1922 	}
1923 
1924 }	// DuplicateSubtree
1925 
1926 
1927 // =================================================================================================
1928