Coverage Report

Created: 2025-11-09 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/xmpsdk/src/XMPUtils-FileInfo.cpp
Line
Count
Source
1
// =================================================================================================
2
// Copyright 2002-2008 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 "XMP_Environment.h"  // ! This must be the first include!
10
#include "XMPCore_Impl.hpp"
11
12
#include "XMPUtils.hpp"
13
14
#include <time.h>
15
#include <string.h>
16
#include <cstdlib>
17
#include <locale.h>
18
#include <errno.h>
19
20
#include <stdio.h>  // For snprintf.
21
22
#if XMP_WinBuild
23
#ifdef _MSC_VER
24
  #pragma warning ( disable : 4800 )  // forcing value to bool 'true' or 'false' (performance warning)
25
#endif
26
#endif
27
28
// =================================================================================================
29
// Local Types and Constants
30
// ========================= 
31
32
typedef unsigned long UniCodePoint;
33
34
enum UniCharKind {
35
  UCK_normal,
36
  UCK_space,
37
  UCK_comma,
38
  UCK_semicolon,
39
  UCK_quote,
40
  UCK_control
41
};
42
typedef enum UniCharKind  UniCharKind;
43
44
0
#define UnsByte(c)  ((unsigned char)(c))
45
0
#define UCP(u)    ((UniCodePoint)(u))
46
  // ! Needed on Windows (& PC Linux?) for inequalities with literals ito avoid sign extension.
47
48
#ifndef TraceMultiFile
49
  #define TraceMultiFile  0
50
#endif
51
52
// =================================================================================================
53
// Static Variables
54
// ================
55
56
// =================================================================================================
57
// Local Utilities
58
// ===============
59
60
// -------------------------------------------------------------------------------------------------
61
// ClassifyCharacter
62
// -----------------
63
64
static void
65
ClassifyCharacter ( XMP_StringPtr fullString, size_t offset,
66
          UniCharKind * charKind, size_t * charSize, UniCodePoint * uniChar )
67
0
{
68
0
  *charKind = UCK_normal; // Assume typical case.
69
  
70
0
  unsigned char currByte = UnsByte ( fullString[offset] );
71
  
72
0
  if ( currByte < UnsByte(0x80) ) {
73
  
74
    // ----------------------------------------
75
    // We've got a single byte ASCII character.
76
77
0
    *charSize = 1;
78
0
    *uniChar = currByte;
79
80
0
    if ( currByte > UnsByte(0x22) ) {
81
82
0
      if ( currByte == UnsByte(0x2C) ) {
83
0
        *charKind = UCK_comma;
84
0
      } else if ( currByte == UnsByte(0x3B) ) {
85
0
        *charKind = UCK_semicolon;
86
0
      } else if ( (currByte == UnsByte(0x5B)) || (currByte == UnsByte(0x5D)) ) {
87
0
        *charKind = UCK_quote;  // ! ASCII '[' and ']' are used as quotes in Chinese and Korean.
88
0
      }
89
90
0
    } else { // currByte <= 0x22
91
92
0
      if ( currByte == UnsByte(0x22) ) {
93
0
        *charKind = UCK_quote;
94
0
      } else if ( currByte == UnsByte(0x21) ) {
95
0
        *charKind = UCK_normal;
96
0
      } else if ( currByte == UnsByte(0x20) ) {
97
0
        *charKind = UCK_space;
98
0
      } else {
99
0
        *charKind = UCK_control;
100
0
      }
101
102
0
    }
103
104
0
  } else { // currByte >= 0x80
105
  
106
    // ---------------------------------------------------------------------------------------
107
    // We've got a multibyte Unicode character. The first byte has the number of bytes and the
108
    // highest order bits. The other bytes each add 6 more bits. Compose the UTF-32 form so we
109
    // can classify directly with the Unicode code points. Order the upperBits tests to be
110
    // fastest for Japan, probably the most common non-ASCII usage.
111
    
112
0
    *charSize = 0;
113
0
    *uniChar = currByte;
114
0
    while ( (*uniChar & 0x80) != 0 ) { // Count the leading 1 bits in the byte.
115
0
      ++(*charSize);
116
0
      *uniChar = *uniChar << 1;
117
0
    }
118
0
    XMP_Assert ( (offset + *charSize) <= strlen(fullString) );
119
    
120
0
    *uniChar = *uniChar & 0x7F;     // Put the character bits in the bottom of uniChar.
121
0
    *uniChar = *uniChar >> *charSize;
122
    
123
0
    for ( size_t i = (offset + 1); i < (offset + *charSize); ++i ) {
124
0
      *uniChar = (*uniChar << 6) | (UnsByte(fullString[i]) & 0x3F);
125
0
    }
126
    
127
0
    XMP_Uns32 upperBits = *uniChar >> 8;  // First filter on just the high order 24 bits.
128
129
0
    if ( upperBits == 0xFF ) {     // U+FFxx
130
131
0
      if ( *uniChar == UCP(0xFF0C) ) {
132
0
        *charKind = UCK_comma;      // U+FF0C, full width comma.
133
0
      } else if ( *uniChar == UCP(0xFF1B) ) {
134
0
        *charKind = UCK_semicolon;    // U+FF1B, full width semicolon.
135
0
      } else if ( *uniChar == UCP(0xFF64) ) {
136
0
        *charKind = UCK_comma;      // U+FF64, half width ideographic comma.
137
0
      }
138
139
0
    } else if ( upperBits == 0xFE ) { // U+FE--
140
141
0
      if ( *uniChar == UCP(0xFE50) ) {
142
0
        *charKind = UCK_comma;      // U+FE50, small comma.
143
0
      } else if ( *uniChar == UCP(0xFE51) ) {
144
0
        *charKind = UCK_comma;      // U+FE51, small ideographic comma.
145
0
      } else if ( *uniChar == UCP(0xFE54) ) {
146
0
        *charKind = UCK_semicolon;    // U+FE54, small semicolon.
147
0
      }
148
149
0
    } else if ( upperBits == 0x30 ) { // U+30--
150
151
0
      if ( *uniChar == UCP(0x3000) ) {
152
0
        *charKind = UCK_space;      // U+3000, ideographic space.
153
0
      } else if ( *uniChar == UCP(0x3001) ) {
154
0
        *charKind = UCK_comma;      // U+3001, ideographic comma.
155
0
      } else if ( (UCP(0x3008) <= *uniChar) && (*uniChar <= UCP(0x300F)) ) {
156
0
        *charKind = UCK_quote;      // U+3008..U+300F, various quotes.
157
0
      } else if ( *uniChar == UCP(0x303F) ) {
158
0
        *charKind = UCK_space;      // U+303F, ideographic half fill space.
159
0
      } else if ( (UCP(0x301D) <= *uniChar) && (*uniChar <= UCP(0x301F)) ) {
160
0
        *charKind = UCK_quote;      // U+301D..U+301F, double prime quotes.
161
0
      }
162
163
0
    } else if ( upperBits == 0x20 ) { // U+20--
164
165
0
      if ( (UCP(0x2000) <= *uniChar) && (*uniChar <= UCP(0x200B)) ) {
166
0
        *charKind = UCK_space;      // U+2000..U+200B, en quad through zero width space.
167
0
      } else if ( *uniChar == UCP(0x2015) ) {
168
0
        *charKind = UCK_quote;      // U+2015, dash quote.
169
0
      } else if ( (UCP(0x2018) <= *uniChar) && (*uniChar <= UCP(0x201F)) ) {
170
0
        *charKind = UCK_quote;      // U+2018..U+201F, various quotes.
171
0
      } else if ( *uniChar == UCP(0x2028) ) {
172
0
        *charKind = UCK_control;      // U+2028, line separator.
173
0
      } else if ( *uniChar == UCP(0x2029) ) {
174
0
        *charKind = UCK_control;      // U+2029, paragraph separator.
175
0
      } else if ( (*uniChar == UCP(0x2039)) || (*uniChar == UCP(0x203A)) ) {
176
0
        *charKind = UCK_quote;      // U+2039 and U+203A, guillemet quotes.
177
0
      }
178
179
0
    } else if ( upperBits == 0x06 ) { // U+06--
180
181
0
      if ( *uniChar == UCP(0x060C) ) {
182
0
        *charKind = UCK_comma;      // U+060C, Arabic comma.
183
0
      } else if ( *uniChar == UCP(0x061B) ) {
184
0
        *charKind = UCK_semicolon;    // U+061B, Arabic semicolon.
185
0
      }
186
187
0
    } else if ( upperBits == 0x05 ) { // U+05--
188
189
0
      if ( *uniChar == UCP(0x055D) ) {
190
0
        *charKind = UCK_comma;      // U+055D, Armenian comma.
191
0
      }
192
193
0
    } else if ( upperBits == 0x03 ) { // U+03--
194
195
0
      if ( *uniChar == UCP(0x037E) ) {
196
0
        *charKind = UCK_semicolon;    // U+037E, Greek "semicolon" (really a question mark).
197
0
      }
198
199
0
    } else if ( upperBits == 0x00 ) { // U+00--
200
201
0
      if ( (*uniChar == UCP(0x00AB)) || (*uniChar == UCP(0x00BB)) ) {
202
0
        *charKind = UCK_quote;      // U+00AB and U+00BB, guillemet quotes.
203
0
      }
204
205
0
    }
206
        
207
0
  }
208
209
0
}  // ClassifyCharacter
210
211
212
// -------------------------------------------------------------------------------------------------
213
// IsClosingingQuote
214
// -----------------
215
216
static inline bool
217
IsClosingingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote )
218
0
{
219
220
0
  if ( (uniChar == closeQuote) ||
221
0
     ( (openQuote == UCP(0x301D)) && ((uniChar == UCP(0x301E)) || (uniChar == UCP(0x301F))) ) ) {
222
0
    return true;
223
0
  } else {
224
0
    return false;
225
0
  }
226
227
0
}  // IsClosingingQuote
228
229
230
// -------------------------------------------------------------------------------------------------
231
// IsSurroundingQuote
232
// ------------------
233
234
static inline bool
235
IsSurroundingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote )
236
0
{
237
238
0
  if ( (uniChar == openQuote) || IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
239
0
    return true;
240
0
  } else {
241
0
    return false;
242
0
  }
243
244
0
}  // IsSurroundingQuote
245
246
247
// -------------------------------------------------------------------------------------------------
248
// GetClosingQuote
249
// ---------------
250
251
static UniCodePoint
252
GetClosingQuote ( UniCodePoint openQuote )
253
0
{
254
0
  UniCodePoint  closeQuote;
255
  
256
0
  switch ( openQuote ) {
257
258
0
    case UCP(0x0022) : closeQuote = UCP(0x0022); // ! U+0022 is both opening and closing.
259
0
               break;
260
0
    case UCP(0x005B) : closeQuote = UCP(0x005D);
261
0
               break;
262
0
    case UCP(0x00AB) : closeQuote = UCP(0x00BB); // ! U+00AB and U+00BB are reversible.
263
0
               break;
264
0
    case UCP(0x00BB) : closeQuote = UCP(0x00AB);
265
0
               break;
266
0
    case UCP(0x2015) : closeQuote = UCP(0x2015); // ! U+2015 is both opening and closing.
267
0
               break;
268
0
    case UCP(0x2018) : closeQuote = UCP(0x2019);
269
0
               break;
270
0
    case UCP(0x201A) : closeQuote = UCP(0x201B);
271
0
               break;
272
0
    case UCP(0x201C) : closeQuote = UCP(0x201D);
273
0
               break;
274
0
    case UCP(0x201E) : closeQuote = UCP(0x201F);
275
0
               break;
276
0
    case UCP(0x2039) : closeQuote = UCP(0x203A); // ! U+2039 and U+203A are reversible.
277
0
               break;
278
0
    case UCP(0x203A) : closeQuote = UCP(0x2039);
279
0
               break;
280
0
    case UCP(0x3008) : closeQuote = UCP(0x3009);
281
0
               break;
282
0
    case UCP(0x300A) : closeQuote = UCP(0x300B);
283
0
               break;
284
0
    case UCP(0x300C) : closeQuote = UCP(0x300D);
285
0
               break;
286
0
    case UCP(0x300E) : closeQuote = UCP(0x300F);
287
0
               break;
288
0
    case UCP(0x301D) : closeQuote = UCP(0x301F); // ! U+301E also closes U+301D.
289
0
               break;
290
0
    default      : closeQuote = 0;
291
0
               break;
292
293
0
  }
294
  
295
0
  return closeQuote;
296
  
297
0
}  // GetClosingQuote
298
299
300
// -------------------------------------------------------------------------------------------------
301
// CodePointToUTF8
302
// ---------------
303
304
static void
305
CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str )
306
0
{
307
0
  size_t i, byteCount;
308
0
  XMP_Uns8 buffer [8];
309
0
  UniCodePoint cpTemp;
310
  
311
0
  if ( uniChar <= 0x7F ) {
312
313
0
    i = 7;
314
0
    byteCount = 1;
315
0
    buffer[7] = char(uniChar);
316
  
317
0
  } else {
318
319
    // ---------------------------------------------------------------------------------------
320
    // Copy the data bits from the low order end to the high order end, include the 0x80 mask.
321
    
322
0
    i = 8;
323
0
    cpTemp = uniChar;
324
0
    while ( cpTemp != 0 ) {
325
0
      -- i; // Exit with i pointing to the last byte stored.
326
0
      buffer[i] = UnsByte(0x80) | (UnsByte(cpTemp) & 0x3F);
327
0
      cpTemp = cpTemp >> 6;
328
0
    }
329
0
    byteCount = 8 - i;  // The total number of bytes needed.
330
0
    XMP_Assert ( (2 <= byteCount) && (byteCount <= 6) );
331
332
    // -------------------------------------------------------------------------------------
333
    // Make sure the high order byte can hold the byte count mask, compute and set the mask.
334
    
335
0
    size_t bitCount = 0;  // The number of data bits in the first byte.
336
0
    for ( cpTemp = (buffer[i] & UnsByte(0x3F)); cpTemp != 0; cpTemp = cpTemp >> 1 ) bitCount += 1;
337
0
    if ( bitCount > (8 - (byteCount + 1)) ) byteCount += 1;
338
    
339
0
    i = 8 - byteCount;  // First byte index and mask shift count.
340
0
    XMP_Assert ( (0 <= i) && (i <= 6) );
341
0
    buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF); // AUDIT: Safe, i is between 0 and 6.
342
  
343
0
  }
344
  
345
0
  utf8Str.assign ( (char*)(&buffer[i]), byteCount );
346
  
