1 // =================================================================================================
2 // Copyright 2002-2007 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 "XMP_Version.h"
11 #include "XMPCore_Impl.hpp"
12 #include "XMPMeta.hpp"	// *** For use of GetNamespacePrefix in FindSchemaNode.
13 
14 #include "UnicodeInlines.incl_cpp"
15 
16 #include <algorithm>
17 
18 using namespace std;
19 
20 #if XMP_WinBuild
21 	#pragma warning ( disable : 4290 )	// C++ exception specification ignored except ... not __declspec(nothrow)
22 	#pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
23 #endif
24 
25 // *** Add debug codegen checks, e.g. that typical masking operations really work
26 // *** Make option constants 0x...UL.
27 
28 // Internal code should be using #if with XMP_MacBuild, XMP_WinBuild, or XMP_UNIXBuild.
29 // This is a sanity check in case of accidental use of *_ENV. Some clients use the poor
30 // practice of defining the *_ENV macro with an empty value.
31 #if defined ( MAC_ENV )
32 	#if ! MAC_ENV
33 		#error "MAC_ENV must be defined so that \"#if MAC_ENV\" is true"
34 	#endif
35 #elif defined ( WIN_ENV )
36 	#if ! WIN_ENV
37 		#error "WIN_ENV must be defined so that \"#if WIN_ENV\" is true"
38 	#endif
39 #elif defined ( UNIX_ENV )
40 	#if ! UNIX_ENV
41 		#error "UNIX_ENV must be defined so that \"#if UNIX_ENV\" is true"
42 	#endif
43 #endif
44 
45 // =================================================================================================
46 // Static Variables
47 // ================
48 
49 XMP_Int32 sXMP_InitCount = 0;
50 
51 XMP_StringMap *	sNamespaceURIToPrefixMap = 0;
52 XMP_StringMap *	sNamespacePrefixToURIMap = 0;
53 
54 XMP_AliasMap *	sRegisteredAliasMap = 0;	// Needed by XMPIterator.
55 
56 XMP_VarString *	sOutputNS  = 0;
57 XMP_VarString *	sOutputStr = 0;
58 XMP_VarString * sExceptionMessage = 0;
59 
60 XMP_Mutex sXMPCoreLock;
61 int sLockCount = 0;
62 
63 #if TraceXMPCalls
64 	FILE * xmpOut = stderr;
65 #endif
66 
67 void *              voidVoidPtr    = 0;	// Used to backfill null output parameters.
68 XMP_StringPtr		voidStringPtr  = 0;
69 XMP_StringLen		voidStringLen  = 0;
70 XMP_OptionBits		voidOptionBits = 0;
71 XMP_Uns8			voidByte       = 0;
72 bool				voidBool       = 0;
73 XMP_Int32			voidInt32      = 0;
74 XMP_Int64			voidInt64      = 0;
75 double				voidDouble     = 0.0;
76 XMP_DateTime		voidDateTime;
77 WXMP_Result 		void_wResult;
78 
79 // =================================================================================================
80 // Mutex Utilities
81 // ===============
82 
83 // ! Note that the mutex need not be "recursive", allowing the same thread to acquire it multiple
84 // ! times. There is a single XMP lock which is acquired in the wrapper classes. Internal calls
85 // ! never go back out to the wrappers.
86 
87 #if XMP_WinBuild
88 
XMP_InitMutex(XMP_Mutex * mutex)89 	bool XMP_InitMutex ( XMP_Mutex * mutex ) {
90 		InitializeCriticalSection ( mutex );
91 		return true;
92 	}
93 
XMP_TermMutex(XMP_Mutex & mutex)94 	void XMP_TermMutex ( XMP_Mutex & mutex ) {
95 		DeleteCriticalSection ( &mutex );
96 	}
97 
XMP_EnterCriticalRegion(XMP_Mutex & mutex)98 	void XMP_EnterCriticalRegion ( XMP_Mutex & mutex ) {
99 		EnterCriticalSection ( &mutex );
100 	}
101 
XMP_ExitCriticalRegion(XMP_Mutex & mutex)102 	void XMP_ExitCriticalRegion ( XMP_Mutex & mutex ) {
103 		LeaveCriticalSection ( &mutex );
104 	}
105 
106 #else
107 
108 	// Use pthread for both Mac and generic UNIX.
109 	// ! Would be nice to specify PTHREAD_MUTEX_ERRORCHECK, but the POSIX documentation is useless.
110 	// ! Would be OK but overkill to specify PTHREAD_MUTEX_RECURSIVE.
111 
XMP_InitMutex(XMP_Mutex * mutex)112 	bool XMP_InitMutex ( XMP_Mutex * mutex ) {
113 		int err = pthread_mutex_init ( mutex, 0 );
114 		return (err == 0 );
115 	}
116 
XMP_TermMutex(XMP_Mutex & mutex)117 	void XMP_TermMutex ( XMP_Mutex & mutex ) {
118 		(void) pthread_mutex_destroy ( &mutex );
119 	}
120 
XMP_EnterCriticalRegion(XMP_Mutex & mutex)121 	void XMP_EnterCriticalRegion ( XMP_Mutex & mutex ) {
122 		int err = pthread_mutex_lock ( &mutex );
123 		if ( err != 0 ) XMP_Throw ( "XMP_EnterCriticalRegion - pthread_mutex_lock failure", kXMPErr_ExternalFailure );
124 	}
125 
XMP_ExitCriticalRegion(XMP_Mutex & mutex)126 	void XMP_ExitCriticalRegion ( XMP_Mutex & mutex ) {
127 		int err = pthread_mutex_unlock ( &mutex );
128 		if ( err != 0 ) XMP_Throw ( "XMP_ExitCriticalRegion - pthread_mutex_unlock failure", kXMPErr_ExternalFailure );
129 	}
130 
131 #endif
132 
133 // =================================================================================================
134 // Local Utilities
135 // ===============
136 
137 // -------------------------------------------------------------------------------------------------
138 // VerifyXPathRoot
139 // ---------------
140 //
141 // Set up the first 2 components of the expanded XPath. Normalizes the various cases of using the
142 // full schema URI and/or a qualified root property name. Returns true for normal processing. If
143 // allowUnknownSchemaNS is true and the schema namespace is not registered, false is returned. If
144 // allowUnknownSchemaNS is false and the schema namespace is not registered, an exception is thrown.
145 
146 // *** Should someday check the full syntax.
147 
148 static void
VerifyXPathRoot(XMP_StringPtr schemaURI,XMP_StringPtr propName,XMP_ExpandedXPath * expandedXPath)149 VerifyXPathRoot	( XMP_StringPtr			schemaURI,
150 				  XMP_StringPtr			propName,
151 				  XMP_ExpandedXPath *	expandedXPath )
152 {
153 	// Do some basic checks on the URI and name. Try to lookup the URI. See if the name is qualified.
154 
155 	XMP_Assert ( (schemaURI != 0) && (propName != 0) && (*propName != 0) );
156 	XMP_Assert ( (expandedXPath != 0) && (expandedXPath->empty()) );
157 
158 	if ( *schemaURI == 0 ) XMP_Throw ( "Schema namespace URI is required", kXMPErr_BadSchema );
159 
160 	if ( (*propName == '?') || (*propName == '@') ) {
161 		XMP_Throw ( "Top level name must not be a qualifier", kXMPErr_BadXPath );
162 	}
163 	for ( XMP_StringPtr ch = propName; *ch != 0; ++ch ) {
164 		if ( (*ch == '/') || (*ch == '[') ) {
165 			XMP_Throw ( "Top level name must be simple", kXMPErr_BadXPath );
166 		}
167 	}
168 
169 	XMP_StringMapPos uriPos = sNamespaceURIToPrefixMap->find ( XMP_VarString ( schemaURI ) );
170 	if ( uriPos == sNamespaceURIToPrefixMap->end() ) {
171 		XMP_Throw ( "Unregistered schema namespace URI", kXMPErr_BadSchema );
172 	}
173 
174 	XMP_StringPtr colonPos = propName;
175 	while ( (*colonPos != 0) && (*colonPos != ':') ) ++colonPos;
176 	VerifySimpleXMLName ( propName, colonPos );	// Verify the part before any colon.
177 
178 	// Verify the various URI and prefix combinations. Initialize the expanded XPath.
179 
180 	if ( *colonPos == 0 ) {
181 
182 		// The propName is unqualified, use the schemaURI and associated prefix.
183 
184 		expandedXPath->push_back ( XPathStepInfo ( schemaURI, kXMP_SchemaNode ) );
185 		expandedXPath->push_back ( XPathStepInfo ( uriPos->second, 0 ) );
186 		(*expandedXPath)[kRootPropStep].step += propName;
187 
188 	} else {
189 
190 		// The propName is qualified. Make sure the prefix is legit. Use the associated URI and qualified name.
191 
192 		size_t prefixLen = colonPos - propName + 1;	// ! Include the colon.
193 		VerifySimpleXMLName ( colonPos+1, colonPos+strlen(colonPos) );
194 
195 		XMP_VarString prefix ( propName, prefixLen );
196 		XMP_StringMapPos prefixPos = sNamespacePrefixToURIMap->find ( prefix );
197 		if ( prefixPos == sNamespacePrefixToURIMap->end() ) {
198 			XMP_Throw ( "Unknown schema namespace prefix", kXMPErr_BadSchema );
199 		}
200 		if ( prefix != uriPos->second ) {
201 			XMP_Throw ( "Schema namespace URI and prefix mismatch", kXMPErr_BadSchema );
202 		}
203 
204 		expandedXPath->push_back ( XPathStepInfo ( schemaURI, kXMP_SchemaNode ) );
205 		expandedXPath->push_back ( XPathStepInfo ( propName, 0 ) );
206 
207 	}
208 
209 }	// VerifyXPathRoot
210 
211 // -------------------------------------------------------------------------------------------------
212 // VerifyQualName
213 // --------------
214 
215 static void
VerifyQualName(XMP_StringPtr qualName,XMP_StringPtr nameEnd)216 VerifyQualName ( XMP_StringPtr qualName, XMP_StringPtr nameEnd )
217 {
218 	if ( qualName >= nameEnd ) XMP_Throw ( "Empty qualified name", kXMPErr_BadXPath );
219 
220 	XMP_StringPtr colonPos = qualName;
221 	while ( (colonPos < nameEnd) && (*colonPos != ':') ) ++colonPos;
222 	if ( (colonPos == qualName) || (colonPos >= nameEnd) ) XMP_Throw ( "Ill-formed qualified name", kXMPErr_BadXPath );
223 
224 	VerifySimpleXMLName ( qualName, colonPos );
225 	VerifySimpleXMLName ( colonPos+1, nameEnd );
226 
227 	size_t prefixLen = colonPos - qualName + 1;	// ! Include the colon.
228 	XMP_VarString prefix ( qualName, prefixLen );
229 	XMP_StringMapPos prefixPos = sNamespacePrefixToURIMap->find ( prefix );
230 	if ( prefixPos == sNamespacePrefixToURIMap->end() ) {
231 		XMP_Throw ( "Unknown namespace prefix for qualified name", kXMPErr_BadXPath );
232 	}
233 
234 }	// VerifyQualName
235 
236 // -------------------------------------------------------------------------------------------------
237 // FindIndexedItem
238 // ---------------
239 //
240 //	[index]	An element of an array.
241 //
242 // Support the implicit creation of a new last item.
243 
244 static XMP_Index
FindIndexedItem(XMP_Node * arrayNode,const XMP_VarString & indexStep,bool createNodes)245 FindIndexedItem ( XMP_Node * arrayNode, const XMP_VarString & indexStep, bool createNodes )
246 {
247 	XMP_Index index = 0;
248 	size_t    chLim = indexStep.size() - 1;
249 
250 	XMP_Assert ( (chLim >= 2) && (indexStep[0] == '[') && (indexStep[chLim] == ']') );
251 
252 	for ( size_t chNum = 1; chNum != chLim; ++chNum ) {
253 		XMP_Assert ( ('0' <= indexStep[chNum]) && (indexStep[chNum] <= '9') );
254 		index = (index * 10) + (indexStep[chNum] - '0');
255 		if ( index < 0 ) {
256 			XMP_Throw ( "Array index overflow", kXMPErr_BadXPath );	// ! Overflow, not truly negative.
257 		}
258 	}
259 
260 	--index;	// Change to a C-style, zero based index.
261 	if ( index < 0 ) XMP_Throw ( "Array index must be larger than zero", kXMPErr_BadXPath );
262 
263 	if ( (index == (XMP_Index)arrayNode->children.size()) && createNodes ) {	// Append a new last+1 node.
264 		XMP_Node * newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, kXMP_NewImplicitNode );
265 		arrayNode->children.push_back ( newItem );
266 	}
267 
268 	// ! Don't throw here for a too large index. SetProperty will throw, GetProperty will not.
269 	if ( index >= (XMP_Index)arrayNode->children.size() ) index = -1;
270 	return index;
271 
272 }	// FindIndexedItem
273 
274 // -------------------------------------------------------------------------------------------------
275 // SplitNameAndValue
276 // -----------------
277 //
278 // Split the name and value parts for field and qualifier selectors:
279 //
280 //	[qualName="value"]	An element in an array of structs, chosen by a field value.
281 //	[?qualName="value"]	An element in an array, chosen by a qualifier value.
282 //
283 // The value portion is a string quoted by ''' or '"'. The value may contain any character including
284 // a doubled quoting character. The value may be empty.
285 
286 static void
SplitNameAndValue(const XMP_VarString & selStep,XMP_VarString * nameStr,XMP_VarString * valueStr)287 SplitNameAndValue ( const XMP_VarString & selStep, XMP_VarString * nameStr, XMP_VarString * valueStr )
288 {
289 	XMP_StringPtr partBegin = selStep.c_str();
290 	XMP_StringPtr partEnd;
291 
292 	const XMP_StringPtr valueEnd = partBegin + (selStep.size() - 2);
293 	const char          quote    = *valueEnd;
294 
295 	XMP_Assert ( (*partBegin == '[') && (*(valueEnd+1) == ']') );
296 	XMP_Assert ( (selStep.size() >= 6) && ((quote == '"') || (quote == '\'')) );
297 
298 	// Extract the name part.
299 
300 	++partBegin;	// Skip the opening '['.
301 	if ( *partBegin == '?' ) ++partBegin;
302 	for ( partEnd = partBegin+1; *partEnd != '='; ++partEnd ) {};
303 
304 	nameStr->assign ( partBegin, (partEnd - partBegin) );
305 
306 	// Extract the value part, reducing doubled quotes.
307 
308 	XMP_Assert ( *(partEnd+1) == quote );
309 
310 	partBegin = partEnd + 2;
311 	valueStr->erase();
312 	valueStr->reserve ( valueEnd - partBegin );	// Maximum length, don't optimize doubled quotes.
313 
314 	for ( partEnd = partBegin; partEnd < valueEnd; ++partEnd ) {
315 		if ( (*partEnd == quote) && (*(partEnd+1) == quote) ) {
316 			++partEnd;
317 			valueStr->append ( partBegin, (partEnd - partBegin) );
318 			partBegin = partEnd+1;	// ! Loop will increment partEnd again.
319 		}
320 	}
321 
322 	valueStr->append ( partBegin, (partEnd - partBegin) );	// ! The loop does not add the last part.
323 
324 }	// SplitNameAndValue
325 
326 // -------------------------------------------------------------------------------------------------
327 // LookupQualSelector
328 // ------------------
329 //
330 //	[?qualName="value"]	An element in an array, chosen by a qualifier value.
331 //
332 // Note that we don't create implicit nodes for qualifier selectors, so no CreateNodes parameter.
333 
334 static XMP_Index
LookupQualSelector(XMP_Node * arrayNode,const XMP_VarString & qualName,XMP_VarString & qualValue)335 LookupQualSelector ( XMP_Node * arrayNode, const XMP_VarString & qualName, XMP_VarString & qualValue )
336 {
337 	XMP_Index index;
338 
339 	if ( qualName == "xml:lang" ) {
340 
341 		// *** Should check that the value is legit RFC 1766/3066.
342 		NormalizeLangValue ( &qualValue );
343 		index = LookupLangItem ( arrayNode, qualValue ) ;
344 
345 	} else {
346 
347 		XMP_Index itemLim;
348 		for ( index = 0, itemLim = arrayNode->children.size(); index != itemLim; ++index ) {
349 
350 			const XMP_Node * currItem = arrayNode->children[index];
351 			XMP_Assert ( currItem->parent == arrayNode );
352 
353 			size_t q, qualLim;
354 			for ( q = 0, qualLim = currItem->qualifiers.size(); q != qualLim; ++q ) {
355 				const XMP_Node * currQual = currItem->qualifiers[q];
356 				XMP_Assert ( currQual->parent == currItem );
357 				if ( currQual->name != qualName ) continue;
358 				if ( currQual->value == qualValue ) break;	// Exit qual loop.
359 			}
360 			if ( q != qualLim ) break;	// Exit child loop, found an item with a matching qualifier.
361 
362 		}
363 		if ( index == itemLim ) index = -1;
364 
365 	}
366 
367 	return index;
368 
369 }	// LookupQualSelector
370 
371 // -------------------------------------------------------------------------------------------------
372 // FollowXPathStep
373 // ---------------
374 //
375 // After processing by ExpandXPath, a step can be of these forms:
376 //	qualName			A top level property or struct field.
377 //	[index]				An element of an array.
378 //	[last()]			The last element of an array.
379 //	[qualName="value"]	An element in an array of structs, chosen by a field value.
380 //	[?qualName="value"]	An element in an array, chosen by a qualifier value.
381 //	?qualName			A general qualifier.
382 //
383 // Find the appropriate child node, resolving aliases, and optionally creating nodes.
384 
385 static XMP_Node *
FollowXPathStep(XMP_Node * parentNode,const XMP_ExpandedXPath & fullPath,size_t stepNum,bool createNodes,XMP_NodePtrPos * ptrPos,bool aliasedArrayItem=false)386 FollowXPathStep	( XMP_Node *	   parentNode,
387 				  const XMP_ExpandedXPath & fullPath,
388 				  size_t		   stepNum,
389 				  bool			   createNodes,
390 				  XMP_NodePtrPos * ptrPos,
391 				  bool			   aliasedArrayItem = false )
392 {
393 	XMP_Node * nextNode = 0;
394 	const XPathStepInfo & nextStep = fullPath[stepNum];
395 	XMP_Index      index    = 0;
396 	XMP_OptionBits stepKind = nextStep.options & kXMP_StepKindMask;
397 
398 	XMP_Assert ( (kXMP_StructFieldStep <= stepKind) && (stepKind <= kXMP_FieldSelectorStep) );
399 
400 	if ( stepKind == kXMP_StructFieldStep ) {
401 
402 		nextNode = FindChildNode ( parentNode, nextStep.step.c_str(), createNodes, ptrPos );
403 
404 	} else if ( stepKind == kXMP_QualifierStep ) {
405 
406 		XMP_StringPtr qualStep = nextStep.step.c_str();
407 		XMP_Assert ( *qualStep == '?' );
408 		++qualStep;
409 		nextNode = FindQualifierNode ( parentNode, qualStep, createNodes, ptrPos );
410 
411 	} else {
412 
413 		// This is an array indexing step. First get the index, then get the node.
414 
415 		if ( ! (parentNode->options & kXMP_PropValueIsArray) ) {
416 			XMP_Throw ( "Indexing applied to non-array", kXMPErr_BadXPath );
417 		}
418 
419 		if ( stepKind == kXMP_ArrayIndexStep ) {
420 			index = FindIndexedItem ( parentNode, nextStep.step, createNodes );
421 		} else if ( stepKind == kXMP_ArrayLastStep ) {
422 			index = parentNode->children.size() - 1;
423 		} else if ( stepKind == kXMP_FieldSelectorStep ) {
424 			XMP_VarString fieldName, fieldValue;
425 			SplitNameAndValue ( nextStep.step, &fieldName, &fieldValue );
426 			index = LookupFieldSelector ( parentNode, fieldName.c_str(), fieldValue.c_str() );
427 		} else if ( stepKind == kXMP_QualSelectorStep ) {
428 			XMP_VarString qualName, qualValue;
429 			SplitNameAndValue ( nextStep.step, &qualName, &qualValue );
430 			index = LookupQualSelector ( parentNode, qualName, qualValue );
431 		} else {
432 			XMP_Throw ( "Unknown array indexing step in FollowXPathStep", kXMPErr_InternalFailure );
433 		}
434 
435 		if ( (0 <= index) && (index <= (XMP_Index)parentNode->children.size()) ) nextNode = parentNode->children[index];
436 
437 		if ( (index == -1) && createNodes && aliasedArrayItem && (stepKind == kXMP_QualSelectorStep) ) {
438 
439 			// An ugly special case without an obvious better place to be. We have an alias to the
440 			// x-default item of an alt-text array. A simple reference via SetProperty must create
441 			// the x-default item if it does not yet exist.
442 
443 			XMP_Assert ( parentNode->options & kXMP_PropArrayIsAltText );
444 			XMP_Assert ( (stepNum == 2) && (nextStep.step == "[?xml:lang=\"x-default\"]") );
445 
446 			nextNode = new XMP_Node ( parentNode, kXMP_ArrayItemName,
447 									  (kXMP_PropHasQualifiers | kXMP_PropHasLang | kXMP_NewImplicitNode) );
448 
449 			XMP_Node * langQual = new XMP_Node ( nextNode, "xml:lang", "x-default", kXMP_PropIsQualifier );
450 			nextNode->qualifiers.push_back ( langQual );
451 
452 			if ( parentNode->children.empty() ) {
453 				parentNode->children.push_back ( nextNode );
454 			} else {
455 				parentNode->children.insert ( parentNode->children.begin(), nextNode );
456 			}
457 
458 			index = 0;	// ! C-style index! The x-default item is always first.
459 
460 		}
461 
462 		if ( (nextNode != 0) && (ptrPos != 0) ) *ptrPos = parentNode->children.begin() + index;
463 
464 	}
465 
466 	if ( (nextNode != 0) && (nextNode->options & kXMP_NewImplicitNode) ) {
467 		nextNode->options |= (nextStep.options & kXMP_PropArrayFormMask);
468 	}
469 
470 	XMP_Assert ( (ptrPos == 0) || (nextNode == 0) || (nextNode == **ptrPos) );
471 	XMP_Assert ( (nextNode != 0) || (! createNodes) );
472 	return nextNode;
473 
474 }	// FollowXPathStep
475 
476 // -------------------------------------------------------------------------------------------------
477 // CheckImplicitStruct
478 // -------------------
479 
480 static inline void
CheckImplicitStruct(XMP_Node * node,const XMP_ExpandedXPath & expandedXPath,size_t stepNum,size_t stepLim)481 CheckImplicitStruct	( XMP_Node * node,
482 					  const XMP_ExpandedXPath &	expandedXPath,
483 					  size_t	 stepNum,
484 					  size_t	 stepLim )
485 {
486 
487 	if ( (stepNum < stepLim) &&
488 		 ((node->options & kXMP_PropCompositeMask) == 0) &&
489 		 (GetStepKind ( expandedXPath[stepNum].options ) == kXMP_StructFieldStep) ) {
490 
491 		node->options |= kXMP_PropValueIsStruct;
492 
493 	}
494 
495 }	// CheckImplicitStruct
496 
497 // -------------------------------------------------------------------------------------------------
498 // DeleteSubtree
499 // -------------
500 
501 // *** Might be useful elsewhere?
502 
503 static void
DeleteSubtree(XMP_NodePtrPos rootNodePos)504 DeleteSubtree ( XMP_NodePtrPos rootNodePos )
505 {
506 	XMP_Node * rootNode   = *rootNodePos;
507 	XMP_Node * rootParent = rootNode->parent;
508 
509 	if ( ! (rootNode->options & kXMP_PropIsQualifier) ) {
510 
511 		rootParent->children.erase ( rootNodePos );
512 
513 	} else {
514 
515 		rootParent->qualifiers.erase ( rootNodePos );
516 
517 		XMP_Assert ( rootParent->options & kXMP_PropHasQualifiers);
518 		if ( rootParent->qualifiers.empty() ) rootParent->options ^= kXMP_PropHasQualifiers;
519 
520 		if ( rootNode->name == "xml:lang" ) {
521 			XMP_Assert ( rootParent->options & kXMP_PropHasLang);
522 			rootParent->options ^= kXMP_PropHasLang;
523 		} else if ( rootNode->name == "rdf:type" ) {
524 			XMP_Assert ( rootParent->options & kXMP_PropHasType);
525 			rootParent->options ^= kXMP_PropHasType;
526 		}
527 
528 	}
529 
530 	delete rootNode;
531 
532 }	// DeleteSubtree
533 
534 // =================================================================================================
535 // =================================================================================================
536 
537 // =================================================================================================
538 // VerifySetOptions
539 // ================
540 //
541 // Normalize and verify the option flags for SetProperty and similar functions. The allowed options
542 // here are just those that apply to the property, that would be kept in the XMP_Node. Others that
543 // affect the selection of the node or other processing must be removed by now. These are:
544 //	kXMP_InsertBeforeItem
545 //	kXMP_InsertAfterItem
546 //	kXMP_KeepQualifiers
547 //	kXMPUtil_AllowCommas
548 
549 enum {
550 	kXMP_AllSetOptionsMask	= (kXMP_PropValueIsURI       |
551 							   kXMP_PropValueIsStruct    |
552 							   kXMP_PropValueIsArray     |
553 							   kXMP_PropArrayIsOrdered   |
554 							   kXMP_PropArrayIsAlternate |
555 							   kXMP_PropArrayIsAltText   |
556 							   kXMP_DeleteExisting)
557 };
558 
559 XMP_OptionBits
VerifySetOptions(XMP_OptionBits options,XMP_StringPtr propValue)560 VerifySetOptions ( XMP_OptionBits options, XMP_StringPtr propValue )
561 {
562 
563 	if ( options & kXMP_PropArrayIsAltText )   options |= kXMP_PropArrayIsAlternate;
564 	if ( options & kXMP_PropArrayIsAlternate ) options |= kXMP_PropArrayIsOrdered;
565 	if ( options & kXMP_PropArrayIsOrdered )   options |= kXMP_PropValueIsArray;
566 
567 	if ( options & ~kXMP_AllSetOptionsMask ) {
568 		XMP_Throw ( "Unrecognized option flags", kXMPErr_BadOptions );
569 	}
570 
571 	if ( (options & kXMP_PropValueIsStruct) && (options & kXMP_PropValueIsArray) ) {
572 		XMP_Throw ( "IsStruct and IsArray options are mutually exclusive", kXMPErr_BadOptions );
573 	}
574 
575 	if ( (options & kXMP_PropValueOptionsMask) && (options & kXMP_PropCompositeMask) ) {
576 		XMP_Throw ( "Structs and arrays can't have \"value\" options", kXMPErr_BadOptions );
577 	}
578 
579 	if ( (propValue != 0) && (options & kXMP_PropCompositeMask) ) {
580 		XMP_Throw ( "Structs and arrays can't have string values", kXMPErr_BadOptions );
581 	}
582 
583 	return options;
584 
585 }	// VerifySetOptions
586 
587 // =================================================================================================
588 // ComposeXPath
589 // ============
590 //
591 // Compose the canonical string form of an expanded XPath expression.
592 
593 extern void
ComposeXPath(const XMP_ExpandedXPath & expandedXPath,XMP_VarString * stringXPath)594 ComposeXPath ( const XMP_ExpandedXPath & expandedXPath,
595 			   XMP_VarString * stringXPath )
596 {
597 	*stringXPath = expandedXPath[kRootPropStep].step;
598 
599 	for ( size_t index = kRootPropStep+1; index < expandedXPath.size(); ++index ) {
600 		const XPathStepInfo & currStep = expandedXPath[index];
601 
602 		switch ( currStep.options & kXMP_StepKindMask ) {
603 
604 			case kXMP_StructFieldStep :
605 			case kXMP_QualifierStep :
606 				*stringXPath += '/';
607 				*stringXPath += currStep.step;
608 				break;
609 
610 			case kXMP_ArrayIndexStep :
611 			case kXMP_ArrayLastStep :
612 			case kXMP_QualSelectorStep :
613 			case kXMP_FieldSelectorStep :
614 				*stringXPath += currStep.step;
615 				break;
616 
617 			default:
618 				XMP_Throw ( "Unexpected", kXMPErr_InternalFailure );
619 
620 		}
621 
622 	}
623 
624 }	// ComposeXPath
625 
626 // =================================================================================================
627 // ExpandXPath
628 // ===========
629 //
630 // Split an XPath expression apart at the conceptual steps, adding the root namespace prefix to the
631 // first property component. The schema URI is put in the first (0th) slot in the expanded XPath.
632 // Check if the top level component is an alias, but don't resolve it.
633 //
634 // In the most verbose case steps are separated by '/', and each step can be of these forms:
635 //
636 //	qualName				A top level property or struct field.
637 //	*[index]				An element of an array.
638 //	*[last()]				The last element of an array.
639 //	*[fieldName="value"]	An element in an array of structs, chosen by a field value.
640 //	*[@xml:lang="value"]	An element in an alt-text array, chosen by the xml:lang qualifier.
641 //	*[?qualName="value"]	An element in an array, chosen by a qualifier value.
642 //	@xml:lang				An xml:lang qualifier.
643 //	?qualName				A general qualifier.
644 //
645 // The logic is complicated though by shorthand for arrays, the separating '/' and leading '*'
646 // are optional. These are all equivalent:  array/*[2]  array/[2]  array*[2]  array[2]
647 // All of these are broken into the 2 steps "array" and "[2]".
648 //
649 // The value portion in the array selector forms is a string quoted by ''' or '"'. The value
650 // may contain any character including a doubled quoting character. The value may be empty.
651 //
652 // The syntax isn't checked, but an XML name begins with a letter or '_', and contains letters,
653 // digits, '.', '-', '_', and a bunch of special non-ASCII Unicode characters. An XML qualified
654 // name is a pair of names separated by a colon.
655 
656 void
ExpandXPath(XMP_StringPtr schemaNS,XMP_StringPtr propPath,XMP_ExpandedXPath * expandedXPath)657 ExpandXPath	( XMP_StringPtr			schemaNS,
658 			  XMP_StringPtr			propPath,
659 			  XMP_ExpandedXPath *	expandedXPath )
660 {
661 	XMP_Assert ( (schemaNS != 0) && (propPath != 0) && (*propPath != 0) && (expandedXPath != 0) );
662 
663 	XMP_StringPtr	stepBegin, stepEnd;
664 	XMP_StringPtr	qualName, nameEnd;
665 	XMP_VarString	currStep;
666 
667 	size_t resCount = 2;	// Guess at the number of steps. At least 2, plus 1 for each '/' or '['.
668 	for ( stepEnd = propPath; *stepEnd != 0; ++stepEnd ) {
669 		if ( (*stepEnd == '/') || (*stepEnd == '[') ) ++resCount;
670 	}
671 
672 	expandedXPath->clear();
673 	expandedXPath->reserve ( resCount );
674 
675 	// -------------------------------------------------------------------------------------------
676 	// Pull out the first component and do some special processing on it: add the schema namespace
677 	// prefix and see if it is an alias. The start must be a qualName.
678 
679 	stepBegin = propPath;
680 	stepEnd = stepBegin;
681 	while ( (*stepEnd != 0) && (*stepEnd != '/') && (*stepEnd != '[') && (*stepEnd != '*') ) ++stepEnd;
682 	if ( stepEnd == stepBegin ) XMP_Throw ( "Empty initial XPath step", kXMPErr_BadXPath );
683 	currStep.assign ( stepBegin, (stepEnd - stepBegin) );
684 
685 	VerifyXPathRoot ( schemaNS, currStep.c_str(), expandedXPath );
686 
687 	XMP_OptionBits stepFlags = kXMP_StructFieldStep;
688 	if ( sRegisteredAliasMap->find ( (*expandedXPath)[kRootPropStep].step ) != sRegisteredAliasMap->end() ) {
689 		stepFlags |= kXMP_StepIsAlias;
690 	}
691 	(*expandedXPath)[kRootPropStep].options |= stepFlags;
692 
693 	// -----------------------------------------------------
694 	// Now continue to process the rest of the XPath string.
695 
696 	while ( *stepEnd != 0 ) {
697 
698 		stepBegin = stepEnd;
699 		if ( *stepBegin == '/' ) ++stepBegin;
700 		if ( *stepBegin == '*' ) {
701 			++stepBegin;
702 			if ( *stepBegin != '[' ) XMP_Throw ( "Missing '[' after '*'", kXMPErr_BadXPath );
703 		}
704 		stepEnd = stepBegin;
705 
706 		if ( *stepBegin != '[' ) {
707 
708 			// A struct field or qualifier.
709 			qualName = stepBegin;
710 			while ( (*stepEnd != 0) && (*stepEnd != '/') && (*stepEnd != '[') && (*stepEnd != '*') ) ++stepEnd;
711 			nameEnd = stepEnd;
712 			stepFlags = kXMP_StructFieldStep;	// ! Touch up later, also changing '@' to '?'.
713 
714 		} else {
715 
716 			// One of the array forms.
717 
718 			++stepEnd;	// Look at the character after the leading '['.
719 
720 			if ( ('0' <= *stepEnd) && (*stepEnd <= '9') ) {
721 
722 				// A numeric (decimal integer) array index.
723 				while ( (*stepEnd != 0) && ('0' <= *stepEnd) && (*stepEnd <= '9') ) ++stepEnd;
724 				if ( *stepEnd != ']' ) XMP_Throw ( "Missing ']' for integer array index", kXMPErr_BadXPath );
725 				stepFlags = kXMP_ArrayIndexStep;
726 
727 			} else {
728 
729 				// Could be "[last()]" or one of the selector forms. Find the ']' or '='.
730 
731 				while ( (*stepEnd != 0) && (*stepEnd != ']') && (*stepEnd != '=') ) ++stepEnd;
732 				if ( *stepEnd == 0 ) XMP_Throw ( "Missing ']' or '=' for array index", kXMPErr_BadXPath );
733 
734 				if ( *stepEnd == ']' ) {
735 
736 					if ( strncmp ( "[last()", stepBegin, (stepEnd - stepBegin) ) != 0 ) {
737 						XMP_Throw ( "Invalid non-numeric array index", kXMPErr_BadXPath );
738 					}
739 					stepFlags = kXMP_ArrayLastStep;
740 
741 				} else {
742 
743 					qualName = stepBegin+1;
744 					nameEnd = stepEnd;
745 					++stepEnd;	// Absorb the '=', remember the quote.
746 					const char quote = *stepEnd;
747 					if ( (quote != '\'') && (quote != '"') ) {
748 						XMP_Throw ( "Invalid quote in array selector", kXMPErr_BadXPath );
749 					}
750 
751 					++stepEnd;	// Absorb the leading quote.
752 					while ( *stepEnd != 0 ) {
753 						if ( *stepEnd == quote ) {
754 							if ( *(stepEnd+1) != quote ) break;
755 							++stepEnd;
756 						}
757 						++stepEnd;
758 					}
759 					if ( *stepEnd == 0 ) {
760 						XMP_Throw ( "No terminating quote for array selector", kXMPErr_BadXPath );
761 					}
762 					++stepEnd;	// Absorb the trailing quote.
763 
764 					stepFlags = kXMP_FieldSelectorStep;	// ! Touch up later, also changing '@' to '?'.
765 
766 				}
767 
768 			}
769 
770 			if ( *stepEnd != ']' ) XMP_Throw ( "Missing ']' for array index", kXMPErr_BadXPath );
771 			++stepEnd;
772 
773 		}
774 
775 		if ( stepEnd == stepBegin ) XMP_Throw ( "Empty XPath step", kXMPErr_BadXPath );
776 		currStep.assign ( stepBegin, (stepEnd - stepBegin) );
777 
778 		if ( GetStepKind ( stepFlags ) == kXMP_StructFieldStep ) {
779 
780 			if ( currStep[0] == '@' ) {
781 				currStep[0] = '?';
782 				if ( currStep != "?xml:lang" ) XMP_Throw ( "Only xml:lang allowed with '@'", kXMPErr_BadXPath );
783 			}
784 			if ( currStep[0] == '?' ) {
785 				++qualName;
786 				stepFlags = kXMP_QualifierStep;
787 			}
788 			VerifyQualName ( qualName, nameEnd );
789 
790 		} else if ( GetStepKind ( stepFlags ) == kXMP_FieldSelectorStep ) {
791 
792 			if ( currStep[1] == '@' ) {
793 				currStep[1] = '?';
794 				if ( strncmp ( currStep.c_str(), "[?xml:lang=", 11 ) != 0 ) {
795 					XMP_Throw ( "Only xml:lang allowed with '@'", kXMPErr_BadXPath );
796 				}
797 			}
798 			if ( currStep[1] == '?' ) {
799 				++qualName;
800 				stepFlags = kXMP_QualSelectorStep;
801 			}
802 			VerifyQualName ( qualName, nameEnd );
803 
804 		}
805 
806 		expandedXPath->push_back ( XPathStepInfo ( currStep, stepFlags ) );
807 
808 	}
809 
810 }	// ExpandXPath
811 
812 // =================================================================================================
813 // FindSchemaNode
814 // ==============
815 //
816 // Find or create a schema node. Returns a pointer to the node, and optionally an iterator for the
817 // node's position in the top level vector of schema nodes. The iterator is unchanged if no schema
818 // node (null) is returned.
819 
820 XMP_Node *
FindSchemaNode(XMP_Node * xmpTree,XMP_StringPtr nsURI,bool createNodes,XMP_NodePtrPos * ptrPos)821 FindSchemaNode	( XMP_Node *		xmpTree,
822 				  XMP_StringPtr		nsURI,
823 				  bool				createNodes,
824 				  XMP_NodePtrPos *	ptrPos /* = 0 */ )
825 {
826 	XMP_Node * schemaNode = 0;
827 
828 	XMP_Assert ( xmpTree->parent == 0 );
829 
830 	for ( size_t schemaNum = 0, schemaLim = xmpTree->children.size(); schemaNum != schemaLim; ++schemaNum ) {
831 		XMP_Node * currSchema = xmpTree->children[schemaNum];
832 		XMP_Assert ( currSchema->parent == xmpTree );
833 		if ( currSchema->name == nsURI ) {
834 			schemaNode = currSchema;
835 			if ( ptrPos != 0 ) *ptrPos = xmpTree->children.begin() + schemaNum;
836 			break;
837 		}
838 	}
839 
840 	if ( (schemaNode == 0) && createNodes ) {
841 
842 		schemaNode = new XMP_Node ( xmpTree, nsURI, (kXMP_SchemaNode | kXMP_NewImplicitNode) );
843 		XMP_StringPtr prefixPtr;
844 		XMP_StringLen prefixLen;
845 		bool found = XMPMeta::GetNamespacePrefix ( nsURI, &prefixPtr, &prefixLen );	// *** Use map directly?
846 		XMP_Assert ( found );
847 
848 		schemaNode->value.assign ( prefixPtr, prefixLen );
849 		xmpTree->children.push_back ( schemaNode );
850 		if ( ptrPos != 0 ) *ptrPos = xmpTree->children.end() - 1;
851 
852 		#if 0	// *** XMP_DebugBuild
853 			schemaNode->_valuePtr = schemaNode->value.c_str();
854 		#endif
855 
856 	}
857 
858 	XMP_Assert ( (ptrPos == 0) || (schemaNode == 0) || (schemaNode == **ptrPos) );
859 	XMP_Assert ( (schemaNode != 0) || (! createNodes) );
860 	return schemaNode;
861 
862 }	// FindSchemaNode
863 
864 // =================================================================================================
865 // FindChildNode
866 // =============
867 //
868 // Find or create a child node under a given parent node. Returns a pointer to the child node, and
869 // optionally an iterator for the node's position in the parent's vector of children. The iterator
870 // is unchanged if no child node (null) is returned.
871 
872 XMP_Node *
FindChildNode(XMP_Node * parent,XMP_StringPtr childName,bool createNodes,XMP_NodePtrPos * ptrPos)873 FindChildNode	( XMP_Node *		parent,
874 				  XMP_StringPtr		childName,
875 				  bool				createNodes,
876 				  XMP_NodePtrPos *	ptrPos /* = 0 */ )
877 {
878 	XMP_Node * childNode = 0;
879 
880 	if ( ! (parent->options & (kXMP_SchemaNode | kXMP_PropValueIsStruct)) ) {
881 		if ( ! (parent->options & kXMP_NewImplicitNode) ) {
882 			XMP_Throw ( "Named children only allowed for schemas and structs", kXMPErr_BadXPath );
883 		}
884 		if ( parent->options & kXMP_PropValueIsArray ) {
885 			XMP_Throw ( "Named children not allowed for arrays", kXMPErr_BadXPath );
886 		}
887 		if ( ! createNodes ) {	// *** Should be assert? If !createNodes, why is the parent a new implicit node?
888 			XMP_Throw ( "Parent is new implicit node, but createNodes is false", kXMPErr_InternalFailure );
889 		}
890 		parent->options |= kXMP_PropValueIsStruct;
891 	}
892 
893 	for ( size_t childNum = 0, childLim = parent->children.size(); childNum != childLim; ++childNum ) {
894 		XMP_Node * currChild = parent->children[childNum];
895 		XMP_Assert ( currChild->parent == parent );
896 		if ( currChild->name == childName ) {
897 			childNode = currChild;
898 			if ( ptrPos != 0 ) *ptrPos = parent->children.begin() + childNum;
899 			break;
900 		}
901 	}
902 
903 	if ( (childNode == 0) && createNodes ) {
904 		childNode = new XMP_Node ( parent, childName, kXMP_NewImplicitNode );
905 		parent->children.push_back ( childNode );
906 		if ( ptrPos != 0 ) *ptrPos = parent->children.end() - 1;
907 	}
908 
909 	XMP_Assert ( (ptrPos == 0) || (childNode == 0) || (childNode == **ptrPos) );
910 	XMP_Assert ( (childNode != 0) || (! createNodes) );
911 	return childNode;
912 
913 }	// FindChildNode
914 
915 // =================================================================================================
916 // FindQualifierNode
917 // =================
918 //
919 // Find or create a qualifier node under a given parent node. Returns a pointer to the qualifier node,
920 // and optionally an iterator for the node's position in the parent's vector of qualifiers. The iterator
921 // is unchanged if no qualifier node (null) is returned.
922 //
923 // ! On entry, the qualName parameter must not have the leading '?' from the XPath step.
924 
925 XMP_Node *
FindQualifierNode(XMP_Node * parent,XMP_StringPtr qualName,bool createNodes,XMP_NodePtrPos * ptrPos)926 FindQualifierNode	( XMP_Node *		parent,
927 					  XMP_StringPtr		qualName,
928 					  bool				createNodes,
929 					  XMP_NodePtrPos *	ptrPos /* = 0 */ )	// *** Require ptrPos internally & remove checks?
930 {
931 	XMP_Node * qualNode = 0;
932 
933 	XMP_Assert ( *qualName != '?' );
934 
935 	for ( size_t qualNum = 0, qualLim = parent->qualifiers.size(); qualNum != qualLim; ++qualNum ) {
936 		XMP_Node * currQual = parent->qualifiers[qualNum];
937 		XMP_Assert ( currQual->parent == parent );
938 		if ( currQual->name == qualName ) {
939 			qualNode = currQual;
940 			if ( ptrPos != 0 ) *ptrPos = parent->qualifiers.begin() + qualNum;
941 			break;
942 		}
943 	}
944 
945 	if ( (qualNode == 0) && createNodes ) {
946 
947 		qualNode = new XMP_Node ( parent, qualName, (kXMP_PropIsQualifier | kXMP_NewImplicitNode) );
948 		parent->options |= kXMP_PropHasQualifiers;
949 
950 		const bool isLang 	 = XMP_LitMatch ( qualName, "xml:lang" );
951 		const bool isType 	 = XMP_LitMatch ( qualName, "rdf:type" );
952 		const bool isSpecial = isLang | isType;
953 
954 		if ( isLang ) {
955 			parent->options |= kXMP_PropHasLang;
956 		} else if ( isType ) {
957 			parent->options |= kXMP_PropHasType;
958 		}
959 
960 		if ( parent->qualifiers.empty() || (! isSpecial) ) {
961 			parent->qualifiers.push_back ( qualNode );
962 			if ( ptrPos != 0 ) *ptrPos = parent->qualifiers.end() - 1;
963 		} else {
964 			XMP_NodePtrPos insertPos = parent->qualifiers.begin();	// ! Lang goes first, type after.
965 			if ( isType && (parent->options & kXMP_PropHasLang) ) ++insertPos;	// *** Does insert at end() work?
966 			insertPos = parent->qualifiers.insert ( insertPos, qualNode );
967 			if ( ptrPos != 0 ) *ptrPos = insertPos;
968 		}
969 
970 	}
971 
972 	XMP_Assert ( (ptrPos == 0) || (qualNode == 0) || (qualNode == **ptrPos) );
973 	XMP_Assert ( (qualNode != 0) || (! createNodes) );
974 	return qualNode;
975 
976 }	// FindQualifierNode
977 
978 // =================================================================================================
979 // LookupFieldSelector
980 // ===================
981 //
982 //	[fieldName="value"]	An element in an array of structs, chosen by a field value.
983 //
984 // Note that we don't create implicit nodes for field selectors, so no CreateNodes parameter.
985 
986 XMP_Index
LookupFieldSelector(const XMP_Node * arrayNode,XMP_StringPtr fieldName,XMP_StringPtr fieldValue)987 LookupFieldSelector ( const XMP_Node * arrayNode, XMP_StringPtr fieldName, XMP_StringPtr fieldValue )
988 {
989 	XMP_Index index, itemLim;
990 
991 	for ( index = 0, itemLim = arrayNode->children.size(); index != itemLim; ++index ) {
992 
993 		const XMP_Node * currItem = arrayNode->children[index];
994 		XMP_Assert ( currItem->parent == arrayNode );
995 
996 		if ( ! (currItem->options & kXMP_PropValueIsStruct) ) {
997 			XMP_Throw ( "Field selector must be used on array of struct", kXMPErr_BadXPath );
998 		}
999 
1000 		size_t f, fieldLim;
1001 		for ( f = 0, fieldLim = currItem->children.size(); f != fieldLim; ++f ) {
1002 			const XMP_Node * currField = currItem->children[f];
1003 			XMP_Assert ( currField->parent == currItem );
1004 			if ( currField->name != fieldName ) continue;
1005 			if ( currField->value == fieldValue ) break;	// Exit qual loop.
1006 		}
1007 		if ( f != fieldLim ) break;	// Exit child loop, found an item with a matching qualifier.
1008 
1009 	}
1010 
1011 	if ( index == itemLim ) index = -1;
1012 	return index;
1013 
1014 }	// LookupFieldSelector
1015 
1016 // =================================================================================================
1017 // LookupLangItem
1018 // ==============
1019 //
1020 // ! Assumes that the language value is already normalized.
1021 
1022 XMP_Index
LookupLangItem(const XMP_Node * arrayNode,XMP_VarString & lang)1023 LookupLangItem ( const XMP_Node * arrayNode, XMP_VarString & lang )
1024 {
1025 	if ( ! (arrayNode->options & kXMP_PropValueIsArray) ) {	// *** Check for alt-text?
1026 		XMP_Throw ( "Language item must be used on array", kXMPErr_BadXPath );
1027 	}
1028 
1029 	XMP_Index index   = 0;
1030 	XMP_Index itemLim = arrayNode->children.size();
1031 
1032 	for ( ; index != itemLim; ++index ) {
1033 		const XMP_Node * currItem = arrayNode->children[index];
1034 		XMP_Assert ( currItem->parent == arrayNode );
1035 		if ( currItem->qualifiers.empty() || (currItem->qualifiers[0]->name != "xml:lang") ) continue;
1036 		if ( currItem->qualifiers[0]->value == lang ) break;
1037 	}
1038 
1039 	if ( index == itemLim ) index = -1;
1040 	return index;
1041 
1042 }	// LookupLangItem
1043 
1044 // =================================================================================================
1045 // FindNode
1046 // ========
1047 //
1048 // Follow an expanded path expression to find or create a node. Returns a pointer to the node, and
1049 // optionally an iterator for the node's position in the parent's vector of children or qualifiers.
1050 // The iterator is unchanged if no child node (null) is returned.
1051 
1052 XMP_Node *
FindNode(XMP_Node * xmpTree,const XMP_ExpandedXPath & expandedXPath,bool createNodes,XMP_OptionBits leafOptions,XMP_NodePtrPos * ptrPos)1053 FindNode ( XMP_Node *		xmpTree,
1054 		   const XMP_ExpandedXPath & expandedXPath,
1055 		   bool				createNodes,
1056 		   XMP_OptionBits	leafOptions /* = 0 */,
1057 	 	   XMP_NodePtrPos * ptrPos /* = 0 */ )
1058 {
1059 	XMP_Node *     currNode = 0;
1060 	XMP_NodePtrPos currPos;
1061 	XMP_NodePtrPos newSubPos;	// Root of implicitly created subtree. Valid only if leaf is new.
1062 	bool           leafIsNew = false;
1063 
1064 	XMP_Assert ( (leafOptions == 0) || createNodes );
1065 
1066 	if ( expandedXPath.empty() ) XMP_Throw ( "Empty XPath", kXMPErr_BadXPath );
1067 
1068 	size_t stepNum = 1;	// By default start calling FollowXPathStep for the top level property step.
1069 	size_t stepLim = expandedXPath.size();
1070 
1071 	// The start of processing deals with the schema node and top level alias. If the top level step
1072 	// is not an alias, lookup the expanded path's schema URI. Otherwise, lookup the expanded path
1073 	// for the actual. While tempting, don't substitute the actual's path into the local one, don't
1074 	// risk messing with the caller's use of that. Also don't call FindNode recursively, we need to
1075 	// keep track of the root of the implicitly created subtree as we move down the path.
1076 
1077 	if ( ! (expandedXPath[kRootPropStep].options & kXMP_StepIsAlias) ) {
1078 
1079 		currNode = FindSchemaNode ( xmpTree, expandedXPath[kSchemaStep].step.c_str(), createNodes, &currPos );
1080 		if ( currNode == 0 ) return 0;
1081 
1082 		if ( currNode->options & kXMP_NewImplicitNode ) {
1083 			currNode->options ^= kXMP_NewImplicitNode;	// Clear the implicit node bit.
1084 			if ( ! leafIsNew ) newSubPos = currPos;	// Save the top most implicit node.
1085 			leafIsNew = true;	// If any parent is new, the leaf will be new also.
1086 		}
1087 
1088 	} else {
1089 
1090 		stepNum = 2;	// ! Continue processing the original path at the second level step.
1091 
1092 		XMP_AliasMapPos aliasPos = sRegisteredAliasMap->find ( expandedXPath[kRootPropStep].step );
1093 		XMP_Assert ( aliasPos != sRegisteredAliasMap->end() );
1094 
1095 		currNode = FindSchemaNode ( xmpTree, aliasPos->second[kSchemaStep].step.c_str(), createNodes, &currPos );
1096 		if ( currNode == 0 ) goto EXIT;
1097 		if ( currNode->options & kXMP_NewImplicitNode ) {
1098 			currNode->options ^= kXMP_NewImplicitNode;	// Clear the implicit node bit.
1099 			if ( ! leafIsNew ) newSubPos = currPos;	// Save the top most implicit node.
1100 			leafIsNew = true;	// If any parent is new, the leaf will be new also.
1101 		}
1102 
1103 		currNode = FollowXPathStep ( currNode, aliasPos->second, 1, createNodes, &currPos );
1104 		if ( currNode == 0 ) goto EXIT;
1105 		if ( currNode->options & kXMP_NewImplicitNode ) {
1106 			currNode->options ^= kXMP_NewImplicitNode;	// Clear the implicit node bit.
1107 			CheckImplicitStruct ( currNode, expandedXPath, 2, stepLim );
1108 			if ( ! leafIsNew ) newSubPos = currPos;	// Save the top most implicit node.
1109 			leafIsNew = true;	// If any parent is new, the leaf will be new also.
1110 		}
1111 
1112 		XMP_OptionBits arrayForm = aliasPos->second[kRootPropStep].options & kXMP_PropArrayFormMask;
1113 		XMP_Assert ( (arrayForm == 0) || (arrayForm & kXMP_PropValueIsArray) );
1114 		XMP_Assert ( (arrayForm == 0) ? (aliasPos->second.size() == 2) : (aliasPos->second.size() == 3) );
1115 
1116 		if ( arrayForm != 0 ) {
1117 			currNode = FollowXPathStep ( currNode, aliasPos->second, 2, createNodes, &currPos, true );
1118 			if ( currNode == 0 ) goto EXIT;
1119 			if ( currNode->options & kXMP_NewImplicitNode ) {
1120 				currNode->options ^= kXMP_NewImplicitNode;	// Clear the implicit node bit.
1121 				CheckImplicitStruct ( currNode, expandedXPath, 2, stepLim );
1122 				if ( ! leafIsNew ) newSubPos = currPos;	// Save the top most implicit node.
1123 				leafIsNew = true;	// If any parent is new, the leaf will be new also.
1124 			}
1125 		}
1126 
1127 	}
1128 
1129 	// Now follow the remaining steps of the original XPath.
1130 
1131 	// *** ??? Change all the num/lim loops back to num<lim? Probably safer.
1132 
1133 	try {
1134 		for ( ; stepNum < stepLim; ++stepNum ) {
1135 			currNode = FollowXPathStep ( currNode, expandedXPath, stepNum, createNodes, &currPos );
1136 			if ( currNode == 0 ) goto EXIT;
1137 			if ( currNode->options & kXMP_NewImplicitNode ) {
1138 				currNode->options ^= kXMP_NewImplicitNode;	// Clear the implicit node bit.
1139 				CheckImplicitStruct ( currNode, expandedXPath, stepNum+1, stepLim );
1140 				if ( ! leafIsNew ) newSubPos = currPos;	// Save the top most implicit node.
1141 				leafIsNew = true;	// If any parent is new, the leaf will be new also.
1142 			}
1143 		}
1144 	} catch ( ... ) {
1145 		if ( leafIsNew ) DeleteSubtree ( newSubPos );
1146 		throw;
1147 	}
1148 
1149 	// Done. Delete the implicitly created subtree if the eventual node was not found.
1150 
1151 EXIT:
1152 
1153 	XMP_Assert ( (currNode == 0) || (currNode == *currPos) );
1154 	XMP_Assert ( (currNode != 0) || (! createNodes) );
1155 
1156 	if ( leafIsNew ) {
1157 		if ( currNode != 0 ) {
1158 			currNode->options |= leafOptions;
1159 		} else {
1160 			DeleteSubtree ( newSubPos );
1161 		}
1162 	}
1163 
1164 	if ( (currNode != 0) && (ptrPos != 0) ) *ptrPos = currPos;
1165 	return currNode;
1166 
1167 }	// FindNode
1168 
1169 // =================================================================================================
1170 // CloneOffspring
1171 // ==============
1172 
1173 void
CloneOffspring(const XMP_Node * origParent,XMP_Node * cloneParent)1174 CloneOffspring ( const XMP_Node * origParent, XMP_Node * cloneParent )
1175 {
1176 	size_t qualCount  = origParent->qualifiers.size();
1177 	size_t childCount = origParent->children.size();
1178 
1179 	if ( qualCount > 0 ) {
1180 
1181 		cloneParent->qualifiers.reserve ( qualCount );
1182 
1183 		for ( size_t qualNum = 0, qualLim = qualCount; qualNum != qualLim; ++qualNum ) {
1184 			const XMP_Node * origQual  = origParent->qualifiers[qualNum];
1185 			XMP_Node *       cloneQual = new XMP_Node ( cloneParent, origQual->name, origQual->value, origQual->options );
1186 			CloneOffspring ( origQual, cloneQual );
1187 			cloneParent->qualifiers.push_back ( cloneQual );
1188 		}
1189 
1190 	}
1191 
1192 	if ( childCount > 0 ) {
1193 
1194 		cloneParent->children.reserve ( childCount );
1195 
1196 		for ( size_t childNum = 0, childLim = childCount; childNum != childLim; ++childNum ) {
1197 			const XMP_Node * origChild  = origParent->children[childNum];
1198 			XMP_Node *       cloneChild = new XMP_Node ( cloneParent, origChild->name, origChild->value, origChild->options );
1199 			CloneOffspring ( origChild, cloneChild );
1200 			cloneParent->children.push_back ( cloneChild );
1201 		}
1202 
1203 	}
1204 
1205 }	// CloneOffspring
1206 
1207 // =================================================================================================
1208 // CloneSubtree
1209 // ============
1210 
1211 XMP_Node *
CloneSubtree(const XMP_Node * origRoot,XMP_Node * cloneParent)1212 CloneSubtree ( const XMP_Node * origRoot, XMP_Node * cloneParent )
1213 {
1214 	#if XMP_DebugBuild
1215 		if ( cloneParent->parent == 0 ) {
1216 			XMP_Assert ( origRoot->options & kXMP_SchemaNode );
1217 			XMP_Assert ( FindConstSchema ( cloneParent, origRoot->name.c_str() ) == 0 );
1218 		} else {
1219 			XMP_Assert ( ! (origRoot->options & kXMP_SchemaNode) );
1220 			if ( cloneParent->options & kXMP_PropValueIsStruct ) {	// Might be an array.
1221 				XMP_Assert ( FindConstChild ( cloneParent, origRoot->name.c_str() ) == 0 );
1222 			}
1223 		}
1224 	#endif
1225 
1226 	XMP_Node * cloneRoot = new XMP_Node ( cloneParent, origRoot->name, origRoot->value, origRoot->options );
1227 	CloneOffspring ( origRoot, cloneRoot ) ;
1228 	cloneParent->children.push_back ( cloneRoot );
1229 
1230 	return cloneRoot;
1231 
1232 }	// CloneSubtree
1233 
1234 // =================================================================================================
1235 // CompareSubtrees
1236 // ===============
1237 //
1238 // Compare 2 subtrees for semantic equality. The comparison includes value, qualifiers, and form.
1239 // Schemas, top level properties, struct fields, and qualifiers are allowed to have differing order,
1240 // the appropriate right node is found from the left node's name. Alt-text arrays are allowed to be
1241 // in differing language order, other arrays are compared in order.
1242 
1243 // *** Might someday consider sorting unordered arrays.
1244 // *** Should expose this through XMPUtils.
1245 
1246 bool
CompareSubtrees(const XMP_Node & leftNode,const XMP_Node & rightNode)1247 CompareSubtrees ( const XMP_Node & leftNode, const XMP_Node & rightNode )
1248 {
1249 	// Don't compare the names here, we want to allow the outermost roots to have different names.
1250 	if ( (leftNode.value != rightNode.value) ||
1251 	     (leftNode.options != rightNode.options) ||
1252 	     (leftNode.children.size() != rightNode.children.size()) ||
1253 	     (leftNode.qualifiers.size() != rightNode.qualifiers.size()) ) return false;
1254 
1255 	// Compare the qualifiers, allowing them to be out of order.
1256 	for ( size_t qualNum = 0, qualLim = leftNode.qualifiers.size(); qualNum != qualLim; ++qualNum ) {
1257 		const XMP_Node * leftQual  = leftNode.qualifiers[qualNum];
1258 		const XMP_Node * rightQual = FindConstQualifier ( &rightNode, leftQual->name.c_str() );
1259 		if ( (rightQual == 0) || (! CompareSubtrees ( *leftQual, *rightQual )) ) return false;
1260 	}
1261 
1262 	if ( (leftNode.parent == 0) || (leftNode.options & (kXMP_SchemaNode | kXMP_PropValueIsStruct)) ) {
1263 
1264 		// The parent node is a tree root, a schema, or a struct.
1265 		for ( size_t childNum = 0, childLim = leftNode.children.size(); childNum != childLim; ++childNum ) {
1266 			const XMP_Node * leftChild  = leftNode.children[childNum];
1267 			const XMP_Node * rightChild = FindConstChild ( &rightNode, leftChild->name.c_str() );
1268 			if ( (rightChild == 0) || (! CompareSubtrees ( *leftChild, *rightChild )) ) return false;
1269 		}
1270 
1271 	} else if ( leftNode.options & kXMP_PropArrayIsAltText ) {
1272 
1273 		// The parent node is an alt-text array.
1274 		for ( size_t childNum = 0, childLim = leftNode.children.size(); childNum != childLim; ++childNum ) {
1275 			const XMP_Node * leftChild = leftNode.children[childNum];
1276 			XMP_Assert ( (! leftChild->qualifiers.empty()) && (leftChild->qualifiers[0]->name == "xml:lang") );
1277 			XMP_Index rightIndex = LookupLangItem ( &rightNode, leftChild->qualifiers[0]->value );
1278 			if ( rightIndex == -1 ) return false;
1279 			const XMP_Node * rightChild = rightNode.children[rightIndex];
1280 			if ( ! CompareSubtrees ( *leftChild, *rightChild ) ) return false;
1281 		}
1282 
1283 	} else {
1284 
1285 		// The parent must be simple or some other (not alt-text) kind of array.
1286 		XMP_Assert ( (! (leftNode.options & kXMP_PropCompositeMask)) || (leftNode.options & kXMP_PropValueIsArray) );
1287 		for ( size_t childNum = 0, childLim = leftNode.children.size(); childNum != childLim; ++childNum ) {
1288 			const XMP_Node * leftChild  = leftNode.children[childNum];
1289 			const XMP_Node * rightChild = rightNode.children[childNum];
1290 			if ( ! CompareSubtrees ( *leftChild, *rightChild ) ) return false;
1291 		}
1292 
1293 	}
1294 
1295 	return true;
1296 
1297 }	// CompareSubtrees
1298 
1299 // =================================================================================================
1300 // DeleteEmptySchema
1301 // =================
1302 
1303 void
DeleteEmptySchema(XMP_Node * schemaNode)1304 DeleteEmptySchema ( XMP_Node * schemaNode )
1305 {
1306 
1307 	if ( XMP_NodeIsSchema ( schemaNode->options ) && schemaNode->children.empty() ) {
1308 
1309 		XMP_Node * xmpTree = schemaNode->parent;
1310 
1311 		size_t schemaNum = 0;
1312 		size_t schemaLim = xmpTree->children.size();
1313 		while ( (schemaNum < schemaLim) && (xmpTree->children[schemaNum] != schemaNode) ) ++schemaNum;
1314 		XMP_Assert ( schemaNum < schemaLim );
1315 
1316 		XMP_NodePtrPos schemaPos = xmpTree->children.begin() + schemaNum;
1317 		XMP_Assert ( *schemaPos == schemaNode );
1318 
1319 		xmpTree->children.erase ( schemaPos );
1320 		delete schemaNode;
1321 
1322 	}
1323 
1324 }	// DeleteEmptySchema
1325 
1326 // =================================================================================================
1327 // NormalizeLangValue
1328 // ==================
1329 //
1330 // Normalize an xml:lang value so that comparisons are effectively case insensitive as required by
1331 // RFC 3066 (which superceeds RFC 1766). The normalization rules:
1332 //
1333 //	- The primary subtag is lower case, the suggested practice of ISO 639.
1334 //	- All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166.
1335 //	- All other subtags are lower case.
1336 
1337 void
NormalizeLangValue(XMP_VarString * value)1338 NormalizeLangValue ( XMP_VarString * value )
1339 {
1340 	char * tagStart;
1341 	char * tagEnd;
1342 
1343 	// Find and process the primary subtag.
1344 
1345 	tagStart = (char*) value->c_str();
1346 	for ( tagEnd = tagStart; (*tagEnd != 0) && (*tagEnd != '-'); ++tagEnd ) {
1347 		if ( ('A' <= *tagEnd) && (*tagEnd <= 'Z') ) *tagEnd += 0x20;
1348 	}
1349 
1350 	// Find and process the secondary subtag.
1351 
1352 	tagStart = tagEnd;
1353 	if ( *tagStart == '-' ) ++tagStart;
1354 	for ( tagEnd = tagStart; (*tagEnd != 0) && (*tagEnd != '-'); ++tagEnd ) {
1355 		if ( ('A' <= *tagEnd) && (*tagEnd <= 'Z') ) *tagEnd += 0x20;
1356 	}
1357 	if ( tagEnd == tagStart+2 ) {
1358 		if ( ('a' <= *tagStart) && (*tagStart <= 'z') ) *tagStart -= 0x20;
1359 		++tagStart;
1360 		if ( ('a' <= *tagStart) && (*tagStart <= 'z') ) *tagStart -= 0x20;
1361 	}
1362 
1363 	// Find and process the remaining subtags.
1364 
1365 	while ( true ) {
1366 		tagStart = tagEnd;
1367 		if ( *tagStart == '-' ) ++tagStart;
1368 		if ( *tagStart == 0 ) break;
1369 		for ( tagEnd = tagStart; (*tagEnd != 0) && (*tagEnd != '-'); ++tagEnd ) {
1370 			if ( ('A' <= *tagEnd) && (*tagEnd <= 'Z') ) *tagEnd += 0x20;
1371 		}
1372 	}
1373 
1374 }	// NormalizeLangValue
1375 
1376 // =================================================================================================
1377 // NormalizeLangArray
1378 // ==================
1379 //
1380 // Make sure the x-default item is first. Touch up "single value" arrays that have a default plus
1381 // one real language. This case should have the same value for both items. Older Adobe apps were
1382 // hardwired to only use the 'x-default' item, so we copy that value to the other item.
1383 
1384 void
NormalizeLangArray(XMP_Node * array)1385 NormalizeLangArray ( XMP_Node * array )
1386 {
1387 	XMP_Assert ( XMP_ArrayIsAltText(array->options) );
1388 
1389 	size_t itemNum;
1390 	size_t itemLim = array->children.size();
1391 	bool   hasDefault = false;
1392 
1393 	for ( itemNum = 0; itemNum < itemLim; ++itemNum ) {
1394 
1395 		if ( array->children[itemNum]->qualifiers.empty() ||
1396 			 (array->children[itemNum]->qualifiers[0]->name != "xml:lang") ) {
1397 			XMP_Throw ( "AltText array items must have an xml:lang qualifier", kXMPErr_BadXMP );
1398 		}
1399 
1400 		if ( array->children[itemNum]->qualifiers[0]->value == "x-default" ) {
1401 			hasDefault = true;
1402 			break;
1403 		}
1404 
1405 	}
1406 
1407 	if ( hasDefault ) {
1408 
1409 		if ( itemNum != 0 ) {
1410 			XMP_Node * temp = array->children[0];
1411 			array->children[0] = array->children[itemNum];
1412 			array->children[itemNum] = temp;
1413 		}
1414 
1415 		if ( itemLim == 2 ) array->children[1]->value = array->children[0]->value;
1416 
1417 	}
1418 
1419 }	// NormalizeLangArray
1420 
1421 // =================================================================================================
1422 // DetectAltText
1423 // =============
1424 //
1425 // See if an array is an alt-text array. If so, make sure the x-default item is first.
1426 
1427 void
DetectAltText(XMP_Node * xmpParent)1428 DetectAltText ( XMP_Node * xmpParent )
1429 {
1430 	XMP_Assert ( XMP_ArrayIsAlternate(xmpParent->options) );
1431 
1432 	size_t itemNum, itemLim;
1433 
1434 	for ( itemNum = 0, itemLim = xmpParent->children.size(); itemNum < itemLim; ++itemNum ) {
1435 		XMP_OptionBits currOptions = xmpParent->children[itemNum]->options;
1436 		if ( (currOptions & kXMP_PropCompositeMask) || (! (currOptions & kXMP_PropHasLang)) ) break;
1437 	}
1438 
1439 	if ( (itemLim != 0) && (itemNum == itemLim) ) {
1440 		xmpParent->options |= kXMP_PropArrayIsAltText;
1441 		NormalizeLangArray ( xmpParent );
1442 	}
1443 
1444 }	// DetectAltText
1445 
1446 // =================================================================================================
1447 // SortNamedNodes
1448 // ==============
1449 //
1450 // Sort the pointers in an XMP_NodeOffspring vector by name.
1451 
Compare(const XMP_Node * left,const XMP_Node * right)1452 static inline bool Compare ( const XMP_Node * left, const XMP_Node * right )
1453 {
1454 	return (left->name < right->name);
1455 }
1456 
1457 void
SortNamedNodes(XMP_NodeOffspring & nodeVector)1458 SortNamedNodes ( XMP_NodeOffspring & nodeVector )
1459 {
1460 	sort ( nodeVector.begin(), nodeVector.end(), Compare );
1461 }	// SortNamedNodes
1462 
1463 // =================================================================================================
1464