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
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