/// <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)); }