347
0
}  // CodePointToUTF8
348
349
350
// -------------------------------------------------------------------------------------------------
351
// ApplyQuotes
352
// -----------
353
354
static void
355
ApplyQuotes ( XMP_VarString * item, UniCodePoint openQuote, UniCodePoint closeQuote, bool allowCommas )
356
0
{
357
0
  bool  prevSpace = false;
358
0
  size_t  charOffset, charLen;
359
0
  UniCharKind   charKind;
360
0
  UniCodePoint  uniChar;
361
  
362
  // -----------------------------------------------------------------------------------------
363
  // See if there are any separators in the value. Stop at the first occurrance. This is a bit
364
  // tricky in order to make typical typing work conveniently. The purpose of applying quotes
365
  // is to preserve the values when splitting them back apart. That is CatenateContainerItems
366
  // and SeparateContainerItems must round trip properly. For the most part we only look for
367
  // separators here. Internal quotes, as in -- Irving "Bud" Jones -- won't cause problems in
368
  // the separation. An initial quote will though, it will make the value look quoted.
369
370
0
  charOffset = 0;
371
0
  ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
372
  
373
0
  if ( charKind != UCK_quote ) {
374
  
375
0
  for ( charOffset = 0; size_t(charOffset) < item->size(); charOffset += charLen ) {
376
377
0
      ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
378
379
0
      if ( charKind == UCK_space ) {
380
0
        if ( prevSpace ) break; // Multiple spaces are a separator.
381
0
        prevSpace = true;
382
0
      } else {
383
0
        prevSpace = false;
384
0
        if ( (charKind == UCK_semicolon) || (charKind == UCK_control) ) break;
385
0
        if ( (charKind == UCK_comma) && (! allowCommas) ) break;
386
0
      }
387
388
0
    }
389
  
390
0
  }
391
  
392
0
  if ( size_t(charOffset) < item->size() ) {
393
  
394
    // --------------------------------------------------------------------------------------
395
    // Create a quoted copy, doubling any internal quotes that match the outer ones. Internal
396
    // quotes did not stop the "needs quoting" search, but they do need doubling. So we have
397
    // to rescan the front of the string for quotes. Handle the special case of U+301D being
398
    // closed by either U+301E or U+301F.
399
    
400
0
    XMP_VarString newItem;
401
0
    size_t      splitPoint;
402
    
403
0
    for ( splitPoint = 0; splitPoint <= charOffset; ++splitPoint ) {
404
0
      ClassifyCharacter ( item->c_str(), splitPoint, &charKind, &charLen, &uniChar );
405
0
      if ( charKind == UCK_quote ) break;
406
0
    }
407
    
408
0
    CodePointToUTF8 ( openQuote, newItem );
409
0
    newItem.append ( *item, 0, splitPoint );  // Copy the leading "normal" portion.
410
411
0
    for ( charOffset = splitPoint; size_t(charOffset) < item->size(); charOffset += charLen ) {
412
0
      ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar );
413
0
      newItem.append ( *item, charOffset, charLen );
414
0
      if ( (charKind == UCK_quote) && IsSurroundingQuote ( uniChar, openQuote, closeQuote ) ) {
415
0
        newItem.append ( *item, charOffset, charLen );
416
0
      }
417
0
    }
418
    
419
0
    XMP_VarString closeStr;
420
0
    CodePointToUTF8 ( closeQuote, closeStr );
421
0
    newItem.append ( closeStr );
422
    
423
0
    *item = newItem;
424
  
425
0
  }
426
  
427
0
}  // ApplyQuotes
428
429
430
// -------------------------------------------------------------------------------------------------
431
// IsInternalProperty
432
// ------------------
433
434
// *** Need static checks of the schema prefixes!
435
436
0
#define IsExternalProperty(s,p) (! IsInternalProperty ( s, p ))
437
438
static bool
439
IsInternalProperty ( const XMP_VarString & schema, const XMP_VarString & prop )
440
0
{
441
0
  bool isInternal = false;
442
443
0
  if ( schema == kXMP_NS_DC ) {
444
  
445
0
    if ( (prop == "dc:format")  ||
446
0
       (prop == "dc:language") ) {
447
0
      isInternal = true;
448
0
    }
449
  
450
0
  } else if ( schema == kXMP_NS_XMP ) {
451
  
452
0
    if ( (prop == "xmp:BaseURL")    ||
453
0
       (prop == "xmp:CreatorTool")  ||
454
0
       (prop == "xmp:Format")      ||
455
0
       (prop == "xmp:Locale")      ||
456
0
       (prop == "xmp:MetadataDate")  ||
457
0
       (prop == "xmp:ModifyDate") ) {
458
0
      isInternal = true;
459
0
    }
460
  
461
0
  } else if ( schema == kXMP_NS_PDF ) {
462
  
463
0
    if ( (prop == "pdf:BaseURL")  ||
464
0
       (prop == "pdf:Creator")  ||
465
0
       (prop == "pdf:ModDate")  ||
466
0
       (prop == "pdf:PDFVersion") ||
467
0
       (prop == "pdf:Producer") ) {
468
0
      isInternal = true;
469
0
    }
470
  
471
0
  } else if ( schema == kXMP_NS_TIFF ) {
472
    
473
0
    isInternal = true;  // ! The TIFF properties are internal by default.
474
0
    if ( (prop == "tiff:ImageDescription")  ||  // ! ImageDescription, Artist, and Copyright are aliased.
475
0
       (prop == "tiff:Artist")      ||
476
0
       (prop == "tiff:Copyright") ) {
477
0
      isInternal = false;
478
0
    }
479
480
0
  } else if ( schema == kXMP_NS_EXIF ) {
481
  
482
0
    isInternal = true;  // ! The EXIF properties are internal by default.
483
0
    if ( prop == "exif:UserComment" ) isInternal = false;
484
485
0
  } else if ( schema == kXMP_NS_EXIF_Aux ) {
486
  
487
0
    isInternal = true;  // ! The EXIF Aux properties are internal by default.
488
  
489
0
  } else if ( schema == kXMP_NS_Photoshop ) {
490
  
491
0
    if ( prop == "photoshop:ICCProfile" ) isInternal = true;
492
  
493
0
  } else if ( schema == kXMP_NS_CameraRaw ) {
494
  
495
0
    if ( (prop == "crs:Version")    ||
496
0
       (prop == "crs:RawFileName")  ||
497
0
       (prop == "crs:ToneCurveName") ) {
498
0
      isInternal = true;
499
0
    }
500
  
501
0
  } else if ( schema == kXMP_NS_AdobeStockPhoto ) {
502
  
503
0
    isInternal = true;  // ! The bmsp schema has only internal properties.
504
505
0
  } else if ( schema == kXMP_NS_XMP_MM ) {
506
  
507
0
    isInternal = true;  // ! The xmpMM schema has only internal properties.
508
  
509
0
  } else if ( schema == kXMP_NS_XMP_Text ) {
510
  
511
0
    isInternal = true;  // ! The xmpT schema has only internal properties.
512
  
513
0
  } else if ( schema == kXMP_NS_XMP_PagedFile ) {
514
  
515
0
    isInternal = true;  // ! The xmpTPg schema has only internal properties.
516
  
517
0
  } else if ( schema == kXMP_NS_XMP_Graphics ) {
518
  
519
0
    isInternal = true;  // ! The xmpG schema has only internal properties.
520
  
521
0
  } else if ( schema == kXMP_NS_XMP_Image ) {
522
  
523
0
    isInternal = true;  // ! The xmpGImg schema has only internal properties.
524
  
525
0
  } else if ( schema == kXMP_NS_XMP_Font ) {
526
  
527
0
    isInternal = true;  // ! The stFNT schema has only internal properties.
528
  
529
0
  }
530
  
531
0
  return isInternal;
532
533
0
}  // IsInternalProperty
534
535
536
// -------------------------------------------------------------------------------------------------
537
// RemoveSchemaChildren
538
// --------------------
539
540
static void
541
RemoveSchemaChildren ( XMP_NodePtrPos schemaPos, bool doAll )
542
0
{
543
0
  XMP_Node * schemaNode = *schemaPos;
544
0
  XMP_Assert ( XMP_NodeIsSchema ( schemaNode->options ) );
545
    
546
  // ! Iterate backwards to reduce shuffling as children are erased and to simplify the logic for
547
  // ! denoting the current child. (Erasing child n makes the old n+1 now be n.)
548
549
0
  size_t       propCount = schemaNode->children.size();
550
0
  XMP_NodePtrPos beginPos  = schemaNode->children.begin();
551
  
552
0
  for ( size_t propNum = propCount-1, propLim = (size_t)(-1); propNum != propLim; --propNum ) {
553
0
    XMP_NodePtrPos currProp = beginPos + propNum;
554
0
    if ( doAll || IsExternalProperty ( schemaNode->name, (*currProp)->name ) ) {
555
0
      delete *currProp; // ! Both delete the node and erase the pointer from the parent.
556
0
      schemaNode->children.erase ( currProp );
557
0
    }
558
0
  }
559
  
560
0
  if ( schemaNode->children.empty() ) {
561
0
    XMP_Node * tree = schemaNode->parent;
562
0
    tree->children.erase ( schemaPos );
563
0
    delete schemaNode;
564
0
  }
565
566
0
}  // RemoveSchemaChildren
567
568
569
// -------------------------------------------------------------------------------------------------
570
// ItemValuesMatch
571
// ---------------
572
//
573
// Does the value comparisons for array merging as part of XMPUtils::AppendProperties.
574
575
static bool
576
ItemValuesMatch ( const XMP_Node * leftNode, const XMP_Node * rightNode )
577
0
{
578
0
  const XMP_OptionBits leftForm  = leftNode->options & kXMP_PropCompositeMask;
579
0
  const XMP_OptionBits rightForm = leftNode->options & kXMP_PropCompositeMask;
580
  
581
0
  if ( leftForm != rightForm ) return false;
582
  
583
0
  if ( leftForm == 0 ) {
584
  
585
    // Simple nodes, check the values and xml:lang qualifiers.
586
    
587
0
    if ( leftNode->value != rightNode->value ) return false;
588
0
    if ( (leftNode->options & kXMP_PropHasLang) != (rightNode->options & kXMP_PropHasLang) ) return false;
589
0
    if ( leftNode->options & kXMP_PropHasLang ) {
590
0
      if ( leftNode->qualifiers[0]->value != rightNode->qualifiers[0]->value ) return false;
591
0
    }
592
  
593
0
  } else if ( leftForm == kXMP_PropValueIsStruct ) {
594
  
595
    // Struct nodes, see if all fields match, ignoring order.
596
    
597
0
    if ( leftNode->children.size() != rightNode->children.size() ) return false;
598
599
0
    for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) {
600
0
      const XMP_Node * leftField  = leftNode->children[leftNum];
601
0
      const XMP_Node * rightField = FindConstChild ( rightNode, leftField->name.c_str() );
602
0
      if ( (rightField == 0) || (! ItemValuesMatch ( leftField, rightField )) ) return false;
603
0
    }
604
    
605
0
  } else {
606
  
607
    // Array nodes, see if the "leftNode" values are present in the "rightNode", ignoring order, duplicates,
608
    // and extra values in the rightNode-> The rightNode is the destination for AppendProperties.
609
610
0
    XMP_Assert ( leftForm & kXMP_PropValueIsArray );
611
    
612
0
    for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) {
613
614
0
      const XMP_Node * leftItem = leftNode->children[leftNum];
615
616
0
      size_t rightNum, rightLim;
617
0
      for ( rightNum = 0, rightLim = rightNode->children.size(); rightNum != rightLim; ++rightNum ) {
618
0
        const XMP_Node * rightItem = rightNode->children[rightNum];
619
0
        if ( ItemValuesMatch ( leftItem, rightItem ) ) break;
620
0
      }
621
0
      if ( rightNum == rightLim ) return false;
622
623
0
    }
