/// <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>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>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>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>Find or create a child node under a given parent node.</summary> /// <remarks> /// Find or create a child node under a given parent node. If the parent node is no /// Returns the found or created child node. /// </remarks> /// <param name="parent">the parent node</param> /// <param name="childName">the node name to find</param> /// <param name="createNodes">flag, if new nodes shall be created.</param> /// <returns>Returns the found or created node or <code>null</code>.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if</exception> internal static XMPNode FindChildNode(XMPNode parent, string childName, bool createNodes) { if (!parent.GetOptions().IsSchemaNode() && !parent.GetOptions().IsStruct()) { if (!parent.IsImplicit()) { throw new XMPException("Named children only allowed for schemas and structs", XMPErrorConstants.Badxpath); } else { if (parent.GetOptions().IsArray()) { throw new XMPException("Named children not allowed for arrays", XMPErrorConstants.Badxpath); } else { if (createNodes) { parent.GetOptions().SetStruct(true); } } } } XMPNode childNode = parent.FindChildByName(childName); if (childNode == null && createNodes) { PropertyOptions options = new PropertyOptions(); childNode = new XMPNode(childName, options); childNode.SetImplicit(true); parent.AddChild(childNode); } System.Diagnostics.Debug.Assert(childNode != null || !createNodes); return(childNode); }
/// <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>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> /// 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> /// 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); } }
// ------------------------------------------------------------------------------------- // 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>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>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>The parent is an RDF pseudo-struct containing an rdf:value field.</summary> /// <remarks> /// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the /// XMP data model. The rdf:value node must be the first child, the other /// children are qualifiers. The form, value, and children of the rdf:value /// node are the real ones. The rdf:value node's qualifiers must be added to /// the others. /// </remarks> /// <param name="xmpParent">the parent xmp node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void FixupQualifiedNode(XMPNode xmpParent) { System.Diagnostics.Debug.Assert(xmpParent.GetOptions().IsStruct() && xmpParent.HasChildren()); XMPNode valueNode = xmpParent.GetChild(1); System.Diagnostics.Debug.Assert("rdf:value".Equals(valueNode.GetName())); // Move the qualifiers on the value node to the parent. // Make sure an xml:lang qualifier stays at the front. // Check for duplicate names between the value node's qualifiers and the parent's children. // The parent's children are about to become qualifiers. Check here, between the groups. // Intra-group duplicates are caught by XMPNode#addChild(...). if (valueNode.GetOptions().GetHasLanguage()) { if (xmpParent.GetOptions().GetHasLanguage()) { throw new XMPException("Redundant xml:lang for rdf:value element", XMPErrorConstants.Badxmp); } XMPNode langQual = valueNode.GetQualifier(1); valueNode.RemoveQualifier(langQual); xmpParent.AddQualifier(langQual); } // Start the remaining copy after the xml:lang qualifier. for (int i = 1; i <= valueNode.GetQualifierLength(); i++) { XMPNode qualifier = valueNode.GetQualifier(i); xmpParent.AddQualifier(qualifier); } // Change the parent's other children into qualifiers. // This loop starts at 1, child 0 is the rdf:value node. for (int i_1 = 2; i_1 <= xmpParent.GetChildrenLength(); i_1++) { XMPNode qualifier = xmpParent.GetChild(i_1); xmpParent.AddQualifier(qualifier); } // Move the options and value last, other checks need the parent's original options. // Move the value node's children to be the parent's children. System.Diagnostics.Debug.Assert(xmpParent.GetOptions().IsStruct() || xmpParent.GetHasValueChild()); xmpParent.SetHasValueChild(false); xmpParent.GetOptions().SetStruct(false); xmpParent.GetOptions().MergeWith(valueNode.GetOptions()); xmpParent.SetValue(valueNode.GetValue()); xmpParent.RemoveChildren(); for (Iterator it = valueNode.IterateChildren(); it.HasNext(); ) { XMPNode child = (XMPNode)it.Next(); xmpParent.AddChild(child); } }
/// <summary>Find or create a child node under a given parent node.</summary> /// <remarks> /// Find or create a child node under a given parent node. If the parent node is no /// Returns the found or created child node. /// </remarks> /// <param name="parent">the parent node</param> /// <param name="childName">the node name to find</param> /// <param name="createNodes">flag, if new nodes shall be created.</param> /// <returns>Returns the found or created node or <code>null</code>.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if</exception> internal static XMPNode FindChildNode(XMPNode parent, string childName, bool createNodes) { if (!parent.GetOptions().IsSchemaNode() && !parent.GetOptions().IsStruct()) { if (!parent.IsImplicit()) { throw new XMPException("Named children only allowed for schemas and structs", XMPErrorConstants.Badxpath); } else { if (parent.GetOptions().IsArray()) { throw new XMPException("Named children not allowed for arrays", XMPErrorConstants.Badxpath); } else { if (createNodes) { parent.GetOptions().SetStruct(true); } } } } XMPNode childNode = parent.FindChildByName(childName); if (childNode == null && createNodes) { PropertyOptions options = new PropertyOptions(); childNode = new XMPNode(childName, options); childNode.SetImplicit(true); parent.AddChild(childNode); } System.Diagnostics.Debug.Assert(childNode != null || !createNodes); return childNode; }
/// <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> /// 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> /// 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; } }
/// <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; }
/// <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="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>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> /// 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>Adds a child node.</summary> /// <param name="xmp">the xmp metadata object that is generated</param> /// <param name="xmpParent">the parent xmp node</param> /// <param name="xmlNode">the currently processed XML node</param> /// <param name="value">Node value</param> /// <param name="isTopLevel">Flag if the node is a top-level node</param> /// <returns>Returns the newly created child node.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static XMPNode AddChildNode(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, string value, bool isTopLevel) { XMPSchemaRegistry registry = XMPMetaFactory.GetSchemaRegistry(); string @namespace = xmlNode.NamespaceURI; string childName; if (@namespace != null) { if (XMPConstConstants.NsDcDeprecated.Equals(@namespace)) { // Fix a legacy DC namespace @namespace = XMPConstConstants.NsDc; } string prefix = registry.GetNamespacePrefix(@namespace); if (prefix == null) { prefix = xmlNode.Prefix != null ? xmlNode.Prefix : DefaultPrefix; prefix = registry.RegisterNamespace(@namespace, prefix); } childName = prefix + xmlNode.LocalName; } else { throw new XMPException("XML namespace required for all elements and attributes", XMPErrorConstants.Badrdf); } // create schema node if not already there PropertyOptions childOptions = new PropertyOptions(); bool isAlias = false; if (isTopLevel) { // Lookup the schema node, adjust the XMP parent pointer. // Incoming parent must be the tree root. XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), @namespace, DefaultPrefix, true); schemaNode.SetImplicit(false); // Clear the implicit node bit. // need runtime check for proper 32 bit code. xmpParent = schemaNode; // If this is an alias set the alias flag in the node // and the hasAliases flag in the tree. if (registry.FindAlias(childName) != null) { isAlias = true; xmp.GetRoot().SetHasAliases(true); schemaNode.SetHasAliases(true); } } // Make sure that this is not a duplicate of a named node. bool isArrayItem = "rdf:li".Equals(childName); bool isValueNode = "rdf:value".Equals(childName); // Create XMP node and so some checks XMPNode newChild = new XMPNode(childName, value, childOptions); newChild.SetAlias(isAlias); // Add the new child to the XMP parent node, a value node first. if (!isValueNode) { xmpParent.AddChild(newChild); } else { xmpParent.AddChild(1, newChild); } if (isValueNode) { if (isTopLevel || !xmpParent.GetOptions().IsStruct()) { throw new XMPException("Misplaced rdf:value element", XMPErrorConstants.Badrdf); } xmpParent.SetHasValueChild(true); } if (isArrayItem) { if (!xmpParent.GetOptions().IsArray()) { throw new XMPException("Misplaced rdf:li element", XMPErrorConstants.Badrdf); } newChild.SetName(XMPConstConstants.ArrayItemName); } return newChild; }