/// <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)
     {
     }
 }
Example #2
0
 /// <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());
 }
 /// <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();
         }
     }
 }
Example #4
0
 /// <summary>Prepares the next node to return if not already done.</summary>
 /// <seealso cref="Sharpen.Iterator{E}.HasNext()"/>
 public override bool HasNext()
 {
     if (this.GetReturnProperty() != null)
     {
         // hasNext has been called before
         return(true);
     }
     else
     {
         if (this._enclosing.skipSiblings)
         {
             return(false);
         }
         else
         {
             if (this.childrenIterator.HasNext())
             {
                 XMPNode child = (XMPNode)this.childrenIterator.Next();
                 this.index++;
                 string path = null;
                 if (child.GetOptions().IsSchemaNode())
                 {
                     this._enclosing.SetBaseNS(child.GetName());
                 }
                 else
                 {
                     if (child.GetParent() != null)
                     {
                         // for all but the root node and schema nodes
                         path = this.AccumulatePath(child, this.parentPath, this.index);
                     }
                 }
                 // report next property, skip not-leaf nodes in case options is set
                 if (!this._enclosing.GetOptions().IsJustLeafnodes() || !child.HasChildren())
                 {
                     this.SetReturnProperty(this.CreatePropertyInfo(child, this._enclosing.GetBaseNS(), path));
                     return(true);
                 }
                 else
                 {
                     return(this.HasNext());
                 }
             }
             else
             {
                 return(false);
             }
         }
     }
 }
Example #5
0
        /// <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);
            }
        }
Example #6
0
 /// <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);
         }
     }
 }
Example #7
0
        /// <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);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
