/// <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="XmpException"/>
        private static void AppendSubtree(XmpMeta destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues)
        {
            var destNode     = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false);
            var valueIsEmpty = false;

            if (deleteEmptyValues)
            {
                valueIsEmpty = sourceNode.Options.IsSimple ? string.IsNullOrEmpty(sourceNode.Value) : !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.Value, sourceNode.Options, 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.
                        var sourceForm = sourceNode.Options;
                        var destForm   = destNode.Options;
                        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 (var it = sourceNode.IterateChildren(); it.HasNext();)
                            {
                                var 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 (var it = sourceNode.IterateChildren(); it.HasNext();)
                            {
                                var sourceItem = (XmpNode)it.Next();

                                if (!sourceItem.HasQualifier || !XmpConstants.XmlLang.Equals(sourceItem.GetQualifier(1).Name))
                                {
                                    continue;
                                }

                                var destIndex = XmpNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).Value);
                                if (deleteEmptyValues && string.IsNullOrEmpty(sourceItem.Value))
                                {
                                    if (destIndex != -1)
                                    {
                                        destNode.RemoveChild(destIndex);
                                        if (!destNode.HasChildren)
                                        {
                                            destParent.RemoveChild(destNode);
                                        }
                                    }
                                }
                                else if (destIndex == -1)
                                {
                                    // Not replacing, keep the existing item.
                                    if (!XmpConstants.XDefault.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren)
                                    {
                                        sourceItem.CloneSubtree(destNode);
                                    }
                                    else
                                    {
                                        var destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options);
                                        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 (var children = sourceNode.IterateChildren(); children.HasNext();)
                            {
                                var sourceItem = (XmpNode)children.Next();

                                var match = false;
                                for (var id = destNode.IterateChildren(); id.HasNext();)
                                {
                                    var destItem = (XmpNode)id.Next();
                                    if (ItemValuesMatch(sourceItem, destItem))
                                    {
                                        match = true;
                                    }
                                }

                                if (!match)
                                {
                                    destNode = (XmpNode)sourceItem.Clone();
                                    destParent.AddChild(destNode);
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>Performs a deep clone of the XMPMeta-object</summary>
        public object Clone()
        {
            var clonedTree = (XmpNode)_tree.Clone();

            return(new XmpMeta(clonedTree));
        }