static void
AppendSubtree ( const XMP_Node * sourceNode, XMP_Node * destParent, const bool replaceOld, const bool deleteEmpty )
{
	XMP_NodePtrPos destPos;
	XMP_Node * destNode = FindChildNode ( destParent, sourceNode->name.c_str(), kXMP_ExistingOnly, &destPos );
	
	bool valueIsEmpty = false;
	if ( deleteEmpty ) {
		if ( XMP_PropIsSimple ( sourceNode->options ) ) {
			valueIsEmpty = sourceNode->value.empty();
		} else {
			valueIsEmpty = sourceNode->children.empty();
		} 
	}
	
	if ( deleteEmpty & valueIsEmpty ) {
	
		if ( destNode != 0 ) {
			delete ( destNode );
			destParent->children.erase ( destPos );
		}
	
	} else if ( destNode == 0 ) {
	
		// The one easy case, the destination does not exist.
		CloneSubtree ( sourceNode, destParent );

	} else if ( replaceOld ) {
	
		// The destination exists and should be replaced.

		destNode->value	  = sourceNode->value;	// *** Should use SetNode.
		destNode->options = sourceNode->options;
		destNode->RemoveChildren();
		destNode->RemoveQualifiers();
		CloneOffspring ( sourceNode, destNode );

		#if 0	// *** XMP_DebugBuild
			destNode->_valuePtr = destNode->value.c_str();
		#endif
	
	} else {

		// The destination exists and is not totally replaced. Structs and arrays are merged.

		XMP_OptionBits sourceForm = sourceNode->options & kXMP_PropCompositeMask;
		XMP_OptionBits destForm	  = destNode->options & kXMP_PropCompositeMask;
		if ( sourceForm != destForm ) return;
		
		if ( sourceForm == kXMP_PropValueIsStruct ) {
		
			// To merge a struct process the fields recursively. E.g. add simple missing fields. The
			// recursive call to AppendSubtree will handle deletion for fields with empty values.

			for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
				const XMP_Node * sourceField = sourceNode->children[sourceNum];
				AppendSubtree ( sourceField, destNode, replaceOld, deleteEmpty );
				if ( deleteEmpty && destNode->children.empty() ) {
					delete ( destNode );
					destParent->children.erase ( destPos );
				}
			}
			
		} else if ( sourceForm & kXMP_PropArrayIsAltText ) {
		
			// Merge AltText arrays by the xml:lang qualifiers. Make sure x-default is first. Make a
			// special check for deletion of empty values. Meaningful in AltText arrays because the
			// xml:lang qualifier provides unambiguous source/dest correspondence.

			for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {

				const XMP_Node * sourceItem = sourceNode->children[sourceNum];
				if ( sourceItem->qualifiers.empty() || (sourceItem->qualifiers[0]->name != "xml:lang") ) continue;
				
				XMP_Index  destIndex = LookupLangItem ( destNode, sourceItem->qualifiers[0]->value );
				
				if ( deleteEmpty && sourceItem->value.empty() ) {

					if ( destIndex != -1 ) {
						delete ( destNode->children[destIndex] );
						destNode->children.erase ( destNode->children.begin() + destIndex );
						if ( destNode->children.empty() ) {
							delete ( destNode );
							destParent->children.erase ( destPos );
						}
					}

				} else {
				
					if (  destIndex != -1 ) continue;	// Not replacing, keep the existing item.
				
					if ( (sourceItem->qualifiers[0]->value != "x-default") || destNode->children.empty() ) {
						CloneSubtree ( sourceItem, destNode );
					} else {
						XMP_Node * destItem = new XMP_Node ( destNode, sourceItem->name, sourceItem->value, sourceItem->options );
						CloneOffspring ( sourceItem, destItem );
						destNode->children.insert ( destNode->children.begin(), destItem );
				}
				
				}

			}
		
		} else if ( sourceForm & kXMP_PropValueIsArray ) {
		
			// Merge other arrays by item values. Don't worry about order or duplicates. Source 
			// items with empty values do not cause deletion, that conflicts horribly with merging.

			for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) {
				const XMP_Node * sourceItem = sourceNode->children[sourceNum];

				size_t	destNum, destLim;
				for ( destNum = 0, destLim = destNode->children.size(); destNum != destLim; ++destNum ) {
					const XMP_Node * destItem = destNode->children[destNum];
					if ( ItemValuesMatch ( sourceItem, destItem ) ) break;
				}
				if ( destNum == destLim ) CloneSubtree ( sourceItem, destNode );

			}
			
		}

	}

}	// AppendSubtree
Example #2
0
static void
TouchUpDataModel ( XMPMeta * xmp )
{
	XMP_Node & tree = xmp->tree;
	
	// Do special case touch ups for certain schema.

	XMP_Node * currSchema = 0;

	currSchema = FindSchemaNode ( &tree, kXMP_NS_EXIF, kXMP_ExistingOnly );
	if ( currSchema != 0 ) {
		// Do a special case fix for exif:GPSTimeStamp.
		XMP_Node * gpsDateTime = FindChildNode ( currSchema, "exif:GPSTimeStamp", kXMP_ExistingOnly );
		if ( gpsDateTime != 0 ) FixGPSTimeStamp ( currSchema, gpsDateTime );
	}

	currSchema = FindSchemaNode ( &tree, kXMP_NS_DM, kXMP_ExistingOnly );
	if ( currSchema != 0 ) {
		// Do a special case migration of xmpDM:copyright to dc:rights['x-default']. Do this before
		// the dc: touch up since it can affect the dc: schema.
		XMP_Node * dmCopyright = FindChildNode ( currSchema, "xmpDM:copyright", kXMP_ExistingOnly );
		if ( dmCopyright != 0 ) MigrateAudioCopyright ( xmp, dmCopyright );
	}

	currSchema = FindSchemaNode ( &tree, kXMP_NS_DC, kXMP_ExistingOnly );
	if ( currSchema != 0 ) {
		// Do a special case fix for dc:subject, make sure it is an unordered array.
		XMP_Node * dcSubject = FindChildNode ( currSchema, "dc:subject", kXMP_ExistingOnly );
		if ( dcSubject != 0 ) {
			XMP_OptionBits keepMask = ~(kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate | kXMP_PropArrayIsAltText);
			dcSubject->options &= keepMask;	// Make sure any ordered array bits are clear.
		}
	}
	
	// Fix any broken AltText arrays that we know about.
	
	RepairAltText ( tree, kXMP_NS_DC, "dc:description" );	// ! Note inclusion of prefixes for direct node lookup!
	RepairAltText ( tree, kXMP_NS_DC, "dc:rights" );
	RepairAltText ( tree, kXMP_NS_DC, "dc:title" );
	RepairAltText ( tree, kXMP_NS_XMP_Rights, "xapRights:UsageTerms" );
	RepairAltText ( tree, kXMP_NS_EXIF, "exif:UserComment" );
	
	// Tweak old XMP: Move an instance ID from rdf:about to the xmpMM:InstanceID property. An old
	// instance ID usually looks like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign
	// 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If the name looks like a UUID
	// simply move it to xmpMM:InstanceID, don't worry about any existing xmpMM:InstanceID. Both
	// will only be present when a newer file with the xmpMM:InstanceID property is updated by an
	// old app that uses rdf:about.
	
	if ( ! tree.name.empty() ) {

		bool nameIsUUID = false;
		XMP_StringPtr nameStr = tree.name.c_str();

		if ( XMP_LitNMatch ( nameStr, "uuid:", 5 ) ) {

			nameIsUUID = true;

		} else if ( tree.name.size() == 36 ) {

			nameIsUUID = true;	// ! Assume true, we'll set it to false below if not.
			for ( int i = 0;  i < 36; ++i ) {
				char ch = nameStr[i];
				if ( ch == '-' ) {
					if ( (i == 8) || (i == 13) || (i == 18) || (i == 23) ) continue;
					nameIsUUID = false;
					break;
				} else {
					if ( (('0' <= ch) && (ch <= '9')) || (('a' <= ch) && (ch <= 'z')) ) continue;
					nameIsUUID = false;
					break;
				}
			}

		}
		
		if ( nameIsUUID ) {

			XMP_ExpandedXPath expPath;
			ExpandXPath ( kXMP_NS_XMP_MM, "InstanceID", &expPath );
			XMP_Node * idNode = FindNode ( &tree, expPath, kXMP_CreateNodes, 0 );
			if ( idNode == 0 ) XMP_Throw ( "Failure creating xmpMM:InstanceID", kXMPErr_InternalFailure );

			idNode->options = 0;	// Clobber any existing xmpMM:InstanceID.
			idNode->value = tree.name;
			idNode->RemoveChildren();
			idNode->RemoveQualifiers();

			tree.name.erase();

		}

	}

}	// TouchUpDataModel
/* 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