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