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