/// <summary>Find or create a schema node if <code>createNodes</code> is true.</summary> /// <param name="tree">the root of the xmp tree.</param> /// <param name="namespaceURI">a namespace</param> /// <param name="suggestedPrefix">If a prefix is suggested, the namespace is allowed to be registered.</param> /// <param name="createNodes"> /// a flag indicating if the node shall be created if not found. /// <em>Note:</em> The namespace must be registered prior to this call. /// </param> /// <returns> /// Returns the schema node if found, <code>null</code> otherwise. /// Note: If <code>createNodes</code> is <code>true</code>, it is <b>always</b> /// returned a valid node. /// </returns> /// <exception cref="Com.Adobe.Xmp.XMPException"> /// An exception is only thrown if an error occurred, not if a /// node was not found. /// </exception> internal static XMPNode FindSchemaNode(XMPNode tree, string namespaceURI, string suggestedPrefix, bool createNodes) { System.Diagnostics.Debug.Assert(tree.GetParent() == null); // make sure that its the root XMPNode schemaNode = tree.FindChildByName(namespaceURI); if (schemaNode == null && createNodes) { schemaNode = new XMPNode(namespaceURI, new PropertyOptions().SetSchemaNode(true)); schemaNode.SetImplicit(true); // only previously registered schema namespaces are allowed in the XMP tree. string prefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(namespaceURI); if (prefix == null) { if (suggestedPrefix != null && suggestedPrefix.Length != 0) { prefix = XMPMetaFactory.GetSchemaRegistry().RegisterNamespace(namespaceURI, suggestedPrefix); } else { throw new XMPException("Unregistered schema namespace URI", XMPErrorConstants.Badschema); } } schemaNode.SetValue(prefix); tree.AddChild(schemaNode); } return schemaNode; }
/// <summary> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> property. /// </summary> /// <remarks> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> property. An old instance ID usually looks /// like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign /// 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If /// the name looks like a UUID simply move it to <em>xmpMM:InstanceID</em>, /// don't worry about any existing <em>xmpMM:InstanceID</em>. Both will /// only be present when a newer file with the <em>xmpMM:InstanceID</em> /// property is updated by an old app that uses <em>rdf:about</em>. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if tweaking fails.</exception> private static void TweakOldXMP(XMPNode tree) { if (tree.GetName() != null && tree.GetName().Length >= Utils.UuidLength) { string nameStr = tree.GetName().ToLower(); if (nameStr.StartsWith("uuid:")) { nameStr = Sharpen.Runtime.Substring(nameStr, 5); } if (Utils.CheckUUIDFormat(nameStr)) { // move UUID to xmpMM:InstanceID and remove it from the root node XMPPath path = XMPPathParser.ExpandXPath(XMPConstConstants.NsXmpMm, "InstanceID"); XMPNode idNode = XMPNodeUtils.FindNode(tree, path, true, null); if (idNode != null) { idNode.SetOptions(null); // Clobber any existing xmpMM:InstanceID. idNode.SetValue("uuid:" + nameStr); idNode.RemoveChildren(); idNode.RemoveQualifiers(); tree.SetName(null); } else { throw new XMPException("Failure creating xmpMM:InstanceID", XMPErrorConstants.Internalfailure); } } } }
/// <summary>Appends a language item to an alt text array.</summary> /// <param name="arrayNode">the language array</param> /// <param name="itemLang">the language of the item</param> /// <param name="itemValue">the content of the item</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if a duplicate property is added</exception> internal static void AppendLangItem(XMPNode arrayNode, string itemLang, string itemValue) { XMPNode newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, itemLang, null); newItem.AddQualifier(langQual); if (!XMPConstConstants.XDefault.Equals(langQual.GetValue())) { arrayNode.AddChild(newItem); } else { arrayNode.AddChild(1, newItem); } }
/// <summary>Deletes the the given node and its children from its parent.</summary> /// <remarks> /// Deletes the the given node and its children from its parent. /// Takes care about adjusting the flags. /// </remarks> /// <param name="node">the top-most node to delete.</param> internal static void DeleteNode(XMPNode node) { XMPNode parent = node.GetParent(); if (node.GetOptions().IsQualifier()) { // root is qualifier parent.RemoveQualifier(node); } else { // root is NO qualifier parent.RemoveChild(node); } // delete empty Schema nodes if (!parent.HasChildren() && parent.GetOptions().IsSchemaNode()) { parent.GetParent().RemoveChild(parent); } }
/// <summary> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br /> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. /// </summary> /// <remarks> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br /> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. <br /> /// The <code>xml:lang</code> attribute was dropped from an /// <code>alt-text</code> item if the language was <code>x-default</code>. /// </remarks> /// <param name="dcSchema">the DC schema node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if normalization fails</exception> private static void NormalizeDCArrays(XMPNode dcSchema) { for (int i = 1; i <= dcSchema.GetChildrenLength(); i++) { XMPNode currProp = dcSchema.GetChild(i); PropertyOptions arrayForm = (PropertyOptions)dcArrayForms.Get(currProp.GetName()); if (arrayForm == null) { continue; } else { if (currProp.GetOptions().IsSimple()) { // create a new array and add the current property as child, // if it was formerly simple XMPNode newArray = new XMPNode(currProp.GetName(), arrayForm); currProp.SetName(XMPConstConstants.ArrayItemName); newArray.AddChild(currProp); dcSchema.ReplaceChild(i, newArray); // fix language alternatives if (arrayForm.IsArrayAltText() && !currProp.GetOptions().GetHasLanguage()) { XMPNode newLang = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); currProp.AddQualifier(newLang); } } else { // clear array options and add corrected array form if it has been an array before currProp.GetOptions().SetOption(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText, false); currProp.GetOptions().MergeWith(arrayForm); if (arrayForm.IsArrayAltText()) { // applying for "dc:description", "dc:rights", "dc:title" RepairAltText(currProp); } } } } }
/// <summary>See if an array is an alt-text array.</summary> /// <remarks> /// See if an array is an alt-text array. If so, make sure the x-default item /// is first. /// </remarks> /// <param name="arrayNode">the array node to check if its an alt-text array</param> internal static void DetectAltText(XMPNode arrayNode) { if (arrayNode.GetOptions().IsArrayAlternate() && arrayNode.HasChildren()) { bool isAltText = false; for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode child = (XMPNode)it.Next(); if (child.GetOptions().GetHasLanguage()) { isAltText = true; break; } } if (isAltText) { arrayNode.GetOptions().SetArrayAltText(true); NormalizeLangArray(arrayNode); } } }
/// <summary>Utility to find or create the array used by <code>SeparateArrayItems()</code>.</summary> /// <param name="schemaNS">a the namespace fo the array</param> /// <param name="arrayName">the name of the array</param> /// <param name="arrayOptions">the options for the array if newly created</param> /// <param name="xmp">the xmp object</param> /// <returns>Returns the array node.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards exceptions</exception> private static XMPNode SeparateFindCreateArray(string schemaNS, string arrayName, PropertyOptions arrayOptions, XMPMetaImpl xmp) { arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null); if (!arrayOptions.IsOnlyArrayOptions()) { throw new XMPException("Options can only provide array form", XMPErrorConstants.Badoptions); } // Find the array node, make sure it is OK. Move the current children // aside, to be readded later if kept. XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); XMPNode arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, false, null); if (arrayNode != null) { // The array exists, make sure the form is compatible. Zero // arrayForm means take what exists. PropertyOptions arrayForm = arrayNode.GetOptions(); if (!arrayForm.IsArray() || arrayForm.IsArrayAlternate()) { throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badxpath); } if (arrayOptions.EqualArrayTypes(arrayForm)) { throw new XMPException("Mismatch of specified and existing array form", XMPErrorConstants.Badxpath); } } else { // *** Right error? // The array does not exist, try to create it. // don't modify the options handed into the method arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions.SetArray(true)); if (arrayNode == null) { throw new XMPException("Failed to create named array", XMPErrorConstants.Badxpath); } } return(arrayNode); }
/// <param name="currNode">the node that will be added to the path.</param> /// <param name="parentPath">the path up to this node.</param> /// <param name="currentIndex">the current array index if an arrey is traversed</param> /// <returns>Returns the updated path.</returns> protected internal virtual string AccumulatePath(XMPNode currNode, string parentPath, int currentIndex) { string separator; string segmentName; if (currNode.GetParent() == null || currNode.GetOptions().IsSchemaNode()) { return(null); } else { if (currNode.GetParent().GetOptions().IsArray()) { separator = string.Empty; segmentName = "[" + currentIndex.ToString() + "]"; } else { separator = "/"; segmentName = currNode.GetName(); } } if (parentPath == null || parentPath.Length == 0) { return(segmentName); } else { if (this._enclosing.GetOptions().IsJustLeafname()) { return(!segmentName.StartsWith("?") ? segmentName : Sharpen.Runtime.Substring(segmentName, 1)); } else { // qualifier return(parentPath + separator + segmentName); } } }
/// <summary>Fixes the GPS Timestamp in EXIF.</summary> /// <param name="exifSchema">the EXIF schema node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if the date conversion fails.</exception> private static void FixGPSTimeStamp(XMPNode exifSchema) { // Note: if dates are not found the convert-methods throws an exceptions, // and this methods returns. XMPNode gpsDateTime = XMPNodeUtils.FindChildNode(exifSchema, "exif:GPSTimeStamp", false); if (gpsDateTime == null) { return; } try { XMPDateTime binGPSStamp; XMPDateTime binOtherDate; binGPSStamp = XMPUtils.ConvertToDate(gpsDateTime.GetValue()); if (binGPSStamp.GetYear() != 0 || binGPSStamp.GetMonth() != 0 || binGPSStamp.GetDay() != 0) { return; } XMPNode otherDate = XMPNodeUtils.FindChildNode(exifSchema, "exif:DateTimeOriginal", false); if (otherDate == null) { otherDate = XMPNodeUtils.FindChildNode(exifSchema, "exif:DateTimeDigitized", false); } binOtherDate = XMPUtils.ConvertToDate(otherDate.GetValue()); Sharpen.Calendar cal = binGPSStamp.GetCalendar(); cal.Set(Sharpen.CalendarEnum.Year, binOtherDate.GetYear()); cal.Set(Sharpen.CalendarEnum.Month, binOtherDate.GetMonth()); cal.Set(Sharpen.CalendarEnum.DayOfMonth, binOtherDate.GetDay()); binGPSStamp = new XMPDateTimeImpl(cal); gpsDateTime.SetValue(XMPUtils.ConvertFromDate(binGPSStamp)); } catch (XMPException) { // Don't let a missing or bad date stop other things. return; } }
/// <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="Com.Adobe.Xmp.XMPException"/> internal static int LookupLanguageItem(XMPNode arrayNode, string language) { if (!arrayNode.GetOptions().IsArray()) { throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); } for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) { XMPNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) { continue; } else { if (language.Equals(child.GetQualifier(1).GetValue())) { return(index); } } } return(-1); }
/// <summary>The outermost call is special.</summary> /// <remarks> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </remarks> /// <param name="aliasNode">the alias node</param> /// <param name="baseNode">the base node of the alias</param> /// <param name="outerCall">marks the outer call of the recursion</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void CompareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode, bool outerCall) { if (!aliasNode.GetValue().Equals(baseNode.GetValue()) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } if (!outerCall && (!aliasNode.GetName().Equals(baseNode.GetName()) || !aliasNode.GetOptions().Equals(baseNode.GetOptions()) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } for (Iterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext(); ) { XMPNode aliasChild = (XMPNode)an.Next(); XMPNode baseChild = (XMPNode)bn.Next(); CompareAliasedSubtrees(aliasChild, baseChild, false); } for (Iterator an_1 = aliasNode.IterateQualifier(), bn_1 = baseNode.IterateQualifier(); an_1.HasNext() && bn_1.HasNext(); ) { XMPNode aliasQual = (XMPNode)an_1.Next(); XMPNode baseQual = (XMPNode)bn_1.Next(); CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <summary>The outermost call is special.</summary> /// <remarks> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </remarks> /// <param name="aliasNode">the alias node</param> /// <param name="baseNode">the base node of the alias</param> /// <param name="outerCall">marks the outer call of the recursion</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void CompareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode, bool outerCall) { if (!aliasNode.GetValue().Equals(baseNode.GetValue()) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } if (!outerCall && (!aliasNode.GetName().Equals(baseNode.GetName()) || !aliasNode.GetOptions().Equals(baseNode.GetOptions()) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } for (Iterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext();) { XMPNode aliasChild = (XMPNode)an.Next(); XMPNode baseChild = (XMPNode)bn.Next(); CompareAliasedSubtrees(aliasChild, baseChild, false); } for (Iterator an_1 = aliasNode.IterateQualifier(), bn_1 = baseNode.IterateQualifier(); an_1.HasNext() && bn_1.HasNext();) { XMPNode aliasQual = (XMPNode)an_1.Next(); XMPNode baseQual = (XMPNode)bn_1.Next(); CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <summary>Make sure that the array is well-formed AltText.</summary> /// <remarks> /// Make sure that the array is well-formed AltText. Each item must be simple /// and have an "xml:lang" qualifier. If repairs are needed, keep simple /// non-empty items by adding the "xml:lang" with value "x-repair". /// </remarks> /// <param name="arrayNode">the property node of the array to repair.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards unexpected exceptions.</exception> private static void RepairAltText(XMPNode arrayNode) { if (arrayNode == null || !arrayNode.GetOptions().IsArray()) { // Already OK or not even an array. return; } // fix options arrayNode.GetOptions().SetArrayOrdered(true).SetArrayAlternate(true).SetArrayAltText(true); for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode currChild = (XMPNode)it.Next(); if (currChild.GetOptions().IsCompositeProperty()) { // Delete non-simple children. it.Remove(); } else { if (!currChild.GetOptions().GetHasLanguage()) { string childValue = currChild.GetValue(); if (childValue == null || childValue.Length == 0) { // Delete empty valued children that have no xml:lang. it.Remove(); } else { // Add an xml:lang qualifier with the value "x-repair". XMPNode repairLang = new XMPNode(XMPConstConstants.XmlLang, "x-repair", null); currChild.AddQualifier(repairLang); } } } } }
/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// </summary> /// <remarks> /// 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. /// </remarks> /// <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="Com.Adobe.Xmp.XMPException"></exception> private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) { if (XMPConstConstants.XmlLang.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = Com.Adobe.Xmp.Impl.XMPNodeUtils.LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) { XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return(1); } else { return(index); } } else { for (int index = 1; index < arrayNode.GetChildrenLength(); index++) { XMPNode currItem = arrayNode.GetChild(index); for (Iterator it = currItem.IterateQualifier(); it.HasNext();) { XMPNode qualifier = (XMPNode)it.Next(); if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) { return(index); } } } return(-1); } }
/// <summary>Constructor for a cloned metadata tree.</summary> /// <param name="tree"> /// an prefilled metadata tree which fulfills all /// <code>XMPNode</code> contracts. /// </param> public XMPMetaImpl(XMPNode tree) { this.tree = tree; }
/// <summary>Constructor for an empty metadata object.</summary> public XMPMetaImpl() { // create root node tree = new XMPNode(null, null, null); }
/// <summary> /// Evaluates a raw node value to the given value type, apply special /// conversions for defined types in XMP. /// </summary> /// <param name="valueType">an int indicating the value type</param> /// <param name="propNode">the node containing the value</param> /// <returns>Returns a literal value for the node.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> private object EvaluateNodeValue(int valueType, XMPNode propNode) { object value; string rawValue = propNode.GetValue(); switch (valueType) { case ValueBoolean: { value = XMPUtils.ConvertToBoolean(rawValue); break; } case ValueInteger: { value = XMPUtils.ConvertToInteger(rawValue); break; } case ValueLong: { value = XMPUtils.ConvertToLong(rawValue); break; } case ValueDouble: { value = XMPUtils.ConvertToDouble(rawValue); break; } case ValueDate: { value = XMPUtils.ConvertToDate(rawValue); break; } case ValueCalendar: { XMPDateTime dt = XMPUtils.ConvertToDate(rawValue); value = dt.GetCalendar(); break; } case ValueBase64: { value = XMPUtils.DecodeBase64(rawValue); break; } case ValueString: default: { // leaf values return empty string instead of null // for the other cases the converter methods provides a "null" // value. // a default value can only occur if this method is made public. value = rawValue != null || propNode.GetOptions().IsCompositeProperty() ? rawValue : string.Empty; break; } } return value; }
// ------------------------------------------------------------------------------------- // private /// <summary>Locate or create the item node and set the value.</summary> /// <remarks> /// Locate or create the item node and set the value. Note the index /// parameter is one-based! The index can be in the range [1..size + 1] or /// "last()", normalize it and check the insert flags. The order of the /// normalization checks is important. If the array is empty we end up with /// an index and location to set item size + 1. /// </remarks> /// <param name="arrayNode">an array node</param> /// <param name="itemIndex">the index where to insert the item</param> /// <param name="itemValue">the item value</param> /// <param name="itemOptions">the options for the new item</param> /// <param name="insert">insert oder overwrite at index position?</param> /// <exception cref="Com.Adobe.Xmp.XMPException"/> private void DoSetArrayItem(XMPNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert) { XMPNode itemNode = new XMPNode(XMPConstConstants.ArrayItemName, null); itemOptions = XMPNodeUtils.VerifySetOptions(itemOptions, itemValue); // in insert mode the index after the last is allowed, // even ArrayLastItem points to the index *after* the last. int maxIndex = insert ? arrayNode.GetChildrenLength() + 1 : arrayNode.GetChildrenLength(); if (itemIndex == XMPConstConstants.ArrayLastItem) { itemIndex = maxIndex; } if (1 <= itemIndex && itemIndex <= maxIndex) { if (!insert) { arrayNode.RemoveChild(itemIndex); } arrayNode.AddChild(itemIndex, itemNode); SetNode(itemNode, itemValue, itemOptions, false); } else { throw new XMPException("Array index out of bounds", XMPErrorConstants.Badindex); } }
/// <summary>Remove all empty schemas from the metadata tree that were generated during the rdf parsing.</summary> /// <param name="tree">the root of the metadata tree</param> private static void DeleteEmptySchemas(XMPNode tree) { // Delete empty schema nodes. Do this last, other cleanup can make empty // schema. for (Iterator it = tree.IterateChildren(); it.HasNext(); ) { XMPNode schema = (XMPNode)it.Next(); if (!schema.HasChildren()) { it.Remove(); } } }
/// <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() && XMPConstConstants.XmlLang.Equals(node.GetName()))) { node.SetValue(strValue); } else { node.SetValue(Utils.NormalizeLangValue(strValue)); } }
/// <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="Com.Adobe.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", XMPErrorConstants.Badxpath); } else { if (!arrayNode.HasChildren()) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltNoValues, null }); } } int foundGenericMatches = 0; XMPNode resultNode = null; XMPNode xDefault = null; // Look for the first partial match with the generic language. for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode currItem = (XMPNode)it.Next(); // perform some checks on the current item if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); } else { if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) { throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); } } string currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSpecificMatch, 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 (XMPConstConstants.XDefault.Equals(currLang)) { xDefault = currItem; } } } } // evaluate loop if (foundGenericMatches == 1) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSingleGeneric, resultNode }); } else { if (foundGenericMatches > 1) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltMultipleGeneric, resultNode }); } else { if (xDefault != null) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltXdefault, xDefault }); } else { // Everything failed, choose the first item. return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltFirstItem, arrayNode.GetChild(1) }); } } } }
/// <summary>Moves an alias node of array form to another schema into an array</summary> /// <param name="propertyIt">the property iterator of the old schema (used to delete the property)</param> /// <param name="childNode">the node to be moved</param> /// <param name="baseArray">the base array for the array item</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void TransplantArrayItemAlias(Iterator propertyIt, XMPNode childNode, XMPNode baseArray) { if (baseArray.GetOptions().IsArrayAltText()) { if (childNode.GetOptions().GetHasLanguage()) { throw new XMPException("Alias to x-default already has a language qualifier", XMPErrorConstants.Badxmp); } XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); childNode.AddQualifier(langQual); } propertyIt.Remove(); childNode.SetName(XMPConstConstants.ArrayItemName); baseArray.AddChild(childNode); }
/// <summary>Make sure that the array is well-formed AltText.</summary> /// <remarks> /// Make sure that the array is well-formed AltText. Each item must be simple /// and have an "xml:lang" qualifier. If repairs are needed, keep simple /// non-empty items by adding the "xml:lang" with value "x-repair". /// </remarks> /// <param name="arrayNode">the property node of the array to repair.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards unexpected exceptions.</exception> private static void RepairAltText(XMPNode arrayNode) { if (arrayNode == null || !arrayNode.GetOptions().IsArray()) { // Already OK or not even an array. return; } // fix options arrayNode.GetOptions().SetArrayOrdered(true).SetArrayAlternate(true).SetArrayAltText(true); for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) { XMPNode currChild = (XMPNode)it.Next(); if (currChild.GetOptions().IsCompositeProperty()) { // Delete non-simple children. it.Remove(); } else { if (!currChild.GetOptions().GetHasLanguage()) { string childValue = currChild.GetValue(); if (childValue == null || childValue.Length == 0) { // Delete empty valued children that have no xml:lang. it.Remove(); } else { // Add an xml:lang qualifier with the value "x-repair". XMPNode repairLang = new XMPNode(XMPConstConstants.XmlLang, "x-repair", null); currChild.AddQualifier(repairLang); } } } } }
/// <summary>Visit all of the top level nodes looking for aliases.</summary> /// <remarks> /// Visit all of the top level nodes looking for aliases. If there is /// no base, transplant the alias subtree. If there is a base and strict /// aliasing is on, make sure the alias and base subtrees match. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <param name="options">th parsing options</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void MoveExplicitAliases(XMPNode tree, ParseOptions options) { if (!tree.GetHasAliases()) { return; } tree.SetHasAliases(false); bool strictAliasing = options.GetStrictAliasing(); for (Iterator schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext(); ) { XMPNode currSchema = (XMPNode)schemaIt.Next(); if (!currSchema.GetHasAliases()) { continue; } for (Iterator propertyIt = currSchema.IterateChildren(); propertyIt.HasNext(); ) { XMPNode currProp = (XMPNode)propertyIt.Next(); if (!currProp.IsAlias()) { continue; } currProp.SetAlias(false); // Find the base path, look for the base schema and root node. XMPAliasInfo info = XMPMetaFactory.GetSchemaRegistry().FindAlias(currProp.GetName()); if (info != null) { // find or create schema XMPNode baseSchema = XMPNodeUtils.FindSchemaNode(tree, info.GetNamespace(), null, true); baseSchema.SetImplicit(false); XMPNode baseNode = XMPNodeUtils.FindChildNode(baseSchema, info.GetPrefix() + info.GetPropName(), false); if (baseNode == null) { if (info.GetAliasForm().IsSimple()) { // A top-to-top alias, transplant the property. // change the alias property name to the base name string qname = info.GetPrefix() + info.GetPropName(); currProp.SetName(qname); baseSchema.AddChild(currProp); // remove the alias property propertyIt.Remove(); } else { // An alias to an array item, // create the array and transplant the property. baseNode = new XMPNode(info.GetPrefix() + info.GetPropName(), info.GetAliasForm().ToPropertyOptions()); baseSchema.AddChild(baseNode); TransplantArrayItemAlias(propertyIt, currProp, baseNode); } } else { if (info.GetAliasForm().IsSimple()) { // The base node does exist and this is a top-to-top alias. // Check for conflicts if strict aliasing is on. // Remove and delete the alias subtree. if (strictAliasing) { CompareAliasedSubtrees(currProp, baseNode, true); } propertyIt.Remove(); } else { // This is an alias to an array item and the array exists. // Look for the aliased item. // Then transplant or check & delete as appropriate. XMPNode itemNode = null; if (info.GetAliasForm().IsArrayAltText()) { int xdIndex = XMPNodeUtils.LookupLanguageItem(baseNode, XMPConstConstants.XDefault); if (xdIndex != -1) { itemNode = baseNode.GetChild(xdIndex); } } else { if (baseNode.HasChildren()) { itemNode = baseNode.GetChild(1); } } if (itemNode == null) { TransplantArrayItemAlias(propertyIt, currProp, baseNode); } else { if (strictAliasing) { CompareAliasedSubtrees(currProp, itemNode, true); } propertyIt.Remove(); } } } } } currSchema.SetHasAliases(false); } }
/// <summary> /// The initial support for WAV files mapped a legacy ID3 audio copyright /// into a new xmpDM:copyright property. /// </summary> /// <remarks> /// The initial support for WAV files mapped a legacy ID3 audio copyright /// into a new xmpDM:copyright property. This is special case code to migrate /// that into dc:rights['x-default']. The rules: /// <pre> /// 1. If there is no dc:rights array, or an empty array - /// Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright. /// 2. If there is a dc:rights array but it has no x-default item - /// Create an x-default item as a copy of the first item then apply rule #3. /// 3. If there is a dc:rights array with an x-default item, /// Look for a double linefeed in the value. /// A. If no double linefeed, compare the x-default value to the xmpDM:copyright value. /// A1. If they match then leave the x-default value alone. /// A2. Otherwise, append a double linefeed and /// the xmpDM:copyright value to the x-default value. /// B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value. /// B1. If they match then leave the x-default value alone. /// B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value. /// 4. In all cases, delete the xmpDM:copyright property. /// </pre> /// </remarks> /// <param name="xmp">the metadata object</param> /// <param name="dmCopyright">the "dm:copyright"-property</param> private static void MigrateAudioCopyright(XMPMeta xmp, XMPNode dmCopyright) { try { XMPNode dcSchema = XMPNodeUtils.FindSchemaNode(((XMPMetaImpl)xmp).GetRoot(), XMPConstConstants.NsDc, true); string dmValue = dmCopyright.GetValue(); string doubleLF = "\n\n"; XMPNode dcRightsArray = XMPNodeUtils.FindChildNode(dcSchema, "dc:rights", false); if (dcRightsArray == null || !dcRightsArray.HasChildren()) { // 1. No dc:rights array, create from double linefeed and xmpDM:copyright. dmValue = doubleLF + dmValue; xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, dmValue, null); } else { int xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); if (xdIndex < 0) { // 2. No x-default item, create from the first item. string firstValue = dcRightsArray.GetChild(1).GetValue(); xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, firstValue, null); xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); } // 3. Look for a double linefeed in the x-default value. XMPNode defaultNode = dcRightsArray.GetChild(xdIndex); string defaultValue = defaultNode.GetValue(); int lfPos = defaultValue.IndexOf(doubleLF); if (lfPos < 0) { // 3A. No double LF, compare whole values. if (!dmValue.Equals(defaultValue)) { // 3A2. Append the xmpDM:copyright to the x-default // item. defaultNode.SetValue(defaultValue + doubleLF + dmValue); } } else { // 3B. Has double LF, compare the tail. if (!Sharpen.Runtime.Substring(defaultValue, lfPos + 2).Equals(dmValue)) { // 3B2. Replace the x-default tail. defaultNode.SetValue(Sharpen.Runtime.Substring(defaultValue, 0, lfPos + 2) + dmValue); } } } // 4. Get rid of the xmpDM:copyright. dmCopyright.GetParent().RemoveChild(dmCopyright); } catch (XMPException) { } }
/// <summary> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// </summary> /// <remarks> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// <li>[index] - An element of an array. /// <li>[last()] - The last element of an array. /// <li>[qualName="value"] - An element in an array of structs, chosen by a field value. /// <li>[?qualName="value"] - An element in an array, chosen by a qualifier value. /// <li>?qualName - A general qualifier. /// </ul> /// Find the appropriate child node, resolving aliases, and optionally creating nodes. /// </remarks> /// <param name="parentNode">the node to start to start from</param> /// <param name="nextStep">the xpath segment</param> /// <param name="createNodes"></param> /// <returns>returns the found or created XMPPath node</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) { XMPNode nextNode = null; int index = 0; int stepKind = nextStep.GetKind(); if (stepKind == XMPPath.StructFieldStep) { nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.QualifierStep) { nextNode = FindQualifierNode(parentNode, Sharpen.Runtime.Substring(nextStep.GetName(), 1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. if (!parentNode.GetOptions().IsArray()) { throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); } if (stepKind == XMPPath.ArrayIndexStep) { index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.ArrayLastStep) { index = parentNode.GetChildrenLength(); } else { if (stepKind == XMPPath.FieldSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else { if (stepKind == XMPPath.QualSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); } else { throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); } } } } if (1 <= index && index <= parentNode.GetChildrenLength()) { nextNode = parentNode.GetChild(index); } } } return(nextNode); }
/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// </summary> /// <remarks> /// 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. /// </remarks> /// <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="Com.Adobe.Xmp.XMPException"></exception> private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) { if (XMPConstConstants.XmlLang.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = Com.Adobe.Xmp.Impl.XMPNodeUtils.LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) { XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return 1; } else { return index; } } else { for (int index = 1; index < arrayNode.GetChildrenLength(); index++) { XMPNode currItem = arrayNode.GetChild(index); for (Iterator it = currItem.IterateQualifier(); it.HasNext(); ) { XMPNode qualifier = (XMPNode)it.Next(); if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) { return index; } } } return -1; } }
/// <summary>Visit all of the top level nodes looking for aliases.</summary> /// <remarks> /// Visit all of the top level nodes looking for aliases. If there is /// no base, transplant the alias subtree. If there is a base and strict /// aliasing is on, make sure the alias and base subtrees match. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <param name="options">th parsing options</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void MoveExplicitAliases(XMPNode tree, ParseOptions options) { if (!tree.GetHasAliases()) { return; } tree.SetHasAliases(false); bool strictAliasing = options.GetStrictAliasing(); for (Iterator schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext();) { XMPNode currSchema = (XMPNode)schemaIt.Next(); if (!currSchema.GetHasAliases()) { continue; } for (Iterator propertyIt = currSchema.IterateChildren(); propertyIt.HasNext();) { XMPNode currProp = (XMPNode)propertyIt.Next(); if (!currProp.IsAlias()) { continue; } currProp.SetAlias(false); // Find the base path, look for the base schema and root node. XMPAliasInfo info = XMPMetaFactory.GetSchemaRegistry().FindAlias(currProp.GetName()); if (info != null) { // find or create schema XMPNode baseSchema = XMPNodeUtils.FindSchemaNode(tree, info.GetNamespace(), null, true); baseSchema.SetImplicit(false); XMPNode baseNode = XMPNodeUtils.FindChildNode(baseSchema, info.GetPrefix() + info.GetPropName(), false); if (baseNode == null) { if (info.GetAliasForm().IsSimple()) { // A top-to-top alias, transplant the property. // change the alias property name to the base name string qname = info.GetPrefix() + info.GetPropName(); currProp.SetName(qname); baseSchema.AddChild(currProp); // remove the alias property propertyIt.Remove(); } else { // An alias to an array item, // create the array and transplant the property. baseNode = new XMPNode(info.GetPrefix() + info.GetPropName(), info.GetAliasForm().ToPropertyOptions()); baseSchema.AddChild(baseNode); TransplantArrayItemAlias(propertyIt, currProp, baseNode); } } else { if (info.GetAliasForm().IsSimple()) { // The base node does exist and this is a top-to-top alias. // Check for conflicts if strict aliasing is on. // Remove and delete the alias subtree. if (strictAliasing) { CompareAliasedSubtrees(currProp, baseNode, true); } propertyIt.Remove(); } else { // This is an alias to an array item and the array exists. // Look for the aliased item. // Then transplant or check & delete as appropriate. XMPNode itemNode = null; if (info.GetAliasForm().IsArrayAltText()) { int xdIndex = XMPNodeUtils.LookupLanguageItem(baseNode, XMPConstConstants.XDefault); if (xdIndex != -1) { itemNode = baseNode.GetChild(xdIndex); } } else { if (baseNode.HasChildren()) { itemNode = baseNode.GetChild(1); } } if (itemNode == null) { TransplantArrayItemAlias(propertyIt, currProp, baseNode); } else { if (strictAliasing) { CompareAliasedSubtrees(currProp, itemNode, true); } propertyIt.Remove(); } } } } } currSchema.SetHasAliases(false); } }
/// <summary> /// Searches for a field selector in a node: /// [fieldName="value] - an element in an array of structs, chosen by a field value. /// </summary> /// <remarks> /// Searches for a field selector in a node: /// [fieldName="value] - an element in an array of structs, chosen by a field value. /// No implicit nodes are created by field selectors. /// </remarks> /// <param name="arrayNode"/> /// <param name="fieldName"/> /// <param name="fieldValue"/> /// <returns>Returns the index of the field if found, otherwise -1.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static int LookupFieldSelector(XMPNode arrayNode, string fieldName, string fieldValue) { int result = -1; for (int index = 1; index <= arrayNode.GetChildrenLength() && result < 0; index++) { XMPNode currItem = arrayNode.GetChild(index); if (!currItem.GetOptions().IsStruct()) { throw new XMPException("Field selector must be used on array of struct", XMPErrorConstants.Badxpath); } for (int f = 1; f <= currItem.GetChildrenLength(); f++) { XMPNode currField = currItem.GetChild(f); if (!fieldName.Equals(currField.GetName())) { continue; } if (fieldValue.Equals(currField.GetValue())) { result = index; break; } } } return result; }
/// <summary>Constructor with optionsl initial values.</summary> /// <remarks> /// Constructor with optionsl initial values. If <code>propName</code> is provided, /// <code>schemaNS</code> has also be provided. /// </remarks> /// <param name="xmp">the iterated metadata object.</param> /// <param name="schemaNS">the iteration is reduced to this schema (optional)</param> /// <param name="propPath">the iteration is redurce to this property within the <code>schemaNS</code></param> /// <param name="options"> /// advanced iteration options, see /// <see cref="Com.Adobe.Xmp.Options.IteratorOptions"/> /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException">If the node defined by the paramters is not existing.</exception> public XMPIteratorImpl(XMPMetaImpl xmp, string schemaNS, string propPath, IteratorOptions options) { // make sure that options is defined at least with defaults this.options = options != null ? options : new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XMPNode startNode = null; string initialPath = null; bool baseSchema = schemaNS != null && schemaNS.Length > 0; bool baseProperty = propPath != null && propPath.Length > 0; if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.GetRoot(); } else { if (baseSchema && baseProperty) { // Schema and property node provided XMPPath path = XMPPathParser.ExpandXPath(schemaNS, propPath); // base path is the prop path without the property leaf XMPPath basePath = new XMPPath(); for (int i = 0; i < path.Size() - 1; i++) { basePath.Add(path.GetSegment(i)); } startNode = XMPNodeUtils.FindNode(xmp.GetRoot(), path, false, null); baseNS = schemaNS; initialPath = basePath.ToString(); } else { if (baseSchema && !baseProperty) { // Only Schema provided startNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNS, false); } else { // !baseSchema && baseProperty // No schema but property provided -> error throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema); } } } // create iterator if (startNode != null) { if (!this.options.IsJustChildren()) { nodeIterator = new XMPIteratorImpl.NodeIterator(this, startNode, initialPath, 1); } else { nodeIterator = new XMPIteratorImpl.NodeIteratorChildren(this, startNode, initialPath); } } else { // create null iterator nodeIterator = Collections.EmptyList().ListIterator(); } }
/// <summary> /// see /// <see cref="Com.Adobe.Xmp.XMPUtils.SeparateArrayItems(Com.Adobe.Xmp.XMPMeta, string, string, string, Com.Adobe.Xmp.Options.PropertyOptions, bool)"/> /// </summary> /// <param name="xmp">The XMP object containing the array to be updated.</param> /// <param name="schemaNS"> /// The schema namespace URI for the array. Must not be null or /// the empty string. /// </param> /// <param name="arrayName"> /// The name of the array. May be a general path expression, must /// not be null or the empty string. Each item in the array must /// be a simple string value. /// </param> /// <param name="catedStr">The string to be separated into the array items.</param> /// <param name="arrayOptions">Option flags to control the separation.</param> /// <param name="preserveCommas">Flag if commas shall be preserved</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) { ParameterAsserts.AssertSchemaNS(schemaNS); ParameterAsserts.AssertArrayName(arrayName); if (catedStr == null) { throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); } ParameterAsserts.AssertImplementation(xmp); XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; // Keep a zero value, has special meaning below. XMPNode arrayNode = SeparateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl); // Extract the item values one at a time, until the whole input string is done. string itemValue; int itemStart; int itemEnd; int nextKind = UckNormal; int charKind = UckNormal; char ch = (char) 0; char nextChar = (char) 0; itemEnd = 0; int endPos = catedStr.Length; while (itemEnd < endPos) { // Skip any leading spaces and separation characters. Always skip commas here. // They can be kept when within a value, but not when alone between values. for (itemStart = itemEnd; itemStart < endPos; itemStart++) { ch = catedStr[itemStart]; charKind = ClassifyCharacter(ch); if (charKind == UckNormal || charKind == UckQuote) { break; } } if (itemStart >= endPos) { break; } if (charKind != UckQuote) { // This is not a quoted value. Scan for the end, create an array // item from the substring. for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind == UckNormal || charKind == UckQuote || (charKind == UckComma && preserveCommas)) { continue; } else { if (charKind != UckSpace) { break; } else { if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(ch); if (nextKind == UckNormal || nextKind == UckQuote || (nextKind == UckComma && preserveCommas)) { continue; } } } } // Anything left? break; } // Have multiple spaces, or a space followed by a // separator. itemValue = Sharpen.Runtime.Substring(catedStr, itemStart, itemEnd); } else { // Accumulate quoted values into a local string, undoubling // internal quotes that // match the surrounding quotes. Do not undouble "unmatching" // quotes. char openQuote = ch; char closeQuote = GetClosingQuote(openQuote); itemStart++; // Skip the opening quote; itemValue = string.Empty; for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind != UckQuote || !IsSurroundingQuote(ch, openQuote, closeQuote)) { // This is not a matching quote, just append it to the // item value. itemValue += ch; } else { // This is a "matching" quote. Is it doubled, or the // final closing quote? // Tolerate various edge cases like undoubled opening // (non-closing) quotes, // or end of input. if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(nextChar); } else { nextKind = UckSemicolon; nextChar = (char)unchecked((int)(0x3B)); } if (ch == nextChar) { // This is doubled, copy it and skip the double. itemValue += ch; // Loop will add in charSize. itemEnd++; } else { if (!IsClosingingQuote(ch, openQuote, closeQuote)) { // This is an undoubled, non-closing quote, copy it. itemValue += ch; } else { // This is an undoubled closing quote, skip it and // exit the loop. itemEnd++; break; } } } } } // Add the separated item to the array. // Keep a matching old value in case it had separators. int foundIndex = -1; for (int oldChild = 1; oldChild <= arrayNode.GetChildrenLength(); oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).GetValue())) { foundIndex = oldChild; break; } } XMPNode newItem = null; if (foundIndex < 0) { newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); arrayNode.AddChild(newItem); } } }
/// <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="Com.Adobe.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", XMPErrorConstants.Badxpath); } else { if (!arrayNode.HasChildren()) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltNoValues, null }; } } int foundGenericMatches = 0; XMPNode resultNode = null; XMPNode xDefault = null; // Look for the first partial match with the generic language. for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) { XMPNode currItem = (XMPNode)it.Next(); // perform some checks on the current item if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); } else { if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) { throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); } } string currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSpecificMatch, 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 (XMPConstConstants.XDefault.Equals(currLang)) { xDefault = currItem; } } } } // evaluate loop if (foundGenericMatches == 1) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSingleGeneric, resultNode }; } else { if (foundGenericMatches > 1) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltMultipleGeneric, resultNode }; } else { if (xDefault != null) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltXdefault, xDefault }; } else { // Everything failed, choose the first item. return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltFirstItem, arrayNode.GetChild(1) }; } } } }
/// <seealso cref="Com.Adobe.Xmp.XMPUtils.AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool)"/> /// <param name="source">The source XMP object.</param> /// <param name="destination">The destination XMP object.</param> /// <param name="doAllProperties">Do internal properties in addition to external properties.</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues">Delete destination values if source property is empty.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void AppendProperties(XMPMeta source, XMPMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) { ParameterAsserts.AssertImplementation(source); ParameterAsserts.AssertImplementation(destination); XMPMetaImpl src = (XMPMetaImpl)source; XMPMetaImpl dest = (XMPMetaImpl)destination; for (Iterator it = src.GetRoot().IterateChildren(); it.HasNext(); ) { XMPNode sourceSchema = (XMPNode)it.Next(); // Make sure we have a destination schema node XMPNode destSchema = XMPNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.GetName(), false); bool createdSchema = false; if (destSchema == null) { destSchema = new XMPNode(sourceSchema.GetName(), sourceSchema.GetValue(), new PropertyOptions().SetSchemaNode(true)); dest.GetRoot().AddChild(destSchema); createdSchema = true; } // Process the source schema's children. for (Iterator ic = sourceSchema.IterateChildren(); ic.HasNext(); ) { XMPNode sourceProp = (XMPNode)ic.Next(); if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.GetName(), sourceProp.GetName())) { AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); } } if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) { // Don't create an empty schema / remove empty schema. dest.GetRoot().RemoveChild(destSchema); } } }
/// <summary> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. /// </summary> /// <remarks> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. Empty schemas are automatically remove /// by <code>XMPNode</code> /// </remarks> /// <param name="schemaNode">a schema node</param> /// <param name="doAllProperties">flag if all properties or only externals shall be removed.</param> /// <returns>Returns true if the schema is empty after the operation.</returns> private static bool RemoveSchemaChildren(XMPNode schemaNode, bool doAllProperties) { for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) { XMPNode currProp = (XMPNode)it.Next(); if (doAllProperties || !Utils.IsInternalProperty(schemaNode.GetName(), currProp.GetName())) { it.Remove(); } } return !schemaNode.HasChildren(); }
/// <seealso cref="AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool, bool)"/> /// <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="Com.Adobe.Xmp.XMPException"/> private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !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.GetValue(), sourceNode.GetOptions(), 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.GetOptions(); PropertyOptions destForm = destNode.GetOptions(); 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 (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) { XMPNode 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 (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) { XMPNode sourceItem = (XMPNode)it.Next(); if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName())) { continue; } int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue()); if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0)) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else { if (destIndex == -1) { // Not replacing, keep the existing item. if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions()); 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 (Iterator @is = sourceNode.IterateChildren(); @is.HasNext(); ) { XMPNode sourceItem = (XMPNode)@is.Next(); bool match = false; for (Iterator id = destNode.IterateChildren(); id.HasNext(); ) { XMPNode destItem = (XMPNode)id.Next(); if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XMPNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } } } } } }
/// <summary> /// The internals for SetProperty() and related calls, used after the node is /// found or created. /// </summary> /// <param name="node">the newly created node</param> /// <param name="value">the node value, can be <code>null</code></param> /// <param name="newOptions">options for the new node, must not be <code>null</code>.</param> /// <param name="deleteExisting">flag if the existing value is to be overwritten</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thrown if options and value do not correspond</exception> internal virtual void SetNode(XMPNode node, object value, PropertyOptions newOptions, bool deleteExisting) { if (deleteExisting) { node.Clear(); } // its checked by SetOptions(), if the merged result is a valid options set node.GetOptions().MergeWith(newOptions); if (!node.GetOptions().IsCompositeProperty()) { // This is setting the value of a leaf node. XMPNodeUtils.SetNodeValue(node, value); } else { if (value != null && value.ToString().Length > 0) { throw new XMPException("Composite nodes can't have values", XMPErrorConstants.Badxpath); } node.RemoveChildren(); } }
/// <summary>Find or create a qualifier node under a given parent node.</summary> /// <remarks> /// Find or create a qualifier node under a given parent node. Returns a pointer to the /// qualifier node, and optionally an iterator for the node's position in /// the parent's vector of qualifiers. The iterator is unchanged if no qualifier node (null) /// is returned. /// <em>Note:</em> On entry, the qualName parameter must not have the leading '?' from the /// XMPPath step. /// </remarks> /// <param name="parent">the parent XMPNode</param> /// <param name="qualName">the qualifier name</param> /// <param name="createNodes">flag if nodes shall be created</param> /// <returns>Returns the qualifier node if found or created, <code>null</code> otherwise.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FindQualifierNode(XMPNode parent, string qualName, bool createNodes) { System.Diagnostics.Debug.Assert(!qualName.StartsWith("?")); XMPNode qualNode = parent.FindQualifierByName(qualName); if (qualNode == null && createNodes) { qualNode = new XMPNode(qualName, null); qualNode.SetImplicit(true); parent.AddQualifier(qualNode); } return qualNode; }
public _XMPProperty_407(XMPNode itemNode) { this.itemNode = itemNode; }
/// <summary> /// see /// <see cref="Com.Adobe.Xmp.XMPUtils.SeparateArrayItems(Com.Adobe.Xmp.XMPMeta, string, string, string, Com.Adobe.Xmp.Options.PropertyOptions, bool)"/> /// </summary> /// <param name="xmp">The XMP object containing the array to be updated.</param> /// <param name="schemaNS"> /// The schema namespace URI for the array. Must not be null or /// the empty string. /// </param> /// <param name="arrayName"> /// The name of the array. May be a general path expression, must /// not be null or the empty string. Each item in the array must /// be a simple string value. /// </param> /// <param name="catedStr">The string to be separated into the array items.</param> /// <param name="arrayOptions">Option flags to control the separation.</param> /// <param name="preserveCommas">Flag if commas shall be preserved</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) { ParameterAsserts.AssertSchemaNS(schemaNS); ParameterAsserts.AssertArrayName(arrayName); if (catedStr == null) { throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); } ParameterAsserts.AssertImplementation(xmp); XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; // Keep a zero value, has special meaning below. XMPNode arrayNode = SeparateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl); // Extract the item values one at a time, until the whole input string is done. string itemValue; int itemStart; int itemEnd; int nextKind = UckNormal; int charKind = UckNormal; char ch = (char)0; char nextChar = (char)0; itemEnd = 0; int endPos = catedStr.Length; while (itemEnd < endPos) { // Skip any leading spaces and separation characters. Always skip commas here. // They can be kept when within a value, but not when alone between values. for (itemStart = itemEnd; itemStart < endPos; itemStart++) { ch = catedStr[itemStart]; charKind = ClassifyCharacter(ch); if (charKind == UckNormal || charKind == UckQuote) { break; } } if (itemStart >= endPos) { break; } if (charKind != UckQuote) { // This is not a quoted value. Scan for the end, create an array // item from the substring. for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind == UckNormal || charKind == UckQuote || (charKind == UckComma && preserveCommas)) { continue; } else { if (charKind != UckSpace) { break; } else { if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(ch); if (nextKind == UckNormal || nextKind == UckQuote || (nextKind == UckComma && preserveCommas)) { continue; } } } } // Anything left? break; } // Have multiple spaces, or a space followed by a // separator. itemValue = Sharpen.Runtime.Substring(catedStr, itemStart, itemEnd); } else { // Accumulate quoted values into a local string, undoubling // internal quotes that // match the surrounding quotes. Do not undouble "unmatching" // quotes. char openQuote = ch; char closeQuote = GetClosingQuote(openQuote); itemStart++; // Skip the opening quote; itemValue = string.Empty; for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind != UckQuote || !IsSurroundingQuote(ch, openQuote, closeQuote)) { // This is not a matching quote, just append it to the // item value. itemValue += ch; } else { // This is a "matching" quote. Is it doubled, or the // final closing quote? // Tolerate various edge cases like undoubled opening // (non-closing) quotes, // or end of input. if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(nextChar); } else { nextKind = UckSemicolon; nextChar = (char)unchecked ((int)(0x3B)); } if (ch == nextChar) { // This is doubled, copy it and skip the double. itemValue += ch; // Loop will add in charSize. itemEnd++; } else { if (!IsClosingingQuote(ch, openQuote, closeQuote)) { // This is an undoubled, non-closing quote, copy it. itemValue += ch; } else { // This is an undoubled closing quote, skip it and // exit the loop. itemEnd++; break; } } } } } // Add the separated item to the array. // Keep a matching old value in case it had separators. int foundIndex = -1; for (int oldChild = 1; oldChild <= arrayNode.GetChildrenLength(); oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).GetValue())) { foundIndex = oldChild; break; } } XMPNode newItem = null; if (foundIndex < 0) { newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); arrayNode.AddChild(newItem); } } }
public _XMPProperty_682(object value, XMPNode propNode) { this.value = value; this.propNode = propNode; }
/// <param name="arrayNode">an array node</param> /// <param name="segment">the segment containing the array index</param> /// <param name="createNodes">flag if new nodes are allowed to be created.</param> /// <returns>Returns the index or index = -1 if not found</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Throws Exceptions</exception> private static int FindIndexedItem(XMPNode arrayNode, string segment, bool createNodes) { int index = 0; try { segment = Sharpen.Runtime.Substring(segment, 1, segment.Length - 1); index = System.Convert.ToInt32(segment); if (index < 1) { throw new XMPException("Array index must be larger than zero", XMPErrorConstants.Badxpath); } } catch (FormatException) { throw new XMPException("Array index not digits.", XMPErrorConstants.Badxpath); } if (createNodes && index == arrayNode.GetChildrenLength() + 1) { // Append a new last + 1 node. XMPNode newItem = new XMPNode(XMPConstConstants.ArrayItemName, null); newItem.SetImplicit(true); arrayNode.AddChild(newItem); } return index; }
/// <summary>Follow an expanded path expression to find or create a node.</summary> /// <param name="xmpTree">the node to begin the search.</param> /// <param name="xpath">the complete xpath</param> /// <param name="createNodes"> /// flag if nodes shall be created /// (when called by <code>setProperty()</code>) /// </param> /// <param name="leafOptions"> /// the options for the created leaf nodes (only when /// <code>createNodes == true</code>). /// </param> /// <returns>Returns the node if found or created or <code>null</code>.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"> /// An exception is only thrown if an error occurred, /// not if a node was not found. /// </exception> internal static XMPNode FindNode(XMPNode xmpTree, XMPPath xpath, bool createNodes, PropertyOptions leafOptions) { // check if xpath is set. if (xpath == null || xpath.Size() == 0) { throw new XMPException("Empty XMPPath", XMPErrorConstants.Badxpath); } // Root of implicitly created subtree to possible delete it later. // Valid only if leaf is new. XMPNode rootImplicitNode = null; XMPNode currNode = null; // resolve schema step currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XMPPath.StepSchema).GetName(), createNodes); if (currNode == null) { return(null); } else { if (currNode.IsImplicit()) { currNode.SetImplicit(false); // Clear the implicit node bit. rootImplicitNode = currNode; } } // Save the top most implicit node. // Now follow the remaining steps of the original XMPPath. try { for (int i = 1; i < xpath.Size(); i++) { currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes); if (currNode == null) { if (createNodes) { // delete implicitly created nodes DeleteNode(rootImplicitNode); } return(null); } else { if (currNode.IsImplicit()) { // clear the implicit node flag currNode.SetImplicit(false); // if node is an ALIAS (can be only in root step, auto-create array // when the path has been resolved from a not simple alias type if (i == 1 && xpath.GetSegment(i).IsAlias() && xpath.GetSegment(i).GetAliasForm() != 0) { currNode.GetOptions().SetOption(xpath.GetSegment(i).GetAliasForm(), true); } else { // "CheckImplicitStruct" in C++ if (i < xpath.Size() - 1 && xpath.GetSegment(i).GetKind() == XMPPath.StructFieldStep && !currNode.GetOptions().IsCompositeProperty()) { currNode.GetOptions().SetStruct(true); } } if (rootImplicitNode == null) { rootImplicitNode = currNode; } } } } } catch (XMPException e) { // Save the top most implicit node. // if new notes have been created prior to the error, delete them if (rootImplicitNode != null) { DeleteNode(rootImplicitNode); } throw; } if (rootImplicitNode != null) { // set options only if a node has been successful created currNode.GetOptions().MergeWith(leafOptions); currNode.SetOptions(currNode.GetOptions()); } return(currNode); }
/// <seealso cref="Com.Adobe.Xmp.XMPUtils.RemoveProperties(Com.Adobe.Xmp.XMPMeta, string, string, bool, bool)"/> /// <param name="xmp">The XMP object containing the properties to be removed.</param> /// <param name="schemaNS"> /// Optional schema namespace URI for the properties to be /// removed. /// </param> /// <param name="propName">Optional path expression for the property to be removed.</param> /// <param name="doAllProperties"> /// Option flag to control the deletion: do internal properties in /// addition to external properties. /// </param> /// <param name="includeAliases"> /// Option flag to control the deletion: Include aliases in the /// "named schema" case above. /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException">If metadata processing fails</exception> public static void RemoveProperties(XMPMeta xmp, string schemaNS, string propName, bool doAllProperties, bool includeAliases) { ParameterAsserts.AssertImplementation(xmp); XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; if (propName != null && propName.Length > 0) { // Remove just the one indicated property. This might be an alias, // the named schema might not actually exist. So don't lookup the // schema node. if (schemaNS == null || schemaNS.Length == 0) { throw new XMPException("Property name requires schema namespace", XMPErrorConstants.Badparam); } XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); XMPNode propNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), expPath, false, null); if (propNode != null) { if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment(XMPPath.StepSchema).GetName(), expPath.GetSegment(XMPPath.StepRootProp).GetName())) { XMPNode parent = propNode.GetParent(); parent.RemoveChild(propNode); if (parent.GetOptions().IsSchemaNode() && !parent.HasChildren()) { // remove empty schema node parent.GetParent().RemoveChild(parent); } } } } else { if (schemaNS != null && schemaNS.Length > 0) { // Remove all properties from the named schema. Optionally include // aliases, in which case // there might not be an actual schema node. // XMP_NodePtrPos schemaPos; XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmpImpl.GetRoot(), schemaNS, false); if (schemaNode != null) { if (RemoveSchemaChildren(schemaNode, doAllProperties)) { xmpImpl.GetRoot().RemoveChild(schemaNode); } } if (includeAliases) { // We're removing the aliases also. Look them up by their // namespace prefix. // But that takes more code and the extra speed isn't worth it. // Lookup the XMP node // from the alias, to make sure the actual exists. XMPAliasInfo[] aliases = XMPMetaFactory.GetSchemaRegistry().FindAliases(schemaNS); for (int i = 0; i < aliases.Length; i++) { XMPAliasInfo info = aliases[i]; XMPPath path = XMPPathParser.ExpandXPath(info.GetNamespace(), info.GetPropName()); XMPNode actualProp = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), path, false, null); if (actualProp != null) { XMPNode parent = actualProp.GetParent(); parent.RemoveChild(actualProp); } } } } else { // Remove all appropriate properties from all schema. In this case // we don't have to be // concerned with aliases, they are handled implicitly from the // actual properties. for (Iterator it = xmpImpl.GetRoot().IterateChildren(); it.HasNext();) { XMPNode schema = (XMPNode)it.Next(); if (RemoveSchemaChildren(schema, doAllProperties)) { it.Remove(); } } } } }
// EMPTY /// <summary>Find or create a schema node if <code>createNodes</code> is false and</summary> /// <param name="tree">the root of the xmp tree.</param> /// <param name="namespaceURI">a namespace</param> /// <param name="createNodes"> /// a flag indicating if the node shall be created if not found. /// <em>Note:</em> The namespace must be registered prior to this call. /// </param> /// <returns> /// Returns the schema node if found, <code>null</code> otherwise. /// Note: If <code>createNodes</code> is <code>true</code>, it is <b>always</b> /// returned a valid node. /// </returns> /// <exception cref="Com.Adobe.Xmp.XMPException"> /// An exception is only thrown if an error occurred, not if a /// node was not found. /// </exception> internal static XMPNode FindSchemaNode(XMPNode tree, string namespaceURI, bool createNodes) { return(FindSchemaNode(tree, namespaceURI, null, createNodes)); }
/// <seealso cref="AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool, bool)"/> /// <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="Com.Adobe.Xmp.XMPException"/> private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !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.GetValue(), sourceNode.GetOptions(), 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.GetOptions(); PropertyOptions destForm = destNode.GetOptions(); 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 (Iterator it = sourceNode.IterateChildren(); it.HasNext();) { XMPNode 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 (Iterator it = sourceNode.IterateChildren(); it.HasNext();) { XMPNode sourceItem = (XMPNode)it.Next(); if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName())) { continue; } int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue()); if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0)) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else { if (destIndex == -1) { // Not replacing, keep the existing item. if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions()); 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 (Iterator @is = sourceNode.IterateChildren(); @is.HasNext();) { XMPNode sourceItem = (XMPNode)@is.Next(); bool match = false; for (Iterator id = destNode.IterateChildren(); id.HasNext();) { XMPNode destItem = (XMPNode)id.Next(); if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XMPNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } } } } } }
/// <summary>Creates a property info object from an <code>XMPNode</code>.</summary> /// <param name="node">an <code>XMPNode</code></param> /// <param name="baseNS">the base namespace to report</param> /// <param name="path">the full property path</param> /// <returns>Returns a <code>XMPProperty</code>-object that serves representation of the node.</returns> protected internal virtual XMPPropertyInfo CreatePropertyInfo(XMPNode node, string baseNS, string path) { string value = node.GetOptions().IsSchemaNode() ? null : node.GetValue(); return(new _XMPPropertyInfo_450(node, baseNS, path, value)); }
/// <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="Com.Adobe.Xmp.XMPException">Forwards exceptions to the calling method.</exception> private static bool ItemValuesMatch(XMPNode leftNode, XMPNode rightNode) { PropertyOptions leftForm = leftNode.GetOptions(); PropertyOptions rightForm = rightNode.GetOptions(); if (leftForm.Equals(rightForm)) { return(false); } if (leftForm.GetOptions() == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.GetValue().Equals(rightNode.GetValue())) { return(false); } if (leftNode.GetOptions().GetHasLanguage() != rightNode.GetOptions().GetHasLanguage()) { return(false); } if (leftNode.GetOptions().GetHasLanguage() && !leftNode.GetQualifier(1).GetValue().Equals(rightNode.GetQualifier(1).GetValue())) { return(false); } } else { if (leftForm.IsStruct()) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength()) { return(false); } for (Iterator it = leftNode.IterateChildren(); it.HasNext();) { XMPNode leftField = (XMPNode)it.Next(); XMPNode rightField = XMPNodeUtils.FindChildNode(rightNode, leftField.GetName(), 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. System.Diagnostics.Debug.Assert(leftForm.IsArray()); for (Iterator il = leftNode.IterateChildren(); il.HasNext();) { XMPNode leftItem = (XMPNode)il.Next(); bool match = false; for (Iterator ir = rightNode.IterateChildren(); ir.HasNext();) { XMPNode rightItem = (XMPNode)ir.Next(); if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return(false); } } } } return(true); }
/// <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="Com.Adobe.Xmp.XMPException">Forwards exceptions to the calling method.</exception> private static bool ItemValuesMatch(XMPNode leftNode, XMPNode rightNode) { PropertyOptions leftForm = leftNode.GetOptions(); PropertyOptions rightForm = rightNode.GetOptions(); if (leftForm.Equals(rightForm)) { return false; } if (leftForm.GetOptions() == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.GetValue().Equals(rightNode.GetValue())) { return false; } if (leftNode.GetOptions().GetHasLanguage() != rightNode.GetOptions().GetHasLanguage()) { return false; } if (leftNode.GetOptions().GetHasLanguage() && !leftNode.GetQualifier(1).GetValue().Equals(rightNode.GetQualifier(1).GetValue())) { return false; } } else { if (leftForm.IsStruct()) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength()) { return false; } for (Iterator it = leftNode.IterateChildren(); it.HasNext(); ) { XMPNode leftField = (XMPNode)it.Next(); XMPNode rightField = XMPNodeUtils.FindChildNode(rightNode, leftField.GetName(), 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. System.Diagnostics.Debug.Assert(leftForm.IsArray()); for (Iterator il = leftNode.IterateChildren(); il.HasNext(); ) { XMPNode leftItem = (XMPNode)il.Next(); bool match = false; for (Iterator ir = rightNode.IterateChildren(); ir.HasNext(); ) { XMPNode rightItem = (XMPNode)ir.Next(); if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return false; } } } } return true; }
/// <summary> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// </summary> /// <remarks> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// <li>[index] - An element of an array. /// <li>[last()] - The last element of an array. /// <li>[qualName="value"] - An element in an array of structs, chosen by a field value. /// <li>[?qualName="value"] - An element in an array, chosen by a qualifier value. /// <li>?qualName - A general qualifier. /// </ul> /// Find the appropriate child node, resolving aliases, and optionally creating nodes. /// </remarks> /// <param name="parentNode">the node to start to start from</param> /// <param name="nextStep">the xpath segment</param> /// <param name="createNodes"></param> /// <returns>returns the found or created XMPPath node</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) { XMPNode nextNode = null; int index = 0; int stepKind = nextStep.GetKind(); if (stepKind == XMPPath.StructFieldStep) { nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.QualifierStep) { nextNode = FindQualifierNode(parentNode, Sharpen.Runtime.Substring(nextStep.GetName(), 1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. if (!parentNode.GetOptions().IsArray()) { throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); } if (stepKind == XMPPath.ArrayIndexStep) { index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.ArrayLastStep) { index = parentNode.GetChildrenLength(); } else { if (stepKind == XMPPath.FieldSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else { if (stepKind == XMPPath.QualSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); } else { throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); } } } } if (1 <= index && index <= parentNode.GetChildrenLength()) { nextNode = parentNode.GetChild(index); } } } return nextNode; }