// EMPTY
 /// <summary>Normalizes a raw parsed XMPMeta-Object</summary>
 /// <param name="xmp">the raw metadata object</param>
 /// <param name="options">the parsing options</param>
 /// <returns>Returns the normalized metadata object</returns>
 /// <exception cref="Com.Adobe.Xmp.XMPException">Collects all severe processing errors.</exception>
 internal static XMPMeta Process(XMPMetaImpl xmp, ParseOptions options)
 {
     XMPNode tree = xmp.GetRoot();
     TouchUpDataModel(xmp);
     MoveExplicitAliases(tree, options);
     TweakOldXMP(tree);
     DeleteEmptySchemas(tree);
     return xmp;
 }
 /// <summary>Static method to serialize the metadata object.</summary>
 /// <remarks>
 /// Static method to serialize the metadata object. For each serialisation, a new XMPSerializer
 /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to
 /// serialialize the same XMPMeta objects in two threads.
 /// </remarks>
 /// <param name="xmp">a metadata implementation object</param>
 /// <param name="out">the output stream to serialize to</param>
 /// <param name="options">serialization options, can be <code>null</code> for default.</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException"/>
 public static void Serialize(XMPMetaImpl xmp, OutputStream @out, SerializeOptions options)
 {
     options = options != null ? options : new SerializeOptions();
     // sort the internal data model on demand
     if (options.GetSort())
     {
         xmp.Sort();
     }
     new XMPSerializerRDF().Serialize(xmp, @out, options);
 }
 /// <summary>Serializes an <code>XMPMeta</code>-object as RDF into a string.</summary>
 /// <remarks>
 /// Serializes an <code>XMPMeta</code>-object as RDF into a string.
 /// <em>Note:</em> Encoding is forced to UTF-16 when serializing to a
 /// string to ensure the correctness of &quot;exact packet size&quot;.
 /// </remarks>
 /// <param name="xmp">a metadata implementation object</param>
 /// <param name="options">
 /// Options to control the serialization (see
 /// <see cref="Com.Adobe.Xmp.Options.SerializeOptions"/>
 /// ).
 /// </param>
 /// <returns>Returns a string containing the serialized RDF.</returns>
 /// <exception cref="Com.Adobe.Xmp.XMPException">on serializsation errors.</exception>
 public static string SerializeToString(XMPMetaImpl xmp, SerializeOptions options)
 {
     // forces the encoding to be UTF-16 to get the correct string length
     options = options != null ? options : new SerializeOptions();
     options.SetEncodeUTF16BE(true);
     ByteArrayOutputStream @out = new ByteArrayOutputStream(2048);
     Serialize(xmp, @out, options);
     try
     {
         return @out.ToString(options.GetEncoding());
     }
     catch (UnsupportedEncodingException)
     {
         // cannot happen as UTF-8/16LE/BE is required to be implemented in
         // Java
         return @out.ToString();
     }
 }
 /// <summary>
 /// 7.2.18 parseTypeResourcePropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, parseResource ) )
 /// propertyEltList
 /// end-element()
 /// Add a new struct node with a qualifier for the possible rdf:ID attribute.
 /// </summary>
 /// <remarks>
 /// 7.2.18 parseTypeResourcePropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, parseResource ) )
 /// propertyEltList
 /// end-element()
 /// Add a new struct node with a qualifier for the possible rdf:ID attribute.
 /// Then process the XML child nodes to get the struct fields.
 /// </remarks>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlNode">the currently processed XML node</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
 {
     XMPNode newStruct = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel);
     newStruct.GetOptions().SetStruct(true);
     for (int i = 0; i < xmlNode.Attributes.Count; i++)
     {
         XmlNode attribute = xmlNode.Attributes.Item(i);
         if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
         {
             continue;
         }
         string attrLocal = attribute.LocalName;
         string attrNS = attribute.NamespaceURI;
         if (XMPConstConstants.XmlLang.Equals(attribute.Name))
         {
             AddQualifierNode(newStruct, XMPConstConstants.XmlLang, attribute.Value);
         }
         else
         {
             if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "parseType".Equals(attrLocal)))
             {
                 continue;
             }
             else
             {
                 // The caller ensured the value is "Resource".
                 // Ignore all rdf:ID attributes.
                 throw new XMPException("Invalid attribute for ParseTypeResource property element", XMPErrorConstants.Badrdf);
             }
         }
     }
     Rdf_PropertyElementList(xmp, newStruct, xmlNode, false);
     if (newStruct.GetHasValueChild())
     {
         FixupQualifiedNode(newStruct);
     }
 }
 /// <summary>
 /// 7.2.10 nodeElementList<br />
 /// ws* ( nodeElement ws* )
 /// Note: this method is only called from the rdf:RDF-node (top level)
 /// </summary>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="rdfRdfNode">the top-level xml node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode rdfRdfNode)
 {
     for (int i = 0; i < rdfRdfNode.ChildNodes.Count; i++)
     {
         XmlNode child = rdfRdfNode.ChildNodes.Item(i);
         // filter whitespaces (and all text nodes)
         if (!IsWhitespaceNode(child))
         {
             Rdf_NodeElement(xmp, xmpParent, child, true);
         }
     }
 }
        /// <summary>
        /// 7.2.7 propertyAttributeURIs
        /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
        /// 7.2.11 nodeElement
        /// start-element ( URI == nodeElementURIs,
        /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
        /// propertyEltList
        /// end-element()
        /// Process the attribute list for an RDF node element.
        /// </summary>
        /// <remarks>
        /// 7.2.7 propertyAttributeURIs
        /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
        /// 7.2.11 nodeElement
        /// start-element ( URI == nodeElementURIs,
        /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
        /// propertyEltList
        /// end-element()
        /// Process the attribute list for an RDF node element. A property attribute URI is
        /// anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored,
        /// as are rdf:about attributes on inner nodes.
        /// </remarks>
        /// <param name="xmp">the xmp metadata object that is generated</param>
        /// <param name="xmpParent">the parent xmp node</param>
        /// <param name="xmlNode">the currently processed XML node</param>
        /// <param name="isTopLevel">Flag if the node is a top-level node</param>
        /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
        private static void Rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
        {
            // Used to detect attributes that are mutually exclusive.
            int exclusiveAttrs = 0;
            for (int i = 0; i < xmlNode.Attributes.Count; i++)
            {
                XmlNode attribute = xmlNode.Attributes.Item(i);
                // quick hack, ns declarations do not appear in C++
                // ignore "ID" without namespace
                if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                int attrTerm = GetRDFTermKind(attribute);
                switch (attrTerm)
                {
                    case RdftermId:
                    case RdftermNodeId:
                    case RdftermAbout:
                    {
                        if (exclusiveAttrs > 0)
                        {
                            throw new XMPException("Mutally exclusive about, ID, nodeID attributes", XMPErrorConstants.Badrdf);
                        }
                        exclusiveAttrs++;
                        if (isTopLevel && (attrTerm == RdftermAbout))
                        {
                            // This is the rdf:about attribute on a top level node. Set
                            // the XMP tree name if
                            // it doesn't have a name yet. Make sure this name matches
                            // the XMP tree name.
                            if (xmpParent.GetName() != null && xmpParent.GetName().Length > 0)
                            {
                                if (!xmpParent.GetName().Equals(attribute.Value))
                                {
                                    throw new XMPException("Mismatched top level rdf:about values", XMPErrorConstants.Badxmp);
                                }
                            }
                            else
                            {
                                xmpParent.SetName(attribute.Value);
                            }
                        }
                        break;
                    }

                    case RdftermOther:
                    {
                        AddChildNode(xmp, xmpParent, attribute, attribute.Value, isTopLevel);
                        break;
                    }

                    default:
                    {
                        throw new XMPException("Invalid nodeElement attribute", XMPErrorConstants.Badrdf);
                    }
                }
            }
        }
 /// <summary>The main parsing method.</summary>
 /// <remarks>
 /// The main parsing method. The XML tree is walked through from the root node and and XMP tree
 /// is created. This is a raw parse, the normalisation of the XMP tree happens outside.
 /// </remarks>
 /// <param name="xmlRoot">the XML root node</param>
 /// <returns>Returns an XMP metadata object (not normalized)</returns>
 /// <exception cref="Com.Adobe.Xmp.XMPException">Occurs if the parsing fails for any reason.</exception>
 internal static XMPMetaImpl Parse(XmlNode xmlRoot)
 {
     XMPMetaImpl xmp = new XMPMetaImpl();
     Rdf_RDF(xmp, xmlRoot);
     return xmp;
 }
		/// <summary>Utility to find or create the array used by <code>SeparateArrayItems()</code>.</summary>
		/// <param name="schemaNS">a the namespace fo the array</param>
		/// <param name="arrayName">the name of the array</param>
		/// <param name="arrayOptions">the options for the array if newly created</param>
		/// <param name="xmp">the xmp object</param>
		/// <returns>Returns the array node.</returns>
		/// <exception cref="Com.Adobe.Xmp.XMPException">Forwards exceptions</exception>
		private static XMPNode SeparateFindCreateArray(string schemaNS, string arrayName, PropertyOptions arrayOptions, XMPMetaImpl xmp)
		{
			arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null);
			if (!arrayOptions.IsOnlyArrayOptions())
			{
				throw new XMPException("Options can only provide array form", XMPErrorConstants.Badoptions);
			}
			// Find the array node, make sure it is OK. Move the current children
			// aside, to be readded later if kept.
			XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName);
			XMPNode arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, false, null);
			if (arrayNode != null)
			{
				// The array exists, make sure the form is compatible. Zero
				// arrayForm means take what exists.
				PropertyOptions arrayForm = arrayNode.GetOptions();
				if (!arrayForm.IsArray() || arrayForm.IsArrayAlternate())
				{
					throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badxpath);
				}
				if (arrayOptions.EqualArrayTypes(arrayForm))
				{
					throw new XMPException("Mismatch of specified and existing array form", XMPErrorConstants.Badxpath);
				}
			}
			else
			{
				// *** Right error?
				// The array does not exist, try to create it.
				// don't modify the options handed into the method
				arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions.SetArray(true));
				if (arrayNode == null)
				{
					throw new XMPException("Failed to create named array", XMPErrorConstants.Badxpath);
				}
			}
			return arrayNode;
		}
 /// <summary>Visit all schemas to do general fixes and handle special cases.</summary>
 /// <param name="xmp">the metadata object implementation</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if the normalisation fails.</exception>
 private static void TouchUpDataModel(XMPMetaImpl xmp)
 {
     // make sure the DC schema is existing, because it might be needed within the normalization
     // if not touched it will be removed by removeEmptySchemas
     XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), XMPConstConstants.NsDc, true);
     // Do the special case fixes within each schema.
     for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); )
     {
         XMPNode currSchema = (XMPNode)it.Next();
         if (XMPConstConstants.NsDc.Equals(currSchema.GetName()))
         {
             NormalizeDCArrays(currSchema);
         }
         else
         {
             if (XMPConstConstants.NsExif.Equals(currSchema.GetName()))
             {
                 // Do a special case fix for exif:GPSTimeStamp.
                 FixGPSTimeStamp(currSchema);
                 XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                 if (arrayNode != null)
                 {
                     RepairAltText(arrayNode);
                 }
             }
             else
             {
                 if (XMPConstConstants.NsDm.Equals(currSchema.GetName()))
                 {
                     // Do a special case migration of xmpDM:copyright to
                     // dc:rights['x-default'].
                     XMPNode dmCopyright = XMPNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false);
                     if (dmCopyright != null)
                     {
                         MigrateAudioCopyright(xmp, dmCopyright);
                     }
                 }
                 else
                 {
                     if (XMPConstConstants.NsXmpRights.Equals(currSchema.GetName()))
                     {
                         XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false);
                         if (arrayNode != null)
                         {
                             RepairAltText(arrayNode);
                         }
                     }
                 }
             }
         }
     }
 }
 /// <summary>
 /// 7.2.15 resourcePropertyElt
 /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
 /// ws* nodeElement ws
 /// end-element()
 /// This handles structs using an rdf:Description node,
 /// arrays using rdf:Bag/Seq/Alt, and typedNodes.
 /// </summary>
 /// <remarks>
 /// 7.2.15 resourcePropertyElt
 /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
 /// ws* nodeElement ws
 /// end-element()
 /// This handles structs using an rdf:Description node,
 /// arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified
 /// properties written with rdf:Description and rdf:value.
 /// </remarks>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlNode">the currently processed XML node</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
 {
     if (isTopLevel && "iX:changes".Equals(xmlNode.Name))
     {
         // Strip old "punchcard" chaff which has on the prefix "iX:".
         return;
     }
     XMPNode newCompound = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel);
     // walk through the attributes
     for (int i = 0; i < xmlNode.Attributes.Count; i++)
     {
         XmlNode attribute = xmlNode.Attributes.Item(i);
         if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
         {
             continue;
         }
         string attrLocal = attribute.LocalName;
         string attrNS = attribute.NamespaceURI;
         if (XMPConstConstants.XmlLang.Equals(attribute.Name))
         {
             AddQualifierNode(newCompound, XMPConstConstants.XmlLang, attribute.Value);
         }
         else
         {
             if ("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS))
             {
                 continue;
             }
             else
             {
                 // Ignore all rdf:ID attributes.
                 throw new XMPException("Invalid attribute for resource property element", XMPErrorConstants.Badrdf);
             }
         }
     }
     // walk through the children
     XmlNode currChild = null;
     bool found = false;
     int i_1;
     for (i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++)
     {
         currChild = xmlNode.ChildNodes.Item(i_1);
         if (!IsWhitespaceNode(currChild))
         {
             if (currChild.NodeType == System.Xml.XmlNodeType.Element && !found)
             {
                 bool isRDF = XMPConstConstants.NsRdf.Equals(currChild.NamespaceURI);
                 string childLocal = currChild.LocalName;
                 if (isRDF && "Bag".Equals(childLocal))
                 {
                     newCompound.GetOptions().SetArray(true);
                 }
                 else
                 {
                     if (isRDF && "Seq".Equals(childLocal))
                     {
                         newCompound.GetOptions().SetArray(true).SetArrayOrdered(true);
                     }
                     else
                     {
                         if (isRDF && "Alt".Equals(childLocal))
                         {
                             newCompound.GetOptions().SetArray(true).SetArrayOrdered(true).SetArrayAlternate(true);
                         }
                         else
                         {
                             newCompound.GetOptions().SetStruct(true);
                             if (!isRDF && !"Description".Equals(childLocal))
                             {
                                 string typeName = currChild.NamespaceURI;
                                 if (typeName == null)
                                 {
                                     throw new XMPException("All XML elements must be in a namespace", XMPErrorConstants.Badxmp);
                                 }
                                 typeName += ':' + childLocal;
                                 AddQualifierNode(newCompound, "rdf:type", typeName);
                             }
                         }
                     }
                 }
                 Rdf_NodeElement(xmp, newCompound, currChild, false);
                 if (newCompound.GetHasValueChild())
                 {
                     FixupQualifiedNode(newCompound);
                 }
                 else
                 {
                     if (newCompound.GetOptions().IsArrayAlternate())
                     {
                         XMPNodeUtils.DetectAltText(newCompound);
                     }
                 }
                 found = true;
             }
             else
             {
                 if (found)
                 {
                     // found second child element
                     throw new XMPException("Invalid child of resource property element", XMPErrorConstants.Badrdf);
                 }
                 else
                 {
                     throw new XMPException("Children of resource property element must be XML elements", XMPErrorConstants.Badrdf);
                 }
             }
         }
     }
     if (!found)
     {
         // didn't found any child elements
         throw new XMPException("Missing child of resource property element", XMPErrorConstants.Badrdf);
     }
 }
 /// <summary>
 /// 7.2.13 propertyEltList
 /// ws* ( propertyElt ws* )
 /// </summary>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlParent">the currently processed XML node</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlParent, bool isTopLevel)
 {
     for (int i = 0; i < xmlParent.ChildNodes.Count; i++)
     {
         XmlNode currChild = xmlParent.ChildNodes.Item(i);
         if (IsWhitespaceNode(currChild))
         {
             continue;
         }
         else
         {
             if (currChild.NodeType != System.Xml.XmlNodeType.Element)
             {
                 throw new XMPException("Expected property element node not found", XMPErrorConstants.Badrdf);
             }
             else
             {
                 Rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel);
             }
         }
     }
 }
 /// <summary>Adds a child node.</summary>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlNode">the currently processed XML node</param>
 /// <param name="value">Node value</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <returns>Returns the newly created child node.</returns>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static XMPNode AddChildNode(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, string value, bool isTopLevel)
 {
     XMPSchemaRegistry registry = XMPMetaFactory.GetSchemaRegistry();
     string @namespace = xmlNode.NamespaceURI;
     string childName;
     if (@namespace != null)
     {
         if (XMPConstConstants.NsDcDeprecated.Equals(@namespace))
         {
             // Fix a legacy DC namespace
             @namespace = XMPConstConstants.NsDc;
         }
         string prefix = registry.GetNamespacePrefix(@namespace);
         if (prefix == null)
         {
             prefix = xmlNode.Prefix != null ? xmlNode.Prefix : DefaultPrefix;
             prefix = registry.RegisterNamespace(@namespace, prefix);
         }
         childName = prefix + xmlNode.LocalName;
     }
     else
     {
         throw new XMPException("XML namespace required for all elements and attributes", XMPErrorConstants.Badrdf);
     }
     // create schema node if not already there
     PropertyOptions childOptions = new PropertyOptions();
     bool isAlias = false;
     if (isTopLevel)
     {
         // Lookup the schema node, adjust the XMP parent pointer.
         // Incoming parent must be the tree root.
         XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), @namespace, DefaultPrefix, true);
         schemaNode.SetImplicit(false);
         // Clear the implicit node bit.
         // need runtime check for proper 32 bit code.
         xmpParent = schemaNode;
         // If this is an alias set the alias flag in the node
         // and the hasAliases flag in the tree.
         if (registry.FindAlias(childName) != null)
         {
             isAlias = true;
             xmp.GetRoot().SetHasAliases(true);
             schemaNode.SetHasAliases(true);
         }
     }
     // Make sure that this is not a duplicate of a named node.
     bool isArrayItem = "rdf:li".Equals(childName);
     bool isValueNode = "rdf:value".Equals(childName);
     // Create XMP node and so some checks
     XMPNode newChild = new XMPNode(childName, value, childOptions);
     newChild.SetAlias(isAlias);
     // Add the new child to the XMP parent node, a value node first.
     if (!isValueNode)
     {
         xmpParent.AddChild(newChild);
     }
     else
     {
         xmpParent.AddChild(1, newChild);
     }
     if (isValueNode)
     {
         if (isTopLevel || !xmpParent.GetOptions().IsStruct())
         {
             throw new XMPException("Misplaced rdf:value element", XMPErrorConstants.Badrdf);
         }
         xmpParent.SetHasValueChild(true);
     }
     if (isArrayItem)
     {
         if (!xmpParent.GetOptions().IsArray())
         {
             throw new XMPException("Misplaced rdf:li element", XMPErrorConstants.Badrdf);
         }
         newChild.SetName(XMPConstConstants.ArrayItemName);
     }
     return newChild;
 }
 /// <summary>
 /// 7.2.14 propertyElt
 /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
 /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt |
 /// parseTypeOtherPropertyElt | emptyPropertyElt
 /// 7.2.15 resourcePropertyElt
 /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
 /// ws* nodeElement ws
 /// end-element()
 /// 7.2.16 literalPropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
 /// text()
 /// end-element()
 /// 7.2.17 parseTypeLiteralPropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
 /// literal
 /// end-element()
 /// 7.2.18 parseTypeResourcePropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
 /// propertyEltList
 /// end-element()
 /// 7.2.19 parseTypeCollectionPropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
 /// nodeElementList
 /// end-element()
 /// 7.2.20 parseTypeOtherPropertyElt
 /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
 /// propertyEltList
 /// end-element()
 /// 7.2.21 emptyPropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
 /// end-element()
 /// The various property element forms are not distinguished by the XML element name,
 /// but by their attributes for the most part.
 /// </summary>
 /// <remarks>
 /// 7.2.14 propertyElt
 /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
 /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt |
 /// parseTypeOtherPropertyElt | emptyPropertyElt
 /// 7.2.15 resourcePropertyElt
 /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
 /// ws* nodeElement ws
 /// end-element()
 /// 7.2.16 literalPropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
 /// text()
 /// end-element()
 /// 7.2.17 parseTypeLiteralPropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
 /// literal
 /// end-element()
 /// 7.2.18 parseTypeResourcePropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
 /// propertyEltList
 /// end-element()
 /// 7.2.19 parseTypeCollectionPropertyElt
 /// start-element (
 /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
 /// nodeElementList
 /// end-element()
 /// 7.2.20 parseTypeOtherPropertyElt
 /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
 /// propertyEltList
 /// end-element()
 /// 7.2.21 emptyPropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
 /// end-element()
 /// The various property element forms are not distinguished by the XML element name,
 /// but by their attributes for the most part. The exceptions are resourcePropertyElt and
 /// literalPropertyElt. They are distinguished by their XML element content.
 /// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can
 /// appear in many of these. We have to allow for it in the attibute counts below.
 /// </remarks>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlNode">the currently processed XML node</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
 {
     int nodeTerm = GetRDFTermKind(xmlNode);
     if (!IsPropertyElementName(nodeTerm))
     {
         throw new XMPException("Invalid property element name", XMPErrorConstants.Badrdf);
     }
     // remove the namespace-definitions from the list
     XmlAttributeCollection attributes = xmlNode.Attributes;
     IList nsAttrs = null;
     for (int i = 0; i < attributes.Count; i++)
     {
         XmlNode attribute = attributes.Item(i);
         if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
         {
             if (nsAttrs == null)
             {
                 nsAttrs = new ArrayList();
             }
             nsAttrs.Add(attribute.Name);
         }
     }
     if (nsAttrs != null)
     {
         for (Iterator it = nsAttrs.Iterator(); it.HasNext(); )
         {
             string ns = (string)it.Next();
             attributes.RemoveNamedItem(ns);
         }
     }
     if (attributes.Count > 3)
     {
         // Only an emptyPropertyElt can have more than 3 attributes.
         Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
     }
     else
     {
         // Look through the attributes for one that isn't rdf:ID or xml:lang,
         // it will usually tell what we should be dealing with.
         // The called routines must verify their specific syntax!
         for (int i_1 = 0; i_1 < attributes.Count; i_1++)
         {
             XmlNode attribute = attributes.Item(i_1);
             string attrLocal = attribute.LocalName;
             string attrNS = attribute.NamespaceURI;
             string attrValue = attribute.Value;
             if (!(XMPConstConstants.XmlLang.Equals(attribute.Name) && !("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS))))
             {
                 if ("datatype".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS))
                 {
                     Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                 }
                 else
                 {
                     if (!("parseType".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)))
                     {
                         Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                     }
                     else
                     {
                         if ("Literal".Equals(attrValue))
                         {
                             Rdf_ParseTypeLiteralPropertyElement();
                         }
                         else
                         {
                             if ("Resource".Equals(attrValue))
                             {
                                 Rdf_ParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                             }
                             else
                             {
                                 if ("Collection".Equals(attrValue))
                                 {
                                     Rdf_ParseTypeCollectionPropertyElement();
                                 }
                                 else
                                 {
                                     Rdf_ParseTypeOtherPropertyElement();
                                 }
                             }
                         }
                     }
                 }
                 return;
             }
         }
         // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt,
         // or an emptyPropertyElt. Look at the child XML nodes to decide which.
         if (xmlNode.HasChildNodes)
         {
             for (int i_2 = 0; i_2 < xmlNode.ChildNodes.Count; i_2++)
             {
                 XmlNode currChild = xmlNode.ChildNodes.Item(i_2);
                 if (currChild.NodeType != System.Xml.XmlNodeType.Text)
                 {
                     Rdf_ResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                     return;
                 }
             }
             Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
         }
         else
         {
             Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
         }
     }
 }
 /// <summary>Serializes an <code>XMPMeta</code>-object as RDF into a byte buffer.</summary>
 /// <param name="xmp">a metadata implementation object</param>
 /// <param name="options">
 /// Options to control the serialization (see
 /// <see cref="Com.Adobe.Xmp.Options.SerializeOptions"/>
 /// ).
 /// </param>
 /// <returns>Returns a byte buffer containing the serialized RDF.</returns>
 /// <exception cref="Com.Adobe.Xmp.XMPException">on serializsation errors.</exception>
 public static sbyte[] SerializeToBuffer(XMPMetaImpl xmp, SerializeOptions options)
 {
     ByteArrayOutputStream @out = new ByteArrayOutputStream(2048);
     Serialize(xmp, @out, options);
     return @out.ToByteArray();
 }
 /// <summary>
 /// Each of these parsing methods is responsible for recognizing an RDF
 /// syntax production and adding the appropriate structure to the XMP tree.
 /// </summary>
 /// <remarks>
 /// Each of these parsing methods is responsible for recognizing an RDF
 /// syntax production and adding the appropriate structure to the XMP tree.
 /// They simply return for success, failures will throw an exception.
 /// </remarks>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="rdfRdfNode">the top-level xml node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 internal static void Rdf_RDF(XMPMetaImpl xmp, XmlNode rdfRdfNode)
 {
     if (rdfRdfNode.HasAttributes())
     {
         Rdf_NodeElementList(xmp, xmp.GetRoot(), rdfRdfNode);
     }
     else
     {
         throw new XMPException("Invalid attributes of rdf:RDF element", XMPErrorConstants.Badrdf);
     }
 }
 /// <summary>Constructor with optionsl initial values.</summary>
 /// <remarks>
 /// Constructor with optionsl initial values. If <code>propName</code> is provided,
 /// <code>schemaNS</code> has also be provided.
 /// </remarks>
 /// <param name="xmp">the iterated metadata object.</param>
 /// <param name="schemaNS">the iteration is reduced to this schema (optional)</param>
 /// <param name="propPath">the iteration is redurce to this property within the <code>schemaNS</code></param>
 /// <param name="options">
 /// advanced iteration options, see
 /// <see cref="Com.Adobe.Xmp.Options.IteratorOptions"/>
 /// </param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">If the node defined by the paramters is not existing.</exception>
 public XMPIteratorImpl(XMPMetaImpl xmp, string schemaNS, string propPath, IteratorOptions options)
 {
     // make sure that options is defined at least with defaults
     this.options = options != null ? options : new IteratorOptions();
     // the start node of the iteration depending on the schema and property filter
     XMPNode startNode = null;
     string initialPath = null;
     bool baseSchema = schemaNS != null && schemaNS.Length > 0;
     bool baseProperty = propPath != null && propPath.Length > 0;
     if (!baseSchema && !baseProperty)
     {
         // complete tree will be iterated
         startNode = xmp.GetRoot();
     }
     else
     {
         if (baseSchema && baseProperty)
         {
             // Schema and property node provided
             XMPPath path = XMPPathParser.ExpandXPath(schemaNS, propPath);
             // base path is the prop path without the property leaf
             XMPPath basePath = new XMPPath();
             for (int i = 0; i < path.Size() - 1; i++)
             {
                 basePath.Add(path.GetSegment(i));
             }
             startNode = XMPNodeUtils.FindNode(xmp.GetRoot(), path, false, null);
             baseNS = schemaNS;
             initialPath = basePath.ToString();
         }
         else
         {
             if (baseSchema && !baseProperty)
             {
                 // Only Schema provided
                 startNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNS, false);
             }
             else
             {
                 // !baseSchema  &&  baseProperty
                 // No schema but property provided -> error
                 throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema);
             }
         }
     }
     // create iterator
     if (startNode != null)
     {
         if (!this.options.IsJustChildren())
         {
             nodeIterator = new XMPIteratorImpl.NodeIterator(this, startNode, initialPath, 1);
         }
         else
         {
             nodeIterator = new XMPIteratorImpl.NodeIteratorChildren(this, startNode, initialPath);
         }
     }
     else
     {
         // create null iterator
         nodeIterator = Sharpen.Collections.EmptyList().Iterator();
     }
 }
        /// <summary>
        /// 7.2.21 emptyPropertyElt
        /// start-element ( URI == propertyElementURIs,
        /// attributes == set (
        /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
        /// end-element()
        /// <ns:Prop1/>  <!-- a simple property with an empty value -->
        /// <ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value -->
        /// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
        /// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
        /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of
        /// attributes.
        /// </summary>
        /// <remarks>
        /// 7.2.21 emptyPropertyElt
        /// start-element ( URI == propertyElementURIs,
        /// attributes == set (
        /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
        /// end-element()
        /// <ns:Prop1/>  <!-- a simple property with an empty value -->
        /// <ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value -->
        /// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
        /// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
        /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of
        /// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
        /// simple property with an empty value (ns:Prop1), a simple property whose value is a URI
        /// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3).
        /// An emptyPropertyElt can also represent an XMP struct whose fields are all simple and
        /// unqualified (ns:Prop4).
        /// It is an error to use both rdf:value and rdf:resource - that can lead to invalid  RDF in the
        /// verbose form written using a literalPropertyElt.
        /// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for
        /// design reasons and partly for historical reasons. The XMP mapping rules are:
        /// <ol>
        /// <li> If there is an rdf:value attribute then this is a simple property
        /// with a text value.
        /// All other attributes are qualifiers.
        /// <li> If there is an rdf:resource attribute then this is a simple property
        /// with a URI value.
        /// All other attributes are qualifiers.
        /// <li> If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID
        /// then this is a simple
        /// property with an empty value.
        /// <li> Otherwise this is a struct, the attributes other than xml:lang, rdf:ID,
        /// or rdf:nodeID are fields.
        /// </ol>
        /// </remarks>
        /// <param name="xmp">the xmp metadata object that is generated</param>
        /// <param name="xmpParent">the parent xmp node</param>
        /// <param name="xmlNode">the currently processed XML node</param>
        /// <param name="isTopLevel">Flag if the node is a top-level node</param>
        /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
        private static void Rdf_EmptyPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
        {
            bool hasPropertyAttrs = false;
            bool hasResourceAttr = false;
            bool hasNodeIDAttr = false;
            bool hasValueAttr = false;
            XmlNode valueNode = null;
            // ! Can come from rdf:value or rdf:resource.
            if (xmlNode.HasChildNodes)
            {
                throw new XMPException("Nested content not allowed with rdf:resource or property attributes", XMPErrorConstants.Badrdf);
            }
            // First figure out what XMP this maps to and remember the XML node for a simple value.
            for (int i = 0; i < xmlNode.Attributes.Count; i++)
            {
                XmlNode attribute = xmlNode.Attributes.Item(i);
                if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                int attrTerm = GetRDFTermKind(attribute);
                switch (attrTerm)
                {
                    case RdftermId:
                    {
                        // Nothing to do.
                        break;
                    }

                    case RdftermResource:
                    {
                        if (hasNodeIDAttr)
                        {
                            throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf);
                        }
                        else
                        {
                            if (hasValueAttr)
                            {
                                throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp);
                            }
                        }
                        hasResourceAttr = true;
                        if (!hasValueAttr)
                        {
                            valueNode = attribute;
                        }
                        break;
                    }

                    case RdftermNodeId:
                    {
                        if (hasResourceAttr)
                        {
                            throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf);
                        }
                        hasNodeIDAttr = true;
                        break;
                    }

                    case RdftermOther:
                    {
                        if ("value".Equals(attribute.LocalName) && XMPConstConstants.NsRdf.Equals(attribute.NamespaceURI))
                        {
                            if (hasResourceAttr)
                            {
                                throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp);
                            }
                            hasValueAttr = true;
                            valueNode = attribute;
                        }
                        else
                        {
                            if (!XMPConstConstants.XmlLang.Equals(attribute.Name))
                            {
                                hasPropertyAttrs = true;
                            }
                        }
                        break;
                    }

                    default:
                    {
                        throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf);
                    }
                }
            }
            // Create the right kind of child node and visit the attributes again
            // to add the fields or qualifiers.
            // ! Because of implementation vagaries,
            //   the xmpParent is the tree root for top level properties.
            // ! The schema is found, created if necessary, by addChildNode.
            XMPNode childNode = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel);
            bool childIsStruct = false;
            if (hasValueAttr || hasResourceAttr)
            {
                childNode.SetValue(valueNode != null ? valueNode.Value : string.Empty);
                if (!hasValueAttr)
                {
                    // ! Might have both rdf:value and rdf:resource.
                    childNode.GetOptions().SetURI(true);
                }
            }
            else
            {
                if (hasPropertyAttrs)
                {
                    childNode.GetOptions().SetStruct(true);
                    childIsStruct = true;
                }
            }
            for (int i_1 = 0; i_1 < xmlNode.Attributes.Count; i_1++)
            {
                XmlNode attribute = xmlNode.Attributes.Item(i_1);
                if (attribute == valueNode || "xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                // Skip the rdf:value or rdf:resource attribute holding the value.
                int attrTerm = GetRDFTermKind(attribute);
                switch (attrTerm)
                {
                    case RdftermId:
                    case RdftermNodeId:
                    {
                        break;
                    }

                    case RdftermResource:
                    {
                        // Ignore all rdf:ID and rdf:nodeID attributes.
                        AddQualifierNode(childNode, "rdf:resource", attribute.Value);
                        break;
                    }

                    case RdftermOther:
                    {
                        if (!childIsStruct)
                        {
                            AddQualifierNode(childNode, attribute.Name, attribute.Value);
                        }
                        else
                        {
                            if (XMPConstConstants.XmlLang.Equals(attribute.Name))
                            {
                                AddQualifierNode(childNode, XMPConstConstants.XmlLang, attribute.Value);
                            }
                            else
                            {
                                AddChildNode(xmp, childNode, attribute, attribute.Value, false);
                            }
                        }
                        break;
                    }

                    default:
                    {
                        throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf);
                    }
                }
            }
        }
 /// <summary>
 /// 7.2.16 literalPropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, datatypeAttr?) )
 /// text()
 /// end-element()
 /// Add a leaf node with the text value and qualifiers for the attributes.
 /// </summary>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlNode">the currently processed XML node</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_LiteralPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
 {
     XMPNode newChild = AddChildNode(xmp, xmpParent, xmlNode, null, isTopLevel);
     for (int i = 0; i < xmlNode.Attributes.Count; i++)
     {
         XmlNode attribute = xmlNode.Attributes.Item(i);
         if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
         {
             continue;
         }
         string attrNS = attribute.NamespaceURI;
         string attrLocal = attribute.LocalName;
         if (XMPConstConstants.XmlLang.Equals(attribute.Name))
         {
             AddQualifierNode(newChild, XMPConstConstants.XmlLang, attribute.Value);
         }
         else
         {
             if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "datatype".Equals(attrLocal)))
             {
                 continue;
             }
             else
             {
                 // Ignore all rdf:ID and rdf:datatype attributes.
                 throw new XMPException("Invalid attribute for literal property element", XMPErrorConstants.Badrdf);
             }
         }
     }
     string textValue = string.Empty;
     for (int i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++)
     {
         XmlNode child = xmlNode.ChildNodes.Item(i_1);
         if (child.NodeType == System.Xml.XmlNodeType.Text)
         {
             textValue += child.Value;
         }
         else
         {
             throw new XMPException("Invalid child of literal property element", XMPErrorConstants.Badrdf);
         }
     }
     newChild.SetValue(textValue);
 }
 /// <summary>
 /// 7.2.5 nodeElementURIs
 /// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
 /// 7.2.11 nodeElement
 /// start-element ( URI == nodeElementURIs,
 /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
 /// propertyEltList
 /// end-element()
 /// A node element URI is rdf:Description or anything else that is not an RDF
 /// term.
 /// </summary>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="xmlNode">the currently processed XML node</param>
 /// <param name="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
 private static void Rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
 {
     int nodeTerm = GetRDFTermKind(xmlNode);
     if (nodeTerm != RdftermDescription && nodeTerm != RdftermOther)
     {
         throw new XMPException("Node element must be rdf:Description or typed node", XMPErrorConstants.Badrdf);
     }
     else
     {
         if (isTopLevel && nodeTerm == RdftermOther)
         {
             throw new XMPException("Top level typed node not allowed", XMPErrorConstants.Badxmp);
         }
         else
         {
             Rdf_NodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel);
             Rdf_PropertyElementList(xmp, xmpParent, xmlNode, isTopLevel);
         }
     }
 }
		/// <seealso cref="AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool, bool)"/>
		/// <param name="destXMP">The destination XMP object.</param>
		/// <param name="sourceNode">the source node</param>
		/// <param name="destParent">the parent of the destination node</param>
		/// <param name="replaceOldValues">Replace the values of existing properties.</param>
		/// <param name="deleteEmptyValues">
		/// flag if properties with empty values should be deleted
		/// in the destination object.
		/// </param>
		/// <exception cref="Com.Adobe.Xmp.XMPException"/>
		private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues)
		{
			XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false);
			bool valueIsEmpty = false;
			if (deleteEmptyValues)
			{
				valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !sourceNode.HasChildren();
			}
			if (deleteEmptyValues && valueIsEmpty)
			{
				if (destNode != null)
				{
					destParent.RemoveChild(destNode);
				}
			}
			else
			{
				if (destNode == null)
				{
					// The one easy case, the destination does not exist.
					destParent.AddChild((XMPNode)sourceNode.Clone());
				}
				else
				{
					if (replaceOldValues)
					{
						// The destination exists and should be replaced.
						destXMP.SetNode(destNode, sourceNode.GetValue(), sourceNode.GetOptions(), true);
						destParent.RemoveChild(destNode);
						destNode = (XMPNode)sourceNode.Clone();
						destParent.AddChild(destNode);
					}
					else
					{
						// The destination exists and is not totally replaced. Structs and
						// arrays are merged.
						PropertyOptions sourceForm = sourceNode.GetOptions();
						PropertyOptions destForm = destNode.GetOptions();
						if (sourceForm != destForm)
						{
							return;
						}
						if (sourceForm.IsStruct())
						{
							// To merge a struct process the fields recursively. E.g. add simple missing fields.
							// The recursive call to AppendSubtree will handle deletion for fields with empty 
							// values.
							for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); )
							{
								XMPNode sourceField = (XMPNode)it.Next();
								AppendSubtree(destXMP, sourceField, destNode, replaceOldValues, deleteEmptyValues);
								if (deleteEmptyValues && !destNode.HasChildren())
								{
									destParent.RemoveChild(destNode);
								}
							}
						}
						else
						{
							if (sourceForm.IsArrayAltText())
							{
								// Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. 
								// Make a special check for deletion of empty values. Meaningful in AltText arrays 
								// because the "xml:lang" qualifier provides unambiguous source/dest correspondence.
								for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); )
								{
									XMPNode sourceItem = (XMPNode)it.Next();
									if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName()))
									{
										continue;
									}
									int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue());
									if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0))
									{
										if (destIndex != -1)
										{
											destNode.RemoveChild(destIndex);
											if (!destNode.HasChildren())
											{
												destParent.RemoveChild(destNode);
											}
										}
									}
									else
									{
										if (destIndex == -1)
										{
											// Not replacing, keep the existing item.						
											if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren())
											{
												sourceItem.CloneSubtree(destNode);
											}
											else
											{
												XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions());
												sourceItem.CloneSubtree(destItem);
												destNode.AddChild(1, destItem);
											}
										}
									}
								}
							}
							else
							{
								if (sourceForm.IsArray())
								{
									// Merge other arrays by item values. Don't worry about order or duplicates. Source 
									// items with empty values do not cause deletion, that conflicts horribly with 
									// merging.
									for (Iterator @is = sourceNode.IterateChildren(); @is.HasNext(); )
									{
										XMPNode sourceItem = (XMPNode)@is.Next();
										bool match = false;
										for (Iterator id = destNode.IterateChildren(); id.HasNext(); )
										{
											XMPNode destItem = (XMPNode)id.Next();
											if (ItemValuesMatch(sourceItem, destItem))
											{
												match = true;
											}
										}
										if (!match)
										{
											destNode = (XMPNode)sourceItem.Clone();
											destParent.AddChild(destNode);
										}
									}
								}
							}
						}
					}
				}
			}
		}
		// UTF-8
		/// <summary>The actual serialization.</summary>
		/// <param name="xmp">the metadata object to be serialized</param>
		/// <param name="out">outputStream the output stream to serialize to</param>
		/// <param name="options">the serialization options</param>
		/// <exception cref="Com.Adobe.Xmp.XMPException">If case of wrong options or any other serialization error.</exception>
		public virtual void Serialize(XMPMeta xmp, OutputStream @out, SerializeOptions options)
		{
			try
			{
				outputStream = new CountOutputStream(@out);
				writer = new OutputStreamWriter(outputStream, options.GetEncoding());
				this.xmp = (XMPMetaImpl)xmp;
				this.options = options;
				this.padding = options.GetPadding();
				writer = new OutputStreamWriter(outputStream, options.GetEncoding());
				CheckOptionsConsistence();
				// serializes the whole packet, but don't write the tail yet 
				// and flush to make sure that the written bytes are calculated correctly
				string tailStr = SerializeAsRDF();
				writer.Flush();
				// adds padding
				AddPadding(tailStr.Length);
				// writes the tail
				Write(tailStr);
				writer.Flush();
				outputStream.Close();
			}
			catch (IOException)
			{
				throw new XMPException("Error writing to the OutputStream", XMPErrorConstants.Unknown);
			}
		}