624
  
625
0
  }
626
627
0
  return true; // All of the checks passed.
628
  
629
0
}  // ItemValuesMatch
630
631
632
// -------------------------------------------------------------------------------------------------
633
// AppendSubtree
634
// -------------
635
//
636
// The main implementation of XMPUtils::AppendProperties. See the description in TXMPMeta.hpp.
637
638
static void
639
AppendSubtree ( const XMP_Node * sourceNode, XMP_Node * destParent, const bool replaceOld, const bool deleteEmpty )
640
0
{
641
0
  XMP_NodePtrPos destPos;
642
0
  XMP_Node * destNode = FindChildNode ( destParent, sourceNode->name.c_str(), kXMP_ExistingOnly, &destPos );
643
  
644
0
  bool valueIsEmpty = false;
645
0
  if ( deleteEmpty ) {
646
0
    if ( XMP_PropIsSimple ( sourceNode->options ) ) {
647
0
      valueIsEmpty = sourceNode->value.empty();
648
0
    } else {
649
0
      valueIsEmpty = sourceNode->children.empty();
650
0
    } 
651
0
  }
652
  
653
0
  if ( deleteEmpty & valueIsEmpty ) {
654
  
655
0
    if ( destNode != 0 ) {
656
0
      delete ( destNode );
657
0
      destParent->children.erase ( destPos );
658
0
    }
659
  
660
0
  } else if ( destNode == 0 ) {
661
  
662
    // The one easy case, the destination does not exist.
663
0
    CloneSubtree ( sourceNode, destParent );
664
665
0
  } else if ( replaceOld ) {
666
  
667
    // The destination exists and should be replaced.
668
669
0
    destNode->value   = sourceNode->value;  // *** Should use SetNode.
670
0
    destNode->options = sourceNode->options;
671
0
    destNode->RemoveChildren();
672
0
    destNode->RemoveQualifiers();
673
0
    CloneOffspring ( sourceNode, destNode );
674
675
    #if 0 // *** XMP_DebugBuild
676
      destNode->_valuePtr = destNode->value.c_str();
677
    #endif
678
  
679
0
  } else {
680
681
    // The destination exists and is not totally replaced. Structs and arrays are merged.
682
683
0
    XMP_OptionBits sourceForm = sourceNode->options & kXMP_PropCompositeMask;
684
0
    XMP_OptionBits destForm   = destNode->options & kXMP_PropCompositeMask;
685
0
    if ( sourceForm != destForm ) return;
686
    
687
0
    if ( sourceForm == kXMP_PropValueIsStruct ) {
688
    
689
      // To merge a struct process the fields recursively. E.g. add simple missing fields. The
690
      // recursive call to AppendSubtree will handle deletion for fields with empty values.
691
692
0
      for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
693
0
        const XMP_Node * sourceField = sourceNode->children[sourceNum];
694
0
        AppendSubtree ( sourceField, destNode, replaceOld, deleteEmpty );
695
0
        if ( deleteEmpty && destNode->children.empty() ) {
696
0
          delete ( destNode );
697
0
          destParent->children.erase ( destPos );
698
0
        }
699
0
      }
700
      
701
0
    } else if ( sourceForm & kXMP_PropArrayIsAltText ) {
702
    
703
      // Merge AltText arrays by the xml:lang qualifiers. Make sure x-default is first. Make a
704
      // special check for deletion of empty values. Meaningful in AltText arrays because the
705
      // xml:lang qualifier provides unambiguous source/dest correspondence.
706
707
0
      for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
708
709
0
        const XMP_Node * sourceItem = sourceNode->children[sourceNum];
710
0
        if ( sourceItem->qualifiers.empty() || (sourceItem->qualifiers[0]->name != "xml:lang") ) continue;
711
        
712
0
        XMP_Index  destIndex = LookupLangItem ( destNode, sourceItem->qualifiers[0]->value );
713
        
714
0
        if ( deleteEmpty && sourceItem->value.empty() ) {
715
716
0
          if ( destIndex != -1 ) {
717
0
            delete ( destNode->children[destIndex] );
718
0
            destNode->children.erase ( destNode->children.begin() + destIndex );
719
0
            if ( destNode->children.empty() ) {
720
0
              delete ( destNode );
721
0
              destParent->children.erase ( destPos );
722
0
            }
723
0
          }
724
725
0
        } else {
726
        
727
0
          if (  destIndex != -1 ) continue; // Not replacing, keep the existing item.
728
        
729
0
          if ( (sourceItem->qualifiers[0]->value != "x-default") || destNode->children.empty() ) {
730
0
            CloneSubtree ( sourceItem, destNode );
731
0
          } else {
732
0
            XMP_Node * destItem = new XMP_Node ( destNode, sourceItem->name, sourceItem->value, sourceItem->options );
733
0
            CloneOffspring ( sourceItem, destItem );
734
0
            destNode->children.insert ( destNode->children.begin(), destItem );
735
0
        }
736
        
737
0
        }
738
739
0
      }
740
    
741
0
    } else if ( sourceForm & kXMP_PropValueIsArray ) {
742
    
743
      // Merge other arrays by item values. Don't worry about order or duplicates. Source 
744
      // items with empty values do not cause deletion, that conflicts horribly with merging.
745
746
0
      for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
747
0
        const XMP_Node * sourceItem = sourceNode->children[sourceNum];
748
749
0
        size_t  destNum, destLim;
750
0
        for ( destNum = 0, destLim = destNode->children.size(); destNum != destLim; ++destNum ) {
751
0
          const XMP_Node * destItem = destNode->children[destNum];
752
0
          if ( ItemValuesMatch ( sourceItem, destItem ) ) break;
753
0
        }
754
0
        if ( destNum == destLim ) CloneSubtree ( sourceItem, destNode );
755
756
0
      }
