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
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
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
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. &#xA; 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