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