/// <seealso cref= XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean) </seealso> /// <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"> </exception> private static void AppendSubtree(XmpMetaImpl destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XmpNode destNode = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.Options.Simple ? 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. PropertyOptions sourceForm = sourceNode.Options; PropertyOptions destForm = destNode.Options; if (sourceForm != destForm) { return; } if (sourceForm.Struct) { // 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 (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceField = (XmpNode) it.Current; if (sourceField == null) continue; AppendSubtree(destXmp, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (sourceForm.ArrayAltText) { // 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 (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceItem = (XmpNode) it.Current; if (sourceItem == null) continue; if (!sourceItem.HasQualifier() || !XML_LANG.Equals(sourceItem.GetQualifier(1).Name)) { continue; } int 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 (!X_DEFAULT.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XmpNode destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } else if (sourceForm.Array) { // 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 (IEnumerator @is = sourceNode.IterateChildren(); @is.MoveNext();) { XmpNode sourceItem = (XmpNode) @is.Current; if (sourceItem == null) continue; bool match = false; for (IEnumerator id = destNode.IterateChildren(); id.MoveNext();) { XmpNode destItem = (XmpNode) id.Current; if (destItem == null) continue; if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XmpNode) sourceItem.Clone(); destParent.AddChild(destNode); } } } } }
/// <seealso cref= XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean) </seealso> /// <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"> </exception> private static void AppendSubtree(XmpMetaImpl destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XmpNode destNode = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.Options.Simple ? 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. PropertyOptions sourceForm = sourceNode.Options; PropertyOptions destForm = destNode.Options; if (sourceForm != destForm) { return; } if (sourceForm.Struct) { // 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 (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceField = (XmpNode)it.Current; if (sourceField == null) { continue; } AppendSubtree(destXmp, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (sourceForm.ArrayAltText) { // 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 (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceItem = (XmpNode)it.Current; if (sourceItem == null) { continue; } if (!sourceItem.HasQualifier() || !XML_LANG.Equals(sourceItem.GetQualifier(1).Name)) { continue; } int 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 (!X_DEFAULT.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XmpNode destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } else if (sourceForm.Array) { // 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 (IEnumerator @is = sourceNode.IterateChildren(); @is.MoveNext();) { XmpNode sourceItem = (XmpNode)@is.Current; if (sourceItem == null) { continue; } bool match = false; for (IEnumerator id = destNode.IterateChildren(); id.MoveNext();) { XmpNode destItem = (XmpNode)id.Current; if (destItem == null) { continue; } if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XmpNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } }