/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// No implicit nodes are created for qualifier selectors, /// except for an alias to an x-default item. /// </summary> /// <param name="arrayNode"> an array node </param> /// <param name="qualName"> the qualifier name </param> /// <param name="qualValue"> the qualifier value </param> /// <param name="aliasForm"> in case the qual selector results from an alias, /// an x-default node is created if there has not been one. </param> /// <returns> Returns the index of th </returns> /// <exception cref="XmpException"> </exception> private static int LookupQualSelector(XmpNode arrayNode, string qualName, string qualValue, uint aliasForm) { if (XML_LANG.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PROP_ARRAY_ALT_TEXT) > 0) { XmpNode langNode = new XmpNode(ARRAY_ITEM_NAME, null); XmpNode xdefault = new XmpNode(XML_LANG, X_DEFAULT, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return(1); } return(index); } for (int index = 1; index < arrayNode.ChildrenLength; index++) { XmpNode currItem = arrayNode.GetChild(index); for (IEnumerator it = currItem.IterateQualifier(); it.MoveNext();) { XmpNode qualifier = (XmpNode)it.Current; if (qualifier != null && qualName.Equals(qualifier.Name) && qualValue.Equals(qualifier.Value)) { return(index); } } } return(-1); }
/// <summary>This is setting the value of a leaf node.</summary> /// <param name="node">an XMPNode</param> /// <param name="value">a value</param> internal static void SetNodeValue(XMPNode node, Object value) { String strValue = SerializeNodeValue(value); if (!(node.GetOptions().IsQualifier() && XML_LANG.Equals(node.GetName()))) { node.SetValue(strValue); } else { node.SetValue(iText.Kernel.XMP.Impl.Utils.NormalizeLangValue(strValue)); } }
/// <summary> /// This is setting the value of a leaf node. /// </summary> /// <param name="node"> an XMPNode </param> /// <param name="value"> a value </param> internal static void SetNodeValue(XmpNode node, object value) { string strValue = SerializeNodeValue(value); if (!(node.Options.Qualifier && XML_LANG.Equals(node.Name))) { node.Value = strValue; } else { node.Value = Utils.NormalizeLangValue(strValue); } }
/// <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>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="iText.Kernel.XMP.XMPException"/> internal static int LookupLanguageItem(XMPNode arrayNode, String language) { if (!arrayNode.GetOptions().IsArray()) { throw new XMPException("Language item must be used on array", XMPError.BADXPATH); } for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) { XMPNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XML_LANG.Equals(child.GetQualifier(1).GetName())) { continue; } else { if (language.Equals(child.GetQualifier(1).GetValue())) { return(index); } } } return(-1); }
/// <summary> /// <ol> /// <li>Look for an exact match with the specific language. /// </summary> /// <remarks> /// <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> /// </remarks> /// <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="iText.Kernel.XMP.XMPException"/> 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.GetOptions().IsArrayAltText()) { throw new XMPException("Localized text array is not alt-text", XMPError.BADXPATH); } else { if (!arrayNode.HasChildren()) { return(new Object[] { XMPNodeUtils.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.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPError.BADXPATH); } else { if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).GetName ())) { throw new XMPException("Alt-text array item has no language qualifier", XMPError. BADXPATH); } } String currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return(new Object[] { XMPNodeUtils.CLT_SPECIFIC_MATCH, currItem }); } else { 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[] { XMPNodeUtils.CLT_SINGLE_GENERIC, resultNode }); } else { if (foundGenericMatches > 1) { return(new Object[] { XMPNodeUtils.CLT_MULTIPLE_GENERIC, resultNode }); } else { if (xDefault != null) { return(new Object[] { XMPNodeUtils.CLT_XDEFAULT, xDefault }); } else { // Everything failed, choose the first item. return(new Object[] { XMPNodeUtils.CLT_FIRST_ITEM, arrayNode.GetChild(1) }); } } } }
/// <seealso cref= XMPMeta#setLocalizedText(String, String, String, String, String, /// PropertyOptions) </seealso> public virtual void SetLocalizedText(string schemaNs, string altTextName, string genericLang, string specificLang, string itemValue, PropertyOptions options) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(altTextName); ParameterAsserts.AssertSpecificLang(specificLang); genericLang = genericLang != null?Utils.NormalizeLangValue(genericLang) : null; specificLang = Utils.NormalizeLangValue(specificLang); XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, altTextName); // Find the array node and set the options if it was just created. XmpNode arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, true, new PropertyOptions(PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED | PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT)); if (arrayNode == null) { throw new XmpException("Failed to find or create array node", XmpError.BADXPATH); } if (!arrayNode.Options.ArrayAltText) { if (!arrayNode.HasChildren() && arrayNode.Options.ArrayAlternate) { arrayNode.Options.ArrayAltText = true; } else { throw new XmpException("Specified property is no alt-text array", XmpError.BADXPATH); } } // Make sure the x-default item, if any, is first. bool haveXDefault = false; XmpNode xdItem = null; foreach (XmpNode currItem in arrayNode.Children) { if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name)) { throw new XmpException("Language qualifier must be first", XmpError.BADXPATH); } if (X_DEFAULT.Equals(currItem.GetQualifier(1).Value)) { xdItem = currItem; haveXDefault = true; break; } } // Moves x-default to the beginning of the array if (xdItem != null && arrayNode.ChildrenLength > 1) { arrayNode.RemoveChild(xdItem); arrayNode.AddChild(1, xdItem); } // Find the appropriate item. // chooseLocalizedText will make sure the array is a language // alternative. object[] result = XmpNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); int match = (int)((int?)result[0]); XmpNode itemNode = (XmpNode)result[1]; bool specificXDefault = X_DEFAULT.Equals(specificLang); switch (match) { case XmpNodeUtils.CLT_NO_VALUES: // Create the array items for the specificLang and x-default, with // x-default first. XmpNodeUtils.AppendLangItem(arrayNode, X_DEFAULT, itemValue); haveXDefault = true; if (!specificXDefault) { XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); } break; case XmpNodeUtils.CLT_SPECIFIC_MATCH: if (!specificXDefault) { // Update the specific item, update x-default if it matches the // old value. if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value.Equals(itemNode.Value)) { xdItem.Value = itemValue; } // ! Do this after the x-default check! itemNode.Value = itemValue; } else { // Update all items whose values match the old x-default value. Debug.Assert(haveXDefault && xdItem == itemNode); foreach (XmpNode currItem in arrayNode.Children) { if (currItem == xdItem || !currItem.Value.Equals(xdItem != null ? xdItem.Value : null)) { continue; } currItem.Value = itemValue; } // And finally do the x-default item. if (xdItem != null) { xdItem.Value = itemValue; } } break; case XmpNodeUtils.CLT_SINGLE_GENERIC: // Update the generic item, update x-default if it matches the old // value. if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value.Equals(itemNode.Value)) { xdItem.Value = itemValue; } itemNode.Value = itemValue; // ! Do this after // the x-default // check! break; case XmpNodeUtils.CLT_MULTIPLE_GENERIC: // Create the specific language, ignore x-default. XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); if (specificXDefault) { haveXDefault = true; } break; case XmpNodeUtils.CLT_XDEFAULT: // Create the specific language, update x-default if it was the only // item. if (xdItem != null && arrayNode.ChildrenLength == 1) { xdItem.Value = itemValue; } XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); break; case XmpNodeUtils.CLT_FIRST_ITEM: // Create the specific language, don't add an x-default item. XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); if (specificXDefault) { haveXDefault = true; } break; default: // does not happen under normal circumstances throw new XmpException("Unexpected result from ChooseLocalizedText", XmpError.INTERNALFAILURE); } // Add an x-default at the front if needed. if (!haveXDefault && arrayNode.ChildrenLength == 1) { XmpNodeUtils.AppendLangItem(arrayNode, X_DEFAULT, itemValue); } }
/// <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); } } } } }