/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 | | // ================================================================================================= |