Beispiel #1
0
static const XMP_Node *
GetNextXMPNode ( IterInfo & info )
{
	const XMP_Node * xmpNode = 0;

	// ----------------------------------------------------------------------------------------------
	// On entry currPos points to an iteration node whose state is either before-visit or visit-self.
	// If it is before-visit then we will return that node's value part now. If it is visit-self it
	// means the previous iteration returned the value portion of that node, so we can advance to the
	// next node in the iteration tree. Then we find the corresponding XMP node, allowing for the XMP
	// tree to have been modified since that part of the iteration tree was constructed.
	
	// ! NOTE: Supporting aliases throws in some nastiness with schemas. There might not be any XMP
	// ! node for the schema, but we still have to visit it because of possible aliases. The static
	// ! sDummySchema is returned if there is no real schema node.

	if ( info.currPos->visitStage != kIter_BeforeVisit ) AdvanceIterPos ( info );
	
	bool isSchemaNode = false;
	XMP_ExpandedXPath expPath;	// Keep outside the loop to avoid constant construct/destruct.
	
	while ( info.currPos != info.endPos ) {

		isSchemaNode = XMP_NodeIsSchema ( info.currPos->options );
		if ( isSchemaNode ) {
			SetCurrSchema ( info, info.currPos->fullPath );
			xmpNode = FindConstSchema ( &info.xmpObj->tree, info.currPos->fullPath.c_str() );
			if ( xmpNode == 0 ) xmpNode = sDummySchema;
		} else {
			ExpandXPath ( info.currSchema.c_str(), info.currPos->fullPath.c_str(), &expPath );
			xmpNode = FindConstNode ( &info.xmpObj->tree, expPath );
		}
		if ( xmpNode != 0 ) break;	// Exit the loop, we found a live XMP node.

		info.currPos->visitStage = kIter_VisitChildren;	// Make AdvanceIterPos move to the next sibling.
		info.currPos->children.clear();
		info.currPos->qualifiers.clear();
		AdvanceIterPos ( info );

	}

	if ( info.currPos == info.endPos ) return 0;
	
	// -------------------------------------------------------------------------------------------
	// Now we've got the iteration node and corresponding XMP node. Add the iteration children for
	// structs and arrays. The children of schema were added when the iterator was constructed.

	XMP_Assert ( info.currPos->visitStage == kIter_BeforeVisit );

	if ( info.currPos->visitStage == kIter_BeforeVisit ) {
		if ( (! isSchemaNode) && (! (info.options & kXMP_IterJustChildren)) ) {
			AddNodeOffspring ( info, *info.currPos, xmpNode );
		}
		info.currPos->visitStage = kIter_VisitSelf;
	}
	
	return xmpNode;

}	// GetNextXMPNode
Beispiel #2
0
XMPIterator::XMPIterator ( const XMPMeta & xmpObj,
						   XMP_StringPtr   schemaNS,
						   XMP_StringPtr   propName,
						   XMP_OptionBits  options ) : clientRefs(0), info(IterInfo(options,&xmpObj))
{
	if ( (options & kXMP_IterClassMask) != kXMP_IterProperties ) {
		XMP_Throw ( "Unsupported iteration kind", kXMPErr_BadOptions );
	}
	
	// *** Lock the XMPMeta object if we ever stop using a full DLL lock.

	if ( *propName != 0 ) {

		// An iterator rooted at a specific node.

		#if TraceIterators
			printf ( "\nNew XMP property iterator for \"%s\", options = %X\n    Schema = %s, root = %s\n",
			         xmpObj.tree.name.c_str(), options, schemaNS, propName );
		#endif
		
		XMP_ExpandedXPath propPath;
		ExpandXPath ( schemaNS, propName, &propPath );
		XMP_Node * propNode = FindConstNode ( &xmpObj.tree, propPath );	// If not found get empty iteration.
		
		if ( propNode != 0 ) {

			XMP_VarString rootName ( propPath[1].step );	// The schema is [0].
			for ( size_t i = 2; i < propPath.size(); ++i ) {
				XMP_OptionBits stepKind = GetStepKind ( propPath[i].options );
				if ( stepKind <= kXMP_QualifierStep ) rootName += '/';
				rootName += propPath[i].step;
			}

			propName = rootName.c_str();
			size_t leafOffset = rootName.size();
			while ( (leafOffset > 0) && (propName[leafOffset] != '/') && (propName[leafOffset] != '[') ) --leafOffset;
			if ( propName[leafOffset] == '/' ) ++leafOffset;

			info.tree.children.push_back ( IterNode ( propNode->options, propName, leafOffset ) );
			SetCurrSchema ( info, propPath[kSchemaStep].step.c_str() );
			if ( info.options & kXMP_IterJustChildren ) {
				AddNodeOffspring ( info, info.tree.children.back(), propNode );
			}

		}
	
	} else if ( *schemaNS != 0 ) {

		// An iterator for all properties in one schema.
		
		#if TraceIterators
			printf ( "\nNew XMP schema iterator for \"%s\", options = %X\n    Schema = %s\n",
			         xmpObj.tree.name.c_str(), options, schemaNS );
		#endif
		
		info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, schemaNS, 0 ) );
		IterNode & iterSchema = info.tree.children.back();
		
		XMP_Node * xmpSchema = FindConstSchema ( &xmpObj.tree, schemaNS );
		if ( xmpSchema != 0 ) AddSchemaProps ( info, iterSchema, xmpSchema );
		
		if ( info.options & kXMP_IterIncludeAliases ) AddSchemaAliases ( info, iterSchema, schemaNS );
		
		if ( iterSchema.children.empty() ) {
			info.tree.children.pop_back();	// No properties, remove the schema node.
		} else {
			SetCurrSchema ( info, schemaNS );
		}
	
	} else {

		// An iterator for all properties in all schema. First add schema that exist (have children),
		// adding aliases from them if appropriate. Then add schema that have no actual properties
		// but do have aliases to existing properties, if we're including aliases in the iteration.
		
		#if TraceIterators
			printf ( "\nNew XMP tree iterator for \"%s\", options = %X\n",
			         xmpObj.tree.name.c_str(), options );
		#endif
		
		// First pick up the schema that exist.
		
		for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum != schemaLim; ++schemaNum ) {

			const XMP_Node * xmpSchema = xmpObj.tree.children[schemaNum];
			info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, xmpSchema->name, 0 ) );
			IterNode & iterSchema = info.tree.children.back();

			if ( ! (info.options & kXMP_IterJustChildren) ) {
				AddSchemaProps ( info, iterSchema, xmpSchema );
				if ( info.options & kXMP_IterIncludeAliases ) AddSchemaAliases ( info, iterSchema, xmpSchema->name.c_str() );
				if ( iterSchema.children.empty() ) info.tree.children.pop_back();	// No properties, remove the schema node.
			}

		}
		
		if ( info.options & kXMP_IterIncludeAliases ) {

			// Add the schema that only have aliases. The most convenient, and safest way, is to go
			// through the registered namespaces, see if it exists, and let AddSchemaAliases do its
			// thing if not. Don't combine with the above loop, it is nicer to have the "real" stuff
			// be in storage order (not subject to the namespace map order).
			
			// ! We don't do the kXMP_IterJustChildren handing in the same way here as above. The
			// ! existing schema (presumably) have actual children. We need to call AddSchemaAliases
			// ! here to determine if the namespace has any aliases to existing properties. We then
			// ! strip the children if necessary.

			XMP_cStringMapPos currNS = sNamespaceURIToPrefixMap->begin();
			XMP_cStringMapPos endNS  = sNamespaceURIToPrefixMap->end();
			for ( ; currNS != endNS; ++currNS ) {
				XMP_StringPtr schemaName = currNS->first.c_str();
				if ( FindConstSchema ( &xmpObj.tree, schemaName ) != 0 ) continue;
				info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, schemaName, 0 ) );
				IterNode & iterSchema = info.tree.children.back();
				AddSchemaAliases ( info, iterSchema, schemaName );
				if ( iterSchema.children.empty() ) {
					info.tree.children.pop_back();	// No aliases, remove the schema node.
				} else if ( info.options & kXMP_IterJustChildren ) {
					iterSchema.children.clear();	// Get rid of the children.
				}
			}

		}

	}
	
	// Set the current iteration position to the first node to be visited.
	
	info.currPos = info.tree.children.begin();
	info.endPos  = info.tree.children.end();
	
	if ( (info.options & kXMP_IterJustChildren) && (info.currPos != info.endPos) && (*schemaNS != 0) ) {
		info.currPos->visitStage = kIter_VisitSelf;
	}

	#if TraceIterators
		if ( info.currPos == info.endPos ) {
			printf ( "    ** Empty iteration **\n" );
		} else {
			printf ( "    Initial node %s, stage = %s, iterator @ %.8X\n",
			         info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this );
		}
	#endif
	
}	// XMPIterator for XMPMeta objects
/* class static */ void
XMPUtils::SeparateArrayItems ( XMPMeta *	  xmpObj,
							   XMP_StringPtr  schemaNS,
							   XMP_StringPtr  arrayName,
							   XMP_OptionBits options,
							   XMP_StringPtr  catedStr )
{
	XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (catedStr != 0) );	// ! Enforced by wrapper.
	
	XMP_VarString itemValue;
	size_t itemStart, itemEnd;
	size_t nextSize, charSize = 0;	// Avoid VS uninit var warnings.
	UniCharKind	  nextKind, charKind = UCK_normal;
	UniCodePoint  nextChar, uniChar = 0;
	
	// Extract "special" option bits, verify and normalize the others.
	
	bool preserveCommas = false;
	if ( options & kXMPUtil_AllowCommas ) {
		preserveCommas = true;
		options ^= kXMPUtil_AllowCommas;
	}

	options = VerifySetOptions ( options, 0 );	// Keep a zero value, has special meaning below.
	if ( options & ~kXMP_PropArrayFormMask ) XMP_Throw ( "Options can only provide array form", kXMPErr_BadOptions );
	
	// Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept.
	
	XMP_ExpandedXPath arrayPath;
	ExpandXPath ( schemaNS, arrayName, &arrayPath );
	XMP_Node * arrayNode = FindNode ( &xmpObj->tree, arrayPath, kXMP_ExistingOnly );
	
	if ( arrayNode != 0 ) {
		// The array exists, make sure the form is compatible. Zero arrayForm means take what exists.
		XMP_OptionBits arrayForm = arrayNode->options & kXMP_PropArrayFormMask;
		if ( (arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
			XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadXPath );
		}
		if ( (options != 0) && (options != arrayForm) ) XMP_Throw ( "Mismatch of specified and existing array form", kXMPErr_BadXPath );	// *** Right error?
	} else {
		// The array does not exist, try to create it.
		arrayNode = FindNode ( &xmpObj->tree, arrayPath, kXMP_CreateNodes, (options | kXMP_PropValueIsArray) );
		if ( arrayNode == 0 ) XMP_Throw ( "Failed to create named array", kXMPErr_BadXPath );
	}

	XMP_NodeOffspring oldChildren ( arrayNode->children );
	size_t oldChildCount = oldChildren.size();
	arrayNode->children.clear();
	
	// Extract the item values one at a time, until the whole input string is done. Be very careful
	// in the extraction about the string positions. They are essentially byte pointers, while the
	// contents are UTF-8. Adding or subtracting 1 does not necessarily move 1 Unicode character!
	
	size_t endPos = strlen ( catedStr );
	
	itemEnd = 0;
	while ( itemEnd < endPos ) {
		
		// Skip any leading spaces and separation characters. Always skip commas here. They can be
		// kept when within a value, but not when alone between values.
		
		for ( itemStart = itemEnd; itemStart < endPos; itemStart += charSize ) {
			ClassifyCharacter ( catedStr, itemStart, &charKind, &charSize, &uniChar );
			if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) break;
		}
		if ( itemStart >= endPos ) break;
		
		if ( charKind != UCK_quote ) {
		
			// This is not a quoted value. Scan for the end, create an array item from the substring.

			for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {

				ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );

				if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) continue;
				if ( (charKind == UCK_comma) && preserveCommas ) continue;
				if ( charKind != UCK_space ) break;

				if ( (itemEnd + charSize) >= endPos ) break;	// Anything left?
				ClassifyCharacter ( catedStr, (itemEnd+charSize), &nextKind, &nextSize, &nextChar );
				if ( (nextKind == UCK_normal) || (nextKind == UCK_quote) ) continue;
				if ( (nextKind == UCK_comma) && preserveCommas ) continue;
				break;	// Have multiple spaces, or a space followed by a separator.

			}		

			itemValue.assign ( catedStr, itemStart, (itemEnd - itemStart) );
		
		} else {
		
			// Accumulate quoted values into a local string, undoubling internal quotes that
			// match the surrounding quotes. Do not undouble "unmatching" quotes.
		
			UniCodePoint openQuote = uniChar;
			UniCodePoint closeQuote = GetClosingQuote ( openQuote );

			itemStart += charSize;	// Skip the opening quote;
			itemValue.erase();
			
			for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) {

				ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar );

				if ( (charKind != UCK_quote) || (! IsSurroundingQuote ( uniChar, openQuote, closeQuote)) ) {
				
					// This is not a matching quote, just append it to the item value.
					itemValue.append ( catedStr, itemEnd, charSize );
					
				} else {
				
					// This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate
					// various edge cases like undoubled opening (non-closing) quotes, or end of input.
					
					if ( (itemEnd + charSize) < endPos ) {
						ClassifyCharacter ( catedStr, itemEnd+charSize, &nextKind, &nextSize, &nextChar );
					} else {
						nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B;
					}
					
					if ( uniChar == nextChar ) {
						// This is doubled, copy it and skip the double.
						itemValue.append ( catedStr, itemEnd, charSize );
						itemEnd += nextSize;	// Loop will add in charSize.
					} else if ( ! IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) {
						// This is an undoubled, non-closing quote, copy it.
						itemValue.append ( catedStr, itemEnd, charSize );
					} else {
						// This is an undoubled closing quote, skip it and exit the loop.
						itemEnd += charSize;
						break;
					}

				}

			}	// Loop to accumulate the quoted value.
		
		}

		// Add the separated item to the array. Keep a matching old value in case it had separators.
		
		size_t oldChild;
		for ( oldChild = 0; oldChild < oldChildCount; ++oldChild ) {
			if ( (oldChildren[oldChild] != 0) && (itemValue == oldChildren[oldChild]->value) ) break;
		}
		
		XMP_Node * newItem = 0;
		if ( oldChild == oldChildCount ) {
			newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 );
		} else {
			newItem = oldChildren[oldChild];
			oldChildren[oldChild] = 0;	// ! Don't match again, let duplicates be seen.
		}
		arrayNode->children.push_back ( newItem );
		
	}	// Loop through all of the returned items.

	// Delete any of the old children that were not kept.
	for ( size_t i = 0; i < oldChildCount; ++i ) {
		if ( oldChildren[i] != 0 ) delete oldChildren[i];
	}
	
}	// SeparateArrayItems
/* class static */ void
XMPUtils::CatenateArrayItems ( const XMPMeta & xmpObj,
							   XMP_StringPtr   schemaNS,
							   XMP_StringPtr   arrayName,
							   XMP_StringPtr   separator,
							   XMP_StringPtr   quotes,
							   XMP_OptionBits  options,
							   XMP_StringPtr * catedStr,
							   XMP_StringLen * catedLen )
{
	XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // ! Enforced by wrapper.
	XMP_Assert ( (separator != 0) && (quotes != 0) && (catedStr != 0) && (catedLen != 0) ); // ! Enforced by wrapper.
	
	size_t		 strLen, strPos, charLen;
	UniCharKind	 charKind;
	UniCodePoint currUCP, openQuote, closeQuote;
	
	const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0);
	
	const XMP_Node * arrayNode = 0; // ! Move up to avoid gcc complaints.
	XMP_OptionBits	 arrayForm = 0;
	const XMP_Node * currItem  = 0;

	// Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces.
	// Any of the recognized semicolons or spaces are allowed.
	
	strPos = 0;
	strLen = strlen ( separator );
	bool haveSemicolon = false;
	
	while ( strPos < strLen ) {
		ClassifyCharacter ( separator, strPos, &charKind, &charLen, &currUCP );
		strPos += charLen;
		if ( charKind == UCK_semicolon ) {
			if ( haveSemicolon ) XMP_Throw ( "Separator can have only one semicolon",  kXMPErr_BadParam );
			haveSemicolon = true;
		} else if ( charKind != UCK_space ) {
			XMP_Throw ( "Separator can have only spaces and one semicolon",	 kXMPErr_BadParam );
		}
	};
	if ( ! haveSemicolon ) XMP_Throw ( "Separator must have one semicolon",	 kXMPErr_BadParam );
	
	// Make sure the open and close quotes are a legitimate pair.

	strLen = strlen ( quotes );
	ClassifyCharacter ( quotes, 0, &charKind, &charLen, &openQuote );
	if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );

	if ( charLen == strLen ) {
		closeQuote = openQuote;
	} else {
		strPos = charLen;
		ClassifyCharacter ( quotes, strPos, &charKind, &charLen, &closeQuote );
		if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam );
		if ( (strPos + charLen) != strLen ) XMP_Throw ( "Quoting string too long", kXMPErr_BadParam );
	}
	if ( closeQuote != GetClosingQuote ( openQuote ) ) XMP_Throw ( "Mismatched quote pair", kXMPErr_BadParam );

	// Return an empty result if the array does not exist, hurl if it isn't the right form.
	
	sCatenatedItems->erase();

	XMP_ExpandedXPath arrayPath;
	ExpandXPath ( schemaNS, arrayName, &arrayPath );

	arrayNode = FindConstNode ( &xmpObj.tree, arrayPath );
	if ( arrayNode == 0 ) goto EXIT;	// ! Need to set the output pointer and length.

	arrayForm = arrayNode->options & kXMP_PropCompositeMask;
	if ( (! (arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate) ) {
		XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadParam );
	}
	if ( arrayNode->children.empty() ) goto EXIT;	// ! Need to set the output pointer and length.
	
	// Build the result, quoting the array items, adding separators. Hurl if any item isn't simple.
	// Start the result with the first value, then add the rest with a preceeding separator.
	
	currItem = arrayNode->children[0];
	
	if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
	*sCatenatedItems = currItem->value;
	ApplyQuotes ( sCatenatedItems, openQuote, closeQuote, allowCommas );
	
	for ( size_t itemNum = 1, itemLim = arrayNode->children.size(); itemNum != itemLim; ++itemNum ) {
		const XMP_Node * currItem = arrayNode->children[itemNum];
		if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam );
		XMP_VarString tempStr ( currItem->value );
		ApplyQuotes ( &tempStr, openQuote, closeQuote, allowCommas );
		*sCatenatedItems += separator;
		*sCatenatedItems += tempStr;
	}
	