757
      
758
0
    }
759
760
0
  }
761
762
0
}  // AppendSubtree
763
764
765
// =================================================================================================
766
// Class Static Functions
767
// ======================
768
769
// -------------------------------------------------------------------------------------------------
770
// CatenateArrayItems
771
// ------------------
772
773
/* class static */ void
774
XMPUtils::CatenateArrayItems ( const XMPMeta & xmpObj,
775
                 XMP_StringPtr   schemaNS,
776
                 XMP_StringPtr   arrayName,
777
                 XMP_StringPtr   separator,
778
                 XMP_StringPtr   quotes,
779
                 XMP_OptionBits  options,
780
                 XMP_StringPtr * catedStr,
781
                 XMP_StringLen * catedLen )
782
0
{
783
0
  XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // ! Enforced by wrapper.
784
0
  XMP_Assert ( (separator != 0) && (quotes != 0) && (catedStr != 0) && (catedLen != 0) ); // ! Enforced by wrapper.
785
  
786
0
  size_t     strLen=0, strPos=0, charLen=0;
787
0
  UniCharKind  charKind;
788
0
  UniCodePoint currUCP, openQuote, closeQuote;
789
  
790
0
  const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0);
791
  
792
0
  const XMP_Node * arrayNode = 0; // ! Move up to avoid gcc complaints.
793
0
  XMP_OptionBits   arrayForm = 0;
794
0
  const XMP_Node * currItem  = 0;
795
796
  // Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces.
797
  // Any of the recognized semicolons or spaces are allowed.
798
  
799
0
  strPos = 0;
800
0
  strLen = strlen ( separator );
801
0
  bool haveSemicolon = false;
802
  
803
0
  while ( strPos < strLen ) {
804
0
    ClassifyCharacter ( separator, strPos, &charKind, &charLen, &currUCP );
805
0
    strPos += charLen;
806
0
    if ( charKind == UCK_semicolon ) {
807
0
      if ( haveSemicolon ) XMP_Throw ( "Separator can have only one semicolon",  kXMPErr_BadParam );
808
0
      haveSemicolon = true;
809
0
    } else if ( charKind != UCK_space ) {
810
0
      XMP_Throw ( "Separator can have only spaces and one semicolon",   kXMPErr_BadParam );
811
0
    }
812
0
  };
813
0
  if ( ! haveSemicolon ) XMP_Throw ( "Separator must have one semicolon",  kXMPErr_BadParam );
814
  
815
  // Make sure the open and close quotes are a legitimate pair.
816
817
0
  strLen = strlen ( quotes );
818
0
  ClassifyCharacter ( quotes, 0, &charKind, &charLen, &openQuote );
819
0
  if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
820
821
0
  if ( charLen == strLen ) {
822
0
    closeQuote = openQuote;
823
0
  } else {
824
0
    strPos = charLen;
825
0
    ClassifyCharacter ( quotes, strPos, &charKind, &charLen, &closeQuote );
826
0
    if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
827
0
    if ( (strPos + charLen) != strLen ) XMP_Throw ( "Quoting string too long", kXMPErr_BadParam );
828
0
  }
829
0
  if ( closeQuote != GetClosingQuote ( openQuote ) ) XMP_Throw ( "Mismatched quote pair", kXMPErr_BadParam );
830
831
  // Return an empty result if the array does not exist, hurl if it isn't the right form.
832
  
833
0
  sCatenatedItems->erase();
834
835
0
  XMP_ExpandedXPath arrayPath;
836
0
  ExpandXPath ( schemaNS, arrayName, &arrayPath );
837
838
0
  arrayNode = FindConstNode ( &xmpObj.tree, arrayPath );
839
0
  if ( arrayNode == 0 ) goto EXIT; // ! Need to set the output pointer and length.
840
841
0
  arrayForm = arrayNode->options & kXMP_PropCompositeMask;
842
0
  if ( (! (arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
843
0
    XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadParam );
844
0
  }
845
0
  if ( arrayNode->children.empty() ) goto EXIT; // ! Need to set the output pointer and length.
846
  
847
  // Build the result, quoting the array items, adding separators. Hurl if any item isn't simple.
848
  // Start the result with the first value, then add the rest with a preceding separator.
849
  
850
0
  currItem = arrayNode->children[0];
851
  
852
0
  if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
853
0
  *sCatenatedItems = currItem->value;
854
0
  ApplyQuotes ( sCatenatedItems, openQuote, closeQuote, allowCommas );
855
  
856
0
  for ( size_t itemNum = 1, itemLim = arrayNode->children.size(); itemNum != itemLim; ++itemNum ) {
857
0
    const XMP_Node * currItem = arrayNode->children[itemNum];
858
0
    if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
859
0
    XMP_VarString tempStr ( currItem->value );
860
0
    ApplyQuotes ( &tempStr, openQuote, closeQuote, allowCommas );
861
0
    *sCatenatedItems += separator;
862
0
    *sCatenatedItems += tempStr;
863
0
  }
864
  
865
0
EXIT:
866
0
  *catedStr = sCatenatedItems->c_str();
867
0
  *catedLen = sCatenatedItems->size();
868
869
0
}  // CatenateArrayItems
870
871
872
// -------------------------------------------------------------------------------------------------
873
// SeparateArrayItems
874
// ------------------
875
876
/* class static */ void
877
XMPUtils::SeparateArrayItems ( XMPMeta *    xmpObj,
878
                 XMP_StringPtr  schemaNS,
879
                 XMP_StringPtr  arrayName,
880
                 XMP_OptionBits options,
881
                 XMP_StringPtr  catedStr )
