/// <summary> /// Make sure the x-default item is first. Touch up "single value" /// arrays that have a default plus one real language. This case should have /// the same value for both items. Older Adobe apps were hardwired to only /// use the "x-default" item, so we copy that value to the other /// item. /// </summary> /// <param name="arrayNode"> /// an alt text array node </param> internal static void NormalizeLangArray(XmpNode arrayNode) { if (!arrayNode.Options.ArrayAltText) { return; } // check if node with x-default qual is first place for (int i = 2; i <= arrayNode.ChildrenLength; i++) { XmpNode child = arrayNode.GetChild(i); if (child.HasQualifier() && X_DEFAULT.Equals(child.GetQualifier(1).Value)) { // move node to first place try { arrayNode.RemoveChild(i); arrayNode.AddChild(1, child); } catch (XmpException) { // cannot occur, because same child is removed before Debug.Assert(false); } if (i == 2) { arrayNode.GetChild(2).Value = child.Value; } break; } } }
/// <summary> /// Looks for the appropriate language item in a text alternative array.item /// </summary> /// <param name="arrayNode"> /// an array node </param> /// <param name="language"> /// the requested language </param> /// <returns> Returns the index if the language has been found, -1 otherwise. </returns> /// <exception cref="XmpException"> </exception> internal static int LookupLanguageItem(XmpNode arrayNode, string language) { if (!arrayNode.Options.Array) { throw new XmpException("Language item must be used on array", XmpError.BADXPATH); } for (int index = 1; index <= arrayNode.ChildrenLength; index++) { XmpNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XML_LANG.Equals(child.GetQualifier(1).Name)) { continue; } if (language.Equals(child.GetQualifier(1).Value)) { return(index); } } return(-1); }
/// <summary> /// Compares two nodes including its children and qualifier. </summary> /// <param name="leftNode"> an <code>XMPNode</code> </param> /// <param name="rightNode"> an <code>XMPNode</code> </param> /// <returns> Returns true if the nodes are equal, false otherwise. </returns> /// <exception cref="XmpException"> Forwards exceptions to the calling method. </exception> private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode) { PropertyOptions leftForm = leftNode.Options; PropertyOptions rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) { return false; } if (leftForm.Options == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.Value.Equals(rightNode.Value)) { return false; } if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage) { return false; } if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value)) { return false; } } else if (leftForm.Struct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.ChildrenLength != rightNode.ChildrenLength) { return false; } for (IEnumerator it = leftNode.IterateChildren(); it.MoveNext();) { XmpNode leftField = (XmpNode) it.Current; if (leftField == null) continue; XmpNode rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, false); if (rightField == null || !ItemValuesMatch(leftField, rightField)) { return false; } } } else { // Array nodes, see if the "leftNode" values are present in the // "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the // destination for AppendProperties. Debug.Assert(leftForm.Array); for (IEnumerator il = leftNode.IterateChildren(); il.MoveNext();) { XmpNode leftItem = (XmpNode) il.Current; if (leftItem == null) continue; bool match = false; for (IEnumerator ir = rightNode.IterateChildren(); ir.MoveNext();) { XmpNode rightItem = (XmpNode) ir.Current; if (rightItem == null) continue; if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return false; } } } return true; // All of the checks passed. }
/// <summary> /// <ol> /// <li>Look for an exact match with the specific language. /// <li>If a generic language is given, look for partial matches. /// <li>Look for an "x-default"-item. /// <li>Choose the first item. /// </ol> /// </summary> /// <param name="arrayNode"> /// the alt text array node </param> /// <param name="genericLang"> /// the generic language </param> /// <param name="specificLang"> /// the specific language </param> /// <returns> Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="XmpException"> </exception> internal static object[] ChooseLocalizedText(XmpNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.Options.ArrayAltText) { throw new XmpException("Localized text array is not alt-text", XmpError.BADXPATH); } if (!arrayNode.HasChildren()) { return(new object[] { CLT_NO_VALUES, null }); } int foundGenericMatches = 0; XmpNode resultNode = null; XmpNode xDefault = null; // Look for the first partial match with the generic language. for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode currItem = (XmpNode)it.Current; // perform some checks on the current item if (currItem == null || currItem.Options == null || currItem.Options.CompositeProperty) { throw new XmpException("Alt-text array item is not simple", XmpError.BADXPATH); } if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name)) { throw new XmpException("Alt-text array item has no language qualifier", XmpError.BADXPATH); } string currLang = currItem.GetQualifier(1).Value; // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return(new object[] { CLT_SPECIFIC_MATCH, currItem }); } if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else if (X_DEFAULT.Equals(currLang)) { xDefault = currItem; } } // evaluate loop if (foundGenericMatches == 1) { return(new object[] { CLT_SINGLE_GENERIC, resultNode }); } if (foundGenericMatches > 1) { return(new object[] { CLT_MULTIPLE_GENERIC, resultNode }); } if (xDefault != null) { return(new object[] { CLT_XDEFAULT, xDefault }); } { // Everything failed, choose the first item. return(new object[] { CLT_FIRST_ITEM, arrayNode.GetChild(1) }); } }
/// <summary> /// Compares two nodes including its children and qualifier. </summary> /// <param name="leftNode"> an <code>XMPNode</code> </param> /// <param name="rightNode"> an <code>XMPNode</code> </param> /// <returns> Returns true if the nodes are equal, false otherwise. </returns> /// <exception cref="XmpException"> Forwards exceptions to the calling method. </exception> private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode) { PropertyOptions leftForm = leftNode.Options; PropertyOptions rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) { return(false); } if (leftForm.Options == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.Value.Equals(rightNode.Value)) { return(false); } if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage) { return(false); } if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value)) { return(false); } } else if (leftForm.Struct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.ChildrenLength != rightNode.ChildrenLength) { return(false); } for (IEnumerator it = leftNode.IterateChildren(); it.MoveNext();) { XmpNode leftField = (XmpNode)it.Current; if (leftField == null) { continue; } XmpNode rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, false); if (rightField == null || !ItemValuesMatch(leftField, rightField)) { return(false); } } } else { // Array nodes, see if the "leftNode" values are present in the // "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the // destination for AppendProperties. Debug.Assert(leftForm.Array); for (IEnumerator il = leftNode.IterateChildren(); il.MoveNext();) { XmpNode leftItem = (XmpNode)il.Current; if (leftItem == null) { continue; } bool match = false; for (IEnumerator ir = rightNode.IterateChildren(); ir.MoveNext();) { XmpNode rightItem = (XmpNode)ir.Current; if (rightItem == null) { continue; } if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return(false); } } } return(true); // All of the checks passed. }
/// <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); } } } } }