/// <summary>
 /// Tweak old XMP: Move an instance ID from rdf:about to the
 /// <em>xmpMM:InstanceID</em> property.
 /// </summary>
 /// <remarks>
 /// Tweak old XMP: Move an instance ID from rdf:about to the
 /// <em>xmpMM:InstanceID</em> property. An old instance ID usually looks
 /// like &quot;uuid:bac965c4-9d87-11d9-9a30-000d936b79c4&quot;, plus InDesign
 /// 3.0 wrote them like &quot;bac965c4-9d87-11d9-9a30-000d936b79c4&quot;. If
 /// the name looks like a UUID simply move it to <em>xmpMM:InstanceID</em>,
 /// don't worry about any existing <em>xmpMM:InstanceID</em>. Both will
 /// only be present when a newer file with the <em>xmpMM:InstanceID</em>
 /// property is updated by an old app that uses <em>rdf:about</em>.
 /// </remarks>
 /// <param name="tree">the root of the metadata tree</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if tweaking fails.</exception>
 private static void TweakOldXMP(XMPNode tree)
 {
     if (tree.GetName() != null && tree.GetName().Length >= Utils.UuidLength)
     {
         string nameStr = tree.GetName().ToLower();
         if (nameStr.StartsWith("uuid:"))
         {
             nameStr = Sharpen.Runtime.Substring(nameStr, 5);
         }
         if (Utils.CheckUUIDFormat(nameStr))
         {
             // move UUID to xmpMM:InstanceID and remove it from the root node
             XMPPath path   = XMPPathParser.ExpandXPath(XMPConstConstants.NsXmpMm, "InstanceID");
             XMPNode idNode = XMPNodeUtils.FindNode(tree, path, true, null);
             if (idNode != null)
             {
                 idNode.SetOptions(null);
                 // Clobber any existing xmpMM:InstanceID.
                 idNode.SetValue("uuid:" + nameStr);
                 idNode.RemoveChildren();
                 idNode.RemoveQualifiers();
                 tree.SetName(null);
             }
             else
             {
                 throw new XMPException("Failure creating xmpMM:InstanceID", XMPErrorConstants.Internalfailure);
             }
         }
     }
 }
 /// <summary>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>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>
 /// Tweak old XMP: Move an instance ID from rdf:about to the
 /// <em>xmpMM:InstanceID</em> property.
 /// </summary>
 /// <remarks>
 /// Tweak old XMP: Move an instance ID from rdf:about to the
 /// <em>xmpMM:InstanceID</em> property. An old instance ID usually looks
 /// like &quot;uuid:bac965c4-9d87-11d9-9a30-000d936b79c4&quot;, plus InDesign
 /// 3.0 wrote them like &quot;bac965c4-9d87-11d9-9a30-000d936b79c4&quot;. If
 /// the name looks like a UUID simply move it to <em>xmpMM:InstanceID</em>,
 /// don't worry about any existing <em>xmpMM:InstanceID</em>. Both will
 /// only be present when a newer file with the <em>xmpMM:InstanceID</em>
 /// property is updated by an old app that uses <em>rdf:about</em>.
 /// </remarks>
 /// <param name="tree">the root of the metadata tree</param>
 /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if tweaking fails.</exception>
 private static void TweakOldXMP(XMPNode tree)
 {
     if (tree.GetName() != null && tree.GetName().Length >= Utils.UuidLength)
     {
         string nameStr = tree.GetName().ToLower();
         if (nameStr.StartsWith("uuid:"))
         {
             nameStr = Sharpen.Runtime.Substring(nameStr, 5);
         }
         if (Utils.CheckUUIDFormat(nameStr))
         {
             // move UUID to xmpMM:InstanceID and remove it from the root node
             XMPPath path = XMPPathParser.ExpandXPath(XMPConstConstants.NsXmpMm, "InstanceID");
             XMPNode idNode = XMPNodeUtils.FindNode(tree, path, true, null);
             if (idNode != null)
             {
                 idNode.SetOptions(null);
                 // Clobber any existing xmpMM:InstanceID.
                 idNode.SetValue("uuid:" + nameStr);
                 idNode.RemoveChildren();
                 idNode.RemoveQualifiers();
                 tree.SetName(null);
             }
             else
             {
                 throw new XMPException("Failure creating xmpMM:InstanceID", XMPErrorConstants.Internalfailure);
             }
         }
     }
 }
 /// <summary>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>
        /// 7.2.7 propertyAttributeURIs
        /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
        /// 7.2.11 nodeElement
        /// start-element ( URI == nodeElementURIs,
        /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
        /// propertyEltList
        /// end-element()
        /// Process the attribute list for an RDF node element.
        /// </summary>
        /// <remarks>
        /// 7.2.7 propertyAttributeURIs
        /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
        /// 7.2.11 nodeElement
        /// start-element ( URI == nodeElementURIs,
        /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
        /// propertyEltList
        /// end-element()
        /// Process the attribute list for an RDF node element. A property attribute URI is
        /// anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored,
        /// as are rdf:about attributes on inner nodes.
        /// </remarks>
        /// <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="isTopLevel">Flag if the node is a top-level node</param>
        /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception>
        private static void Rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel)
        {
            // Used to detect attributes that are mutually exclusive.
            int exclusiveAttrs = 0;
            for (int i = 0; i < xmlNode.Attributes.Count; i++)
            {
                XmlNode attribute = xmlNode.Attributes.Item(i);
                // quick hack, ns declarations do not appear in C++
                // ignore "ID" without namespace
                if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                int attrTerm = GetRDFTermKind(attribute);
                switch (attrTerm)
                {
                    case RdftermId:
                    case RdftermNodeId:
                    case RdftermAbout:
                    {
                        if (exclusiveAttrs > 0)
                        {
                            throw new XMPException("Mutally exclusive about, ID, nodeID attributes", XMPErrorConstants.Badrdf);
                        }
                        exclusiveAttrs++;
                        if (isTopLevel && (attrTerm == RdftermAbout))
                        {
                            // This is the rdf:about attribute on a top level node. Set
                            // the XMP tree name if
                            // it doesn't have a name yet. Make sure this name matches
                            // the XMP tree name.
                            if (xmpParent.GetName() != null && xmpParent.GetName().Length > 0)
                            {
                                if (!xmpParent.GetName().Equals(attribute.Value))
                                {
                                    throw new XMPException("Mismatched top level rdf:about values", XMPErrorConstants.Badxmp);
                                }
                            }
                            else
                            {
                                xmpParent.SetName(attribute.Value);
                            }
                        }
                        break;
                    }

                    case RdftermOther:
                    {
                        AddChildNode(xmp, xmpParent, attribute, attribute.Value, isTopLevel);
                        break;
                    }

                    default:
                    {
                        throw new XMPException("Invalid nodeElement attribute", XMPErrorConstants.Badrdf);
                    }
                }
            }
        }
 /// <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;
 }