Exemple #1
0
        /// <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="XmpException">Thrown if a duplicate property is added</exception>
        internal static void AppendLangItem(XmpNode arrayNode, string itemLang, string itemValue)
        {
            var newItem  = new XmpNode(XmpConstants.ArrayItemName, itemValue, null);
            var langQual = new XmpNode(XmpConstants.XmlLang, itemLang, null);

            newItem.AddQualifier(langQual);
            if (!XmpConstants.XDefault.Equals(langQual.Value))
            {
                arrayNode.AddChild(newItem);
            }
            else
            {
                arrayNode.AddChild(1, newItem);
            }
        }
        /// <summary>Find or create a schema node if <c>createNodes</c> 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, <c>null</c> otherwise.
        /// Note: If <c>createNodes</c> is <c>true</c>, it is <b>always</b>
        /// returned a valid node.
        /// </returns>
        /// <exception cref="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)
        {
            Debug.Assert(tree.Parent == null);
            // make sure that its the root
            var schemaNode = tree.FindChildByName(namespaceUri);

            if (schemaNode == null && createNodes)
            {
                var po = new PropertyOptions {
                    IsSchemaNode = true
                };
                schemaNode = new XmpNode(namespaceUri, po)
                {
                    IsImplicit = true
                };
                // only previously registered schema namespaces are allowed in the XMP tree.
                var prefix = XmpMetaFactory.SchemaRegistry.GetNamespacePrefix(namespaceUri);
                if (prefix == null)
                {
                    if (!string.IsNullOrEmpty(suggestedPrefix))
                    {
                        prefix = XmpMetaFactory.SchemaRegistry.RegisterNamespace(namespaceUri, suggestedPrefix);
                    }
                    else
                    {
                        throw new XmpException("Unregistered schema namespace URI", XmpErrorCode.BadSchema);
                    }
                }
                schemaNode.Value = 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 <c>null</c>.</returns>
        /// <exception cref="XmpException">Thrown if</exception>
        internal static XmpNode FindChildNode(XmpNode parent, string childName, bool createNodes)
        {
            if (!parent.Options.IsSchemaNode && !parent.Options.IsStruct)
            {
                if (!parent.IsImplicit)
                {
                    throw new XmpException("Named children only allowed for schemas and structs", XmpErrorCode.BadXPath);
                }
                if (parent.Options.IsArray)
                {
                    throw new XmpException("Named children not allowed for arrays", XmpErrorCode.BadXPath);
                }
                if (createNodes)
                {
                    parent.Options.IsStruct = true;
                }
            }
            var childNode = parent.FindChildByName(childName);

            if (childNode == null && createNodes)
            {
                var options = new PropertyOptions();
                childNode = new XmpNode(childName, options)
                {
                    IsImplicit = true
                };
                parent.AddChild(childNode);
            }
            Debug.Assert(childNode != null || !createNodes);
            return(childNode);
        }
        /// <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="XmpException"/>
        private void DoSetArrayItem(XmpNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert)
        {
            var itemNode = new XmpNode(XmpConstants.ArrayItemName, null);

            itemOptions = XmpNodeUtils.VerifySetOptions(itemOptions, itemValue);
            // in insert mode the index after the last is allowed,
            // even ARRAY_LAST_ITEM points to the index *after* the last.
            var maxIndex = insert ? arrayNode.GetChildrenLength() + 1 : arrayNode.GetChildrenLength();

            if (itemIndex == XmpConstants.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", XmpErrorCode.BadIndex);
            }
        }
Exemple #5
0
 /// <summary>Make sure the x-default item is first.</summary>
 /// <remarks>
 /// Make sure the x-default item is first. Touch up &quot;single value&quot;
 /// 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 &quot;x-default&quot; 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.Options.IsArrayAltText)
     {
         return;
     }
     // check if node with x-default qual is first place
     for (var i = 2; i <= arrayNode.GetChildrenLength(); i++)
     {
         var child = arrayNode.GetChild(i);
         if (child.HasQualifier && XmpConstants.XDefault.Equals(child.GetQualifier(1).Value))
         {
             // move node to first place
             try
             {
                 arrayNode.RemoveChild(i);
                 arrayNode.AddChild(1, child);
             }
             catch (XmpException)
             {
                 // cannot occur, because same child is removed before
                 Debug.Assert(false);
             }
             if (i == 2)
             {
                 arrayNode.GetChild(2).Value = child.Value;
             }
             break;
         }
     }
 }
Exemple #6
0
 /// <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="XmpException"></exception>
 private static int LookupQualSelector(XmpNode arrayNode, string qualName, string qualValue, int aliasForm)
 {
     if (XmpConstants.XmlLang.Equals(qualName))
     {
         qualValue = Utils.NormalizeLangValue(qualValue);
         var index = LookupLanguageItem(arrayNode, qualValue);
         if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0)
         {
             var langNode = new XmpNode(XmpConstants.ArrayItemName, null);
             var xdefault = new XmpNode(XmpConstants.XmlLang, XmpConstants.XDefault, null);
             langNode.AddQualifier(xdefault);
             arrayNode.AddChild(1, langNode);
             return(1);
         }
         return(index);
     }
     for (var index = 1; index < arrayNode.GetChildrenLength(); index++)
     {
         var currItem = arrayNode.GetChild(index);
         for (var it = currItem.IterateQualifier(); it.HasNext();)
         {
             var qualifier = (XmpNode)it.Next();
             if (qualName.Equals(qualifier.Name) && qualValue.Equals(qualifier.Value))
             {
                 return(index);
             }
         }
     }
     return(-1);
 }
