/// <summary>Make sure the x-default item is first.</summary> /// <remarks> /// Make sure the x-default item is first. Touch up "single value" /// arrays that have a default plus one real language. This case should have /// the same value for both items. Older Adobe apps were hardwired to only /// use the "x-default" item, so we copy that value to the other /// item. /// </remarks> /// <param name="arrayNode">an alt text array node</param> internal static void NormalizeLangArray(XMPNode arrayNode) { if (!arrayNode.GetOptions().IsArrayAltText()) { return; } // check if node with x-default qual is first place for (int i = 2; i <= arrayNode.GetChildrenLength(); i++) { XMPNode child = arrayNode.GetChild(i); if (child.HasQualifier() && XMPConstConstants.XDefault.Equals(child.GetQualifier(1).GetValue())) { // move node to first place try { arrayNode.RemoveChild(i); arrayNode.AddChild(1, child); } catch (XMPException) { // cannot occur, because same child is removed before System.Diagnostics.Debug.Assert(false); } if (i == 2) { arrayNode.GetChild(2).SetValue(child.GetValue()); } break; } } }
/// <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); } }
// ------------------------------------------------------------------------------------- // 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); } }
/// <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); } } } } } } } } }
/// <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(); } } } } }
/// <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); } } } } } } } } }