diff options
Diffstat (limited to 'gpr/source/lib/xmp_core/XMPMeta-Serialize.cpp')
-rw-r--r-- | gpr/source/lib/xmp_core/XMPMeta-Serialize.cpp | 1396 |
1 files changed, 1396 insertions, 0 deletions
diff --git a/gpr/source/lib/xmp_core/XMPMeta-Serialize.cpp b/gpr/source/lib/xmp_core/XMPMeta-Serialize.cpp new file mode 100644 index 0000000..3c46269 --- /dev/null +++ b/gpr/source/lib/xmp_core/XMPMeta-Serialize.cpp @@ -0,0 +1,1396 @@ +// ================================================================================================= +// Copyright 2003-2009 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 "public/include/XMP_Version.h" +#include "UnicodeInlines.incl_cpp" +#include "UnicodeConversions.hpp" + +#include "md5.h" + +#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 +// ========================= + +static const char * kPacketHeader = "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>"; +static const char * kPacketTrailer = "<?xpacket end=\"w\"?>"; // ! The w/r is at [size-4]. + +static const char * kTXMP_SchemaGroup = "XMP_SchemaGroup"; + +static const char * kRDF_XMPMetaStart = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\""; +static const char * kRDF_XMPMetaEnd = "</x:xmpmeta>"; + +static const char * kRDF_RDFStart = "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">"; +static const char * kRDF_RDFEnd = "</rdf:RDF>"; + +static const char * kRDF_SchemaStart = "<rdf:Description rdf:about="; +static const char * kRDF_SchemaEnd = "</rdf:Description>"; + +static const char * kRDF_StructStart = "<rdf:Description>"; +static const char * kRDF_StructEnd = "</rdf:Description>"; + +static const char * kRDF_BagStart = "<rdf:Bag>"; +static const char * kRDF_BagEnd = "</rdf:Bag>"; + +static const char * kRDF_SeqStart = "<rdf:Seq>"; +static const char * kRDF_SeqEnd = "</rdf:Seq>"; + +static const char * kRDF_AltStart = "<rdf:Alt>"; +static const char * kRDF_AltEnd = "</rdf:Alt>"; + +static const char * kRDF_ItemStart = "<rdf:li>"; +static const char * kRDF_ItemEnd = "</rdf:li>"; + +static const char * kRDF_ValueStart = "<rdf:value>"; +static const char * kRDF_ValueEnd = "</rdf:value>"; + + +// ================================================================================================= +// Static Variables +// ================ + + +// ================================================================================================= +// Local Utilities +// =============== + + +// ------------------------------------------------------------------------------------------------- +// EstimateRDFSize +// --------------- + +// *** Pull the strlen(kXyz) calls into constants. + +static size_t +EstimateRDFSize ( const XMP_Node * currNode, XMP_Index indent, size_t indentLen ) +{ + size_t outputLen = 2 * (indent*indentLen + currNode->name.size() + 4); // The property element tags. + + if ( ! currNode->qualifiers.empty() ) { + // This node has qualifiers, assume it is written using rdf:value and estimate the qualifiers. + + indent += 2; // Everything else is indented inside the rdf:Description element. + outputLen += 2 * ((indent-1)*indentLen + strlen(kRDF_StructStart) + 2); // The rdf:Description tags. + outputLen += 2 * (indent*indentLen + strlen(kRDF_ValueStart) + 2); // The rdf:value tags. + + for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = currNode->qualifiers[qualNum]; + outputLen += EstimateRDFSize ( currQual, indent, indentLen ); + } + + } + + if ( currNode->options & kXMP_PropValueIsStruct ) { + indent += 1; + outputLen += 2 * (indent*indentLen + strlen(kRDF_StructStart) + 2); // The rdf:Description tags. + } else if ( currNode->options & kXMP_PropValueIsArray ) { + indent += 2; + outputLen += 2 * ((indent-1)*indentLen + strlen(kRDF_BagStart) + 2); // The rdf:Bag/Seq/Alt tags. + outputLen += 2 * currNode->children.size() * (strlen(kRDF_ItemStart) + 2); // The rdf:li tags, indent counted in children. + } else if ( ! (currNode->options & kXMP_SchemaNode) ) { + outputLen += currNode->value.size(); // This is a leaf value node. + } + + for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = currNode->children[childNum]; + outputLen += EstimateRDFSize ( currChild, indent+1, indentLen ); + } + + return outputLen; + +} // EstimateRDFSize + + +// ------------------------------------------------------------------------------------------------- +// DeclareOneNamespace +// ------------------- + +static void +DeclareOneNamespace ( XMP_StringPtr nsPrefix, + XMP_StringPtr nsURI, + XMP_VarString & usedNS, // ! A catenation of the prefixes with colons. + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + XMP_VarString boundedPrefix = ":"; + boundedPrefix += nsPrefix; + size_t nsPos = usedNS.find ( boundedPrefix ); + + if ( nsPos == XMP_VarString::npos ) { + + outputStr += newline; + for ( ; indent > 0; --indent ) outputStr += indentStr; + outputStr += "xmlns:"; + outputStr += nsPrefix; + outputStr[outputStr.size()-1] = '='; // Change the colon to =. + outputStr += '"'; + outputStr += nsURI; + outputStr += '"'; + + usedNS += nsPrefix; + + } + +} // DeclareOneNamespace + + +// ------------------------------------------------------------------------------------------------- +// DeclareElemNamespace +// -------------------- + +static void +DeclareElemNamespace ( const XMP_VarString & elemName, + XMP_VarString & usedNS, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + size_t colonPos = elemName.find ( ':' ); + + if ( colonPos != XMP_VarString::npos ) { + XMP_VarString nsPrefix ( elemName.substr ( 0, colonPos+1 ) ); + XMP_StringPtr nsURI; + bool nsFound = sRegisteredNamespaces->GetURI ( nsPrefix.c_str(), &nsURI, 0 ); + XMP_Enforce ( nsFound ); + DeclareOneNamespace ( nsPrefix.c_str(), nsURI, usedNS, outputStr, newline, indentStr, indent ); + } + +} // DeclareElemNamespace + + +// ------------------------------------------------------------------------------------------------- +// DeclareUsedNamespaces +// --------------------- + +static void +DeclareUsedNamespaces ( const XMP_Node * currNode, + XMP_VarString & usedNS, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + + if ( currNode->options & kXMP_SchemaNode ) { + // The schema node name is the URI, the value is the prefix. + DeclareOneNamespace ( currNode->value.c_str(), currNode->name.c_str(), usedNS, outputStr, newline, indentStr, indent ); + } else if ( currNode->options & kXMP_PropValueIsStruct ) { + for ( size_t fieldNum = 0, fieldLim = currNode->children.size(); fieldNum < fieldLim; ++fieldNum ) { + const XMP_Node * currField = currNode->children[fieldNum]; + DeclareElemNamespace ( currField->name, usedNS, outputStr, newline, indentStr, indent ); + } + } + + for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = currNode->children[childNum]; + DeclareUsedNamespaces ( currChild, usedNS, outputStr, newline, indentStr, indent ); + } + + for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = currNode->qualifiers[qualNum]; + DeclareElemNamespace ( currQual->name, usedNS, outputStr, newline, indentStr, indent ); + DeclareUsedNamespaces ( currQual, usedNS, outputStr, newline, indentStr, indent ); + } + +} // DeclareUsedNamespaces + +// ------------------------------------------------------------------------------------------------- +// EmitRDFArrayTag +// --------------- + +enum { + kIsStartTag = true, + kIsEndTag = false +}; + +static void +EmitRDFArrayTag ( XMP_OptionBits arrayForm, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent, + XMP_Index arraySize, + bool isStartTag ) +{ + if ( (! isStartTag) && (arraySize == 0) ) return; + + for ( XMP_Index level = indent; level > 0; --level ) outputStr += indentStr; + if ( isStartTag ) { + outputStr += "<rdf:"; + } else { + outputStr += "</rdf:"; + } + + if ( arrayForm & kXMP_PropArrayIsAlternate ) { + outputStr += "Alt"; + } else if ( arrayForm & kXMP_PropArrayIsOrdered ) { + outputStr += "Seq"; + } else { + outputStr += "Bag"; + } + + if ( isStartTag && (arraySize == 0) ) outputStr += '/'; + outputStr += '>'; + outputStr += newline; + +} // EmitRDFArrayTag + + +// ------------------------------------------------------------------------------------------------- +// AppendNodeValue +// --------------- +// +// Append a property or qualifier value to the output with appropriate XML escaping. The escaped +// characters for elements and attributes are '&', '<', '>', and ASCII controls (tab, LF, CR). In +// addition, '"' is escaped for attributes. For efficiency, this is done in a double loop. The outer +// loop makes sure the whole value is processed. The inner loop does a contiguous unescaped run +// followed by one escaped character (if we're not at the end). +// +// We depend on parsing and SetProperty logic to make sure there are no invalid ASCII controls in +// the XMP values. The XML spec only allows tab, LF, and CR. Others are not even allowed as +// numeric escape sequences. + +enum { + kForAttribute = true, + kForElement = false +}; + +static void +AppendNodeValue ( XMP_VarString & outputStr, const XMP_VarString & value, bool forAttribute ) +{ + + unsigned char * runStart = (unsigned char *) value.c_str(); + unsigned char * runLimit = runStart + value.size(); + unsigned char * runEnd; + unsigned char ch; + + while ( runStart < runLimit ) { + + for ( runEnd = runStart; runEnd < runLimit; ++runEnd ) { + ch = *runEnd; + if ( forAttribute && (ch == '"') ) break; + if ( (ch < 0x20) || (ch == '&') || (ch == '<') || (ch == '>') ) break; + } + + outputStr.append ( (char *) runStart, (runEnd - runStart) ); + + if ( runEnd < runLimit ) { + + if ( ch < 0x20 ) { + + XMP_Assert ( (ch == kTab) || (ch == kLF) || (ch == kCR) ); + + char hexBuf[16]; + memcpy ( hexBuf, "&#xn;", 6 ); // AUDIT: Length of "&#xn;" is 5, hexBuf size is 16. + hexBuf[3] = kHexDigits[ch&0xF]; + outputStr.append ( hexBuf, 5 ); + + } else { + + if ( ch == '"' ) { + outputStr += """; + } else if ( ch == '<' ) { + outputStr += "<"; + } else if ( ch == '>' ) { + outputStr += ">"; + } else { + XMP_Assert ( ch == '&' ); + outputStr += "&"; + } + + } + + ++runEnd; + + } + + runStart = runEnd; + + } + +} // AppendNodeValue + + +// ------------------------------------------------------------------------------------------------- +// CanBeRDFAttrProp +// ---------------- + +static bool +CanBeRDFAttrProp ( const XMP_Node * propNode ) +{ + + if ( propNode->name[0] == '[' ) return false; + if ( ! propNode->qualifiers.empty() ) return false; + if ( propNode->options & kXMP_PropValueIsURI ) return false; + if ( propNode->options & kXMP_PropCompositeMask ) return false; + + return true; + +} // CanBeRDFAttrProp + + +// ------------------------------------------------------------------------------------------------- +// IsRDFAttrQualifier +// ------------------ + +static XMP_StringPtr sAttrQualifiers[] = { "xml:lang", "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID", "" }; + +static bool +IsRDFAttrQualifier ( XMP_VarString qualName ) +{ + + for ( size_t i = 0; *sAttrQualifiers[i] != 0; ++i ) { + if ( qualName == sAttrQualifiers[i] ) return true; + } + + return false; + +} // IsRDFAttrQualifier + + +// ------------------------------------------------------------------------------------------------- +// StartOuterRDFDescription +// ------------------------ +// +// Start the outer rdf:Description element, including all needed xmlns attributes. Leave the element +// open so that the compact form can add proprtty attributes. + +static void +StartOuterRDFDescription ( const XMP_Node & xmpTree, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent ) +{ + + // Begin the outer rdf:Description start tag. + + for ( XMP_Index level = baseIndent+2; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_SchemaStart; + outputStr += '"'; + outputStr += xmpTree.name; + outputStr += '"'; + + // Write all necessary xmlns attributes. + + XMP_VarString usedNS; + usedNS.reserve ( 400 ); // The predefined prefixes add up to about 320 bytes. + usedNS = ":xml:rdf:"; + + for ( size_t schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { + const XMP_Node * currSchema = xmpTree.children[schema]; + DeclareUsedNamespaces ( currSchema, usedNS, outputStr, newline, indentStr, baseIndent+4 ); + } + +} // StartOuterRDFDescription + +// ------------------------------------------------------------------------------------------------- +// SerializeCanonicalRDFProperty +// ----------------------------- +// +// Recursively handles the "value" for a node. It does not matter if it is a top level property, a +// field of a struct, or an item of an array. The indent is that for the property element. An +// xml:lang qualifier is written as an attribute of the property start tag, not by itself forcing +// the qualified property form. The patterns below mostly ignore attribute qualifiers like xml:lang. +// Except for the one struct case, attribute qualifiers don't affect the output form. +// +// <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty> +// +// <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier) +// <rdf:Description> +// ... Fields, same forms as top level properties +// </rdf:Description> +// </ns:UnqualifiedStructProperty> +// +// <ns:ResourceStructProperty rdf:resource="URI" +// ... Fields as attributes +// > +// +// <ns:UnqualifiedArrayProperty> +// <rdf:Bag> or Seq or Alt +// ... Array items as rdf:li elements, same forms as top level properties +// </rdf:Bag> +// </ns:UnqualifiedArrayProperty> +// +// <ns:QualifiedProperty> +// <rdf:Description> +// <rdf:value> ... Property "value" following the unqualified forms ... </rdf:value> +// ... Qualifiers looking like named struct fields +// </rdf:Description> +// </ns:QualifiedProperty> + +enum { kUseCanonicalRDF = true, kUseAdobeVerboseRDF = false }; +enum { kEmitAsRDFValue = true, kEmitAsNormalValue = false }; + +static void +SerializeCanonicalRDFProperty ( const XMP_Node * propNode, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent, + bool useCanonicalRDF, + bool emitAsRDFValue ) +{ + XMP_Index level; + bool emitEndTag = true; + bool indentEndTag = true; + + XMP_OptionBits propForm = propNode->options & kXMP_PropCompositeMask; + + // ------------------------------------------------------------------------------------------ + // Determine the XML element name. Open the start tag with the name and attribute qualifiers. + + XMP_StringPtr elemName = propNode->name.c_str(); + if ( emitAsRDFValue ) { + elemName= "rdf:value"; + } else if ( *elemName == '[' ) { + elemName = "rdf:li"; + } + + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += '<'; + outputStr += elemName; + + bool hasGeneralQualifiers = false; + bool hasRDFResourceQual = false; + + for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + if ( ! IsRDFAttrQualifier ( currQual->name ) ) { + hasGeneralQualifiers = true; + } else { + if ( currQual->name == "rdf:resource" ) hasRDFResourceQual = true; + if ( ! emitAsRDFValue ) { + outputStr += ' '; + outputStr += currQual->name; + outputStr += "=\""; + AppendNodeValue ( outputStr, currQual->value, kForAttribute ); + outputStr += '"'; + } + } + } + + // -------------------------------------------------------- + // Process the property according to the standard patterns. + + if ( hasGeneralQualifiers && (! emitAsRDFValue) ) { + + // ----------------------------------------------------------------------------------------- + // This node has general, non-attribute, qualifiers. Emit using the qualified property form. + // ! The value is output by a recursive call ON THE SAME NODE with emitAsRDFValue set. + + if ( hasRDFResourceQual ) { + XMP_Throw ( "Can't mix rdf:resource and general qualifiers", kXMPErr_BadRDF ); + } + + if ( ! useCanonicalRDF ) { + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + } else { + outputStr += '>'; + outputStr += newline; + indent += 1; + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "<rdf:Description>"; + outputStr += newline; + } + + SerializeCanonicalRDFProperty ( propNode, outputStr, newline, indentStr, indent+1, + useCanonicalRDF, kEmitAsRDFValue ); + + for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + if ( IsRDFAttrQualifier ( currQual->name ) ) continue; + SerializeCanonicalRDFProperty ( currQual, outputStr, newline, indentStr, indent+1, + useCanonicalRDF, kEmitAsNormalValue ); + } + + if ( useCanonicalRDF ) { + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "</rdf:Description>"; + outputStr += newline; + indent -= 1; + } + + } else { + + // -------------------------------------------------------------------- + // This node has no general qualifiers. Emit using an unqualified form. + + if ( propForm == 0 ) { + + // -------------------------- + // This is a simple property. + + if ( propNode->options & kXMP_PropValueIsURI ) { + outputStr += " rdf:resource=\""; + AppendNodeValue ( outputStr, propNode->value, kForAttribute ); + outputStr += "\"/>"; + outputStr += newline; + emitEndTag = false; + } else if ( propNode->value.empty() ) { + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + } else { + outputStr += '>'; + AppendNodeValue ( outputStr, propNode->value, kForElement ); + indentEndTag = false; + } + + } else if ( propForm & kXMP_PropValueIsArray ) { + + // This is an array. + outputStr += '>'; + outputStr += newline; + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsStartTag ); + if ( XMP_ArrayIsAltText(propNode->options) ) NormalizeLangArray ( (XMP_Node*)propNode ); + for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = propNode->children[childNum]; + SerializeCanonicalRDFProperty ( currChild, outputStr, newline, indentStr, indent+2, + useCanonicalRDF, kEmitAsNormalValue ); + } + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsEndTag ); + + + } else if ( ! hasRDFResourceQual ) { + + // This is a "normal" struct, use the nested field element form form. + XMP_Assert ( propForm & kXMP_PropValueIsStruct ); + if ( propNode->children.size() == 0 ) { + if ( ! useCanonicalRDF ) { + outputStr += " rdf:parseType=\"Resource\"/>"; + outputStr += newline; + emitEndTag = false; + } else { + outputStr += '>'; + outputStr += newline; + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += "<rdf:Description/>"; + outputStr += newline; + } + } else { + if ( ! useCanonicalRDF ) { + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + } else { + outputStr += '>'; + outputStr += newline; + indent += 1; + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "<rdf:Description>"; + outputStr += newline; + } + for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = propNode->children[childNum]; + SerializeCanonicalRDFProperty ( currChild, outputStr, newline, indentStr, indent+1, + useCanonicalRDF, kEmitAsNormalValue ); + } + if ( useCanonicalRDF ) { + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "</rdf:Description>"; + outputStr += newline; + indent -= 1; + } + } + + } else { + + // This is a struct with an rdf:resource attribute, use the "empty property element" form. + XMP_Assert ( propForm & kXMP_PropValueIsStruct ); + for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = propNode->children[childNum]; + if ( ! CanBeRDFAttrProp ( currChild ) ) { + XMP_Throw ( "Can't mix rdf:resource and complex fields", kXMPErr_BadRDF ); + } + outputStr += newline; + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += ' '; + outputStr += currChild->name; + outputStr += "=\""; + outputStr += currChild->value; + outputStr += '"'; + } + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + + } + + } + + // ---------------------------------- + // Emit the property element end tag. + + if ( emitEndTag ) { + if ( indentEndTag ) for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "</"; + outputStr += elemName; + outputStr += '>'; + outputStr += newline; + } + +} // SerializeCanonicalRDFProperty + + +// ------------------------------------------------------------------------------------------------- +// SerializeCanonicalRDFSchemas +// ---------------------------- +// +// Each schema's properties are written to the single rdf:Description element. All of the necessary +// namespaces are declared in the rdf:Description element. The baseIndent is the base level for the +// entire serialization, that of the x:xmpmeta element. An xml:lang qualifier is written as an +// attribute of the property start tag, not by itself forcing the qualified property form. +// +// <rdf:Description rdf:about="TreeName" +// xmlns:ns="URI" ... > +// +// ... The actual properties of the schema, see SerializeCanonicalRDFProperty +// +// <!-- ns1:Alias is aliased to ns2:Actual --> ... If alias comments are wanted +// +// </rdf:Description> + +static void +SerializeCanonicalRDFSchemas ( const XMP_Node & xmpTree, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent, + bool useCanonicalRDF ) +{ + + StartOuterRDFDescription ( xmpTree, outputStr, newline, indentStr, baseIndent ); + + if ( xmpTree.children.size() > 0 ) { + outputStr += ">"; + outputStr += newline; + } else { + outputStr += "/>"; + outputStr += newline; + return; // ! Done if there are no XMP properties. + } + + for ( size_t schemaNum = 0, schemaLim = xmpTree.children.size(); schemaNum < schemaLim; ++schemaNum ) { + const XMP_Node * currSchema = xmpTree.children[schemaNum]; + for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) { + const XMP_Node * currProp = currSchema->children[propNum]; + SerializeCanonicalRDFProperty ( currProp, outputStr, newline, indentStr, baseIndent+3, + useCanonicalRDF, kEmitAsNormalValue ); + } + } + + // Write the rdf:Description end tag. + for ( XMP_Index level = baseIndent+2; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_SchemaEnd; + outputStr += newline; + +} // SerializeCanonicalRDFSchemas + + +// ------------------------------------------------------------------------------------------------- +// SerializeCompactRDFAttrProps +// ---------------------------- +// +// Write each of the parent's simple unqualified properties as an attribute. Returns true if all +// of the properties are written as attributes. + +static bool +SerializeCompactRDFAttrProps ( const XMP_Node * parentNode, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + size_t prop, propLim; + bool allAreAttrs = true; + + for ( prop = 0, propLim = parentNode->children.size(); prop != propLim; ++prop ) { + + const XMP_Node * currProp = parentNode->children[prop]; + if ( ! CanBeRDFAttrProp ( currProp ) ) { + allAreAttrs = false; + continue; + } + + outputStr += newline; + for ( XMP_Index level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += currProp->name; + outputStr += "=\""; + AppendNodeValue ( outputStr, currProp->value, kForAttribute ); + outputStr += '"'; + + } + + return allAreAttrs; + +} // SerializeCompactRDFAttrProps + + +// ------------------------------------------------------------------------------------------------- +// SerializeCompactRDFElemProps +// ---------------------------- +// +// Recursively handles the "value" for a node that must be written as an RDF property element. It +// does not matter if it is a top level property, a field of a struct, or an item of an array. The +// indent is that for the property element. The patterns bwlow ignore attribute qualifiers such as +// xml:lang, they don't affect the output form. +// +// <ns:UnqualifiedStructProperty-1 +// ... The fields as attributes, if all are simple and unqualified +// /> +// +// <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource"> +// ... The fields as elements, if none are simple and unqualified +// </ns:UnqualifiedStructProperty-2> +// +// <ns:UnqualifiedStructProperty-3> +// <rdf:Description +// ... The simple and unqualified fields as attributes +// > +// ... The compound or qualified fields as elements +// </rdf:Description> +// </ns:UnqualifiedStructProperty-3> +// +// <ns:UnqualifiedArrayProperty> +// <rdf:Bag> or Seq or Alt +// ... Array items as rdf:li elements, same forms as top level properties +// </rdf:Bag> +// </ns:UnqualifiedArrayProperty> +// +// <ns:QualifiedProperty rdf:parseType="Resource"> +// <rdf:value> ... Property "value" following the unqualified forms ... </rdf:value> +// ... Qualifiers looking like named struct fields +// </ns:QualifiedProperty> + +// *** Consider numbered array items, but has compatibility problems. +// *** Consider qualified form with rdf:Description and attributes. + +static void +SerializeCompactRDFElemProps ( const XMP_Node * parentNode, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + XMP_Index level; + + for ( size_t prop = 0, propLim = parentNode->children.size(); prop != propLim; ++prop ) { + + const XMP_Node * propNode = parentNode->children[prop]; + if ( CanBeRDFAttrProp ( propNode ) ) continue; + + bool emitEndTag = true; + bool indentEndTag = true; + + XMP_OptionBits propForm = propNode->options & kXMP_PropCompositeMask; + + // ----------------------------------------------------------------------------------- + // Determine the XML element name, write the name part of the start tag. Look over the + // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute + // qualifiers at the same time. + + XMP_StringPtr elemName = propNode->name.c_str(); + if ( *elemName == '[' ) elemName = "rdf:li"; + + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += '<'; + outputStr += elemName; + + bool hasGeneralQualifiers = false; + bool hasRDFResourceQual = false; + + for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + if ( ! IsRDFAttrQualifier ( currQual->name ) ) { + hasGeneralQualifiers = true; + } else { + if ( currQual->name == "rdf:resource" ) hasRDFResourceQual = true; + outputStr += ' '; + outputStr += currQual->name; + outputStr += "=\""; + AppendNodeValue ( outputStr, currQual->value, kForAttribute ); + outputStr += '"'; + } + } + + // -------------------------------------------------------- + // Process the property according to the standard patterns. + + if ( hasGeneralQualifiers ) { + + // ------------------------------------------------------------------------------------- + // The node has general qualifiers, ones that can't be attributes on a property element. + // Emit using the qualified property pseudo-struct form. The value is output by a call + // to SerializeCanonicalRDFProperty with emitAsRDFValue set. + + // *** We're losing compactness in the calls to SerializeCanonicalRDFProperty. + // *** Should refactor to have SerializeCompactRDFProperty that does one node. + + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + + SerializeCanonicalRDFProperty ( propNode, outputStr, newline, indentStr, indent+1, + kUseAdobeVerboseRDF, kEmitAsRDFValue ); + + size_t qualNum = 0; + size_t qualLim = propNode->qualifiers.size(); + if ( propNode->options & kXMP_PropHasLang ) ++qualNum; + + for ( ; qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + SerializeCanonicalRDFProperty ( currQual, outputStr, newline, indentStr, indent+1, + kUseAdobeVerboseRDF, kEmitAsNormalValue ); + } + + } else { + + // -------------------------------------------------------------------- + // This node has only attribute qualifiers. Emit as a property element. + + if ( propForm == 0 ) { + + // -------------------------- + // This is a simple property. + + if ( propNode->options & kXMP_PropValueIsURI ) { + outputStr += " rdf:resource=\""; + AppendNodeValue ( outputStr, propNode->value, kForAttribute ); + outputStr += "\"/>"; + outputStr += newline; + emitEndTag = false; + } else if ( propNode->value.empty() ) { + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + } else { + outputStr += '>'; + AppendNodeValue ( outputStr, propNode->value, kForElement ); + indentEndTag = false; + } + + } else if ( propForm & kXMP_PropValueIsArray ) { + + // ----------------- + // This is an array. + + outputStr += '>'; + outputStr += newline; + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsStartTag ); + + if ( XMP_ArrayIsAltText(propNode->options) ) NormalizeLangArray ( (XMP_Node*)propNode ); + SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+2 ); + + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsEndTag ); + + } else { + + // ---------------------- + // This must be a struct. + + XMP_Assert ( propForm & kXMP_PropValueIsStruct ); + + bool hasAttrFields = false; + bool hasElemFields = false; + + size_t field, fieldLim; + for ( field = 0, fieldLim = propNode->children.size(); field != fieldLim; ++field ) { + XMP_Node * currField = propNode->children[field]; + if ( CanBeRDFAttrProp ( currField ) ) { + hasAttrFields = true; + if ( hasElemFields ) break; // No sense looking further. + } else { + hasElemFields = true; + if ( hasAttrFields ) break; // No sense looking further. + } + } + + if ( hasRDFResourceQual && hasElemFields ) { + XMP_Throw ( "Can't mix rdf:resource qualifier and element fields", kXMPErr_BadRDF ); + } + + if ( propNode->children.size() == 0 ) { + + // Catch an empty struct as a special case. The case below would emit an empty + // XML element, which gets reparsed as a simple property with an empty value. + outputStr += " rdf:parseType=\"Resource\"/>"; + outputStr += newline; + emitEndTag = false; + + } else if ( ! hasElemFields ) { + + // All fields can be attributes, use the emptyPropertyElt form. + SerializeCompactRDFAttrProps ( propNode, outputStr, newline, indentStr, indent+1 ); + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + + } else if ( ! hasAttrFields ) { + + // All fields must be elements, use the parseTypeResourcePropertyElt form. + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+1 ); + + } else { + + // Have a mix of attributes and elements, use an inner rdf:Description. + outputStr += '>'; + outputStr += newline; + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += "<rdf:Description"; + SerializeCompactRDFAttrProps ( propNode, outputStr, newline, indentStr, indent+2 ); + outputStr += ">"; + outputStr += newline; + SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+1 ); + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_StructEnd; + outputStr += newline; + + } + + } + + } + + // ---------------------------------- + // Emit the property element end tag. + + if ( emitEndTag ) { + if ( indentEndTag ) for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "</"; + outputStr += elemName; + outputStr += '>'; + outputStr += newline; + } + + } + +} // SerializeCompactRDFElemProps + + +// ------------------------------------------------------------------------------------------------- +// SerializeCompactRDFSchemas +// -------------------------- +// +// All properties from all schema are written in a single rdf:Description element, as are all of the +// necessary namespace declarations. The baseIndent is the base level for the entire serialization, +// that of the x:xmpmeta element. The x:xmpmeta and rdf:RDF elements have already been written. +// +// Top level simple unqualified properties are written as attributes of the (only) rdf:Description +// element. Structs, arrays, and qualified properties are written by SerializeCompactRDFElemProp. An +// xml:lang qualifier on a simple property prevents the attribute form. +// +// <rdf:Description rdf:about="TreeName" +// xmlns:ns="URI" ... +// ns:UnqualifiedSimpleProperty="value" ... > +// ... The remaining properties of the schema, see SerializeCompactRDFElemProps +// </rdf:Description> + +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; + + StartOuterRDFDescription ( xmpTree, outputStr, newline, indentStr, baseIndent ); + + // 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. + for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_SchemaEnd; + outputStr += newline; + +} // SerializeCompactRDFSchemas + +// ------------------------------------------------------------------------------------------------- +// SerializeAsRDF +// -------------- +// +// <?xpacket begin... ?> +// <x:xmpmeta xmlns:x=... > +// <rdf:RDF xmlns:rdf=... > +// +// ... The properties, see SerializeCanonicalRDFSchema or SerializeCompactRDFSchemas +// +// </rdf:RDF> +// </x:xmpmeta> +// <?xpacket end... ?> + +// *** Need to strip empty arrays? +// *** Option to strip/keep empty structs? +// *** Need to verify handling of rdf:type qualifiers in canonical and compact. +// *** Need to verify round tripping of rdf:ID and similar qualifiers, see RDF 7.2.21. +// *** Check cases of rdf:resource plus explicit attr qualifiers (like xml:lang). + +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; + + { + context_md5_t ctx; + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char*)rdfstring.c_str(), (unsigned int)rdfstring.size() ); + MD5Final(digestBin, &ctx); + } + + 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; + } + + for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr; + headStr+= rdfstring ; + 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'; + } + +} // SerializeAsRDF + + +// ------------------------------------------------------------------------------------------------- +// SerializeToBuffer +// ----------------- + +void +XMPMeta::SerializeToBuffer ( XMP_VarString * rdfString, + XMP_OptionBits options, + XMP_StringLen padding, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent ) const +{ + XMP_Enforce( rdfString != 0 ); + XMP_Assert ( (newline != 0) && (indentStr != 0) ); + rdfString->erase(); + + // Fix up some default parameters. + + enum { kDefaultPad = 2048 }; + size_t unicodeUnitSize = 1; + XMP_OptionBits charEncoding = options & kXMP_EncodingMask; + + if ( charEncoding != kXMP_EncodeUTF8 ) { + if ( options & _XMP_UTF16_Bit ) { + if ( options & _XMP_UTF32_Bit ) XMP_Throw ( "Can't use both _XMP_UTF16_Bit and _XMP_UTF32_Bit", kXMPErr_BadOptions ); + unicodeUnitSize = 2; + } else if ( options & _XMP_UTF32_Bit ) { + unicodeUnitSize = 4; + } else { + XMP_Throw ( "Can't use _XMP_LittleEndian_Bit by itself", kXMPErr_BadOptions ); + } + } + + if ( options & kXMP_OmitAllFormatting ) { + newline = " "; // ! Yes, a space for "newline". This ensures token separation. + indentStr = ""; + } else { + if ( *newline == 0 ) newline = "\xA"; // Linefeed + if ( *indentStr == 0 ) { + indentStr = " "; + if ( ! (options & kXMP_UseCompactFormat) ) indentStr = " "; + } + } + + if ( options & kXMP_ExactPacketLength ) { + if ( options & (kXMP_OmitPacketWrapper | kXMP_IncludeThumbnailPad) ) { + XMP_Throw ( "Inconsistent options for exact size serialize", kXMPErr_BadOptions ); + } + if ( (padding & (unicodeUnitSize-1)) != 0 ) { + XMP_Throw ( "Exact size must be a multiple of the Unicode element", kXMPErr_BadOptions ); + } + } else if ( options & kXMP_ReadOnlyPacket ) { + if ( options & (kXMP_OmitPacketWrapper | kXMP_IncludeThumbnailPad) ) { + XMP_Throw ( "Inconsistent options for read-only packet", kXMPErr_BadOptions ); + } + padding = 0; + } else if ( options & kXMP_OmitPacketWrapper ) { + if ( options & kXMP_IncludeThumbnailPad ) { + XMP_Throw ( "Inconsistent options for non-packet serialize", kXMPErr_BadOptions ); + } + padding = 0; + } else if ( options & kXMP_OmitXMPMetaElement ) { + if ( options & kXMP_IncludeRDFHash ) { + XMP_Throw ( "Inconsistent options for x:xmpmeta serialize", kXMPErr_BadOptions ); + } + padding = 0; + } else { + if ( padding == 0 ) { + padding = kDefaultPad * unicodeUnitSize; + } else if ( (padding >> 28) != 0 ) { + XMP_Throw ( "Outrageously large padding size", kXMPErr_BadOptions ); // Bigger than 256 MB. + } + if ( options & kXMP_IncludeThumbnailPad ) { + if ( ! this->DoesPropertyExist ( kXMP_NS_XMP, "Thumbnails" ) ) padding += (10000 * unicodeUnitSize); // *** Need a better estimate. + } + } + + // Serialize as UTF-8, then convert to UTF-16 or UTF-32 if necessary, and assemble with the padding and tail. + + std::string tailStr; + + SerializeAsRDF ( *this, *rdfString, tailStr, options, newline, indentStr, baseIndent ); + + if ( charEncoding == kXMP_EncodeUTF8 ) { + + if ( options & kXMP_ExactPacketLength ) { + size_t minSize = rdfString->size() + tailStr.size(); + if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); + padding -= minSize; // Now the actual amount of padding to add. + } + + size_t newlineLen = strlen ( newline ); + + if ( padding < newlineLen ) { + rdfString->append ( padding, ' ' ); + } else { + padding -= newlineLen; // Write this newline last. + while ( padding >= (100 + newlineLen) ) { + rdfString->append ( 100, ' ' ); + *rdfString += newline; + padding -= (100 + newlineLen); + } + rdfString->append ( padding, ' ' ); + *rdfString += newline; + } + + *rdfString += tailStr; + + } else { + + // Need to convert the encoding. Swap the UTF-8 into a local string and convert back. Assemble everything. + + XMP_VarString utf8Str, newlineStr; + bool bigEndian = ((charEncoding & _XMP_LittleEndian_Bit) == 0); + + if ( charEncoding & _XMP_UTF16_Bit ) { + + std::string padStr ( " " ); padStr[0] = 0; // Assume big endian. + + utf8Str.swap ( *rdfString ); + ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), rdfString, bigEndian ); + utf8Str.swap ( tailStr ); + ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &tailStr, bigEndian ); + + if ( options & kXMP_ExactPacketLength ) { + size_t minSize = rdfString->size() + tailStr.size(); + if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); + padding -= minSize; // Now the actual amount of padding to add (in bytes). + } + + utf8Str.assign ( newline ); + ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &newlineStr, bigEndian ); + size_t newlineLen = newlineStr.size(); + + if ( padding < newlineLen ) { + for ( int i = padding/2; i > 0; --i ) *rdfString += padStr; + } else { + padding -= newlineLen; // Write this newline last. + while ( padding >= (200 + newlineLen) ) { + for ( int i = 100; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + padding -= (200 + newlineLen); + } + for ( int i = padding/2; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + } + + *rdfString += tailStr; + + } else { + + std::string padStr ( " " ); padStr[0] = padStr[1] = padStr[2] = 0; // Assume big endian. + UTF8_to_UTF32_Proc Converter = UTF8_to_UTF32BE; + + if ( charEncoding & _XMP_LittleEndian_Bit ) { + padStr[0] = ' '; padStr[1] = padStr[2] = padStr[3] = 0; + Converter = UTF8_to_UTF32LE; + } + + utf8Str.swap ( *rdfString ); + ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), rdfString, bigEndian ); + utf8Str.swap ( tailStr ); + ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &tailStr, bigEndian ); + + if ( options & kXMP_ExactPacketLength ) { + size_t minSize = rdfString->size() + tailStr.size(); + if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); + padding -= minSize; // Now the actual amount of padding to add (in bytes). + } + + utf8Str.assign ( newline ); + ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &newlineStr, bigEndian ); + size_t newlineLen = newlineStr.size(); + + if ( padding < newlineLen ) { + for ( int i = padding/4; i > 0; --i ) *rdfString += padStr; + } else { + padding -= newlineLen; // Write this newline last. + while ( padding >= (400 + newlineLen) ) { + for ( int i = 100; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + padding -= (400 + newlineLen); + } + for ( int i = padding/4; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + } + + *rdfString += tailStr; + + } + + } + +} // SerializeToBuffer + +// ================================================================================================= |