예제 #1
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
예제 #2
0
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