882
0
{
883
0
  XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (catedStr != 0) );  // ! Enforced by wrapper.
884
  
885
0
  XMP_VarString itemValue;
886
0
  size_t itemStart, itemEnd;
887
0
  size_t nextSize, charSize = 0;  // Avoid VS uninit var warnings.
888
0
  UniCharKind   nextKind, charKind = UCK_normal;
889
0
  UniCodePoint  nextChar, uniChar = 0;
890
  
891
  // Extract "special" option bits, verify and normalize the others.
892
  
893
0
  bool preserveCommas = false;
894
0
  if ( options & kXMPUtil_AllowCommas ) {
895
0
    preserveCommas = true;
896
0
    options ^= kXMPUtil_AllowCommas;
897
0
  }
898
899
0
  options = VerifySetOptions ( options, 0 );  // Keep a zero value, has special meaning below.
900
0
  if ( options & ~kXMP_PropArrayFormMask ) XMP_Throw ( "Options can only provide array form", kXMPErr_BadOptions );
901
  
902
  // Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept.
903
  
904
0
  XMP_ExpandedXPath arrayPath;
905
0
  ExpandXPath ( schemaNS, arrayName, &arrayPath );
906
0
  XMP_Node * arrayNode = FindNode ( &xmpObj->tree, arrayPath, kXMP_ExistingOnly );
907
  
908
0
  if ( arrayNode != 0 ) {
909
    // The array exists, make sure the form is compatible. Zero arrayForm means take what exists.
910
0
    XMP_OptionBits arrayForm = arrayNode->options & kXMP_PropArrayFormMask;
911
0
    if ( (arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
912
0
      XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadXPath );
913
0
    }
914
0
    if ( (options != 0) && (options != arrayForm) ) XMP_Throw ( "Mismatch of specified and existing array form", kXMPErr_BadXPath ); // *** Right error?
915
0
  } else {
916
    // The array does not exist, try to create it.
917
0
    arrayNode = FindNode ( &xmpObj->tree, arrayPath, kXMP_CreateNodes, (options | kXMP_PropValueIsArray) );
918
0
    if ( arrayNode == 0 ) XMP_Throw ( "Failed to create named array", kXMPErr_BadXPath );
919
0
  }
920
921
0
  XMP_NodeOffspring oldChildren ( arrayNode->children );
922
0
  size_t oldChildCount = oldChildren.size();
923
0
  arrayNode->children.clear();
924
  
925
  // Extract the item values one at a time, until the whole input string is done. Be very careful
926
  // in the extraction about the string positions. They are essentially byte pointers, while the
927
  // contents are UTF-8. Adding or subtracting 1 does not necessarily move 1 Unicode character!
928
  
929
0
  size_t endPos = strlen ( catedStr );
930
  
931
0
  itemEnd = 0;
932
0
  while ( itemEnd < endPos ) {
933
    
934
    // Skip any leading spaces and separation characters. Always skip commas here. They can be
935
    // kept when within a value, but not when alone between values.
936
    
937
0
    for ( itemStart = itemEnd; itemStart < endPos; itemStart += charSize ) {
938
0
      ClassifyCharacter ( catedStr, itemStart, &charKind, &charSize, &uniChar );
939
0
      if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) break;
940
0
    }
941
0
    if ( itemStart >= endPos ) break;
942
    
943
0
    if ( charKind != UCK_quote ) {
944
    
945
      // This is not a quoted value. Scan for the end, create an array item from the substring.
946
947
0
      for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {
948
949
0
        ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );
950
951
0
        if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) continue;
952
0
        if ( (charKind == UCK_comma) && preserveCommas ) continue;
953
0
        if ( charKind != UCK_space ) break;
954
955
0
        if ( (itemEnd + charSize) >= endPos ) break; // Anything left?
956
0
        ClassifyCharacter ( catedStr, (itemEnd+charSize), &nextKind, &nextSize, &nextChar );
957
0
        if ( (nextKind == UCK_normal) || (nextKind == UCK_quote) ) continue;
958
0
        if ( (nextKind == UCK_comma) && preserveCommas ) continue;
959
0
        break; // Have multiple spaces, or a space followed by a separator.
960
961
0
      }   
962
963
0
      itemValue.assign ( catedStr, itemStart, (itemEnd - itemStart) );
964
    
965
0
    } else {
966
    
967
      // Accumulate quoted values into a local string, undoubling internal quotes that
968
      // match the surrounding quotes. Do not undouble "unmatching" quotes.
969
    
970
0
      UniCodePoint openQuote = uniChar;
971
0
      UniCodePoint closeQuote = GetClosingQuote ( openQuote );
972
973
0
      itemStart += charSize;  // Skip the opening quote;
974
0
      itemValue.erase();
975
      
976
0
      for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {
977
978
0
        ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );
979
980
0
        if ( (charKind != UCK_quote) || (! IsSurroundingQuote ( uniChar, openQuote, closeQuote)) ) {
981
        
982
          // This is not a matching quote, just append it to the item value.
983
0
          itemValue.append ( catedStr, itemEnd, charSize );
984
          
985
0
        } else {
986
        
987
          // This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate
988
          // various edge cases like undoubled opening (non-closing) quotes, or end of input.
989
          
990
0
          if ( (itemEnd + charSize) < endPos ) {
991
0
            ClassifyCharacter ( catedStr, itemEnd+charSize, &nextKind, &nextSize, &nextChar );
992
0
          } else {
993
0
            nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B;
994
0
          }
995
          
996
0
          if ( uniChar == nextChar ) {
997
            // This is doubled, copy it and skip the double.
998
0
            itemValue.append ( catedStr, itemEnd, charSize );
999
0
            itemEnd += nextSize;  // Loop will add in charSize.
1000
0
          } else if ( ! IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
1001
            // This is an undoubled, non-closing quote, copy it.
1002
0
            itemValue.append ( catedStr, itemEnd, charSize );
1003
0
          } else {
1004
            // This is an undoubled closing quote, skip it and exit the loop.
1005
0
            itemEnd += charSize;
1006
0
            break;
1007
0
          }
1008
1009
0
        }
1010
1011
0
      } // Loop to accumulate the quoted value.
1012
    
1013
0
    }
1014
1015
    // Add the separated item to the array. Keep a matching old value in case it had separators.
1016
    
1017
0
    size_t oldChild;
1018
0
    for ( oldChild = 0; oldChild < oldChildCount; ++oldChild ) {
1019
0
      if ( (oldChildren[oldChild] != 0) && (itemValue == oldChildren[oldChild]->value) ) break;
1020
0
    }
1021
    
1022
0
    XMP_Node * newItem = 0;
1023
0
    if ( oldChild == oldChildCount ) {
1024
0
      newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 );
1025
0
    } else {
1026
0
      newItem = oldChildren[oldChild];
1027
0
      oldChildren[oldChild] = 0;  // ! Don't match again, let duplicates be seen.
1028
0
    }
1029
0
    arrayNode->children.push_back ( newItem );
1030
    
1031
0
  } // Loop through all of the returned items.
1032
1033
  // Delete any of the old children that were not kept.
1034
0
  for ( size_t i = 0; i < oldChildCount; ++i ) {
1035
0
    if ( oldChildren[i] != 0 ) delete oldChildren[i];
1036
0
  }
1037
  
