static RDFTermKind GetRDFTermKind ( const XMP_VarString & name ) { RDFTermKind term = kRDFTerm_Other; // Arranged to hopefully minimize the parse time for large XMP. if ( (name.size() > 4) && (strncmp ( name.c_str(), "rdf:", 4 ) == 0) ) { if ( name == "rdf:li" ) { term = kRDFTerm_li; } else if ( name == "rdf:parseType" ) { term = kRDFTerm_parseType; } else if ( name == "rdf:Description" ) { term = kRDFTerm_Description; } else if ( name == "rdf:about" ) { term = kRDFTerm_about; } else if ( name == "rdf:resource" ) { term = kRDFTerm_resource; } else if ( name == "rdf:RDF" ) { term = kRDFTerm_RDF; } else if ( name == "rdf:ID" ) { term = kRDFTerm_ID; } else if ( name == "rdf:nodeID" ) { term = kRDFTerm_nodeID; } else if ( name == "rdf:datatype" ) { term = kRDFTerm_datatype; } else if ( name == "rdf:aboutEach" ) { term = kRDFTerm_aboutEach; } else if ( name == "rdf:aboutEachPrefix" ) { term = kRDFTerm_aboutEachPrefix; } else if ( name == "rdf:bagID" ) { term = kRDFTerm_bagID; } } return term; } // GetRDFTermKind
static void SplitNameAndValue ( const XMP_VarString & selStep, XMP_VarString * nameStr, XMP_VarString * valueStr ) { XMP_StringPtr partBegin = selStep.c_str(); XMP_StringPtr partEnd; const XMP_StringPtr valueEnd = partBegin + (selStep.size() - 2); const char quote = *valueEnd; XMP_Assert ( (*partBegin == '[') && (*(valueEnd+1) == ']') ); XMP_Assert ( (selStep.size() >= 6) && ((quote == '"') || (quote == '\'')) ); // Extract the name part. ++partBegin; // Skip the opening '['. if ( *partBegin == '?' ) ++partBegin; for ( partEnd = partBegin+1; *partEnd != '='; ++partEnd ) {}; nameStr->assign ( partBegin, (partEnd - partBegin) ); // Extract the value part, reducing doubled quotes. XMP_Assert ( *(partEnd+1) == quote ); partBegin = partEnd + 2; valueStr->erase(); valueStr->reserve ( valueEnd - partBegin ); // Maximum length, don't optimize doubled quotes. for ( partEnd = partBegin; partEnd < valueEnd; ++partEnd ) { if ( (*partEnd == quote) && (*(partEnd+1) == quote) ) { ++partEnd; valueStr->append ( partBegin, (partEnd - partBegin) ); partBegin = partEnd+1; // ! Loop will increment partEnd again. } } valueStr->append ( partBegin, (partEnd - partBegin) ); // ! The loop does not add the last part. } // SplitNameAndValue
void ExpatAdapter::ParseBuffer ( const void * buffer, size_t length, bool last /* = true */ ) { enum XML_Status status; if ( length == 0 ) { // Expat does not like empty buffers. if ( ! last ) return; buffer = kOneSpace; length = 1; } status = XML_Parse ( this->parser, (const char *)buffer, length, last ); #if BanAllEntityUsage if ( this->isAborted ) { XMP_Error error(kXMPErr_BadXML, "DOCTYPE is not allowed" ) this->NotifyClient ( kXMPErrSev_Recoverable, error ); } #endif if ( status != XML_STATUS_OK ) { XMP_StringPtr errMsg = "XML parsing failure"; #if 0 // XMP_DebugBuild // Disable for now to make test output uniform. Restore later with thread safety. // *** This is a good candidate for a callback error notification mechanism. // *** This code is not thread safe, the sExpatMessage isn't locked. But that's OK for debug usage. enum XML_Error expatErr = XML_GetErrorCode ( this->parser ); const char * expatMsg = XML_ErrorString ( expatErr ); int errLine = XML_GetCurrentLineNumber ( this->parser ); char msgBuffer[1000]; // AUDIT: Use of sizeof(msgBuffer) for snprintf length is safe. snprintf ( msgBuffer, sizeof(msgBuffer), "# Expat error %d at line %d, \"%s\"", expatErr, errLine, expatMsg ); sExpatMessage = msgBuffer; errMsg = sExpatMessage.c_str(); #if DumpXMLParseEvents if ( this->parseLog != 0 ) fprintf ( this->parseLog, "%s\n", errMsg, expatErr, errLine, expatMsg ); #endif #endif XMP_Error error(kXMPErr_BadXML, errMsg); this->NotifyClient ( kXMPErrSev_Recoverable, error ); } } // ExpatAdapter::ParseBuffer
static void CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str ) { size_t i, byteCount; XMP_Uns8 buffer [8]; UniCodePoint cpTemp; if ( uniChar <= 0x7F ) { i = 7; byteCount = 1; buffer[7] = char(uniChar); } else { // --------------------------------------------------------------------------------------- // Copy the data bits from the low order end to the high order end, include the 0x80 mask. i = 8; cpTemp = uniChar; while ( cpTemp != 0 ) { -- i; // Exit with i pointing to the last byte stored. buffer[i] = UnsByte(0x80) | (UnsByte(cpTemp) & 0x3F); cpTemp = cpTemp >> 6; } byteCount = 8 - i; // The total number of bytes needed. XMP_Assert ( (2 <= byteCount) && (byteCount <= 6) ); // ------------------------------------------------------------------------------------- // Make sure the high order byte can hold the byte count mask, compute and set the mask. size_t bitCount = 0; // The number of data bits in the first byte. for ( cpTemp = (buffer[i] & UnsByte(0x3F)); cpTemp != 0; cpTemp = cpTemp >> 1 ) bitCount += 1; if ( bitCount > (8 - (byteCount + 1)) ) byteCount += 1; i = 8 - byteCount; // First byte index and mask shift count. XMP_Assert ( (0 <= i) && (i <= 6) ); buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF); // AUDIT: Safe, i is between 0 and 6. } utf8Str.assign ( (char*)(&buffer[i]), byteCount ); } // CodePointToUTF8
bool XMP_NamespaceTable::Define ( XMP_StringPtr _uri, XMP_StringPtr _suggPrefix, XMP_StringPtr * prefixPtr, XMP_StringLen * prefixLen ) { XMP_AutoLock tableLock ( &this->lock, kXMP_WriteLock ); bool prefixMatches = false; XMP_Assert ( (_uri != 0) && (*_uri != 0) && (_suggPrefix != 0) && (*_suggPrefix != 0) ); XMP_VarString uri ( _uri ); XMP_VarString suggPrefix ( _suggPrefix ); if ( suggPrefix[suggPrefix.size()-1] != ':' ) suggPrefix += ':'; VerifySimpleXMLName ( _suggPrefix, _suggPrefix+suggPrefix.size()-1 ); // Exclude the colon. XMP_StringMapPos uriPos = this->uriToPrefixMap.find ( uri ); if ( uriPos == this->uriToPrefixMap.end() ) { // The URI is not yet registered, make sure we use a unique prefix. XMP_VarString uniqPrefix ( suggPrefix ); int suffix = 0; char buffer [32]; // AUDIT: Plenty of room for the "_%d_" suffix. while ( true ) { if ( this->prefixToURIMap.find ( uniqPrefix ) == this->prefixToURIMap.end() ) break; ++suffix; snprintf ( buffer, sizeof(buffer), "_%d_:", suffix ); // AUDIT: Using sizeof for snprintf length is safe. uniqPrefix = suggPrefix; uniqPrefix.erase ( uniqPrefix.size()-1 ); // ! Remove the trailing ':'. uniqPrefix += buffer; } // Add the new namespace to both maps. XMP_StringPair newNS ( uri, uniqPrefix ); uriPos = this->uriToPrefixMap.insert ( this->uriToPrefixMap.end(), newNS ); newNS.first.swap ( newNS.second ); (void) this->prefixToURIMap.insert ( this->prefixToURIMap.end(), newNS ); } // Return the actual prefix and see if it matches the suggested prefix. if ( prefixPtr != 0 ) *prefixPtr = uriPos->second.c_str(); if ( prefixLen != 0 ) *prefixLen = (XMP_StringLen)uriPos->second.size(); prefixMatches = ( uriPos->second == suggPrefix ); return prefixMatches; } // XMP_NamespaceTable::Define
void DumpClearString ( const XMP_VarString & value, XMP_TextOutputProc outProc, void * refCon ) { char buffer [20]; bool prevNormal; XMP_Status status = 0; XMP_StringPtr spanStart, spanEnd; XMP_StringPtr valueEnd = &value[0] + value.size(); spanStart = &value[0]; while ( spanStart < valueEnd ) { // Output the next span of regular characters. for ( spanEnd = spanStart; spanEnd < valueEnd; ++spanEnd ) { //if ( *spanEnd > 0x7F ) break; if ( (*spanEnd < 0x20) && (*spanEnd != kTab) && (*spanEnd != kLF) ) break; } if ( spanStart != spanEnd ) status = (*outProc) ( refCon, spanStart, (XMP_StringLen)(spanEnd-spanStart) ); if ( status != 0 ) break; spanStart = spanEnd; // Output the next span of irregular characters. prevNormal = true; for ( spanEnd = spanStart; spanEnd < valueEnd; ++spanEnd ) { if ( ((0x20 <= *spanEnd) /*&& (*spanEnd <= 0x7F)*/) || (*spanEnd == kTab) || (*spanEnd == kLF) ) break; char space = ' '; if ( prevNormal ) space = '<'; status = (*outProc) ( refCon, &space, 1 ); if ( status != 0 ) break; OutProcHexByte ( *spanEnd ); prevNormal = false; } if ( ! prevNormal ) { status = (*outProc) ( refCon, ">", 1 ); if ( status != 0 ) return; } spanStart = spanEnd; } } // DumpClearString
void IOUtils::GetMatchingChildren ( XMP_StringVector & matchingChildList, const XMP_VarString & rootPath, const XMP_StringVector & regExStringVec, XMP_Bool includeFolders, XMP_Bool includeFiles, XMP_Bool prefixRootPath ) { try { XMP_StringVector listOfAllResources; ListAllChildren (rootPath.c_str(), listOfAllResources, includeFolders, includeFiles, true); XMP_Bool matchRequired = !regExStringVec.empty(); if ( matchRequired ) { size_t childCount = listOfAllResources.size(); for ( size_t index = 0; index < childCount; index++ ) { XMP_Bool match = false; size_t regExpCount = regExStringVec.size(); for ( size_t index2 = 0; index2 < regExpCount; index2++ ) { XMP_RegExp regexObj ( regExStringVec[index2].c_str() ); match = regexObj.Match ( listOfAllResources[index].c_str() ); if ( match ) { if ( prefixRootPath ) { std::string fullPath = rootPath; if (fullPath[fullPath.length() - 1] != kDirChar ) fullPath += kDirChar; fullPath += listOfAllResources[index]; matchingChildList.push_back ( fullPath ); } else matchingChildList.push_back ( listOfAllResources[index] ); break; } } } } } catch ( XMP_Error & ) { // do nothing } } // GetMatchingChildren
static void SerializeAsRDF ( const XMPMeta & xmpObj, XMP_VarString & headStr, // Everything up to the padding. XMP_VarString & tailStr, // Everything after the padding. XMP_OptionBits options, XMP_StringPtr newline, XMP_StringPtr indentStr, XMP_Index baseIndent ) { const size_t treeNameLen = xmpObj.tree.name.size(); const size_t indentLen = strlen ( indentStr ); // First estimate the worst case space and reserve room in the output string. This optimization // avoids reallocating and copying the output as it grows. The initial count does not look at // the values of properties, so it does not account for character entities, e.g. 
 for newline. // Since there can be a lot of these in things like the base 64 encoding of a large thumbnail, // inflate the count by 1/4 (easy to do) to accommodate. // *** Need to include estimate for alias comments. size_t outputLen = 2 * (strlen(kPacketHeader) + strlen(kRDF_XMPMetaStart) + strlen(kRDF_RDFStart) + 3*baseIndent*indentLen); for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { const XMP_Node * currSchema = xmpObj.tree.children[schemaNum]; outputLen += 2*(baseIndent+2)*indentLen + strlen(kRDF_SchemaStart) + treeNameLen + strlen(kRDF_SchemaEnd) + 2; outputLen += EstimateRDFSize ( currSchema, baseIndent+2, indentLen ); } outputLen += (outputLen >> 2); // Inflate by 1/4, an empirical fudge factor. // Now generate the RDF into the head string as UTF-8. XMP_Index level; std::string rdfstring; headStr.erase(); rdfstring.reserve ( outputLen ); // Write the rdf:RDF start tag. rdfstring += kRDF_RDFStart; rdfstring += newline; // Write all of the properties. if ( options & kXMP_UseCompactFormat ) { SerializeCompactRDFSchemas ( xmpObj.tree, rdfstring, newline, indentStr, baseIndent ); } else { bool useCanonicalRDF = XMP_OptionIsSet ( options, kXMP_UseCanonicalFormat ); SerializeCanonicalRDFSchemas ( xmpObj.tree, rdfstring, newline, indentStr, baseIndent, useCanonicalRDF ); } // Write the rdf:RDF end tag. for ( level = baseIndent+1; level > 0; --level ) rdfstring += indentStr; rdfstring += kRDF_RDFEnd; // Write the packet header PI. if ( ! (options & kXMP_OmitPacketWrapper) ) { for ( level = baseIndent; level > 0; --level ) headStr += indentStr; headStr += kPacketHeader; headStr += newline; } // Write the xmpmeta element's start tag. if ( ! (options & kXMP_OmitXMPMetaElement) ) { for ( level = baseIndent; level > 0; --level ) headStr += indentStr; headStr += kRDF_XMPMetaStart; headStr += kXMPCore_VersionMessage "\""; std::string digestStr; unsigned char digestBin [16]; if (options & kXMP_IncludeRDFHash) { std::string hashrdf; MD5_CTX context; MD5Init ( &context ); MD5Update ( &context, (XMP_Uns8*)rdfstring.c_str(), (unsigned int)rdfstring.size() ); MD5Final ( digestBin, &context ); char buffer [40]; for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { XMP_Uns8 byte = digestBin[in]; buffer[out] = kHexDigits [ byte >> 4 ]; buffer[out+1] = kHexDigits [ byte & 0xF ]; } buffer[32] = 0; digestStr.append ( buffer ); headStr += " rdfhash=\""; headStr += digestStr + "\""; headStr += " merged=\"0\""; } headStr += ">"; headStr += newline; }
static void SerializeCompactRDFSchemas ( const XMP_Node & xmpTree, XMP_VarString & outputStr, XMP_StringPtr newline, XMP_StringPtr indentStr, XMP_Index baseIndent ) { XMP_Index level; size_t schema, schemaLim; // Begin the rdf:Description start tag. for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; outputStr += kRDF_SchemaStart; outputStr += '"'; outputStr += xmpTree.name; outputStr += '"'; // Write all necessary xmlns attributes. size_t totalLen = 8; // Start at 8 for "xml:rdf:". XMP_cStringMapPos currPos = sNamespacePrefixToURIMap->begin(); XMP_cStringMapPos endPos = sNamespacePrefixToURIMap->end(); for ( ; currPos != endPos; ++currPos ) totalLen += currPos->first.size(); XMP_VarString usedNS; usedNS.reserve ( totalLen ); usedNS = "xml:rdf:"; for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { const XMP_Node * currSchema = xmpTree.children[schema]; DeclareUsedNamespaces ( currSchema, usedNS, outputStr, newline, indentStr, baseIndent+4 ); } // Write the top level "attrProps" and close the rdf:Description start tag. bool allAreAttrs = true; for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { const XMP_Node * currSchema = xmpTree.children[schema]; allAreAttrs &= SerializeCompactRDFAttrProps ( currSchema, outputStr, newline, indentStr, baseIndent+3 ); } if ( ! allAreAttrs ) { outputStr += ">"; outputStr += newline; } else { outputStr += "/>"; outputStr += newline; return; // ! Done if all properties in all schema are written as attributes. } // Write the remaining properties for each schema. for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { const XMP_Node * currSchema = xmpTree.children[schema]; SerializeCompactRDFElemProps ( currSchema, outputStr, newline, indentStr, baseIndent+3 ); } // Write the rdf:Description end tag. // *** Elide the end tag if everything (all props in all schema) is an attr. for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; outputStr += kRDF_SchemaEnd; outputStr += newline; } // SerializeCompactRDFSchemas
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
static void SerializeAsRDF ( const XMPMeta & xmpObj, XMP_VarString & headStr, // Everything up to the padding. XMP_VarString & tailStr, // Everything after the padding. XMP_OptionBits options, XMP_StringPtr newline, XMP_StringPtr indentStr, XMP_Index baseIndent ) { const size_t treeNameLen = xmpObj.tree.name.size(); const size_t indentLen = strlen ( indentStr ); // First estimate the worst case space and reserve room in the output string. This optimization // avoids reallocating and copying the output as it grows. The initial count does not look at // the values of properties, so it does not account for character entities, e.g. 
 for newline. // Since there can be a lot of these in things like the base 64 encoding of a large thumbnail, // inflate the count by 1/4 (easy to do) to accommodate. // *** Need to include estimate for alias comments. size_t outputLen = 2 * (strlen(kPacketHeader) + strlen(kRDF_XMPMetaStart) + strlen(kRDF_RDFStart) + 3*baseIndent*indentLen); for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { const XMP_Node * currSchema = xmpObj.tree.children[schemaNum]; outputLen += 2*(baseIndent+2)*indentLen + strlen(kRDF_SchemaStart) + treeNameLen + strlen(kRDF_SchemaEnd) + 2; outputLen += EstimateRDFSize ( currSchema, baseIndent+2, indentLen ); } outputLen += (outputLen >> 2); // Inflate by 1/4, an empirical fudge factor. // Now generate the RDF into the head string as UTF-8. XMP_Index level; headStr.erase(); headStr.reserve ( outputLen ); // Write the packet header PI. if ( ! (options & kXMP_OmitPacketWrapper) ) { for ( level = baseIndent; level > 0; --level ) headStr += indentStr; headStr += kPacketHeader; headStr += newline; } // Write the xmpmeta element's start tag. if ( ! (options & kXMP_OmitXMPMetaElement) ) { for ( level = baseIndent; level > 0; --level ) headStr += indentStr; headStr += kRDF_XMPMetaStart; headStr += kXMPCore_VersionMessage "\">"; headStr += newline; } // Write the rdf:RDF start tag. for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr; headStr += kRDF_RDFStart; headStr += newline; // Write all of the properties. if ( options & kXMP_UseCompactFormat ) { SerializeCompactRDFSchemas ( xmpObj.tree, headStr, newline, indentStr, baseIndent ); } else { if ( xmpObj.tree.children.size() > 0 ) { for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { const XMP_Node * currSchema = xmpObj.tree.children[schemaNum]; SerializePrettyRDFSchema ( xmpObj.tree.name, currSchema, headStr, options, newline, indentStr, baseIndent ); } } else { for ( XMP_Index level = baseIndent+2; level > 0; --level ) headStr += indentStr; headStr += kRDF_SchemaStart; // Special case an empty XMP object. headStr += '"'; headStr += xmpObj.tree.name; headStr += "\"/>"; headStr += newline; } } // Write the rdf:RDF end tag. for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr; headStr += kRDF_RDFEnd; headStr += newline; // Write the xmpmeta end tag. if ( ! (options & kXMP_OmitXMPMetaElement) ) { for ( level = baseIndent; level > 0; --level ) headStr += indentStr; headStr += kRDF_XMPMetaEnd; headStr += newline; } // Write the packet trailer PI into the tail string as UTF-8. tailStr.erase(); if ( ! (options & kXMP_OmitPacketWrapper) ) { tailStr.reserve ( strlen(kPacketTrailer) + (strlen(indentStr) * baseIndent) ); for ( level = baseIndent; level > 0; --level ) tailStr += indentStr; tailStr += kPacketTrailer; if ( options & kXMP_ReadOnlyPacket ) tailStr[tailStr.size()-4] = 'r'; } // ! This assert is just a performance check, to see if the reserve was enough. // *** XMP_Assert ( headStr.size() <= outputLen ); // *** Don't use an assert. Think of some way to track this without risk of aborting the client. } // SerializeAsRDF
static void SerializePrettyRDFSchema ( const XMP_VarString & treeName, const XMP_Node * schemaNode, XMP_VarString & outputStr, XMP_OptionBits options, XMP_StringPtr newline, XMP_StringPtr indentStr, XMP_Index baseIndent ) { XMP_Assert ( schemaNode->options & kXMP_SchemaNode ); XMP_Assert ( schemaNode->qualifiers.empty() ); // Write the rdf:Description start tag with the namespace declarations. XMP_Index level; for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; outputStr += kRDF_SchemaStart; outputStr += '"'; outputStr += treeName; outputStr += '"'; size_t totalLen = 8; // Start at 8 for "xml:rdf:". XMP_cStringMapPos currPos = sNamespacePrefixToURIMap->begin(); XMP_cStringMapPos endPos = sNamespacePrefixToURIMap->end(); for ( ; currPos != endPos; ++currPos ) totalLen += currPos->first.size(); XMP_VarString usedNS; usedNS.reserve ( totalLen ); usedNS = "xml:rdf:"; DeclareUsedNamespaces ( schemaNode, usedNS, outputStr, newline, indentStr, baseIndent+4 ); outputStr += ">"; outputStr += newline; // Write alias comments, if wanted. if ( options & kXMP_WriteAliasComments ) { // *** Hoist into a routine, used for Plain XMP also. #if 0 // *** Buggy, disable for now. XMP_cAliasMapPos aliasPos = sRegisteredAliasMap->begin(); XMP_cAliasMapPos aliasEnd = sRegisteredAliasMap->end(); for ( ; aliasPos != aliasEnd; ++aliasPos ) { size_t nsPos = aliasPos->first.find ( schemaNode->value ); if ( nsPos == XMP_VarString::npos ) continue; XMP_Assert ( nsPos == 0 ); for ( level = baseIndent+3; level > 0; --level ) outputStr += indentStr; outputStr += "<!-- "; outputStr += aliasPos->first; outputStr += " is aliased to "; for ( size_t step = 1, stepLim = aliasPos->second.size(); step != stepLim; ++step ) { outputStr += aliasPos->second[step].step; } outputStr += " -->"; outputStr += newline; } #endif } // Write each of the schema's actual properties. for ( size_t propNum = 0, propLim = schemaNode->children.size(); propNum < propLim; ++propNum ) { const XMP_Node * currProp = schemaNode->children[propNum]; SerializePrettyRDFProperty ( currProp, outputStr, newline, indentStr, baseIndent+3 ); } // Write the rdf:Description end tag. for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; outputStr += kRDF_SchemaEnd; outputStr += newline; } // SerializePrettyRDFSchema
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; 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; } } } // 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
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
namespace DngXmpSdk { #if XMP_WinBuild #pragma warning ( disable : 4996 ) // '...' was declared deprecated #endif // *** Set memory handlers. #ifndef DumpXMLParseEvents #define DumpXMLParseEvents 0 #endif #define FullNameSeparator '@' // ================================================================================================= static void StartNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix, XMP_StringPtr uri ); static void EndNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix ); static void StartElementHandler ( void * userData, XMP_StringPtr name, XMP_StringPtr* attrs ); static void EndElementHandler ( void * userData, XMP_StringPtr name ); static void CharacterDataHandler ( void * userData, XMP_StringPtr cData, int len ); static void StartCdataSectionHandler ( void * userData ); static void EndCdataSectionHandler ( void * userData ); static void ProcessingInstructionHandler ( void * userData, XMP_StringPtr target, XMP_StringPtr data ); static void CommentHandler ( void * userData, XMP_StringPtr comment ); #if BanAllEntityUsage // For now we do this by banning DOCTYPE entirely. This is easy and consistent with what is // available in recent Java XML parsers. Another, somewhat less drastic, approach would be to // ban all entity declarations. We can't allow declarations and ban references, Expat does not // call the SkippedEntityHandler for references in attribute values. // ! Standard entities (&, <, >, ", ', and numeric character references) are // ! not banned. Expat handles them transparently no matter what. static void StartDoctypeDeclHandler ( void * userData, XMP_StringPtr doctypeName, XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset ); #endif // ================================================================================================= extern "C" ExpatAdapter * XMP_NewExpatAdapter() { return new ExpatAdapter; } // XMP_NewExpatAdapter // ================================================================================================= ExpatAdapter::ExpatAdapter() : parser(0) { #if XMP_DebugBuild this->elemNesting = 0; #if DumpXMLParseEvents if ( this->parseLog == 0 ) this->parseLog = stdout; #endif #endif this->parser = XML_ParserCreateNS ( 0, FullNameSeparator ); if ( this->parser == 0 ) XMP_Throw ( "Failure creating Expat parser", kXMPErr_ExternalFailure ); XML_SetUserData ( this->parser, this ); XML_SetNamespaceDeclHandler ( this->parser, StartNamespaceDeclHandler, EndNamespaceDeclHandler ); XML_SetElementHandler ( this->parser, StartElementHandler, EndElementHandler ); XML_SetCharacterDataHandler ( this->parser, CharacterDataHandler ); XML_SetCdataSectionHandler ( this->parser, StartCdataSectionHandler, EndCdataSectionHandler ); XML_SetProcessingInstructionHandler ( this->parser, ProcessingInstructionHandler ); XML_SetCommentHandler ( this->parser, CommentHandler ); #if BanAllEntityUsage XML_SetStartDoctypeDeclHandler ( this->parser, StartDoctypeDeclHandler ); isAborted = false; #endif this->parseStack.push_back ( &this->tree ); // Push the XML root node. } // ExpatAdapter::ExpatAdapter // ================================================================================================= ExpatAdapter::~ExpatAdapter() { if ( this->parser != 0 ) XML_ParserFree ( this->parser ); this->parser = 0; } // ExpatAdapter::~ExpatAdapter // ================================================================================================= #if XMP_DebugBuild static XMP_VarString sExpatMessage; #endif static const char * kOneSpace = " "; void ExpatAdapter::ParseBuffer ( const void * buffer, size_t length, bool last /* = true */ ) { enum XML_Status status; if ( length == 0 ) { // Expat does not like empty buffers. if ( ! last ) return; buffer = kOneSpace; length = 1; } status = XML_Parse ( this->parser, (const char *)buffer, length, last ); #if BanAllEntityUsage if ( this->isAborted ) XMP_Throw ( "DOCTYPE is not allowed", kXMPErr_BadXML ); #endif if ( status != XML_STATUS_OK ) { XMP_StringPtr errMsg = "XML parsing failure"; #if 0 // XMP_DebugBuild // Disable for now to make test output uniform. Restore later with thread safety. // *** This is a good candidate for a callback error notification mechanism. // *** This code is not thread safe, the sExpatMessage isn't locked. But that's OK for debug usage. enum XML_Error expatErr = XML_GetErrorCode ( this->parser ); const char * expatMsg = XML_ErrorString ( expatErr ); int errLine = XML_GetCurrentLineNumber ( this->parser ); char msgBuffer[1000]; // AUDIT: Use of sizeof(msgBuffer) for snprintf length is safe. snprintf ( msgBuffer, sizeof(msgBuffer), "# Expat error %d at line %d, \"%s\"", expatErr, errLine, expatMsg ); sExpatMessage = msgBuffer; errMsg = sExpatMessage.c_str(); #if DumpXMLParseEvents if ( this->parseLog != 0 ) fprintf ( this->parseLog, "%s\n", errMsg, expatErr, errLine, expatMsg ); #endif #endif XMP_Throw ( errMsg, kXMPErr_BadXML ); } } // ExpatAdapter::ParseBuffer // ================================================================================================= // ================================================================================================= #if XMP_DebugBuild & DumpXMLParseEvents static inline void PrintIndent ( FILE * file, size_t count ) { for ( ; count > 0; --count ) fprintf ( file, " " ); } #endif // ================================================================================================= static void SetQualName ( XMP_StringPtr fullName, XML_Node * node ) { // Expat delivers the full name as a catenation of namespace URI, separator, and local name. // As a compatibility hack, an "about" or "ID" attribute of an rdf:Description element is // changed to "rdf:about" or rdf:ID. Easier done here than in the RDF recognizer. // As a bug fix hack, change a URI of "http://purl.org/dc/1.1/" to ""http://purl.org/dc/elements/1.1/. // Early versions of Flash that put XMP in SWF used a bad URI for the dc: namespace. // ! This code presumes the RDF namespace prefix is "rdf". size_t sepPos = strlen(fullName); for ( --sepPos; sepPos > 0; --sepPos ) { if ( fullName[sepPos] == FullNameSeparator ) break; } if ( fullName[sepPos] == FullNameSeparator ) { XMP_StringPtr prefix; XMP_StringLen prefixLen; XMP_StringPtr localPart = fullName + sepPos + 1; node->ns.assign ( fullName, sepPos ); if ( node->ns == "http://purl.org/dc/1.1/" ) node->ns = "http://purl.org/dc/elements/1.1/"; bool found = XMPMeta::GetNamespacePrefix ( node->ns.c_str(), &prefix, &prefixLen ); if ( ! found ) XMP_Throw ( "Unknown URI in Expat full name", kXMPErr_ExternalFailure ); node->nsPrefixLen = prefixLen; // ! Includes the ':'. node->name = prefix; node->name += localPart; } else { node->name = fullName; // The name is not in a namespace. if ( node->parent->name == "rdf:Description" ) { if ( node->name == "about" ) { node->ns = kXMP_NS_RDF; node->name = "rdf:about"; node->nsPrefixLen = 4; // ! Include the ':'. } else if ( node->name == "ID" ) { node->ns = kXMP_NS_RDF; node->name = "rdf:ID"; node->nsPrefixLen = 4; // ! Include the ':'. } } } } // SetQualName // ================================================================================================= static void StartNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix, XMP_StringPtr uri ) { IgnoreParam(userData); // As a bug fix hack, change a URI of "http://purl.org/dc/1.1/" to ""http://purl.org/dc/elements/1.1/. // Early versions of Flash that put XMP in SWF used a bad URI for the dc: namespace. #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. ExpatAdapter * thiz = (ExpatAdapter*)userData; #endif if ( prefix == 0 ) prefix = "_dflt_"; // Have default namespace. if ( uri == 0 ) return; // Ignore, have xmlns:pre="", no URI to register. #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "StartNamespace: %s - \"%s\"\n", prefix, uri ); } #endif if ( XMP_LitMatch ( uri, "http://purl.org/dc/1.1/" ) ) uri = "http://purl.org/dc/elements/1.1/"; (void) XMPMeta::RegisterNamespace ( uri, prefix, &voidStringPtr, &voidStringLen ); } // StartNamespaceDeclHandler // ================================================================================================= static void EndNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix ) { IgnoreParam(userData); #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. ExpatAdapter * thiz = (ExpatAdapter*)userData; #endif if ( prefix == 0 ) prefix = "_dflt_"; // Have default namespace. #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "EndNamespace: %s\n", prefix ); } #endif // ! Nothing to do, Expat has done all of the XML processing. } // EndNamespaceDeclHandler // ================================================================================================= static void StartElementHandler ( void * userData, XMP_StringPtr name, XMP_StringPtr* attrs ) { XMP_Assert ( attrs != 0 ); ExpatAdapter * thiz = (ExpatAdapter*)userData; size_t attrCount = 0; for ( XMP_StringPtr* a = attrs; *a != 0; ++a ) ++attrCount; if ( (attrCount & 1) != 0 ) XMP_Throw ( "Expat attribute info has odd length", kXMPErr_ExternalFailure ); attrCount = attrCount/2; // They are name/value pairs. #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "StartElement: %s, %d attrs", name, attrCount ); for ( XMP_StringPtr* attr = attrs; *attr != 0; attr += 2 ) { XMP_StringPtr attrName = *attr; XMP_StringPtr attrValue = *(attr+1); fprintf ( thiz->parseLog, ", %s = \"%s\"", attrName, attrValue ); } fprintf ( thiz->parseLog, "\n" ); } #endif XML_Node * parentNode = thiz->parseStack.back(); XML_Node * elemNode = new XML_Node ( parentNode, "", kElemNode ); SetQualName ( name, elemNode ); for ( XMP_StringPtr* attr = attrs; *attr != 0; attr += 2 ) { XMP_StringPtr attrName = *attr; XMP_StringPtr attrValue = *(attr+1); XML_Node * attrNode = new XML_Node ( elemNode, "", kAttrNode ); SetQualName ( attrName, attrNode ); attrNode->value = attrValue; if ( attrNode->name == "xml:lang" ) NormalizeLangValue ( &attrNode->value ); elemNode->attrs.push_back ( attrNode ); } parentNode->content.push_back ( elemNode ); thiz->parseStack.push_back ( elemNode ); if ( elemNode->name == "rdf:RDF" ) { thiz->rootNode = elemNode; ++thiz->rootCount; } #if XMP_DebugBuild ++thiz->elemNesting; #endif } // StartElementHandler // ================================================================================================= static void EndElementHandler ( void * userData, XMP_StringPtr name ) { IgnoreParam(name); ExpatAdapter * thiz = (ExpatAdapter*)userData; #if XMP_DebugBuild --thiz->elemNesting; #endif (void) thiz->parseStack.pop_back(); #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "EndElement: %s\n", name ); } #endif } // EndElementHandler // ================================================================================================= static void CharacterDataHandler ( void * userData, XMP_StringPtr cData, int len ) { ExpatAdapter * thiz = (ExpatAdapter*)userData; if ( (cData == 0) || (len == 0) ) { cData = ""; len = 0; } #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "CharContent: \"" ); for ( int i = 0; i < len; ++i ) fprintf ( thiz->parseLog, "%c", cData[i] ); fprintf ( thiz->parseLog, "\"\n" ); } #endif XML_Node * parentNode = thiz->parseStack.back(); XML_Node * cDataNode = new XML_Node ( parentNode, "", kCDataNode ); cDataNode->value.assign ( cData, len ); parentNode->content.push_back ( cDataNode ); } // CharacterDataHandler // ================================================================================================= static void StartCdataSectionHandler ( void * userData ) { IgnoreParam(userData); #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. ExpatAdapter * thiz = (ExpatAdapter*)userData; #endif #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "StartCDATA\n" ); } #endif // *** Since markup isn't recognized inside CDATA, this affects XMP's double escaping. } // StartCdataSectionHandler // ================================================================================================= static void EndCdataSectionHandler ( void * userData ) { IgnoreParam(userData); #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. ExpatAdapter * thiz = (ExpatAdapter*)userData; #endif #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "EndCDATA\n" ); } #endif } // EndCdataSectionHandler // ================================================================================================= static void ProcessingInstructionHandler ( void * userData, XMP_StringPtr target, XMP_StringPtr data ) { XMP_Assert ( target != 0 ); ExpatAdapter * thiz = (ExpatAdapter*)userData; if ( ! XMP_LitMatch ( target, "xpacket" ) ) return; // Ignore all PIs except the XMP packet wrapper. if ( data == 0 ) data = ""; #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "PI: %s - \"%s\"\n", target, data ); } #endif XML_Node * parentNode = thiz->parseStack.back(); XML_Node * piNode = new XML_Node ( parentNode, target, kPINode ); piNode->value.assign ( data ); parentNode->content.push_back ( piNode ); } // ProcessingInstructionHandler // ================================================================================================= static void CommentHandler ( void * userData, XMP_StringPtr comment ) { IgnoreParam(userData); #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. ExpatAdapter * thiz = (ExpatAdapter*)userData; #endif if ( comment == 0 ) comment = ""; #if XMP_DebugBuild & DumpXMLParseEvents if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "Comment: \"%s\"\n", comment ); } #endif // ! Comments are ignored. } // CommentHandler // ================================================================================================= #if BanAllEntityUsage static void StartDoctypeDeclHandler ( void * userData, XMP_StringPtr doctypeName, XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset ) { IgnoreParam(userData); ExpatAdapter * thiz = (ExpatAdapter*)userData; #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. if ( thiz->parseLog != 0 ) { PrintIndent ( thiz->parseLog, thiz->elemNesting ); fprintf ( thiz->parseLog, "DocType: \"%s\"\n", doctypeName ); } #endif thiz->isAborted = true; // ! Can't throw an exception across the plain C Expat frames. (void) XML_StopParser ( thiz->parser, XML_FALSE /* not resumable */ ); } // StartDoctypeDeclHandler #endif } // namespace DngXmpSdk
void ExpandXPath ( XMP_StringPtr schemaNS, XMP_StringPtr propPath, XMP_ExpandedXPath * expandedXPath ) { XMP_Assert ( (schemaNS != 0) && (propPath != 0) && (*propPath != 0) && (expandedXPath != 0) ); XMP_StringPtr stepBegin, stepEnd; XMP_StringPtr qualName, nameEnd; XMP_VarString currStep; size_t resCount = 2; // Guess at the number of steps. At least 2, plus 1 for each '/' or '['. for ( stepEnd = propPath; *stepEnd != 0; ++stepEnd ) { if ( (*stepEnd == '/') || (*stepEnd == '[') ) ++resCount; } expandedXPath->clear(); expandedXPath->reserve ( resCount ); // ------------------------------------------------------------------------------------------- // Pull out the first component and do some special processing on it: add the schema namespace // prefix and see if it is an alias. The start must be a qualName. stepBegin = propPath; stepEnd = stepBegin; while ( (*stepEnd != 0) && (*stepEnd != '/') && (*stepEnd != '[') && (*stepEnd != '*') ) ++stepEnd; if ( stepEnd == stepBegin ) XMP_Throw ( "Empty initial XPath step", kXMPErr_BadXPath ); currStep.assign ( stepBegin, (stepEnd - stepBegin) ); VerifyXPathRoot ( schemaNS, currStep.c_str(), expandedXPath ); XMP_OptionBits stepFlags = kXMP_StructFieldStep; if ( sRegisteredAliasMap->find ( (*expandedXPath)[kRootPropStep].step ) != sRegisteredAliasMap->end() ) { stepFlags |= kXMP_StepIsAlias; } (*expandedXPath)[kRootPropStep].options |= stepFlags; // ----------------------------------------------------- // Now continue to process the rest of the XPath string. while ( *stepEnd != 0 ) { stepBegin = stepEnd; if ( *stepBegin == '/' ) ++stepBegin; if ( *stepBegin == '*' ) { ++stepBegin; if ( *stepBegin != '[' ) XMP_Throw ( "Missing '[' after '*'", kXMPErr_BadXPath ); } stepEnd = stepBegin; if ( *stepBegin != '[' ) { // A struct field or qualifier. qualName = stepBegin; while ( (*stepEnd != 0) && (*stepEnd != '/') && (*stepEnd != '[') && (*stepEnd != '*') ) ++stepEnd; nameEnd = stepEnd; stepFlags = kXMP_StructFieldStep; // ! Touch up later, also changing '@' to '?'. } else { // One of the array forms. ++stepEnd; // Look at the character after the leading '['. if ( ('0' <= *stepEnd) && (*stepEnd <= '9') ) { // A numeric (decimal integer) array index. while ( (*stepEnd != 0) && ('0' <= *stepEnd) && (*stepEnd <= '9') ) ++stepEnd; if ( *stepEnd != ']' ) XMP_Throw ( "Missing ']' for integer array index", kXMPErr_BadXPath ); stepFlags = kXMP_ArrayIndexStep; } else { // Could be "[last()]" or one of the selector forms. Find the ']' or '='. while ( (*stepEnd != 0) && (*stepEnd != ']') && (*stepEnd != '=') ) ++stepEnd; if ( *stepEnd == 0 ) XMP_Throw ( "Missing ']' or '=' for array index", kXMPErr_BadXPath ); if ( *stepEnd == ']' ) { if ( strncmp ( "[last()", stepBegin, (stepEnd - stepBegin) ) != 0 ) { XMP_Throw ( "Invalid non-numeric array index", kXMPErr_BadXPath ); } stepFlags = kXMP_ArrayLastStep; } else { qualName = stepBegin+1; nameEnd = stepEnd; ++stepEnd; // Absorb the '=', remember the quote. const char quote = *stepEnd; if ( (quote != '\'') && (quote != '"') ) { XMP_Throw ( "Invalid quote in array selector", kXMPErr_BadXPath ); } ++stepEnd; // Absorb the leading quote. while ( *stepEnd != 0 ) { if ( *stepEnd == quote ) { if ( *(stepEnd+1) != quote ) break; ++stepEnd; } ++stepEnd; } if ( *stepEnd == 0 ) { XMP_Throw ( "No terminating quote for array selector", kXMPErr_BadXPath ); } ++stepEnd; // Absorb the trailing quote. stepFlags = kXMP_FieldSelectorStep; // ! Touch up later, also changing '@' to '?'. } } if ( *stepEnd != ']' ) XMP_Throw ( "Missing ']' for array index", kXMPErr_BadXPath ); ++stepEnd; } if ( stepEnd == stepBegin ) XMP_Throw ( "Empty XPath step", kXMPErr_BadXPath ); currStep.assign ( stepBegin, (stepEnd - stepBegin) ); if ( GetStepKind ( stepFlags ) == kXMP_StructFieldStep ) { if ( currStep[0] == '@' ) { currStep[0] = '?'; if ( currStep != "?xml:lang" ) XMP_Throw ( "Only xml:lang allowed with '@'", kXMPErr_BadXPath ); } if ( currStep[0] == '?' ) { ++qualName; stepFlags = kXMP_QualifierStep; } VerifyQualName ( qualName, nameEnd ); } else if ( GetStepKind ( stepFlags ) == kXMP_FieldSelectorStep ) { if ( currStep[1] == '@' ) { currStep[1] = '?'; if ( strncmp ( currStep.c_str(), "[?xml:lang=", 11 ) != 0 ) { XMP_Throw ( "Only xml:lang allowed with '@'", kXMPErr_BadXPath ); } } if ( currStep[1] == '?' ) { ++qualName; stepFlags = kXMP_QualSelectorStep; } VerifyQualName ( qualName, nameEnd ); } expandedXPath->push_back ( XPathStepInfo ( currStep, stepFlags ) ); } } // ExpandXPath
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
static void AddNodeOffspring ( IterInfo & info, IterNode & iterParent, const XMP_Node * xmpParent ) { XMP_VarString currPath ( iterParent.fullPath ); size_t leafOffset = iterParent.fullPath.size(); if ( (! xmpParent->qualifiers.empty()) && (! (info.options & kXMP_IterOmitQualifiers)) ) { #if TraceIterators printf ( " Adding qualifiers of %s\n", currPath.c_str() ); #endif currPath += "/?"; // All qualifiers are named and use paths like "Prop/?Qual". leafOffset += 2; for ( size_t qualNum = 0, qualLim = xmpParent->qualifiers.size(); qualNum != qualLim; ++qualNum ) { const XMP_Node * xmpQual = xmpParent->qualifiers[qualNum]; currPath += xmpQual->name; iterParent.qualifiers.push_back ( IterNode ( xmpQual->options, currPath, leafOffset ) ); currPath.erase ( leafOffset ); #if TraceIterators printf ( " %s\n", xmpQual->name.c_str() ); #endif } leafOffset -= 2; currPath.erase ( leafOffset ); } if ( ! xmpParent->children.empty() ) { #if TraceIterators printf ( " Adding children of %s\n", currPath.c_str() ); #endif XMP_Assert ( xmpParent->options & kXMP_PropCompositeMask ); if ( xmpParent->options & kXMP_PropValueIsStruct ) { currPath += '/'; leafOffset += 1; } for ( size_t childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) { const XMP_Node * xmpChild = xmpParent->children[childNum]; if ( ! (xmpParent->options & kXMP_PropValueIsArray) ) { currPath += xmpChild->name; } else { char buffer [32]; // AUDIT: Using sizeof(buffer) below for snprintf length is safe. snprintf ( buffer, sizeof(buffer), "[%lu]", static_cast<unsigned long>(childNum+1) ); // ! XPath indices are one-based. currPath += buffer; } iterParent.children.push_back ( IterNode ( xmpChild->options, currPath, leafOffset ) ); currPath.erase ( leafOffset ); #if TraceIterators printf ( " %s\n", (iterParent.children.back().fullPath.c_str() + leafOffset) ); #endif } } } // AddNodeOffspring
static void ApplyQuotes ( XMP_VarString * item, UniCodePoint openQuote, UniCodePoint closeQuote, bool allowCommas ) { bool prevSpace = false; size_t charOffset, charLen; UniCharKind charKind; UniCodePoint uniChar; // ----------------------------------------------------------------------------------------- // See if there are any separators in the value. Stop at the first occurrance. This is a bit // tricky in order to make typical typing work conveniently. The purpose of applying quotes // is to preserve the values when splitting them back apart. That is CatenateContainerItems // and SeparateContainerItems must round trip properly. For the most part we only look for // separators here. Internal quotes, as in -- Irving "Bud" Jones -- won't cause problems in // the separation. An initial quote will though, it will make the value look quoted. charOffset = 0; ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar ); if ( charKind != UCK_quote ) { for ( charOffset = 0; size_t(charOffset) < item->size(); charOffset += charLen ) { ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar ); if ( charKind == UCK_space ) { if ( prevSpace ) break; // Multiple spaces are a separator. prevSpace = true; } else { prevSpace = false; if ( (charKind == UCK_semicolon) || (charKind == UCK_control) ) break; if ( (charKind == UCK_comma) && (! allowCommas) ) break; } } } if ( size_t(charOffset) < item->size() ) { // -------------------------------------------------------------------------------------- // Create a quoted copy, doubling any internal quotes that match the outer ones. Internal // quotes did not stop the "needs quoting" search, but they do need doubling. So we have // to rescan the front of the string for quotes. Handle the special case of U+301D being // closed by either U+301E or U+301F. XMP_VarString newItem; size_t splitPoint; for ( splitPoint = 0; splitPoint <= charOffset; ++splitPoint ) { ClassifyCharacter ( item->c_str(), splitPoint, &charKind, &charLen, &uniChar ); if ( charKind == UCK_quote ) break; } CodePointToUTF8 ( openQuote, newItem ); newItem.append ( *item, 0, splitPoint ); // Copy the leading "normal" portion. for ( charOffset = splitPoint; size_t(charOffset) < item->size(); charOffset += charLen ) { ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar ); newItem.append ( *item, charOffset, charLen ); if ( (charKind == UCK_quote) && IsSurroundingQuote ( uniChar, openQuote, closeQuote ) ) { newItem.append ( *item, charOffset, charLen ); } } XMP_VarString closeStr; CodePointToUTF8 ( closeQuote, closeStr ); newItem.append ( closeStr ); *item = newItem; } } // ApplyQuotes
bool HandleNonConstAlias( const spIMetadata & meta, XMP_ExpandedXPath & expandedXPath, bool createNodes, XMP_OptionBits leafOptions, spINode & destNode, sizet & nodeIndex, bool ignoreLastStep, const spINode & inputNode ) { destNode = meta; spcIUTF8String inputNodeValue; if ( inputNode && inputNode->GetNodeType() == INode::kNTSimple ) { inputNodeValue = inputNode->ConvertToSimpleNode()->GetValue(); } bool isAliasBeingCreated = expandedXPath.size() == 2; if ( expandedXPath.empty() ) NOTIFY_ERROR( IError::kEDDataModel, kDMECBadXPath, "Empty XPath", IError::kESOperationFatal, false, false ); if ( !( expandedXPath[ kSchemaStep ].options & kXMP_SchemaNode ) ) { return false; } else { XMP_VarString namespaceName = expandedXPath[ kSchemaStep ].step.c_str(); size_t colonPos = expandedXPath[ kRootPropStep ].step.find( ":" ); assert( colonPos != std::string::npos ); XMP_VarString propertyName = expandedXPath[ kRootPropStep ].step.substr( colonPos + 1 ); spcINode childNode = meta->GetNode( namespaceName.c_str(), namespaceName.size(), propertyName.c_str(), propertyName.size() ); if ( !childNode && !createNodes ) return false; if ( expandedXPath.size() == 2 ) { if ( childNode ) return true; XMP_OptionBits createOptions = 0; spINode tempNode; if ( isAliasBeingCreated ) tempNode = CreateTerminalNode( namespaceName.c_str(), propertyName.c_str(), leafOptions ); else tempNode = CreateTerminalNode( namespaceName.c_str(), propertyName.c_str(), createOptions ); if ( !tempNode ) return false; if ( inputNodeValue ) tempNode->ConvertToSimpleNode()->SetValue( inputNodeValue->c_str(), inputNodeValue->size() ); if ( destNode == meta ) { meta->InsertNode( tempNode ); } else { destNode->ConvertToStructureNode()->AppendNode( tempNode ); } destNode = tempNode; if ( destNode ) return true; return false; } XMP_Assert( expandedXPath.size() == 3 ); if ( expandedXPath[ 2 ].options == kXMP_ArrayIndexStep ) { XMP_Assert( expandedXPath[ 2 ].step == "[1]" ); destNode = meta->GetNode( namespaceName.c_str(), namespaceName.size(), propertyName.c_str(), propertyName.size() ); if ( !destNode && !createNodes ) return false; if ( !destNode ) { spINode arrayNode = CreateTerminalNode( namespaceName.c_str(), propertyName.c_str(), kXMP_PropArrayIsOrdered | kXMP_PropValueIsArray ); meta->AppendNode( arrayNode ); destNode = arrayNode; } if ( destNode->ConvertToArrayNode()->GetNodeAtIndex( 1 ) ) { destNode = destNode->ConvertToArrayNode()->GetNodeAtIndex( 1 ); if ( nodeIndex ) nodeIndex = 1; return true; } else { spISimpleNode indexNode = ISimpleNode::CreateSimpleNode( namespaceName.c_str(), namespaceName.size(), propertyName.c_str(), propertyName.size() ); if ( inputNodeValue ) { indexNode->SetValue( inputNodeValue->c_str(), inputNodeValue->size() ); } destNode->ConvertToArrayNode()->InsertNodeAtIndex( indexNode, 1 ); destNode = indexNode; return true; } return false; } else if ( expandedXPath[ 2 ].options == kXMP_QualSelectorStep ) { assert( expandedXPath[ 2 ].step == "[?xml:lang=\"x-default\"]" ); destNode = meta->GetNode( namespaceName.c_str(), namespaceName.size(), propertyName.c_str(), propertyName.size() ); if ( !destNode && !createNodes ) return false; spINode arrayNode = CreateTerminalNode( namespaceName.c_str(), propertyName.c_str(), kXMP_PropValueIsArray | kXMP_PropArrayIsAltText); meta->AppendNode( arrayNode ); destNode = arrayNode; auto iter = destNode->ConvertToArrayNode()->Iterator(); XMP_Index index = 1; while ( iter ) { spINode node = iter->GetNode(); spINode qualNode = node->GetQualifier( "http://www.w3.org/XML/1998/namespace", AdobeXMPCommon::npos, "lang", AdobeXMPCommon::npos ); if ( qualNode->GetNodeType() == INode::kNTSimple ) { if ( !qualNode->ConvertToSimpleNode()->GetValue()->compare( "x-default" ) ) { destNode = node; if ( nodeIndex ) nodeIndex = index; return true; } } index++; iter = iter->Next(); } spISimpleNode qualifierNode = ISimpleNode::CreateSimpleNode( "http://www.w3.org/XML/1998/namespace", AdobeXMPCommon::npos, "lang", AdobeXMPCommon::npos, "x-default", AdobeXMPCommon::npos ); if ( destNode->ConvertToArrayNode()->GetNodeAtIndex( 1 ) ) { destNode = destNode->ConvertToArrayNode()->GetNodeAtIndex( 1 ); if ( nodeIndex ) nodeIndex = 1; destNode->InsertQualifier( qualifierNode ); return true; } else { spISimpleNode indexNode = ISimpleNode::CreateSimpleNode( namespaceName.c_str(), AdobeXMPCommon::npos, propertyName.c_str(), AdobeXMPCommon::npos ); if ( inputNodeValue ) { indexNode->SetValue( inputNodeValue->c_str(), inputNodeValue->size() ); } destNode->ConvertToArrayNode()->InsertNodeAtIndex( indexNode, 1 ); destNode->InsertQualifier( qualifierNode ); destNode = indexNode; return true; } } } return false; }
/* 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