EXIT:
	*catedStr = sCatenatedItems->c_str();
	*catedLen = sCatenatedItems->size();

}	// CatenateArrayItems
/* class static */ void
XMPUtils::DuplicateSubtree ( const XMPMeta & source,
							 XMPMeta *		 dest,
							 XMP_StringPtr	 sourceNS,
							 XMP_StringPtr	 sourceRoot,
							 XMP_StringPtr	 destNS,
							 XMP_StringPtr	 destRoot,
							 XMP_OptionBits	 options )
{
	options = options;	// Avoid unused parameter warning.
	
	bool fullSourceTree = false;
	bool fullDestTree   = false;
	
	XMP_ExpandedXPath sourcePath, destPath; 

	const XMP_Node * sourceNode = 0;
	XMP_Node * destNode = 0;
	
	XMP_Assert ( (sourceNS != 0) && (*sourceNS != 0) );
	XMP_Assert ( (sourceRoot != 0) && (*sourceRoot != 0) );
	XMP_Assert ( (dest != 0) && (destNS != 0) && (destRoot != 0) );

	if ( *destNS == 0 )	  destNS   = sourceNS;
	if ( *destRoot == 0 ) destRoot = sourceRoot;
	
	if ( XMP_LitMatch ( sourceNS, "*" ) ) fullSourceTree = true;
	if ( XMP_LitMatch ( destNS, "*" ) )   fullDestTree   = true;
	
	if ( (&source == dest) && (fullSourceTree | fullDestTree) ) {
		XMP_Throw ( "Can't duplicate tree onto itself", kXMPErr_BadParam );
	}
	
	if ( fullSourceTree & fullDestTree ) XMP_Throw ( "Use Clone for full tree to full tree", kXMPErr_BadParam );

	if ( fullSourceTree ) {
	
		// The destination must be an existing empty struct, copy all of the source top level as fields.

		ExpandXPath ( destNS, destRoot, &destPath );
		destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );

		if ( (destNode == 0) || (! XMP_PropIsStruct ( destNode->options )) ) {
			XMP_Throw ( "Destination must be an existing struct", kXMPErr_BadXPath );
		}
		
		if ( ! destNode->children.empty() ) {
			if ( options & kXMP_DeleteExisting ) {
				destNode->RemoveChildren();
			} else {
				XMP_Throw ( "Destination must be an empty struct", kXMPErr_BadXPath );
			}
		}
		
		for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) {

			const XMP_Node * currSchema = source.tree.children[schemaNum];

			for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) {
				sourceNode = currSchema->children[propNum];
				XMP_Node * copyNode = new XMP_Node ( destNode, sourceNode->name, sourceNode->value, sourceNode->options );
				destNode->children.push_back ( copyNode );
				CloneOffspring ( sourceNode, copyNode );
			}

		}
	
	} else if ( fullDestTree ) {

		// The source node must be an existing struct, copy all of the fields to the dest top level.

		XMP_ExpandedXPath sourcePath; 
		ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
		sourceNode = FindConstNode ( &source.tree, sourcePath );

		if ( (sourceNode == 0) || (! XMP_PropIsStruct ( sourceNode->options )) ) {
			XMP_Throw ( "Source must be an existing struct", kXMPErr_BadXPath );
		}
		
		destNode = &dest->tree;
		
		if ( ! destNode->children.empty() ) {
			if ( options & kXMP_DeleteExisting ) {
				destNode->RemoveChildren();
			} else {
				XMP_Throw ( "Destination tree must be empty", kXMPErr_BadXPath );
			}
		}
		
		std::string   nsPrefix;
		XMP_StringPtr nsURI;
		XMP_StringLen nsLen;
		
		for ( size_t fieldNum = 0, fieldLim = sourceNode->children.size(); fieldNum < fieldLim; ++fieldNum ) {

			const XMP_Node * currField = sourceNode->children[fieldNum];

			size_t colonPos = currField->name.find ( ':' );
			nsPrefix.assign ( currField->name.c_str(), colonPos );
			bool nsOK = XMPMeta::GetNamespaceURI ( nsPrefix.c_str(), &nsURI, &nsLen );
			if ( ! nsOK ) XMP_Throw ( "Source field namespace is not global", kXMPErr_BadSchema );
			
			XMP_Node * destSchema = FindSchemaNode ( &dest->tree, nsURI, kXMP_CreateNodes );
			if ( destSchema == 0 ) XMP_Throw ( "Failed to find destination schema", kXMPErr_BadSchema );

			XMP_Node * copyNode = new XMP_Node ( destSchema, currField->name, currField->value, currField->options );
			destSchema->children.push_back ( copyNode );
			CloneOffspring ( currField, copyNode );

		}
		
	} else {

		// Find the root nodes for the source and destination subtrees.
		
		ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
		ExpandXPath ( destNS, destRoot, &destPath );
	
		sourceNode = FindConstNode ( &source.tree, sourcePath );
		if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath );
		
		destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );	// Dest must not yet exist.
		if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath );
		
		destNode = FindNode ( &dest->tree, destPath, kXMP_CreateNodes );	// Now create the dest.
		if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath );
		
		// Make sure the destination is not within the source! The source can't be inside the destination
		// because the source already existed and the destination was just created.
		
		if ( &source == dest ) {
			for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) {
				if ( testNode == sourceNode ) {
					// *** delete the just-created dest root node
					XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath );
				}
			}
		}
	
		// *** Could use a CloneTree util here and maybe elsewhere.
		
		destNode->value	  = sourceNode->value;	// *** Should use SetNode.
		destNode->options = sourceNode->options;
		CloneOffspring ( sourceNode, destNode );

	}

}	// DuplicateSubtree
/* class static */ void
XMPUtils::RemoveProperties ( XMPMeta *		xmpObj,
							 XMP_StringPtr	schemaNS,
							 XMP_StringPtr	propName,
							 XMP_OptionBits options )
{
	XMP_Assert ( (schemaNS != 0) && (propName != 0) );	// ! Enforced by wrapper.
	
	const bool doAll = XMP_TestOption (options, kXMPUtil_DoAllProperties );
	const bool includeAliases = XMP_TestOption ( options, kXMPUtil_IncludeAliases );
	
	if ( *propName != 0 ) {
	
		// Remove just the one indicated property. This might be an alias, the named schema might
		// not actually exist. So don't lookup the schema node.
		
		if ( *schemaNS == 0 ) XMP_Throw ( "Property name requires schema namespace", kXMPErr_BadParam );
		
		XMP_ExpandedXPath expPath;
		ExpandXPath ( schemaNS, propName, &expPath );
		
		XMP_NodePtrPos propPos;
		XMP_Node * propNode = FindNode ( &(xmpObj->tree), expPath, kXMP_ExistingOnly, kXMP_NoOptions, &propPos );
		if ( propNode != 0 ) {
			if ( doAll || IsExternalProperty ( expPath[kSchemaStep].step, expPath[kRootPropStep].step ) ) {
				XMP_Node * parent = propNode->parent;	// *** Should have XMP_Node::RemoveChild(pos).
				delete propNode;	// ! Both delete the node and erase the pointer from the parent.
				parent->children.erase ( propPos );
				DeleteEmptySchema ( parent );
			}
		}
	
	} else if ( *schemaNS != 0 ) {
	
		// Remove all properties from the named schema. Optionally include aliases, in which case
		// there might not be an actual schema node. 

		XMP_NodePtrPos schemaPos;
		XMP_Node * schemaNode = FindSchemaNode ( &xmpObj->tree, schemaNS, kXMP_ExistingOnly, &schemaPos );
		if ( schemaNode != 0 ) RemoveSchemaChildren ( schemaPos, doAll );
		
		if ( includeAliases ) {
		
			// We're removing the aliases also. Look them up by their namespace prefix. Yes, the
			// alias map is sorted so we could process just that portion. But that takes more code
			// and the extra speed isn't worth it. (Plus this way we avoid a dependence on the map
			// implementation.) Lookup the XMP node from the alias, to make sure the actual exists.

			XMP_StringPtr nsPrefix;
			XMP_StringLen nsLen;
			(void) XMPMeta::GetNamespacePrefix ( schemaNS, &nsPrefix, &nsLen );
			
			XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin();
			XMP_AliasMapPos endAlias  = sRegisteredAliasMap->end();
			
			for ( ; currAlias != endAlias; ++currAlias ) {
				if ( strncmp ( currAlias->first.c_str(), nsPrefix, nsLen ) == 0 ) {
					XMP_NodePtrPos actualPos;
					XMP_Node * actualProp = FindNode ( &xmpObj->tree, currAlias->second, kXMP_ExistingOnly, kXMP_NoOptions, &actualPos );
					if ( actualProp != 0 ) {
						XMP_Node * rootProp = actualProp;
						while ( ! XMP_NodeIsSchema ( rootProp->parent->options ) ) rootProp = rootProp->parent;
						if ( doAll || IsExternalProperty ( rootProp->parent->name, rootProp->name ) ) {
							XMP_Node * parent = actualProp->parent;
							delete actualProp;	// ! Both delete the node and erase the pointer from the parent.
							parent->children.erase ( actualPos );
							DeleteEmptySchema ( parent );
						}
					}
				}
			}

		}

	} else {
		
		// Remove all appropriate properties from all schema. In this case we don't have to be
		// concerned with aliases, they are handled implicitly from the actual properties.

		// ! Iterate backwards to reduce shuffling if schema are erased and to simplify the logic
		// ! for denoting the current schema. (Erasing schema n makes the old n+1 now be n.)

		size_t		   schemaCount = xmpObj->tree.children.size();
		XMP_NodePtrPos beginPos	   = xmpObj->tree.children.begin();
		
		for ( size_t schemaNum = schemaCount-1, schemaLim = (size_t)(-1); schemaNum != schemaLim; --schemaNum ) {
			XMP_NodePtrPos currSchema = beginPos + schemaNum;
			RemoveSchemaChildren ( currSchema, doAll );
		}
	
	}

}	// RemoveProperties
Beispiel #7
0
XMPIterator::XMPIterator ( const XMPMeta & xmpObj,
						   XMP_StringPtr   schemaNS,
						   XMP_StringPtr   propName,
						   XMP_OptionBits  options ) : clientRefs(0), info(IterInfo(options,&xmpObj))
{
	if ( (options & kXMP_IterClassMask) != kXMP_IterProperties ) {
		XMP_Throw ( "Unsupported iteration kind", kXMPErr_BadOptions );
	}
	
	// *** Lock the XMPMeta object if we ever stop using a full DLL lock.

	if ( *propName != 0 ) {

		// An iterator rooted at a specific node.

		#if TraceIterators
			printf ( "\nNew XMP property iterator for \"%s\", options = %X\n    Schema = %s, root = %s\n",
			         xmpObj.tree.name.c_str(), options, schemaNS, propName );
		#endif
		
		XMP_ExpandedXPath propPath;
		ExpandXPath ( schemaNS, propName, &propPath );
		XMP_Node * propNode = FindConstNode ( &xmpObj.tree, propPath );	// If not found get empty iteration.
		
		if ( propNode != 0 ) {

			XMP_VarString rootName ( propPath[1].step );	// The schema is [0].
			for ( size_t i = 2; i < propPath.size(); ++i ) {
				XMP_OptionBits stepKind = GetStepKind ( propPath[i].options );
				if ( stepKind <= kXMP_QualifierStep ) rootName += '/';
				rootName += propPath[i].step;
			}

			propName = rootName.c_str();
			size_t leafOffset = rootName.size();
			while ( (leafOffset > 0) && (propName[leafOffset] != '/') && (propName[leafOffset] != '[') ) --leafOffset;
			if ( propName[leafOffset] == '/' ) ++leafOffset;

			info.tree.children.push_back ( IterNode ( propNode->options, propName, leafOffset ) );
			SetCurrSchema ( info, propPath[kSchemaStep].step.c_str() );
			if ( info.options & kXMP_IterJustChildren ) {
				AddNodeOffspring ( info, info.tree.children.back(), propNode );
			}

		}
	
	} else if ( *schemaNS != 0 ) {

		// An iterator for all properties in one schema.
		
		#if TraceIterators
			printf ( "\nNew XMP schema iterator for \"%s\", options = %X\n    Schema = %s\n",
			         xmpObj.tree.name.c_str(), options, schemaNS );
		#endif
		
		info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, schemaNS, 0 ) );
		IterNode & iterSchema = info.tree.children.back();
		
		XMP_Node * xmpSchema = FindConstSchema ( &xmpObj.tree, schemaNS );
		if ( xmpSchema != 0 ) AddSchemaProps ( info, iterSchema, xmpSchema );
		
		if ( iterSchema.children.empty() ) {
			info.tree.children.pop_back();	// No properties, remove the schema node.
		} else {
			SetCurrSchema ( info, schemaNS );
		}
	
	} else {

		// An iterator for all properties in all schema. First add schema that exist (have children),
		// adding aliases from them if appropriate. Then add schema that have no actual properties
		// but do have aliases to existing properties, if we're including aliases in the iteration.
		
		#if TraceIterators
			printf ( "\nNew XMP tree iterator for \"%s\", options = %X\n",
			         xmpObj.tree.name.c_str(), options );
		#endif
		
		// First pick up the schema that exist.
		
		for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum != schemaLim; ++schemaNum ) {

			const XMP_Node * xmpSchema = xmpObj.tree.children[schemaNum];
			info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, xmpSchema->name, 0 ) );
			IterNode & iterSchema = info.tree.children.back();

			if ( ! (info.options & kXMP_IterJustChildren) ) {
				AddSchemaProps ( info, iterSchema, xmpSchema );
				if ( iterSchema.children.empty() ) info.tree.children.pop_back();	// No properties, remove the schema node.
			}

		}

	}
	
	// Set the current iteration position to the first node to be visited.
	
	info.currPos = info.tree.children.begin();
	info.endPos  = info.tree.children.end();
	
	if ( (info.options & kXMP_IterJustChildren) && (info.currPos != info.endPos) && (*schemaNS != 0) ) {
		info.currPos->visitStage = kIter_VisitSelf;
	}

	#if TraceIterators
		if ( info.currPos == info.endPos ) {
			printf ( "    ** Empty iteration **\n" );
		} else {
			printf ( "    Initial node %s, stage = %s, iterator @ %.8X\n",
			         info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this );
		}
	#endif
	
}	// XMPIterator for XMPMeta objects