Exemple #7
0
        /// <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="XmpException">Throws Exceptions</exception>
        private static int FindIndexedItem(XmpNode arrayNode, string segment, bool createNodes)
        {
            var index = 0;

            try
            {
                segment = segment.Substring(1, segment.Length - 1 - 1);
                index   = Convert.ToInt32(segment);
                if (index < 1)
                {
                    throw new XmpException("Array index must be larger than zero", XmpErrorCode.BadXPath);
                }
            }
            catch (FormatException)
            {
                throw new XmpException("Array index not digits.", XmpErrorCode.BadXPath);
            }
            if (createNodes && index == arrayNode.GetChildrenLength() + 1)
            {
                // Append a new last + 1 node.
                var newItem = new XmpNode(XmpConstants.ArrayItemName, null)
                {
                    IsImplicit = 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="XmpException">Forwards XMP errors</exception>
 private static void TransplantArrayItemAlias(IIterator propertyIt, XmpNode childNode, XmpNode baseArray)
 {
     if (baseArray.Options.IsArrayAltText)
     {
         if (childNode.Options.HasLanguage)
         {
             throw new XmpException("Alias to x-default already has a language qualifier", XmpErrorCode.BadXmp);
         }
         var langQual = new XmpNode(XmpConstants.XmlLang, XmpConstants.XDefault, null);
         childNode.AddQualifier(langQual);
     }
     propertyIt.Remove();
     childNode.Name = XmpConstants.ArrayItemName;
     baseArray.AddChild(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="XmpException">Throws Exceptions</exception>
        private static int FindIndexedItem(XmpNode arrayNode, string segment, bool createNodes)
        {
            if (!int.TryParse(segment.Substring(1, segment.Length - 1 - 1), out int index))
            {
                throw new XmpException("Array index not digits.", XmpErrorCode.BadXPath);
            }

            if (createNodes && index == arrayNode.GetChildrenLength() + 1)
            {
                // Append a new last + 1 node.
                var newItem = new XmpNode(XmpConstants.ArrayItemName, null)
                {
                    IsImplicit = true
                };
                arrayNode.AddChild(newItem);
            }

            return(index);
        }
Exemple #10
0
 /// <summary>
 /// Performs a <b>deep clone</b> of the complete subtree (children and
 /// qualifier )into and add it to the destination node.
 /// </summary>
 /// <param name="destination">the node to add the cloned subtree</param>
 public void CloneSubtree(XmpNode destination)
 {
     try
     {
         for (var it = IterateChildren(); it.HasNext();)
         {
             var child = (XmpNode)it.Next();
             destination.AddChild((XmpNode)child.Clone());
         }
         for (var it1 = IterateQualifier(); it1.HasNext();)
         {
             var qualifier = (XmpNode)it1.Next();
             destination.AddQualifier((XmpNode)qualifier.Clone());
         }
     }
     catch (XmpException)
     {
         // cannot happen (duplicate childs/quals do not exist in this node)
         Debug.Assert(false);
     }
 }
 /// <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 <c>xml:lang</c> attribute was dropped from an
 /// <c>alt-text</c> item if the language was <c>x-default</c>.
 /// </remarks>
 /// <param name="dcSchema">the DC schema node</param>
 /// <exception cref="XmpException">Thrown if normalization fails</exception>
 private static void NormalizeDcArrays(XmpNode dcSchema)
 {
     for (var i = 1; i <= dcSchema.GetChildrenLength(); i++)
     {
         var currProp  = dcSchema.GetChild(i);
         var arrayForm = (PropertyOptions)_dcArrayForms[currProp.Name];
         if (arrayForm == null)
         {
             continue;
         }
         if (currProp.Options.IsSimple)
         {
             // create a new array and add the current property as child,
             // if it was formerly simple
             var newArray = new XmpNode(currProp.Name, arrayForm);
             currProp.Name = XmpConstants.ArrayItemName;
             newArray.AddChild(currProp);
             dcSchema.ReplaceChild(i, newArray);
             // fix language alternatives
             if (arrayForm.IsArrayAltText && !currProp.Options.HasLanguage)
             {
                 var newLang = new XmpNode(XmpConstants.XmlLang, XmpConstants.XDefault, null);
                 currProp.AddQualifier(newLang);
             }
         }
         else
         {
             // clear array options and add corrected array form if it has been an array before
             currProp.Options.SetOption(PropertyOptions.ArrayFlag | PropertyOptions.ArrayOrderedFlag | PropertyOptions.ArrayAlternateFlag | PropertyOptions.ArrayAltTextFlag, false);
             currProp.Options.MergeWith(arrayForm);
             if (arrayForm.IsArrayAltText)
             {
                 // applying for "dc:description", "dc:rights", "dc:title"
                 RepairAltText(currProp);
             }
         }
     }
 }
        /// <param name="destXmp">The destination XMP object.</param>
        /// <param name="sourceNode">the source node</param>
        /// <param name="destParent">the parent of the destination node</param>
        /// <param name="replaceOldValues">Replace the values of existing properties.</param>
        /// <param name="deleteEmptyValues">flag if properties with empty values should be deleted in the destination object.</param>
        /// <exception cref="XmpException"/>
        private static void AppendSubtree(XmpMeta destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues)
        {
            var destNode     = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false);
            var valueIsEmpty = false;

            if (deleteEmptyValues)
            {
                valueIsEmpty = sourceNode.Options.IsSimple ? string.IsNullOrEmpty(sourceNode.Value) : !sourceNode.HasChildren;
            }

            if (deleteEmptyValues && valueIsEmpty)
            {
                if (destNode != null)
                {
                    destParent.RemoveChild(destNode);
                }
            }
            else
            {
                if (destNode == null)
                {
                    // The one easy case, the destination does not exist.
                    destParent.AddChild((XmpNode)sourceNode.Clone());
                }
                else
                {
                    if (replaceOldValues)
                    {
                        // The destination exists and should be replaced.
                        destXmp.SetNode(destNode, sourceNode.Value, sourceNode.Options, true);
                        destParent.RemoveChild(destNode);
                        destNode = (XmpNode)sourceNode.Clone();
                        destParent.AddChild(destNode);
                    }
                    else
                    {
                        // The destination exists and is not totally replaced. Structs and arrays are merged.
                        var sourceForm = sourceNode.Options;
                        var destForm   = destNode.Options;
                        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 (var it = sourceNode.IterateChildren(); it.HasNext();)
                            {
                                var 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 (var it = sourceNode.IterateChildren(); it.HasNext();)
                            {
                                var sourceItem = (XmpNode)it.Next();

                                if (!sourceItem.HasQualifier || !XmpConstants.XmlLang.Equals(sourceItem.GetQualifier(1).Name))
                                {
                                    continue;
                                }

                                var destIndex = XmpNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).Value);
                                if (deleteEmptyValues && string.IsNullOrEmpty(sourceItem.Value))
                                {
                                    if (destIndex != -1)
                                    {
                                        destNode.RemoveChild(destIndex);
                                        if (!destNode.HasChildren)
                                        {
                                            destParent.RemoveChild(destNode);
                                        }
                                    }
                                }
                                else if (destIndex == -1)
                                {
                                    // Not replacing, keep the existing item.
                                    if (!XmpConstants.XDefault.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren)
                                    {
                                        sourceItem.CloneSubtree(destNode);
                                    }
                                    else
                                    {
                                        var destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options);
                                        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 (var children = sourceNode.IterateChildren(); children.HasNext();)
                            {
                                var sourceItem = (XmpNode)children.Next();

                                var match = false;
                                for (var id = destNode.IterateChildren(); id.HasNext();)
                                {
                                    var destItem = (XmpNode)id.Next();
                                    if (ItemValuesMatch(sourceItem, destItem))
                                    {
                                        match = true;
                                    }
                                }

                                if (!match)
                                {
                                    destNode = (XmpNode)sourceItem.Clone();
                                    destParent.AddChild(destNode);
                                }
                            }
                        }
                    }
                }
            }
        }