1038
0
}  // SeparateArrayItems
1039
1040
1041
// -------------------------------------------------------------------------------------------------
1042
// RemoveProperties
1043
// ----------------
1044
1045
/* class static */ void
1046
XMPUtils::RemoveProperties ( XMPMeta *    xmpObj,
1047
               XMP_StringPtr  schemaNS,
1048
               XMP_StringPtr  propName,
1049
               XMP_OptionBits options )
1050
0
{
1051
0
  XMP_Assert ( (schemaNS != 0) && (propName != 0) );  // ! Enforced by wrapper.
1052
  
1053
0
  const bool doAll = XMP_TestOption (options, kXMPUtil_DoAllProperties );
1054
0
  const bool includeAliases = XMP_TestOption ( options, kXMPUtil_IncludeAliases );
1055
  
1056
0
  if ( *propName != 0 ) {
1057
  
1058
    // Remove just the one indicated property. This might be an alias, the named schema might
1059
    // not actually exist. So don't lookup the schema node.
1060
    
1061
0
    if ( *schemaNS == 0 ) XMP_Throw ( "Property name requires schema namespace", kXMPErr_BadParam );
1062
    
1063
0
    XMP_ExpandedXPath expPath;
1064
0
    ExpandXPath ( schemaNS, propName, &expPath );
1065
    
1066
0
    XMP_NodePtrPos propPos;
1067
0
    XMP_Node * propNode = FindNode ( &(xmpObj->tree), expPath, kXMP_ExistingOnly, kXMP_NoOptions, &propPos );
1068
0
    if ( propNode != 0 ) {
1069
0
      if ( doAll || IsExternalProperty ( expPath[kSchemaStep].step, expPath[kRootPropStep].step ) ) {
1070
0
        XMP_Node * parent = propNode->parent; // *** Should have XMP_Node::RemoveChild(pos).
1071
0
        delete propNode;  // ! Both delete the node and erase the pointer from the parent.
1072
0
        parent->children.erase ( propPos );
1073
0
        DeleteEmptySchema ( parent );
1074
0
      }
1075
0
    }
1076
  
1077
0
  } else if ( *schemaNS != 0 ) {
1078
  
1079
    // Remove all properties from the named schema. Optionally include aliases, in which case
1080
    // there might not be an actual schema node. 
1081
1082
0
    XMP_NodePtrPos schemaPos;
1083
0
    XMP_Node * schemaNode = FindSchemaNode ( &xmpObj->tree, schemaNS, kXMP_ExistingOnly, &schemaPos );
1084
0
    if ( schemaNode != 0 ) RemoveSchemaChildren ( schemaPos, doAll );
1085
    
1086
0
    if ( includeAliases ) {
1087
    
1088
      // We're removing the aliases also. Look them up by their namespace prefix. Yes, the
1089
      // alias map is sorted so we could process just that portion. But that takes more code
1090
      // and the extra speed isn't worth it. (Plus this way we avoid a dependence on the map
1091
      // implementation.) Lookup the XMP node from the alias, to make sure the actual exists.
1092
1093
0
      XMP_StringPtr nsPrefix;
1094
0
      XMP_StringLen nsLen;
1095
0
      (void) XMPMeta::GetNamespacePrefix ( schemaNS, &nsPrefix, &nsLen );
1096
      
1097
0
      XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin();
1098
0
      XMP_AliasMapPos endAlias  = sRegisteredAliasMap->end();
1099
      
1100
0
      for ( ; currAlias != endAlias; ++currAlias ) {
1101
0
        if ( strncmp ( currAlias->first.c_str(), nsPrefix, nsLen ) == 0 ) {
1102
0
          XMP_NodePtrPos actualPos;
1103
0
          XMP_Node * actualProp = FindNode ( &xmpObj->tree, currAlias->second, kXMP_ExistingOnly, kXMP_NoOptions, &actualPos );
1104
0
          if ( actualProp != 0 ) {
1105
0
            XMP_Node * rootProp = actualProp;
1106
0
            while ( ! XMP_NodeIsSchema ( rootProp->parent->options ) ) rootProp = rootProp->parent;
1107
0
            if ( doAll || IsExternalProperty ( rootProp->parent->name, rootProp->name ) ) {
1108
0
              XMP_Node * parent = actualProp->parent;
1109
0
              delete actualProp;  // ! Both delete the node and erase the pointer from the parent.
1110
0
              parent->children.erase ( actualPos );
1111
0
              DeleteEmptySchema ( parent );
1112
0
            }
1113
0
          }
1114
0
        }
1115
0
      }
1116
1117
0
    }
1118
1119
0
  } else {
1120
    
1121
    // Remove all appropriate properties from all schema. In this case we don't have to be
1122
    // concerned with aliases, they are handled implicitly from the actual properties.
1123
1124
    // ! Iterate backwards to reduce shuffling if schema are erased and to simplify the logic
1125
    // ! for denoting the current schema. (Erasing schema n makes the old n+1 now be n.)
1126
1127
0
    size_t       schemaCount = xmpObj->tree.children.size();
1128
0
    XMP_NodePtrPos beginPos    = xmpObj->tree.children.begin();
1129
    
1130
0
    for ( size_t schemaNum = schemaCount-1, schemaLim = (size_t)(-1); schemaNum != schemaLim; --schemaNum ) {
1131
0
      XMP_NodePtrPos currSchema = beginPos + schemaNum;
1132
0
      RemoveSchemaChildren ( currSchema, doAll );
1133
0
    }
1134
  
1135
0
  }
1136
1137
0
}  // RemoveProperties
1138
1139
1140
// -------------------------------------------------------------------------------------------------
1141
// AppendProperties
1142
// ----------------
1143
1144
/* class static */ void
1145
XMPUtils::AppendProperties ( const XMPMeta & source,
1146
               XMPMeta *     dest,
1147
               XMP_OptionBits  options )
1148
0
{
1149
0
  XMP_Assert ( dest != 0 ); // ! Enforced by wrapper.
1150
1151
0
  const bool doAll     = ((options & kXMPUtil_DoAllProperties) != 0);
1152
0
  const bool replaceOld  = ((options & kXMPUtil_ReplaceOldValues) != 0);
1153
0
  const bool deleteEmpty = ((options & kXMPUtil_DeleteEmptyValues) != 0);
1154
1155
0
  for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum != schemaLim; ++schemaNum ) {
1156
1157
0
    const XMP_Node * sourceSchema = source.tree.children[schemaNum];
1158
1159
    // Make sure we have a destination schema node. Remember if it is newly created.
1160
    
1161
0
    XMP_Node * destSchema = FindSchemaNode ( &dest->tree, sourceSchema->name.c_str(), kXMP_ExistingOnly );
1162
0
    const bool newDestSchema = (destSchema == 0);
1163
0
    if ( newDestSchema ) {
1164
0
      destSchema = new XMP_Node ( &dest->tree, sourceSchema->name, sourceSchema->value, kXMP_SchemaNode );
1165
0
      dest->tree.children.push_back ( destSchema );
1166
0
    }
1167
1168
    // Process the source schema's children. Do this backwards in case deleteEmpty is set.
1169
    
1170
0
    for ( long propNum = ((long)sourceSchema->children.size() - 1); propNum >= 0; --propNum ) {
1171
0
      const XMP_Node * sourceProp = sourceSchema->children[propNum];
1172
0
      if ( doAll || IsExternalProperty ( sourceSchema->name, sourceProp->name ) ) {
1173
0
        AppendSubtree ( sourceProp, destSchema, replaceOld, deleteEmpty );
1174
// ***        RemoveMultiValueInfo ( dest, sourceSchema->name.c_str(), sourceProp->name.c_str() );
1175
0
      }
1176
0
    }
1177
    
1178
0
    if ( destSchema->children.empty() ) {
1179
0
      if ( newDestSchema ) {
1180
0
        delete ( destSchema );
1181
0
        dest->tree.children.pop_back();
1182
0
      } else if ( deleteEmpty ) {
1183
0
        DeleteEmptySchema ( destSchema );
1184
0
      }
1185
0
    }
1186
    
1187
0
  }
