diff options
Diffstat (limited to 'gpr/source/lib/xmp_core/XMPMeta-GetSet.cpp')
-rw-r--r-- | gpr/source/lib/xmp_core/XMPMeta-GetSet.cpp | 1309 |
1 files changed, 1309 insertions, 0 deletions
diff --git a/gpr/source/lib/xmp_core/XMPMeta-GetSet.cpp b/gpr/source/lib/xmp_core/XMPMeta-GetSet.cpp new file mode 100644 index 0000000..44f10a4 --- /dev/null +++ b/gpr/source/lib/xmp_core/XMPMeta-GetSet.cpp @@ -0,0 +1,1309 @@ +// ================================================================================================= +// Copyright 2003 Adobe Systems Incorporated +// All Rights Reserved. +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of +// one format in a file with a different format', inventors: Sean Parent, Greg Gilley. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include! +#include "XMPCore_Impl.hpp" + +#include "XMPMeta.hpp" +#include "XMPIterator.hpp" +#include "XMPUtils.hpp" + +#include "public/include/XMP_Version.h" +#include "UnicodeInlines.incl_cpp" +#include "UnicodeConversions.hpp" +#include "ExpatAdapter.hpp" + +#if XMP_DebugBuild + #include <iostream> +#endif + +using namespace std; + +#if XMP_WinBuild + #pragma warning ( disable : 4533 ) // initialization of '...' is skipped by 'goto ...' + #pragma warning ( disable : 4702 ) // unreachable code + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) +#endif + + +// *** Use the XMP_PropIsXyz (Schema, Simple, Struct, Array, ...) macros +// *** Add debug codegen checks, e.g. that typical masking operations really work +// *** Change all uses of strcmp and strncmp to XMP_LitMatch and XMP_LitNMatch + + +// ================================================================================================= +// Local Types and Constants +// ========================= + +typedef unsigned char XMP_CLTMatch; + +enum { // Values for XMP_CLTMatch. + kXMP_CLT_NoValues, + kXMP_CLT_SpecificMatch, + kXMP_CLT_SingleGeneric, + kXMP_CLT_MultipleGeneric, + kXMP_CLT_XDefault, + kXMP_CLT_FirstItem +}; + + +// ================================================================================================= +// Static Variables +// ================ + + +// ================================================================================================= +// Local Utilities +// =============== + + +// ------------------------------------------------------------------------------------------------- +// SetNodeValue +// ------------ + +static inline void +SetNodeValue ( XMP_Node * node, XMP_StringPtr value ) +{ + + #if XMP_DebugBuild // ! Hack to force an assert. + if ( (node->name == "xmp:TestAssertNotify") && XMP_LitMatch ( value, "DoIt!" ) ) { + XMP_Assert ( node->name != "xmp:TestAssertNotify" ); + } + #endif + + std::string newValue = value; // Need a local copy to tweak and not change node.value for errors. + + XMP_Uns8* chPtr = (XMP_Uns8*) newValue.c_str(); // Check for valid UTF-8, replace ASCII controls with a space. + while ( *chPtr != 0 ) { + + while ( (*chPtr != 0) && (*chPtr < 0x80) ) { + if ( *chPtr < 0x20 ) { + if ( (*chPtr != kTab) && (*chPtr != kLF) && (*chPtr != kCR) ) *chPtr = 0x20; + } else if (*chPtr == 0x7F ) { + *chPtr = 0x20; + } + ++chPtr; + } + + XMP_Assert ( (*chPtr == 0) || (*chPtr >= 0x80) ); + + if ( *chPtr != 0 ) { + XMP_Uns32 cp = GetCodePoint ( (const XMP_Uns8 **) &chPtr ); // Throws for bad UTF-8. + if ( (cp == 0xFFFE) || (cp == 0xFFFF) ) { + XMP_Throw ( "U+FFFE and U+FFFF are not allowed in XML", kXMPErr_BadXML ); + } + } + + } + + if ( XMP_PropIsQualifier(node->options) && (node->name == "xml:lang") ) NormalizeLangValue ( &newValue ); + + node->value.swap ( newValue ); + + #if 0 // *** XMP_DebugBuild + node->_valuePtr = node->value.c_str(); + #endif + +} // SetNodeValue + + +// ------------------------------------------------------------------------------------------------- +// SetNode +// ------- +// +// The internals for SetProperty and related calls, used after the node is found or created. + +static void +SetNode ( XMP_Node * node, XMP_StringPtr value, XMP_OptionBits options ) +{ + if ( options & kXMP_DeleteExisting ) { + XMP_ClearOption ( options, kXMP_DeleteExisting ); + node->options = options; + node->value.erase(); + node->RemoveChildren(); + node->RemoveQualifiers(); + } + + node->options |= options; // Keep options set by FindNode when creating a new node. + + if ( value != 0 ) { + + // This is setting the value of a leaf node. + if ( node->options & kXMP_PropCompositeMask ) XMP_Throw ( "Composite nodes can't have values", kXMPErr_BadXPath ); + XMP_Assert ( node->children.empty() ); + SetNodeValue ( node, value ); + + } else { + + // This is setting up an array or struct. + if ( ! node->value.empty() ) XMP_Throw ( "Composite nodes can't have values", kXMPErr_BadXPath ); + if ( node->options & kXMP_PropCompositeMask ) { // Can't change an array to a struct, or vice versa. + if ( (options & kXMP_PropCompositeMask) != (node->options & kXMP_PropCompositeMask) ) { + XMP_Throw ( "Requested and existing composite form mismatch", kXMPErr_BadXPath ); + } + } + node->RemoveChildren(); + + } + +} // SetNode + + +// ------------------------------------------------------------------------------------------------- +// DoSetArrayItem +// -------------- + +static void +DoSetArrayItem ( XMP_Node * arrayNode, + XMP_Index itemIndex, + XMP_StringPtr itemValue, + XMP_OptionBits options ) +{ + XMP_OptionBits itemLoc = options & kXMP_PropArrayLocationMask; + XMP_Index arraySize = arrayNode->children.size(); + + options &= ~kXMP_PropArrayLocationMask; + options = VerifySetOptions ( options, itemValue ); + + // Now locate or create the item node and set the value. Note the index parameter is one-based! + // The index can be in the range [0..size+1] or "last", normalize it and check the insert flags. + // The order of the normalization checks is important. If the array is empty we end up with an + // index and location to set item size+1. + + XMP_Node * itemNode = 0; + + if ( itemIndex == kXMP_ArrayLastItem ) itemIndex = arraySize; + if ( (itemIndex == 0) && (itemLoc == kXMP_InsertAfterItem) ) { + itemIndex = 1; + itemLoc = kXMP_InsertBeforeItem; + } + if ( (itemIndex == arraySize) && (itemLoc == kXMP_InsertAfterItem) ) { + itemIndex += 1; + itemLoc = 0; + } + if ( (itemIndex == arraySize+1) && (itemLoc == kXMP_InsertBeforeItem) ) itemLoc = 0; + + if ( itemIndex == arraySize+1 ) { + + if ( itemLoc != 0 ) XMP_Throw ( "Can't insert before or after implicit new item", kXMPErr_BadIndex ); + itemNode = new XMP_Node ( arrayNode, kXMP_ArrayItemName, 0 ); + arrayNode->children.push_back ( itemNode ); + + } else { + + if ( (itemIndex < 1) || (itemIndex > arraySize) ) XMP_Throw ( "Array index out of bounds", kXMPErr_BadIndex ); + --itemIndex; // ! Convert the index to a C zero-based value! + if ( itemLoc == 0 ) { + itemNode = arrayNode->children[itemIndex]; + } else { + XMP_NodePtrPos itemPos = arrayNode->children.begin() + itemIndex; + if ( itemLoc == kXMP_InsertAfterItem ) ++itemPos; + itemNode = new XMP_Node ( arrayNode, kXMP_ArrayItemName, 0 ); + itemPos = arrayNode->children.insert ( itemPos, itemNode ); + } + + } + + SetNode ( itemNode, itemValue, options ); + +} // DoSetArrayItem + + +// ------------------------------------------------------------------------------------------------- +// ChooseLocalizedText +// ------------------- +// +// 1. Look for an exact match with the specific language. +// 2. If a generic language is given, look for partial matches. +// 3. Look for an "x-default" item. +// 4. Choose the first item. + +static XMP_CLTMatch +ChooseLocalizedText ( const XMP_Node * arrayNode, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang, + const XMP_Node * * itemNode ) +{ + const XMP_Node * currItem = 0; + const size_t itemLim = arrayNode->children.size(); + size_t itemNum; + + // See if the array has the right form. Allow empty alt arrays, that is what parsing returns. + // *** Should check alt-text bit when that is reliably maintained. + + if ( ! ( XMP_ArrayIsAltText(arrayNode->options) || + (arrayNode->children.empty() && XMP_ArrayIsAlternate(arrayNode->options)) ) ) { + XMP_Throw ( "Localized text array is not alt-text", kXMPErr_BadXPath ); + } + if ( arrayNode->children.empty() ) { + *itemNode = 0; + return kXMP_CLT_NoValues; + } + + for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { + currItem = arrayNode->children[itemNum]; + if ( currItem->options & kXMP_PropCompositeMask ) { + XMP_Throw ( "Alt-text array item is not simple", kXMPErr_BadXPath ); + } + if ( currItem->qualifiers.empty() || (currItem->qualifiers[0]->name != "xml:lang") ) { + XMP_Throw ( "Alt-text array item has no language qualifier", kXMPErr_BadXPath ); + } + } + + // Look for an exact match with the specific language. + for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { + currItem = arrayNode->children[itemNum]; + if ( currItem->qualifiers[0]->value == specificLang ) { + *itemNode = currItem; + return kXMP_CLT_SpecificMatch; + } + } + + if ( *genericLang != 0 ) { + + // Look for the first partial match with the generic language. + const size_t genericLen = strlen ( genericLang ); + for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { + currItem = arrayNode->children[itemNum]; + XMP_StringPtr currLang = currItem->qualifiers[0]->value.c_str(); + const size_t currLangSize = currItem->qualifiers[0]->value.size(); + if ( (currLangSize >= genericLen) && + XMP_LitNMatch ( currLang, genericLang, genericLen ) && + ((currLangSize == genericLen) || (currLang[genericLen] == '-')) ) { + *itemNode = currItem; + break; // ! Don't return, need to look for other matches. + } + } + + if ( itemNum < itemLim ) { + + // Look for a second partial match with the generic language. + for ( ++itemNum; itemNum < itemLim; ++itemNum ) { + currItem = arrayNode->children[itemNum]; + XMP_StringPtr currLang = currItem->qualifiers[0]->value.c_str(); + const size_t currLangSize = currItem->qualifiers[0]->value.size(); + if ( (currLangSize >= genericLen) && + XMP_LitNMatch ( currLang, genericLang, genericLen ) && + ((currLangSize == genericLen) || (currLang[genericLen] == '-')) ) { + return kXMP_CLT_MultipleGeneric; // ! Leave itemNode with the first partial match. + } + } + return kXMP_CLT_SingleGeneric; // No second partial match was found. + + } + + } + + // Look for an 'x-default' item. + for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { + currItem = arrayNode->children[itemNum]; + if ( currItem->qualifiers[0]->value == "x-default" ) { + *itemNode = currItem; + return kXMP_CLT_XDefault; + } + } + + // Everything failed, choose the first item. + *itemNode = arrayNode->children[0]; + return kXMP_CLT_FirstItem; + +} // ChooseLocalizedText + + +// ------------------------------------------------------------------------------------------------- +// AppendLangItem +// -------------- + +static void +AppendLangItem ( XMP_Node * arrayNode, XMP_StringPtr itemLang, XMP_StringPtr itemValue ) +{ + XMP_Node * newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, (kXMP_PropHasQualifiers | kXMP_PropHasLang) ); + XMP_Node * langQual = new XMP_Node ( newItem, "xml:lang", kXMP_PropIsQualifier ); + + try { // ! Use SetNodeValue, not constructors above, to get the character checks. + SetNodeValue ( newItem, itemValue ); + SetNodeValue ( langQual, itemLang ); + } catch (...) { + delete newItem; + delete langQual; + throw; + } + + newItem->qualifiers.push_back ( langQual ); + + if ( (arrayNode->children.empty()) || (langQual->value != "x-default") ) { + arrayNode->children.push_back ( newItem ); + } else { + arrayNode->children.insert ( arrayNode->children.begin(), newItem ); + } + +} // AppendLangItem + + +// ================================================================================================= +// Class Methods +// ============= +// +// +// ================================================================================================= + + +// ------------------------------------------------------------------------------------------------- +// GetProperty +// ----------- + +bool +XMPMeta::GetProperty ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr * propValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + XMP_Assert ( (propValue != 0) && (valueSize != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; + ExpandXPath ( schemaNS, propName, &expPath ); + + XMP_Node * propNode = FindConstNode ( &tree, expPath ); + if ( propNode == 0 ) return false; + + *propValue = propNode->value.c_str(); + *valueSize = propNode->value.size(); + *options = propNode->options; + + return true; + +} // GetProperty + + +// ------------------------------------------------------------------------------------------------- +// GetArrayItem +// ------------ + +bool +XMPMeta::GetArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_StringPtr * itemValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. + XMP_Assert ( (itemValue != 0) && (options != 0) ); // Enforced by wrapper. + + // ! Special case check to make errors consistent if the array does not exist. The other array + // ! functions and existing array here (empty or not) already throw. + if ( (itemIndex <= 0) && (itemIndex != kXMP_ArrayLastItem) ) XMP_Throw ( "Array index must be larger than zero", kXMPErr_BadXPath ); + + XMP_VarString itemPath; + XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &itemPath ); + return GetProperty ( schemaNS, itemPath.c_str(), itemValue, valueSize, options ); + +} // GetArrayItem + + +// ------------------------------------------------------------------------------------------------- +// GetStructField +// -------------- + +bool +XMPMeta::GetStructField ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr * fieldValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. + XMP_Assert ( (fieldValue != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_VarString fieldPath; + XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); + return GetProperty ( schemaNS, fieldPath.c_str(), fieldValue, valueSize, options ); + +} // GetStructField + + +// ------------------------------------------------------------------------------------------------- +// GetQualifier +// ------------ + +bool +XMPMeta::GetQualifier ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_StringPtr * qualValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. + XMP_Assert ( (qualValue != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_VarString qualPath; + XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); + return GetProperty ( schemaNS, qualPath.c_str(), qualValue, valueSize, options ); + +} // GetQualifier + + +// ------------------------------------------------------------------------------------------------- +// SetProperty +// ----------- + +// *** Should handle array items specially, calling SetArrayItem. + +void +XMPMeta::SetProperty ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr propValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + options = VerifySetOptions ( options, propValue ); + + XMP_ExpandedXPath expPath; + ExpandXPath ( schemaNS, propName, &expPath ); + + XMP_Node * propNode = FindNode ( &tree, expPath, kXMP_CreateNodes, options ); + if ( propNode == 0 ) XMP_Throw ( "Specified property does not exist", kXMPErr_BadXPath ); + + SetNode ( propNode, propValue, options ); + +} // SetProperty + + +// ------------------------------------------------------------------------------------------------- +// SetArrayItem +// ------------ + +void +XMPMeta::SetArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_StringPtr itemValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. + + XMP_ExpandedXPath arrayPath; + ExpandXPath ( schemaNS, arrayName, &arrayPath ); + XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_ExistingOnly ); // Just lookup, don't try to create. + if ( arrayNode == 0 ) XMP_Throw ( "Specified array does not exist", kXMPErr_BadXPath ); + + DoSetArrayItem ( arrayNode, itemIndex, itemValue, options ); + +} // SetArrayItem + + +// ------------------------------------------------------------------------------------------------- +// AppendArrayItem +// --------------- + +void +XMPMeta::AppendArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_OptionBits arrayOptions, + XMP_StringPtr itemValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. + + arrayOptions = VerifySetOptions ( arrayOptions, 0 ); + if ( (arrayOptions & ~kXMP_PropArrayFormMask) != 0 ) { + XMP_Throw ( "Only array form flags allowed for arrayOptions", kXMPErr_BadOptions ); + } + + // Locate or create the array. If it already exists, make sure the array form from the options + // parameter is compatible with the current state. + + XMP_ExpandedXPath arrayPath; + ExpandXPath ( schemaNS, arrayName, &arrayPath ); + XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_ExistingOnly ); // Just lookup, don't try to create. + + if ( arrayNode != 0 ) { + // The array exists, make sure the form is compatible. Zero arrayForm means take what exists. + if ( ! (arrayNode->options & kXMP_PropValueIsArray) ) { + XMP_Throw ( "The named property is not an array", kXMPErr_BadXPath ); + } + #if 0 + // *** Disable for now. Need to do some general rethinking of semantic checks. + if ( (arrayOptions != 0) && (arrayOptions != (arrayNode->options & kXMP_PropArrayFormMask)) ) { + XMP_Throw ( "Mismatch of existing and specified array form", kXMPErr_BadOptions ); + } + #endif + } else { + // The array does not exist, try to create it. + if ( arrayOptions == 0 ) XMP_Throw ( "Explicit arrayOptions required to create new array", kXMPErr_BadOptions ); + arrayNode = FindNode ( &tree, arrayPath, kXMP_CreateNodes, arrayOptions ); + if ( arrayNode == 0 ) XMP_Throw ( "Failure creating array node", kXMPErr_BadXPath ); + } + + DoSetArrayItem ( arrayNode, kXMP_ArrayLastItem, itemValue, (options | kXMP_InsertAfterItem) ); + +} // AppendArrayItem + + +// ------------------------------------------------------------------------------------------------- +// SetStructField +// -------------- + +void +XMPMeta::SetStructField ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr fieldValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. + + XMP_VarString fieldPath; + XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); + SetProperty ( schemaNS, fieldPath.c_str(), fieldValue, options ); + +} // SetStructField + + +// ------------------------------------------------------------------------------------------------- +// SetQualifier +// ------------ + +void +XMPMeta::SetQualifier ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_StringPtr qualValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; + ExpandXPath ( schemaNS, propName, &expPath ); + XMP_Node * propNode = FindNode ( &tree, expPath, kXMP_ExistingOnly ); + if ( propNode == 0 ) XMP_Throw ( "Specified property does not exist", kXMPErr_BadXPath ); + + XMP_VarString qualPath; + XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); + SetProperty ( schemaNS, qualPath.c_str(), qualValue, options ); + +} // SetQualifier + + +// ------------------------------------------------------------------------------------------------- +// DeleteProperty +// -------------- + +void +XMPMeta::DeleteProperty ( XMP_StringPtr schemaNS, + XMP_StringPtr propName ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; + ExpandXPath ( schemaNS, propName, &expPath ); + + XMP_NodePtrPos ptrPos; + XMP_Node * propNode = FindNode ( &tree, expPath, kXMP_ExistingOnly, kXMP_NoOptions, &ptrPos ); + if ( propNode == 0 ) return; + XMP_Node * parentNode = propNode->parent; + + // Erase the pointer from the parent's vector, then delete the node and all below it. + + if ( ! (propNode->options & kXMP_PropIsQualifier) ) { + + parentNode->children.erase ( ptrPos ); + DeleteEmptySchema ( parentNode ); + + } else { + + if ( propNode->name == "xml:lang" ) { + XMP_Assert ( parentNode->options & kXMP_PropHasLang ); // *** &= ~flag would be safer + parentNode->options ^= kXMP_PropHasLang; + } else if ( propNode->name == "rdf:type" ) { + XMP_Assert ( parentNode->options & kXMP_PropHasType ); + parentNode->options ^= kXMP_PropHasType; + } + + parentNode->qualifiers.erase ( ptrPos ); + XMP_Assert ( parentNode->options & kXMP_PropHasQualifiers ); + if ( parentNode->qualifiers.empty() ) parentNode->options ^= kXMP_PropHasQualifiers; + + } + + delete propNode; // ! The destructor takes care of the whole subtree. + +} // DeleteProperty + + +// ------------------------------------------------------------------------------------------------- +// DeleteArrayItem +// --------------- + +void +XMPMeta::DeleteArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex ) +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. + + XMP_VarString itemPath; + XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &itemPath ); + DeleteProperty ( schemaNS, itemPath.c_str() ); + +} // DeleteArrayItem + + +// ------------------------------------------------------------------------------------------------- +// DeleteStructField +// ----------------- + +void +XMPMeta::DeleteStructField ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName ) +{ + XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. + + XMP_VarString fieldPath; + XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); + DeleteProperty ( schemaNS, fieldPath.c_str() ); + +} // DeleteStructField + + +// ------------------------------------------------------------------------------------------------- +// DeleteQualifier +// --------------- + +void +XMPMeta::DeleteQualifier ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. + + XMP_VarString qualPath; + XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); + DeleteProperty ( schemaNS, qualPath.c_str() ); + +} // DeleteQualifier + + +// ------------------------------------------------------------------------------------------------- +// DoesPropertyExist +// ----------------- + +bool +XMPMeta::DoesPropertyExist ( XMP_StringPtr schemaNS, + XMP_StringPtr propName ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; + ExpandXPath ( schemaNS, propName, &expPath ); + + XMP_Node * propNode = FindConstNode ( &tree, expPath ); + return (propNode != 0); + +} // DoesPropertyExist + + +// ------------------------------------------------------------------------------------------------- +// DoesArrayItemExist +// ------------------ + +bool +XMPMeta::DoesArrayItemExist ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex ) const +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. + + XMP_VarString itemPath; + XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &itemPath ); + return DoesPropertyExist ( schemaNS, itemPath.c_str() ); + +} // DoesArrayItemExist + + +// ------------------------------------------------------------------------------------------------- +// DoesStructFieldExist +// -------------------- + +bool +XMPMeta::DoesStructFieldExist ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName ) const +{ + XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. + + XMP_VarString fieldPath; + XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); + return DoesPropertyExist ( schemaNS, fieldPath.c_str() ); + +} // DoesStructFieldExist + + +// ------------------------------------------------------------------------------------------------- +// DoesQualifierExist +// ------------------ + +bool +XMPMeta::DoesQualifierExist ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. + + XMP_VarString qualPath; + XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); + return DoesPropertyExist ( schemaNS, qualPath.c_str() ); + +} // DoesQualifierExist + + +// ------------------------------------------------------------------------------------------------- +// GetLocalizedText +// ---------------- + +bool +XMPMeta::GetLocalizedText ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr _genericLang, + XMP_StringPtr _specificLang, + XMP_StringPtr * actualLang, + XMP_StringLen * langSize, + XMP_StringPtr * itemValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. + XMP_Assert ( (actualLang != 0) && (langSize != 0) ); // Enforced by wrapper. + XMP_Assert ( (itemValue != 0) && (valueSize != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_VarString zGenericLang ( _genericLang ); + XMP_VarString zSpecificLang ( _specificLang ); + NormalizeLangValue ( &zGenericLang ); + NormalizeLangValue ( &zSpecificLang ); + + XMP_StringPtr genericLang = zGenericLang.c_str(); + XMP_StringPtr specificLang = zSpecificLang.c_str(); + + XMP_ExpandedXPath arrayPath; + ExpandXPath ( schemaNS, arrayName, &arrayPath ); + + const XMP_Node * arrayNode = FindConstNode ( &tree, arrayPath ); // *** This expand/find idiom is used in 3 Getters. + if ( arrayNode == 0 ) return false; // *** Should extract it into a local utility. + + XMP_CLTMatch match; + const XMP_Node * itemNode; + + match = ChooseLocalizedText ( arrayNode, genericLang, specificLang, &itemNode ); + if ( match == kXMP_CLT_NoValues ) return false; + + *actualLang = itemNode->qualifiers[0]->value.c_str(); + *langSize = itemNode->qualifiers[0]->value.size(); + *itemValue = itemNode->value.c_str(); + *valueSize = itemNode->value.size(); + *options = itemNode->options; + + return true; + +} // GetLocalizedText + + +// ------------------------------------------------------------------------------------------------- +// SetLocalizedText +// ---------------- + +void +XMPMeta::SetLocalizedText ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr _genericLang, + XMP_StringPtr _specificLang, + XMP_StringPtr itemValue, + XMP_OptionBits options ) +{ + IgnoreParam(options); + + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. + + XMP_VarString zGenericLang ( _genericLang ); + XMP_VarString zSpecificLang ( _specificLang ); + NormalizeLangValue ( &zGenericLang ); + NormalizeLangValue ( &zSpecificLang ); + + XMP_StringPtr genericLang = zGenericLang.c_str(); + XMP_StringPtr specificLang = zSpecificLang.c_str(); + + XMP_ExpandedXPath arrayPath; + ExpandXPath ( schemaNS, arrayName, &arrayPath ); + + // Find the array node and set the options if it was just created. + XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_CreateNodes, + (kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate) ); + if ( arrayNode == 0 ) XMP_Throw ( "Failed to find or create array node", kXMPErr_BadXPath ); + if ( ! XMP_ArrayIsAltText(arrayNode->options) ) { + if ( arrayNode->children.empty() && XMP_ArrayIsAlternate(arrayNode->options) ) { + arrayNode->options |= kXMP_PropArrayIsAltText; + } else { + XMP_Throw ( "Localized text array is not alt-text", kXMPErr_BadXPath ); + } + } + + // Make sure the x-default item, if any, is first. + + size_t itemNum, itemLim; + XMP_Node * xdItem = 0; + bool haveXDefault = false; + + for ( itemNum = 0, itemLim = arrayNode->children.size(); itemNum < itemLim; ++itemNum ) { + XMP_Node * currItem = arrayNode->children[itemNum]; + XMP_Assert ( XMP_PropHasLang(currItem->options) ); + if ( currItem->qualifiers.empty() || (currItem->qualifiers[0]->name != "xml:lang") ) { + XMP_Throw ( "Language qualifier must be first", kXMPErr_BadXPath ); + } + if ( currItem->qualifiers[0]->value == "x-default" ) { + xdItem = currItem; + haveXDefault = true; + break; + } + } + + if ( haveXDefault && (itemNum != 0) ) { + XMP_Assert ( arrayNode->children[itemNum]->qualifiers[0]->value == "x-default" ); + XMP_Node * temp = arrayNode->children[0]; + arrayNode->children[0] = arrayNode->children[itemNum]; + arrayNode->children[itemNum] = temp; + } + + // Find the appropriate item. ChooseLocalizedText will make sure the array is a language alternative. + + const XMP_Node * cItemNode; // ! ChooseLocalizedText returns a pointer to a const node. + XMP_CLTMatch match = ChooseLocalizedText ( arrayNode, genericLang, specificLang, &cItemNode ); + XMP_Node * itemNode = const_cast<XMP_Node*> ( cItemNode ); + + const bool specificXDefault = XMP_LitMatch ( specificLang, "x-default" ); + + switch ( match ) { + + case kXMP_CLT_NoValues : + + // Create the array items for the specificLang and x-default, with x-default first. + AppendLangItem ( arrayNode, "x-default", itemValue ); + haveXDefault = true; + if ( ! specificXDefault ) AppendLangItem ( arrayNode, specificLang, itemValue ); + break; + + case kXMP_CLT_SpecificMatch : + + if ( ! specificXDefault ) { + // Update the specific item, update x-default if it matches the old value. + if ( xdItem != NULL && haveXDefault && (xdItem != itemNode) && (xdItem->value == itemNode->value) ) { + SetNodeValue ( xdItem, itemValue ); + } + SetNodeValue ( itemNode, itemValue ); // ! Do this after the x-default check! + } else { + // Update all items whose values match the old x-default value. + XMP_Assert ( xdItem != NULL && haveXDefault && (xdItem == itemNode) ); + for ( itemNum = 0, itemLim = arrayNode->children.size(); itemNum < itemLim; ++itemNum ) { + XMP_Node * currItem = arrayNode->children[itemNum]; + if ( (currItem == xdItem) || (currItem->value != xdItem->value) ) continue; + SetNodeValue ( currItem, itemValue ); + } + SetNodeValue ( xdItem, itemValue ); // And finally do the x-default item. + } + break; + + case kXMP_CLT_SingleGeneric : + + // Update the generic item, update x-default if it matches the old value. + if ( xdItem != NULL && haveXDefault && (xdItem != itemNode) && (xdItem->value == itemNode->value) ) { + SetNodeValue ( xdItem, itemValue ); + } + SetNodeValue ( itemNode, itemValue ); // ! Do this after the x-default check! + break; + + case kXMP_CLT_MultipleGeneric : + + // Create the specific language, ignore x-default. + AppendLangItem ( arrayNode, specificLang, itemValue ); + if ( specificXDefault ) haveXDefault = true; + break; + + case kXMP_CLT_XDefault : + + // Create the specific language, update x-default if it was the only item. + if ( arrayNode->children.size() == 1 ) SetNodeValue ( xdItem, itemValue ); + AppendLangItem ( arrayNode, specificLang, itemValue ); + break; + + case kXMP_CLT_FirstItem : + + // Create the specific language, don't add an x-default item. + AppendLangItem ( arrayNode, specificLang, itemValue ); + if ( specificXDefault ) haveXDefault = true; + break; + + default : + XMP_Throw ( "Unexpected result from ChooseLocalizedText", kXMPErr_InternalFailure ); + + } + + // Add an x-default at the front if needed. + if ( (! haveXDefault) && (arrayNode->children.size() == 1) ) { + AppendLangItem ( arrayNode, "x-default", itemValue ); + } + +} // SetLocalizedText + +// ------------------------------------------------------------------------------------------------- +// DeleteLocalizedText +// ------------------- + +void +XMPMeta::DeleteLocalizedText ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr _genericLang, + XMP_StringPtr _specificLang ) +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. + + XMP_VarString zGenericLang ( _genericLang ); + XMP_VarString zSpecificLang ( _specificLang ); + NormalizeLangValue ( &zGenericLang ); + NormalizeLangValue ( &zSpecificLang ); + + XMP_StringPtr genericLang = zGenericLang.c_str(); + XMP_StringPtr specificLang = zSpecificLang.c_str(); + + XMP_ExpandedXPath arrayPath; + ExpandXPath ( schemaNS, arrayName, &arrayPath ); + + // Find the LangAlt array and the selected array item. + + XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_ExistingOnly ); + if ( arrayNode == 0 ) return; + size_t arraySize = arrayNode->children.size(); + + XMP_CLTMatch match; + XMP_Node * itemNode; + + match = ChooseLocalizedText ( arrayNode, genericLang, specificLang, (const XMP_Node **) &itemNode ); + if ( match != kXMP_CLT_SpecificMatch ) return; + + size_t itemIndex = 0; + for ( ; itemIndex < arraySize; ++itemIndex ) { + if ( arrayNode->children[itemIndex] == itemNode ) break; + } + XMP_Enforce ( itemIndex < arraySize ); + + // Decide if the selected item is x-default or not, find relevant matching item. + + bool itemIsXDefault = false; + if ( ! itemNode->qualifiers.empty() ) { + XMP_Node * qualNode = itemNode->qualifiers[0]; + if ( (qualNode->name == "xml:lang") && (qualNode->value == "x-default") ) itemIsXDefault = true; + } + + if ( itemIsXDefault && (itemIndex != 0) ) { // Enforce the x-default is first policy. + XMP_Node * temp = arrayNode->children[0]; + arrayNode->children[0] = arrayNode->children[itemIndex]; + arrayNode->children[itemIndex] = temp; + itemIndex = 0; + } + + XMP_Node * assocNode = 0; + size_t assocIndex; + size_t assocIsXDefault = false; + + if ( itemIsXDefault ) { + + for ( assocIndex = 1; assocIndex < arraySize; ++assocIndex ) { + if ( arrayNode->children[assocIndex]->value == itemNode->value ) { + assocNode = arrayNode->children[assocIndex]; + break; + } + } + + } else if ( itemIndex > 0 ) { + + XMP_Node * itemZero = arrayNode->children[0]; + if ( itemZero->value == itemNode->value ) { + XMP_Node * qualNode = itemZero->qualifiers[0]; + if ( (qualNode->name == "xml:lang") && (qualNode->value == "x-default") ) { + assocNode = arrayNode->children[0]; + assocIndex = 0; + assocIsXDefault = true; + } + } + + } + + // Delete the appropriate nodes. + + XMP_NodePtrPos arrayBegin = arrayNode->children.begin(); + + if ( assocNode == 0 ) { + arrayNode->children.erase ( arrayBegin + itemIndex ); + } else if ( itemIndex < assocIndex ) { + arrayNode->children.erase ( arrayBegin + assocIndex ); + arrayNode->children.erase ( arrayBegin + itemIndex ); + } else { + arrayNode->children.erase ( arrayBegin + itemIndex ); + arrayNode->children.erase ( arrayBegin + assocIndex ); + } + + delete itemNode; + if ( assocNode != 0 ) delete assocNode; + +} // DeleteLocalizedText + +// ------------------------------------------------------------------------------------------------- +// GetProperty_Bool +// ---------------- + +bool +XMPMeta::GetProperty_Bool ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + bool * propValue, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_StringPtr valueStr; + XMP_StringLen valueLen; + + bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); + if ( found ) { + if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); + *propValue = XMPUtils::ConvertToBool ( valueStr ); + } + return found; + +} // GetProperty_Bool + + +// ------------------------------------------------------------------------------------------------- +// GetProperty_Int +// --------------- + +bool +XMPMeta::GetProperty_Int ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int32 * propValue, + XMP_OptionBits * options ) const +{ + XMP_Int64 tempValue64 = 0; + if ( GetProperty_Int64( schemaNS, propName, &tempValue64, options ) ) { + if ( tempValue64 < (XMP_Int64) Min_XMP_Int32 || tempValue64 > (XMP_Int64) Max_XMP_Int32 ) { + // overflow condition + XMP_Throw ( "Overflow condition", kXMPErr_BadValue ); + } else { + *propValue = (XMP_Int32) tempValue64; + return true; + } + } + return false; + +} // GetProperty_Int + + +// ------------------------------------------------------------------------------------------------- +// GetProperty_Int64 +// ----------------- + +bool +XMPMeta::GetProperty_Int64 ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int64 * propValue, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_StringPtr valueStr; + XMP_StringLen valueLen; + + bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); + if ( found ) { + if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); + std::string propValueStr; + propValueStr.append( valueStr, valueLen ); + XMPUtils::Trim( propValueStr ); + *propValue = XMPUtils::ConvertToInt64 ( propValueStr.c_str() ); + } + return found; + +} // GetProperty_Int64 + + +// ------------------------------------------------------------------------------------------------- +// GetProperty_Float +// ----------------- + +bool +XMPMeta::GetProperty_Float ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + double * propValue, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_StringPtr valueStr; + XMP_StringLen valueLen; + + bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); + if ( found ) { + if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); + std::string propValueStr; + propValueStr.append( valueStr, valueLen ); + XMPUtils::Trim( propValueStr ); + *propValue = XMPUtils::ConvertToFloat ( propValueStr.c_str() ); + } + return found; + +} // GetProperty_Float + + +// ------------------------------------------------------------------------------------------------- +// GetProperty_Date +// ---------------- + +bool +XMPMeta::GetProperty_Date ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_DateTime * propValue, + XMP_OptionBits * options ) const +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. + + XMP_StringPtr valueStr; + XMP_StringLen valueLen; + + bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); + if ( found ) { + if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); + XMPUtils::ConvertToDate ( valueStr, propValue ); + } + return found; + +} // GetProperty_Date + + +// ------------------------------------------------------------------------------------------------- +// SetProperty_Bool +// ---------------- + +void +XMPMeta::SetProperty_Bool ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + bool propValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_VarString valueStr; + XMPUtils::ConvertFromBool ( propValue, &valueStr ); + SetProperty ( schemaNS, propName, valueStr.c_str(), options ); + +} // SetProperty_Bool + + +// ------------------------------------------------------------------------------------------------- +// SetProperty_Int +// --------------- + +void +XMPMeta::SetProperty_Int ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int32 propValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_VarString valueStr; + XMPUtils::ConvertFromInt ( propValue, "", &valueStr ); + SetProperty ( schemaNS, propName, valueStr.c_str(), options ); + +} // SetProperty_Int + + +// ------------------------------------------------------------------------------------------------- +// SetProperty_Int64 +// ----------------- + +void +XMPMeta::SetProperty_Int64 ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int64 propValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_VarString valueStr; + XMPUtils::ConvertFromInt64 ( propValue, "", &valueStr ); + SetProperty ( schemaNS, propName, valueStr.c_str(), options ); + +} // SetProperty_Int64 + + +// ------------------------------------------------------------------------------------------------- +// SetProperty_Float +// ----------------- + +void +XMPMeta::SetProperty_Float ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + double propValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_VarString valueStr; + XMPUtils::ConvertFromFloat ( propValue, "", &valueStr ); + SetProperty ( schemaNS, propName, valueStr.c_str(), options ); + +} // SetProperty_Float + + +// ------------------------------------------------------------------------------------------------- +// SetProperty_Date +// ---------------- + +void +XMPMeta::SetProperty_Date ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + const XMP_DateTime & propValue, + XMP_OptionBits options ) +{ + XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. + + XMP_VarString valueStr; + XMPUtils::ConvertFromDate ( propValue, &valueStr ); + SetProperty ( schemaNS, propName, valueStr.c_str(), options ); + +} // SetProperty_Date + +// ================================================================================================= + |