Example #8
0
        /// <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();
                        }
                    }
                }
            }
        }
		/// <summary>Serializes a struct property.</summary>
		/// <param name="node">an XMPNode</param>
		/// <param name="indent">the current indent level</param>
		/// <param name="hasRDFResourceQual">Flag if the element has resource qualifier</param>
		/// <returns>Returns true if an end flag shall be emitted.</returns>
		/// <exception cref="System.IO.IOException">Forwards the writer exceptions.</exception>
		/// <exception cref="Com.Adobe.Xmp.XMPException">If qualifier and element fields are mixed.</exception>
		private bool SerializeCompactRDFStructProp(XMPNode node, int indent, bool hasRDFResourceQual)
		{
			// This must be a struct.
			bool hasAttrFields = false;
			bool hasElemFields = false;
			bool emitEndTag = true;
			for (Iterator ic = node.IterateChildren(); ic.HasNext(); )
			{
				XMPNode field = (XMPNode)ic.Next();
				if (CanBeRDFAttrProp(field))
				{
					hasAttrFields = true;
				}
				else
				{
					hasElemFields = true;
				}
				if (hasAttrFields && hasElemFields)
				{
					break;
				}
			}
			// No sense looking further.
			if (hasRDFResourceQual && hasElemFields)
			{
				throw new XMPException("Can't mix rdf:resource qualifier and element fields", XMPErrorConstants.Badrdf);
			}
			if (!node.HasChildren())
			{
				// Catch an empty struct as a special case. The case
				// below would emit an empty
				// XML element, which gets reparsed as a simple property
				// with an empty value.
				Write(" rdf:parseType=\"Resource\"/>");
				WriteNewline();
				emitEndTag = false;
			}
			else
			{
				if (!hasElemFields)
				{
					// All fields can be attributes, use the
					// emptyPropertyElt form.
					SerializeCompactRDFAttrProps(node, indent + 1);
					Write("/>");
					WriteNewline();
					emitEndTag = false;
				}
				else
				{
					if (!hasAttrFields)
					{
						// All fields must be elements, use the
						// parseTypeResourcePropertyElt form.
						Write(" rdf:parseType=\"Resource\">");
						WriteNewline();
						SerializeCompactRDFElementProps(node, indent + 1);
					}
					else
					{
						// Have a mix of attributes and elements, use an inner rdf:Description.
						Write('>');
						WriteNewline();
						WriteIndent(indent + 1);
						Write(RdfStructStart);
						SerializeCompactRDFAttrProps(node, indent + 2);
						Write(">");
						WriteNewline();
						SerializeCompactRDFElementProps(node, indent + 1);
						WriteIndent(indent + 1);
						Write(RdfStructEnd);
						WriteNewline();
					}
				}
			}
			return emitEndTag;
		}
		/// <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();
		}
 /// <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>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>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>Recursively handles the "value" for a node.</summary>
		/// <remarks>
		/// Recursively handles the "value" for a node. It does not matter if it is a
		/// top level property, a field of a struct, or an item of an array. The
		/// indent is that for the property element. An xml:lang qualifier is written
		/// as an attribute of the property start tag, not by itself forcing the
		/// qualified property form. The patterns below mostly ignore attribute
		/// qualifiers like xml:lang. Except for the one struct case, attribute
		/// qualifiers don't affect the output form.
		/// <blockquote>
		/// <pre>
		/// &lt;ns:UnqualifiedSimpleProperty&gt;value&lt;/ns:UnqualifiedSimpleProperty&gt;
		/// &lt;ns:UnqualifiedStructProperty&gt; (If no rdf:resource qualifier)
		/// &lt;rdf:Description&gt;
		/// ... Fields, same forms as top level properties
		/// &lt;/rdf:Description&gt;
		/// &lt;/ns:UnqualifiedStructProperty&gt;
		/// &lt;ns:ResourceStructProperty rdf:resource=&quot;URI&quot;
		/// ... Fields as attributes
		/// &gt;
		/// &lt;ns:UnqualifiedArrayProperty&gt;
		/// &lt;rdf:Bag&gt; or Seq or Alt
		/// ... Array items as rdf:li elements, same forms as top level properties
		/// &lt;/rdf:Bag&gt;
		/// &lt;/ns:UnqualifiedArrayProperty&gt;
		/// &lt;ns:QualifiedProperty&gt;
		/// &lt;rdf:Description&gt;
		/// &lt;rdf:value&gt; ... Property &quot;value&quot; following the unqualified
		/// forms ... &lt;/rdf:value&gt;
		/// ... Qualifiers looking like named struct fields
		/// &lt;/rdf:Description&gt;
		/// &lt;/ns:QualifiedProperty&gt;
		/// </pre>
		/// </blockquote>
		/// </remarks>
		/// <param name="node">the property node</param>
		/// <param name="emitAsRDFValue">property shall be rendered as attribute rather than tag</param>
		/// <param name="useCanonicalRDF">
		/// use canonical form with inner description tag or
		/// the compact form with rdf:ParseType=&quot;resource&quot; attribute.
		/// </param>
		/// <param name="indent">the current indent level</param>
		/// <exception cref="System.IO.IOException">Forwards all writer exceptions.</exception>
		/// <exception cref="Com.Adobe.Xmp.XMPException">If &quot;rdf:resource&quot; and general qualifiers are mixed.</exception>
		private void SerializeCanonicalRDFProperty(XMPNode node, bool useCanonicalRDF, bool emitAsRDFValue, int indent)
		{
			bool emitEndTag = true;
			bool indentEndTag = true;
			// Determine the XML element name. Open the start tag with the name and
			// attribute qualifiers.
			string elemName = node.GetName();
			if (emitAsRDFValue)
			{
				elemName = "rdf:value";
			}
			else
			{
				if (XMPConstConstants.ArrayItemName.Equals(elemName))
				{
					elemName = "rdf:li";
				}
			}
			WriteIndent(indent);
			Write('<');
			Write(elemName);
			bool hasGeneralQualifiers = false;
			bool hasRDFResourceQual = false;
			for (Iterator it = node.IterateQualifier(); it.HasNext(); )
			{
				XMPNode qualifier = (XMPNode)it.Next();
				if (!RdfAttrQualifier.Contains(qualifier.GetName()))
				{
					hasGeneralQualifiers = true;
				}
				else
				{
					hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName());
					if (!emitAsRDFValue)
					{
						Write(' ');
						Write(qualifier.GetName());
						Write("=\"");
						AppendNodeValue(qualifier.GetValue(), true);
						Write('"');
					}
				}
			}
			// Process the property according to the standard patterns.
			if (hasGeneralQualifiers && !emitAsRDFValue)
			{
				// This node has general, non-attribute, qualifiers. Emit using the
				// qualified property form.
				// ! The value is output by a recursive call ON THE SAME NODE with
				// emitAsRDFValue set.
				if (hasRDFResourceQual)
				{
					throw new XMPException("Can't mix rdf:resource and general qualifiers", XMPErrorConstants.Badrdf);
				}
				// Change serialization to canonical format with inner rdf:Description-tag
				// depending on option
				if (useCanonicalRDF)
				{
					Write(">");
					WriteNewline();
					indent++;
					WriteIndent(indent);
					Write(RdfStructStart);
					Write(">");
				}
				else
				{
					Write(" rdf:parseType=\"Resource\">");
				}
				WriteNewline();
				SerializeCanonicalRDFProperty(node, useCanonicalRDF, true, indent + 1);
				for (Iterator it_1 = node.IterateQualifier(); it_1.HasNext(); )
				{
					XMPNode qualifier = (XMPNode)it_1.Next();
					if (!RdfAttrQualifier.Contains(qualifier.GetName()))
					{
						SerializeCanonicalRDFProperty(qualifier, useCanonicalRDF, false, indent + 1);
					}
				}
				if (useCanonicalRDF)
				{
					WriteIndent(indent);
					Write(RdfStructEnd);
					WriteNewline();
					indent--;
				}
			}
			else
			{
				// This node has no general qualifiers. Emit using an unqualified form.
				if (!node.GetOptions().IsCompositeProperty())
				{
					// This is a simple property.
					if (node.GetOptions().IsURI())
					{
						Write(" rdf:resource=\"");
						AppendNodeValue(node.GetValue(), true);
						Write("\"/>");
						WriteNewline();
						emitEndTag = false;
					}
					else
					{
						if (node.GetValue() == null || string.Empty.Equals(node.GetValue()))
						{
							Write("/>");
							WriteNewline();
							emitEndTag = false;
						}
						else
						{
							Write('>');
							AppendNodeValue(node.GetValue(), false);
							indentEndTag = false;
						}
					}
				}
				else
				{
					if (node.GetOptions().IsArray())
					{
						// This is an array.
						Write('>');
						WriteNewline();
						EmitRDFArrayTag(node, true, indent + 1);
						if (node.GetOptions().IsArrayAltText())
						{
							XMPNodeUtils.NormalizeLangArray(node);
						}
						for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); )
						{
							XMPNode child = (XMPNode)it_1.Next();
							SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 2);
						}
						EmitRDFArrayTag(node, false, indent + 1);
					}
					else
					{
						if (!hasRDFResourceQual)
						{
							// This is a "normal" struct, use the rdf:parseType="Resource" form.
							if (!node.HasChildren())
							{
								// Change serialization to canonical format with inner rdf:Description-tag
								// if option is set
								if (useCanonicalRDF)
								{
									Write(">");
									WriteNewline();
									WriteIndent(indent + 1);
									Write(RdfEmptyStruct);
								}
								else
								{
									Write(" rdf:parseType=\"Resource\"/>");
									emitEndTag = false;
								}
								WriteNewline();
							}
							else
							{
								// Change serialization to canonical format with inner rdf:Description-tag
								// if option is set
								if (useCanonicalRDF)
								{
									Write(">");
									WriteNewline();
									indent++;
									WriteIndent(indent);
									Write(RdfStructStart);
									Write(">");
								}
								else
								{
									Write(" rdf:parseType=\"Resource\">");
								}
								WriteNewline();
								for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); )
								{
									XMPNode child = (XMPNode)it_1.Next();
									SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 1);
								}
								if (useCanonicalRDF)
								{
									WriteIndent(indent);
									Write(RdfStructEnd);
									WriteNewline();
									indent--;
								}
							}
						}
						else
						{
							// This is a struct with an rdf:resource attribute, use the
							// "empty property element" form.
							for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); )
							{
								XMPNode child = (XMPNode)it_1.Next();
								if (!CanBeRDFAttrProp(child))
								{
									throw new XMPException("Can't mix rdf:resource and complex fields", XMPErrorConstants.Badrdf);
								}
								WriteNewline();
								WriteIndent(indent + 1);
								Write(' ');
								Write(child.GetName());
								Write("=\"");
								AppendNodeValue(child.GetValue(), true);
								Write('"');
							}
							Write("/>");
							WriteNewline();
							emitEndTag = false;
						}
					}
				}
			}
			// Emit the property element end tag.
			if (emitEndTag)
			{
				if (indentEndTag)
				{
					WriteIndent(indent);
				}
				Write("</");
				Write(elemName);
				Write('>');
				WriteNewline();
			}
		}
		/// <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);
										}
									}
								}
							}
						}
					}
				}
			}
		}
Example #16
0
        /// <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>Writes the array start and end tags.</summary>
		/// <param name="arrayNode">an array node</param>
		/// <param name="isStartTag">flag if its the start or end tag</param>
		/// <param name="indent">the current indent level</param>
		/// <exception cref="System.IO.IOException">forwards writer exceptions</exception>
		private void EmitRDFArrayTag(XMPNode arrayNode, bool isStartTag, int indent)
		{
			if (isStartTag || arrayNode.HasChildren())
			{
				WriteIndent(indent);
				Write(isStartTag ? "<rdf:" : "</rdf:");
				if (arrayNode.GetOptions().IsArrayAlternate())
				{
					Write("Alt");
				}
				else
				{
					if (arrayNode.GetOptions().IsArrayOrdered())
					{
						Write("Seq");
					}
					else
					{
						Write("Bag");
					}
				}
				if (isStartTag && !arrayNode.HasChildren())
				{
					Write("/>");
				}
				else
				{
					Write(">");
				}
				WriteNewline();
			}
		}