1188
1189
0
}  // AppendProperties
1190
1191
1192
// -------------------------------------------------------------------------------------------------
1193
// DuplicateSubtree
1194
// ----------------
1195
1196
/* class static */ void
1197
XMPUtils::DuplicateSubtree ( const XMPMeta & source,
1198
               XMPMeta *     dest,
1199
               XMP_StringPtr   sourceNS,
1200
               XMP_StringPtr   sourceRoot,
1201
               XMP_StringPtr   destNS,
1202
               XMP_StringPtr   destRoot,
1203
               XMP_OptionBits  options )
1204
0
{
1205
0
  UNUSED(options);
1206
  
1207
0
  bool fullSourceTree = false;
1208
0
  bool fullDestTree   = false;
1209
  
1210
0
  XMP_ExpandedXPath sourcePath, destPath; 
1211
1212
0
  const XMP_Node * sourceNode = 0;
1213
0
  XMP_Node * destNode = 0;
1214
  
1215
0
  XMP_Assert ( (sourceNS != 0) && (*sourceNS != 0) );
1216
0
  XMP_Assert ( (sourceRoot != 0) && (*sourceRoot != 0) );
1217
0
  XMP_Assert ( (dest != 0) && (destNS != 0) && (destRoot != 0) );
1218
1219
0
  if ( *destNS == 0 )    destNS   = sourceNS;
1220
0
  if ( *destRoot == 0 ) destRoot = sourceRoot;
1221
  
1222
0
  if ( XMP_LitMatch ( sourceNS, "*" ) ) fullSourceTree = true;
1223
0
  if ( XMP_LitMatch ( destNS, "*" ) )   fullDestTree   = true;
1224
  
1225
0
  if ( (&source == dest) && (fullSourceTree | fullDestTree) ) {
1226
0
    XMP_Throw ( "Can't duplicate tree onto itself", kXMPErr_BadParam );
1227
0
  }
1228
  
1229
0
  if ( fullSourceTree & fullDestTree ) XMP_Throw ( "Use Clone for full tree to full tree", kXMPErr_BadParam );
1230
1231
0
  if ( fullSourceTree ) {
1232
  
1233
    // The destination must be an existing empty struct, copy all of the source top level as fields.
1234
1235
0
    ExpandXPath ( destNS, destRoot, &destPath );
1236
0
    destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );
1237
1238
0
    if ( (destNode == 0) || (! XMP_PropIsStruct ( destNode->options )) ) {
1239
0
      XMP_Throw ( "Destination must be an existing struct", kXMPErr_BadXPath );
1240
0
    }
1241
    
1242
0
    if ( ! destNode->children.empty() ) {
1243
0
      if ( options & kXMP_DeleteExisting ) {
1244
0
        destNode->RemoveChildren();
1245
0
      } else {
1246
0
        XMP_Throw ( "Destination must be an empty struct", kXMPErr_BadXPath );
1247
0
      }
1248
0
    }
1249
    
1250
0
    for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) {
1251
1252
0
      const XMP_Node * currSchema = source.tree.children[schemaNum];
1253
1254
0
      for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) {
1255
0
        sourceNode = currSchema->children[propNum];
1256
0
        XMP_Node * copyNode = new XMP_Node ( destNode, sourceNode->name, sourceNode->value, sourceNode->options );
1257
0
        destNode->children.push_back ( copyNode );
1258
0
        CloneOffspring ( sourceNode, copyNode );
1259
0
      }
1260
1261
0
    }
1262
  
1263
0
  } else if ( fullDestTree ) {
1264
1265
    // The source node must be an existing struct, copy all of the fields to the dest top level.
1266
1267
0
    XMP_ExpandedXPath sourcePath; 
1268
0
    ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
1269
0
    sourceNode = FindConstNode ( &source.tree, sourcePath );
1270
1271
0
    if ( (sourceNode == 0) || (! XMP_PropIsStruct ( sourceNode->options )) ) {
1272
0
      XMP_Throw ( "Source must be an existing struct", kXMPErr_BadXPath );
1273
0
    }
1274
    
1275
0
    destNode = &dest->tree;
1276
    
1277
0
    if ( ! destNode->children.empty() ) {
1278
0
      if ( options & kXMP_DeleteExisting ) {
1279
0
        destNode->RemoveChildren();
1280
0
      } else {
1281
0
        XMP_Throw ( "Destination tree must be empty", kXMPErr_BadXPath );
1282
0
      }
1283
0
    }
1284
    
1285
0
    std::string   nsPrefix;
1286
0
    XMP_StringPtr nsURI;
1287
0
    XMP_StringLen nsLen;
1288
    
1289
0
    for ( size_t fieldNum = 0, fieldLim = sourceNode->children.size(); fieldNum < fieldLim; ++fieldNum ) {
1290
1291
0
      const XMP_Node * currField = sourceNode->children[fieldNum];
1292
1293
0
      size_t colonPos = currField->name.find ( ':' );
1294
0
      nsPrefix.assign ( currField->name.c_str(), colonPos );
1295
0
      bool nsOK = XMPMeta::GetNamespaceURI ( nsPrefix.c_str(), &nsURI, &nsLen );
1296
0
      if ( ! nsOK ) XMP_Throw ( "Source field namespace is not global", kXMPErr_BadSchema );
1297
      
1298
0
      XMP_Node * destSchema = FindSchemaNode ( &dest->tree, nsURI, kXMP_CreateNodes );
1299
0
      if ( destSchema == 0 ) XMP_Throw ( "Failed to find destination schema", kXMPErr_BadSchema );
1300
1301
0
      XMP_Node * copyNode = new XMP_Node ( destSchema, currField->name, currField->value, currField->options );
1302
0
      destSchema->children.push_back ( copyNode );
1303
0
      CloneOffspring ( currField, copyNode );
1304
1305
0
    }
1306
    
1307
0
  } else {
1308
1309
    // Find the root nodes for the source and destination subtrees.
1310
    
1311
0
    ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
1312
0
    ExpandXPath ( destNS, destRoot, &destPath );
1313
  
1314
0
    sourceNode = FindConstNode ( &source.tree, sourcePath );
1315
0
    if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath );
1316
    
1317
0
    destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly ); // Dest must not yet exist.
1318
0
    if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath );
1319
    
1320
0
    destNode = FindNode ( &dest->tree, destPath, kXMP_CreateNodes ); // Now create the dest.
1321
0
    if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath );
1322
    
1323
    // Make sure the destination is not within the source! The source can't be inside the destination
1324
    // because the source already existed and the destination was just created.
1325
    
1326
0
    if ( &source == dest ) {
1327
0
      for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) {
1328
0
        if ( testNode == sourceNode ) {
1329
          // *** delete the just-created dest root node
1330
0
          XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath );
1331
0
        }
1332
0
      }
1333
0
    }
1334
  
1335
    // *** Could use a CloneTree util here and maybe elsewhere.
1336
    
1337
0
    destNode->value   = sourceNode->value;  // *** Should use SetNode.
1338
0
    destNode->options = sourceNode->options;
1339
0
    CloneOffspring ( sourceNode, destNode );
1340
1341
0
  }
1342
1343
0
}  // DuplicateSubtree
1344
1345
1346
// =================================================================================================