Beispiel #1
0
 // UTF-8
 /// <summary>The actual serialization.</summary>
 /// <param name="xmp">the metadata object to be serialized</param>
 /// <param name="stream">outputStream the output stream to serialize to</param>
 /// <param name="options">the serialization options</param>
 /// <exception cref="XmpException">If case of wrong options or any other serialization error.</exception>
 public void Serialize(IXmpMeta xmp, Stream stream, SerializeOptions options)
 {
     try
     {
         _stream   = stream;
         _startPos = _stream.Position;
         _writer   = new StreamWriter(_stream, options.GetEncoding());
         _xmp      = (XmpMeta)xmp;
         _options  = options;
         _padding  = options.Padding;
         _writer   = new StreamWriter(_stream, options.GetEncoding());
         CheckOptionsConsistence();
         // serializes the whole packet, but don't write the tail yet
         // and flush to make sure that the written bytes are calculated correctly
         var tailStr = SerializeAsRdf();
         _writer.Flush();
         // adds padding
         AddPadding(tailStr.Length);
         // writes the tail
         Write(tailStr);
         _writer.Flush();
     }
     catch (IOException)
     {
         throw new XmpException("Error writing to the OutputStream", XmpErrorCode.Unknown);
     }
 }
        /// <summary>Serializes an <c>XMPMeta</c>-object as RDF into a byte buffer.</summary>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="options">Options to control the serialization (see <see cref="SerializeOptions"/>).</param>
        /// <returns>Returns a byte buffer containing the serialized RDF.</returns>
        /// <exception cref="XmpException">on serialization errors.</exception>
        public static byte[] SerializeToBuffer(XmpMeta xmp, SerializeOptions options)
        {
            var output = new MemoryStream(2048);

            Serialize(xmp, output, options);
            return(output.ToArray());
        }
 /// <summary>Normalizes a raw parsed XMPMeta-Object</summary>
 /// <param name="xmp">the raw metadata object</param>
 /// <param name="options">the parsing options</param>
 /// <returns>Returns the normalized metadata object</returns>
 /// <exception cref="XmpException">Collects all severe processing errors.</exception>
 internal static IXmpMeta Process(XmpMeta xmp, ParseOptions options)
 {
     var tree = xmp.GetRoot();
     TouchUpDataModel(xmp);
     MoveExplicitAliases(tree, options);
     TweakOldXmp(tree);
     DeleteEmptySchemas(tree);
     return xmp;
 }
        /// <summary>Static method to serialize the metadata object.</summary>
        /// <remarks>
        /// For each serialisation, a new XMPSerializer
        /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so that its possible to
        /// serialialize the same XMPMeta objects in two threads.
        /// </remarks>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="stream">the output stream to serialize to</param>
        /// <param name="options">serialization options, can be <c>null</c> for default.</param>
        /// <exception cref="XmpException"/>
        public static void Serialize(XmpMeta xmp, Stream stream, SerializeOptions options)
        {
            options = options ?? new SerializeOptions();

            // sort the internal data model on demand
            if (options.Sort)
                xmp.Sort();

            new XmpSerializerRdf().Serialize(xmp, stream, options);
        }
        /// <summary>Normalizes a raw parsed XMPMeta-Object</summary>
        /// <param name="xmp">the raw metadata object</param>
        /// <param name="options">the parsing options</param>
        /// <returns>Returns the normalized metadata object</returns>
        /// <exception cref="XmpException">Collects all severe processing errors.</exception>
        internal static IXmpMeta Process(XmpMeta xmp, ParseOptions options)
        {
            var tree = xmp.GetRoot();

            TouchUpDataModel(xmp);
            MoveExplicitAliases(tree, options);
            TweakOldXmp(tree);
            DeleteEmptySchemas(tree);
            return(xmp);
        }
Beispiel #6
0
        /// <summary>Visit all schemas to do general fixes and handle special cases.</summary>
        /// <param name="xmp">the metadata object implementation</param>
        /// <exception cref="XmpException">Thrown if the normalisation fails.</exception>
        private static void TouchUpDataModel(XmpMeta xmp)
        {
            // make sure the DC schema is existing, because it might be needed within the normalization
            // if not touched it will be removed by removeEmptySchemas
            XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), XmpConstants.NsDC, true);

            // Do the special case fixes within each schema.
            for (var it = xmp.GetRoot().IterateChildren(); it.HasNext();)
            {
                var currSchema = (XmpNode)it.Next();

                switch (currSchema.Name)
                {
                case XmpConstants.NsDC:
                {
                    NormalizeDcArrays(currSchema);
                    break;
                }

                case XmpConstants.NsExif:
                {
                    // Do a special case fix for exif:GPSTimeStamp.
                    FixGpsTimeStamp(currSchema);
                    var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                    if (arrayNode != null)
                    {
                        RepairAltText(arrayNode);
                    }
                    break;
                }

                case XmpConstants.NsDm:
                {
                    // Do a special case migration of xmpDM:copyright to
                    // dc:rights['x-default'].
                    var dmCopyright = XmpNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false);
                    if (dmCopyright != null)
                    {
                        MigrateAudioCopyright(xmp, dmCopyright);
                    }
                    break;
                }

                case XmpConstants.NsXmpRights:
                {
                    var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false);
                    if (arrayNode != null)
                    {
                        RepairAltText(arrayNode);
                    }
                    break;
                }
                }
            }
        }
Beispiel #7
0
        /// <summary>Constructor with optional initial values.</summary>
        /// <remarks>If <c>propName</c> is provided, <c>schemaNS</c> has also be provided.</remarks>
        /// <param name="xmp">the iterated metadata object.</param>
        /// <param name="schemaNs">the iteration is reduced to this schema (optional)</param>
        /// <param name="propPath">the iteration is reduced to this property within the <c>schemaNS</c></param>
        /// <param name="options">advanced iteration options, see <see cref="IteratorOptions"/></param>
        /// <exception cref="XmpException">If the node defined by the parameters is not existing.</exception>
        public XmpIterator(XmpMeta xmp, string schemaNs, string propPath, IteratorOptions options)
        {
            // make sure that options is defined at least with defaults
            Options = options ?? new IteratorOptions();
            // the start node of the iteration depending on the schema and property filter
            XmpNode startNode    = null;
            string  initialPath  = null;
            var     baseSchema   = !string.IsNullOrEmpty(schemaNs);
            var     baseProperty = !string.IsNullOrEmpty(propPath);

            if (!baseSchema && !baseProperty)
            {
                // complete tree will be iterated
                startNode = xmp.GetRoot();
            }
            else
            {
                if (baseSchema && baseProperty)
                {
                    // Schema and property node provided
                    var path = XmpPathParser.ExpandXPath(schemaNs, propPath);
                    // base path is the prop path without the property leaf
                    var basePath = new XmpPath();
                    for (var i = 0; i < path.Size() - 1; i++)
                    {
                        basePath.Add(path.GetSegment(i));
                    }
                    startNode     = XmpNodeUtils.FindNode(xmp.GetRoot(), path, false, null);
                    BaseNamespace = schemaNs;
                    initialPath   = basePath.ToString();
                }
                else
                {
                    if (baseSchema && !baseProperty)
                    {
                        // Only Schema provided
                        startNode = XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNs, false);
                    }
                    else
                    {
                        // !baseSchema  &&  baseProperty
                        // No schema but property provided -> error
                        throw new XmpException("Schema namespace URI is required", XmpErrorCode.BadSchema);
                    }
                }
            }
            // create iterator
            _nodeIterator = startNode != null
                ? (IIterator)(!Options.IsJustChildren
                    ? new NodeIterator(this, startNode, initialPath, 1)
                    : new NodeIteratorChildren(this, startNode, initialPath))
                : Enumerable.Empty <object>().Iterator();
        }
        /// <summary>Static method to serialize the metadata object.</summary>
        /// <remarks>
        /// For each serialisation, a new XMPSerializer
        /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so that its possible to
        /// serialize the same XMPMeta objects in two threads.
        /// </remarks>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="stream">the output stream to serialize to</param>
        /// <param name="options">serialization options, can be <c>null</c> for default.</param>
        /// <exception cref="XmpException"/>
        public static void Serialize(XmpMeta xmp, Stream stream, SerializeOptions options)
        {
            options = options ?? new SerializeOptions();

            // sort the internal data model on demand
            if (options.Sort)
            {
                xmp.Sort();
            }

            new XmpSerializerRdf().Serialize(xmp, stream, options);
        }
Beispiel #9
0
 /// <summary>Constructor with optional initial values.</summary>
 /// <remarks>If <c>propName</c> is provided, <c>schemaNS</c> has also be provided.</remarks>
 /// <param name="xmp">the iterated metadata object.</param>
 /// <param name="schemaNs">the iteration is reduced to this schema (optional)</param>
 /// <param name="propPath">the iteration is reduced to this property within the <c>schemaNS</c></param>
 /// <param name="options">advanced iteration options, see <see cref="IteratorOptions"/></param>
 /// <exception cref="XmpException">If the node defined by the parameters is not existing.</exception>
 public XmpIterator(XmpMeta xmp, string schemaNs, string propPath, IteratorOptions options)
 {
     // make sure that options is defined at least with defaults
     Options = options ?? new IteratorOptions();
     // the start node of the iteration depending on the schema and property filter
     XmpNode startNode = null;
     string initialPath = null;
     var baseSchema = !string.IsNullOrEmpty(schemaNs);
     var baseProperty = !string.IsNullOrEmpty(propPath);
     if (!baseSchema && !baseProperty)
     {
         // complete tree will be iterated
         startNode = xmp.GetRoot();
     }
     else
     {
         if (baseSchema && baseProperty)
         {
             // Schema and property node provided
             var path = XmpPathParser.ExpandXPath(schemaNs, propPath);
             // base path is the prop path without the property leaf
             var basePath = new XmpPath();
             for (var i = 0; i < path.Size() - 1; i++)
             {
                 basePath.Add(path.GetSegment(i));
             }
             startNode = XmpNodeUtils.FindNode(xmp.GetRoot(), path, false, null);
             BaseNamespace = schemaNs;
             initialPath = basePath.ToString();
         }
         else
         {
             if (baseSchema && !baseProperty)
             {
                 // Only Schema provided
                 startNode = XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNs, false);
             }
             else
             {
                 // !baseSchema  &&  baseProperty
                 // No schema but property provided -> error
                 throw new XmpException("Schema namespace URI is required", XmpErrorCode.BadSchema);
             }
         }
     }
     // create iterator
     _nodeIterator = startNode != null
         ? (IIterator)(!Options.IsJustChildren
             ? new NodeIterator(this, startNode, initialPath, 1)
             : new NodeIteratorChildren(this, startNode, initialPath))
         : Enumerable.Empty<object>().Iterator();
 }
Beispiel #10
0
        /// <summary>Normalizes a raw parsed XMPMeta-Object</summary>
        /// <param name="xmp">the raw metadata object</param>
        /// <param name="options">the parsing options</param>
        /// <returns>Returns the normalized metadata object</returns>
        /// <exception cref="XmpException">Collects all severe processing errors.</exception>
        internal static IXmpMeta Process(XmpMeta xmp, ParseOptions options)
        {
            var tree = xmp.GetRoot();

            TouchUpDataModel(xmp);

            // FfF: collect the aliases to prevent browsing the tree again

            MoveExplicitAliases(tree, options);
            TweakOldXmp(tree);
            DeleteEmptySchemas(tree);
            return(xmp);
        }
        /// <summary>Serializes an <c>XMPMeta</c>-object as RDF into a string.</summary>
        /// <remarks>
        /// <em>Note:</em> Encoding is forced to UTF-16 when serializing to a
        /// string to ensure the correctness of &quot;exact packet size&quot;.
        /// </remarks>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="options">Options to control the serialization (see <see cref="SerializeOptions"/>).</param>
        /// <returns>Returns a string containing the serialized RDF.</returns>
        /// <exception cref="XmpException">on serialization errors.</exception>
        public static string SerializeToString(XmpMeta xmp, SerializeOptions options)
        {
            // forces the encoding to be UTF-16 to get the correct string length
            options = options ?? new SerializeOptions();
            options.EncodeUtf16Be = true;

            var output = new MemoryStream(2048);
            Serialize(xmp, output, options);
            try
            {
                return options.GetEncoding().GetString(output.GetBuffer(), 0, (int)output.Length);
            }
            catch
            {
                // Should not happen as UTF-8/16LE/BE are all available
                return output.ToString();
            }
        }
        /// <summary>Serializes an <c>XMPMeta</c>-object as RDF into a string.</summary>
        /// <remarks>
        /// <em>Note:</em> Encoding is forced to UTF-16 when serializing to a
        /// string to ensure the correctness of &quot;exact packet size&quot;.
        /// </remarks>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="options">Options to control the serialization (see <see cref="SerializeOptions"/>).</param>
        /// <returns>Returns a string containing the serialized RDF.</returns>
        /// <exception cref="XmpException">on serialization errors.</exception>
        public static string SerializeToString(XmpMeta xmp, SerializeOptions options)
        {
            // forces the encoding to be UTF-16 to get the correct string length
            options = options ?? new SerializeOptions();
            options.EncodeUtf16Be = true;

            var output = new MemoryStream(2048);

            Serialize(xmp, output, options);
            try
            {
                return(options.GetEncoding().GetString(output.ToArray(), 0, (int)output.Length));
            }
            catch
            {
                // Should not happen as UTF-8/16LE/BE are all available
                return(output.ToString());
            }
        }
        /// <summary>Static method to serialize the metadata object.</summary>
        /// <remarks>
        /// For each serialisation, a new XMPSerializer
        /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so that its possible to
        /// serialize the same XMPMeta objects in two threads.
        /// </remarks>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="stream">the output stream to serialize to</param>
        /// <param name="options">serialization options, can be <c>null</c> for default.</param>
        /// <exception cref="XmpException" />
        public static void Serialize(XmpMeta xmp, Stream stream, SerializeOptions options)
        {
            options = options ?? new SerializeOptions();

            // sort the internal data model on demand
            if (options.Sort)
            {
                xmp.Sort();
            }

            // <#AdobePrivate> This is not very aesthetic, but needed to strip the block
            // The Plain XMP format is disabled
            //        if (options.getUsePlainXMP())
            //        {
            //            new XMPSerializerPlain().serialize(xmp, out, options);
            //            return;
            //        }
            // </#AdobePrivate>

            new XmpSerializerRdf().Serialize(xmp, stream, options);
        }
        /// <summary>Serializes an <c>XMPMeta</c>-object as RDF into a string.</summary>
        /// <remarks>
        /// <em>Note:</em> Encoding is forced to UTF-16 when serializing to a
        /// string to ensure the correctness of &quot;exact packet size&quot;.
        /// </remarks>
        /// <param name="xmp">a metadata implementation object</param>
        /// <param name="options">Options to control the serialization (see <see cref="SerializeOptions"/>).</param>
        /// <returns>Returns a string containing the serialized RDF.</returns>
        /// <exception cref="XmpException">on serialization errors.</exception>
        public static string SerializeToString(XmpMeta xmp, SerializeOptions options)
        {
            // forces the encoding to be UTF-16 to get the correct string length
            options = options ?? new SerializeOptions();
            // By default encoding is utf8
            // options should be set by the client. Commenting setting utf16 option
            // so that users can get the string in whichever encoding they want (by setting the options bits)
            //options.EncodeUtf16Be = true;

            var output = new MemoryStream(2048);

            Serialize(xmp, output, options);
            try
            {
                return(options.GetEncoding().GetString(output.ToArray(), 0, (int)output.Length));
            }
            catch
            {
                // Should not happen as UTF-8/16LE/BE are all available
                return(output.ToString());
            }
        }
Beispiel #15
0
 /// <summary>
 /// 7.2.16 literalPropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, datatypeAttr?) )
 /// text()
 /// end-element()
 /// Add a leaf node with the text value and qualifiers for the attributes.
 /// </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="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="XmpException">thrown on parsing errors</exception>
 private static void Rdf_LiteralPropertyElement(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
 {
     var newChild = AddChildNode(xmp, xmpParent, xmlNode, null, isTopLevel);
     foreach (var attribute in xmlNode.Attributes())
     {
         var prefix = xmlNode.GetPrefixOfNamespace(attribute.Name.Namespace);
         if ("xmlns".Equals(prefix) || (prefix == null && "xmlns".Equals(attribute.Name)))
         {
             continue;
         }
         var attrNs = attribute.Name.NamespaceName;
         var attrLocal = attribute.Name.LocalName;
         if (XmpConstants.XmlLang.Equals("xml:" + attribute.Name.LocalName))
         {
             AddQualifierNode(newChild, XmpConstants.XmlLang, attribute.Value);
         }
         else
         {
             if (XmpConstants.NsRdf.Equals(attrNs) && ("ID".Equals(attrLocal) || "datatype".Equals(attrLocal)))
             {
                 continue;
             }
             // Ignore all rdf:ID and rdf:datatype attributes.
             throw new XmpException("Invalid attribute for literal property element", XmpErrorCode.BadRdf);
         }
     }
     var textValue = string.Empty;
     foreach(var child in xmlNode.Nodes())
     {
         if (child.NodeType == System.Xml.XmlNodeType.Text)
         {
             textValue += ((XText)child).Value;
         }
         else
         {
             throw new XmpException("Invalid child of literal property element", XmpErrorCode.BadRdf);
         }
     }
     newChild.Value = textValue;
 }
Beispiel #16
0
        /// <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);
                                }
                            }
                        }
                    }
                }
            }
        }
Beispiel #17
0
        /// <summary>Utility to find or create the array used by <c>separateArrayItems()</c>.</summary>
        /// <param name="schemaNs">a the namespace fo the array</param>
        /// <param name="arrayName">the name of the array</param>
        /// <param name="arrayOptions">the options for the array if newly created</param>
        /// <param name="xmp">the xmp object</param>
        /// <returns>Returns the array node.</returns>
        /// <exception cref="XmpException">Forwards exceptions</exception>
        private static XmpNode SeparateFindCreateArray(string schemaNs, string arrayName, PropertyOptions arrayOptions, XmpMeta xmp)
        {
            arrayOptions = XmpNodeUtils.VerifySetOptions(arrayOptions, null);

            if (!arrayOptions.IsOnlyArrayOptions)
                throw new XmpException("Options can only provide array form", XmpErrorCode.BadOptions);

            // Find the array node, make sure it is OK. Move the current children
            // aside, to be readded later if kept.
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            var arrayNode = XmpNodeUtils.FindNode(xmp.GetRoot(), arrayPath, false, null);
            if (arrayNode != null)
            {
                // The array exists, make sure the form is compatible. Zero
                // arrayForm means take what exists.
                var arrayForm = arrayNode.Options;

                if (!arrayForm.IsArray || arrayForm.IsArrayAlternate)
                    throw new XmpException("Named property must be non-alternate array", XmpErrorCode.BadXPath);
                if (arrayOptions.EqualArrayTypes(arrayForm))
                    throw new XmpException("Mismatch of specified and existing array form", XmpErrorCode.BadXPath);
            }
            else
            {
                // *** Right error?
                // The array does not exist, try to create it.
                // don't modify the options handed into the method
                arrayOptions.IsArray = true;
                arrayNode = XmpNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions);
                if (arrayNode == null)
                    throw new XmpException("Failed to create named array", XmpErrorCode.BadXPath);
            }
            return arrayNode;
        }
Beispiel #18
0
 /// <summary>
 /// Each of these parsing methods is responsible for recognizing an RDF
 /// syntax production and adding the appropriate structure to the XMP tree.
 /// </summary>
 /// <remarks>
 /// Each of these parsing methods is responsible for recognizing an RDF
 /// syntax production and adding the appropriate structure to the XMP tree.
 /// They simply return for success, failures will throw an exception.
 /// </remarks>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="rdfRdfNode">the top-level xml node</param>
 /// <exception cref="XmpException">thrown on parsing errors</exception>
 internal static void Rdf_RDF(XmpMeta xmp, XElement rdfRdfNode)
 {
     if (rdfRdfNode.Attributes().Count() > 0)
     {
         Rdf_NodeElementList(xmp, xmp.GetRoot(), rdfRdfNode);
     }
     else
     {
         throw new XmpException("Invalid attributes of rdf:RDF element", XmpErrorCode.BadRdf);
     }
 }
 /// <summary>Visit all schemas to do general fixes and handle special cases.</summary>
 /// <param name="xmp">the metadata object implementation</param>
 /// <exception cref="XmpException">Thrown if the normalisation fails.</exception>
 private static void TouchUpDataModel(XmpMeta xmp)
 {
     // make sure the DC schema is existing, because it might be needed within the normalization
     // if not touched it will be removed by removeEmptySchemas
     XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), XmpConstants.NsDC, true);
     // Do the special case fixes within each schema.
     for (var it = xmp.GetRoot().IterateChildren(); it.HasNext(); )
     {
         var currSchema = (XmpNode)it.Next();
         if (XmpConstants.NsDC.Equals(currSchema.Name))
         {
             NormalizeDcArrays(currSchema);
         }
         else
         {
             if (XmpConstants.NsExif.Equals(currSchema.Name))
             {
                 // Do a special case fix for exif:GPSTimeStamp.
                 FixGpsTimeStamp(currSchema);
                 var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                 if (arrayNode != null)
                 {
                     RepairAltText(arrayNode);
                 }
             }
             else
             {
                 if (XmpConstants.NsDm.Equals(currSchema.Name))
                 {
                     // Do a special case migration of xmpDM:copyright to
                     // dc:rights['x-default'].
                     var dmCopyright = XmpNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false);
                     if (dmCopyright != null)
                     {
                         MigrateAudioCopyright(xmp, dmCopyright);
                     }
                 }
                 else
                 {
                     if (XmpConstants.NsXmpRights.Equals(currSchema.Name))
                     {
                         var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false);
                         if (arrayNode != null)
                         {
                             RepairAltText(arrayNode);
                         }
                     }
                 }
             }
         }
     }
 }
Beispiel #20
0
        /// <summary>
        /// 7.2.14 propertyElt
        /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
        /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt |
        /// parseTypeOtherPropertyElt | emptyPropertyElt
        /// 7.2.15 resourcePropertyElt
        /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
        /// ws* nodeElement ws
        /// end-element()
        /// 7.2.16 literalPropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
        /// text()
        /// end-element()
        /// 7.2.17 parseTypeLiteralPropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
        /// literal
        /// end-element()
        /// 7.2.18 parseTypeResourcePropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
        /// propertyEltList
        /// end-element()
        /// 7.2.19 parseTypeCollectionPropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
        /// nodeElementList
        /// end-element()
        /// 7.2.20 parseTypeOtherPropertyElt
        /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
        /// propertyEltList
        /// end-element()
        /// 7.2.21 emptyPropertyElt
        /// start-element ( URI == propertyElementURIs,
        /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
        /// end-element()
        /// The various property element forms are not distinguished by the XML element name,
        /// but by their attributes for the most part.
        /// </summary>
        /// <remarks>
        /// 7.2.14 propertyElt
        /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
        /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt |
        /// parseTypeOtherPropertyElt | emptyPropertyElt
        /// 7.2.15 resourcePropertyElt
        /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
        /// ws* nodeElement ws
        /// end-element()
        /// 7.2.16 literalPropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
        /// text()
        /// end-element()
        /// 7.2.17 parseTypeLiteralPropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
        /// literal
        /// end-element()
        /// 7.2.18 parseTypeResourcePropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
        /// propertyEltList
        /// end-element()
        /// 7.2.19 parseTypeCollectionPropertyElt
        /// start-element (
        /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
        /// nodeElementList
        /// end-element()
        /// 7.2.20 parseTypeOtherPropertyElt
        /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
        /// propertyEltList
        /// end-element()
        /// 7.2.21 emptyPropertyElt
        /// start-element ( URI == propertyElementURIs,
        /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
        /// end-element()
        /// The various property element forms are not distinguished by the XML element name,
        /// but by their attributes for the most part. The exceptions are resourcePropertyElt and
        /// literalPropertyElt. They are distinguished by their XML element content.
        /// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can
        /// appear in many of these. We have to allow for it in the attibute counts below.
        /// </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="XmpException">thrown on parsing errors</exception>
        private static void Rdf_PropertyElement(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
        {
            var nodeTerm = GetRdfTermKind(xmlNode);
            if (!IsPropertyElementName(nodeTerm))
            {
                throw new XmpException("Invalid property element name", XmpErrorCode.BadRdf);
            }

            var attributes = xmlNode.Attributes();

            if (attributes.Count() > 3)
            {
                // Only an emptyPropertyElt can have more than 3 attributes.
                Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
            }
            else
            {
                // Look through the attributes for one that isn't rdf:ID or xml:lang,
                // it will usually tell what we should be dealing with.
                // The called routines must verify their specific syntax!

                var attrLocal = "";
                var attrNs = "";
                var attrValue = "";

                XAttribute foundAttrib = null;
                foreach (var attribute in attributes)
                {
                    attrLocal = attribute.Name.LocalName;
                    attrNs = attribute.Name.NamespaceName;
                    attrValue = attribute.Value;

                    if (!(XmpConstants.XmlLang.Equals("xml:" + attrLocal)) && !("ID".Equals(attrLocal) && XmpConstants.NsRdf.Equals(attrNs)))
                    {
                        foundAttrib = attribute;
                        break;
                    }
                }
                if (foundAttrib != null)   // meaning, contains one node other than xml:lang and rdf:ID
                {
                    attrLocal = foundAttrib.Name.LocalName;
                    attrNs = foundAttrib.Name.NamespaceName;
                    attrValue = foundAttrib.Value;

                    if ("datatype".Equals(attrLocal) && XmpConstants.NsRdf.Equals(attrNs))
                    {
                        Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                    }
                    else if (!("parseType".Equals(attrLocal) && XmpConstants.NsRdf.Equals(attrNs)))
                    {
                        Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                    }
                    else if ("Literal".Equals(attrValue))
                    {
                        Rdf_ParseTypeLiteralPropertyElement();
                    }
                    else if ("Resource".Equals(attrValue))
                    {
                        Rdf_ParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                    }
                    else if ("Collection".Equals(attrValue))
                    {
                        Rdf_ParseTypeCollectionPropertyElement();
                    }
                    else
                    {
                        Rdf_ParseTypeOtherPropertyElement();
                    }
                }
                else
                {
                    // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt,
                    // or an emptyPropertyElt. Look at the child XML nodes to decide which.
                    if (xmlNode.IsEmpty)
                    {
                        Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                    }
                    else
                    {
                        var nonTextNode = xmlNode.Nodes().FirstOrDefault(t => t.NodeType != System.Xml.XmlNodeType.Text);
                        if(nonTextNode == null)
                            Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                        else
                            Rdf_ResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
                    }

                }
            }
        }
Beispiel #21
0
        /// <summary>
        /// 7.2.21 emptyPropertyElt
        /// start-element ( URI == propertyElementURIs,
        /// attributes == set (
        /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
        /// end-element()
        /// <ns:Prop1/>  <!-- a simple property with an empty value -->
        /// <ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value -->
        /// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
        /// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
        /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of
        /// attributes.
        /// </summary>
        /// <remarks>
        /// 7.2.21 emptyPropertyElt
        /// start-element ( URI == propertyElementURIs,
        /// attributes == set (
        /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
        /// end-element()
        /// <ns:Prop1/>  <!-- a simple property with an empty value -->
        /// <ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value -->
        /// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
        /// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
        /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of
        /// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
        /// simple property with an empty value (ns:Prop1), a simple property whose value is a URI
        /// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3).
        /// An emptyPropertyElt can also represent an XMP struct whose fields are all simple and
        /// unqualified (ns:Prop4).
        /// It is an error to use both rdf:value and rdf:resource - that can lead to invalid  RDF in the
        /// verbose form written using a literalPropertyElt.
        /// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for
        /// design reasons and partly for historical reasons. The XMP mapping rules are:
        /// <list type="bullet">
        /// <item> If there is an rdf:value attribute then this is a simple property
        /// with a text value.
        /// All other attributes are qualifiers.</item>
        /// <item> If there is an rdf:resource attribute then this is a simple property
        /// with a URI value.
        /// All other attributes are qualifiers.</item>
        /// <item> If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID
        /// then this is a simple
        /// property with an empty value.</item>
        /// <item> Otherwise this is a struct, the attributes other than xml:lang, rdf:ID,
        /// or rdf:nodeID are fields.</item>
        /// </list>
        /// </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="XmpException">thrown on parsing errors</exception>
        private static void Rdf_EmptyPropertyElement(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
        {
            var hasPropertyAttrs = false;
            var hasResourceAttr = false;
            var hasNodeIdAttr = false;
            var hasValueAttr = false;
            XAttribute valueNode = null;
            // ! Can come from rdf:value or rdf:resource.
            if (!(xmlNode.FirstNode == null))
            {
                throw new XmpException("Nested content not allowed with rdf:resource or property attributes", XmpErrorCode.BadRdf);
            }
            // First figure out what XMP this maps to and remember the XML node for a simple value.
            foreach (var attribute in xmlNode.Attributes())
            {
                var prefix = xmlNode.GetPrefixOfNamespace(attribute.Name.Namespace);
                if ("xmlns".Equals(prefix) || (prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                var attrTerm = GetRdfTermKind(attribute);
                switch (attrTerm)
                {
                    case RdfTerm.Id:
                    {
                        // Nothing to do.
                        break;
                    }

                    case RdfTerm.Resource:
                    {
                        if (hasNodeIdAttr)
                        {
                            throw new XmpException("Empty property element can't have both rdf:resource and rdf:nodeID", XmpErrorCode.BadRdf);
                        }
                        if (hasValueAttr)
                        {
                            throw new XmpException("Empty property element can't have both rdf:value and rdf:resource", XmpErrorCode.BadXmp);
                        }
                        hasResourceAttr = true;
                        if (!hasValueAttr)
                        {
                            valueNode = attribute;
                        }
                        break;
                    }

                    case RdfTerm.NodeId:
                    {
                        if (hasResourceAttr)
                        {
                            throw new XmpException("Empty property element can't have both rdf:resource and rdf:nodeID", XmpErrorCode.BadRdf);
                        }
                        hasNodeIdAttr = true;
                        break;
                    }

                    case RdfTerm.Other:
                    {
                        if ("value".Equals(attribute.Name.LocalName) && XmpConstants.NsRdf.Equals(attribute.Name.NamespaceName))
                        {
                            if (hasResourceAttr)
                            {
                                throw new XmpException("Empty property element can't have both rdf:value and rdf:resource", XmpErrorCode.BadXmp);
                            }
                            hasValueAttr = true;
                            valueNode = attribute;
                        }
                        else
                        {
                            if (!XmpConstants.XmlLang.Equals("xml:" + attribute.Name.LocalName))
                            {
                                hasPropertyAttrs = true;
                            }
                        }
                        break;
                    }

                    default:
                    {
                        throw new XmpException("Unrecognized attribute of empty property element", XmpErrorCode.BadRdf);
                    }
                }
            }
            // Create the right kind of child node and visit the attributes again
            // to add the fields or qualifiers.
            // ! Because of implementation vagaries,
            //   the xmpParent is the tree root for top level properties.
            // ! The schema is found, created if necessary, by addChildNode.
            var childNode = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel);
            var childIsStruct = false;
            if (hasValueAttr || hasResourceAttr)
            {
                childNode.Value = valueNode != null ? valueNode.Value : string.Empty;
                if (!hasValueAttr)
                {
                    // ! Might have both rdf:value and rdf:resource.
                    childNode.Options.IsUri = true;
                }
            }
            else
            {
                if (hasPropertyAttrs)
                {
                    childNode.Options.IsStruct = true;
                    childIsStruct = true;
                }
            }
            foreach(var attribute in xmlNode.Attributes())
            {
                var prefix = xmlNode.GetPrefixOfNamespace(attribute.Name.Namespace);
                if (attribute == valueNode || "xmlns".Equals(prefix) || (prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                // Skip the rdf:value or rdf:resource attribute holding the value.
                var attrTerm = GetRdfTermKind(attribute);
                switch (attrTerm)
                {
                    case RdfTerm.Id:
                    case RdfTerm.NodeId:
                    {
                        break;
                    }

                    case RdfTerm.Resource:
                    {
                        // Ignore all rdf:ID and rdf:nodeID attributes.
                        AddQualifierNode(childNode, "rdf:resource", attribute.Value);
                        break;
                    }

                    case RdfTerm.Other:
                    {
                        if (!childIsStruct)
                        {
                            AddQualifierNode(childNode, attribute.Name.LocalName, attribute.Value);
                        }
                        else
                        {
                            if (XmpConstants.XmlLang.Equals("xml:" + attribute.Name.LocalName))
                            {
                                AddQualifierNode(childNode, XmpConstants.XmlLang, attribute.Value);
                            }
                            else
                            {
                                AddChildNode(xmp, childNode, attribute, attribute.Value, false);
                            }
                        }
                        break;
                    }

                    default:
                    {
                        throw new XmpException("Unrecognized attribute of empty property element", XmpErrorCode.BadRdf);
                    }
                }
            }
        }
Beispiel #22
0
 /// <summary>The main parsing method.</summary>
 /// <remarks>
 /// The main parsing method. The XML tree is walked through from the root node and and XMP tree
 /// is created. This is a raw parse, the normalisation of the XMP tree happens outside.
 /// </remarks>
 /// <param name="xmlRoot">the XML root node</param>
 /// <returns>Returns an XMP metadata object (not normalized)</returns>
 /// <exception cref="XmpException">Occurs if the parsing fails for any reason.</exception>
 internal static XmpMeta Parse(XElement xmlRoot)
 {
     var xmp = new XmpMeta();
     Rdf_RDF(xmp, xmlRoot);
     return xmp;
 }
Beispiel #23
0
 /// <summary>
 /// 7.2.5 nodeElementURIs
 /// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
 /// 7.2.11 nodeElement
 /// start-element ( URI == nodeElementURIs,
 /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
 /// propertyEltList
 /// end-element()
 /// A node element URI is rdf:Description or anything else that is not an RDF
 /// term.
 /// </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="isTopLevel">Flag if the node is a top-level node</param>
 /// <exception cref="XmpException">thrown on parsing errors</exception>
 private static void Rdf_NodeElement(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
 {
     var nodeTerm = GetRdfTermKind(xmlNode);
     if (nodeTerm != RdfTerm.Description && nodeTerm != RdfTerm.Other)
     {
         throw new XmpException("Node element must be rdf:Description or typed node", XmpErrorCode.BadRdf);
     }
     if (isTopLevel && nodeTerm == RdfTerm.Other)
     {
         throw new XmpException("Top level typed node not allowed", XmpErrorCode.BadXmp);
     }
     Rdf_NodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel);
     Rdf_PropertyElementList(xmp, xmpParent, xmlNode, isTopLevel);
 }
 // UTF-8
 /// <summary>The actual serialization.</summary>
 /// <param name="xmp">the metadata object to be serialized</param>
 /// <param name="stream">outputStream the output stream to serialize to</param>
 /// <param name="options">the serialization options</param>
 /// <exception cref="XmpException">If case of wrong options or any other serialization error.</exception>
 public void Serialize(IXmpMeta xmp, Stream stream, SerializeOptions options)
 {
     try
     {
         _stream = stream;
         _startPos = _stream.Position;
         _writer = new StreamWriter(_stream, options.GetEncoding());
         _xmp = (XmpMeta)xmp;
         _options = options;
         _padding = options.Padding;
         _writer = new StreamWriter(_stream, options.GetEncoding());
         CheckOptionsConsistence();
         // serializes the whole packet, but don't write the tail yet
         // and flush to make sure that the written bytes are calculated correctly
         var tailStr = SerializeAsRdf();
         _writer.Flush();
         // adds padding
         AddPadding(tailStr.Length);
         // writes the tail
         Write(tailStr);
         _writer.Flush();
         _stream.Close();
     }
     catch (IOException)
     {
         throw new XmpException("Error writing to the OutputStream", XmpErrorCode.Unknown);
     }
 }
Beispiel #25
0
 private static XmpNode AddChildNode(XmpMeta xmp, XmpNode xmpParent, XAttribute xmlNode, string value, bool isTopLevel)
 {
     return AddChildNode(xmp, xmpParent, xmlNode.Name, xmlNode.Parent.GetPrefixOfNamespace(xmlNode.Name.Namespace), value, isTopLevel);
 }
Beispiel #26
0
        /// <summary>Visit all schemas to do general fixes and handle special cases.</summary>
        /// <param name="xmp">the metadata object implementation</param>
        /// <exception cref="XmpException">Thrown if the normalisation fails.</exception>
        private static void TouchUpDataModel(XmpMeta xmp)
        {
            // make sure the DC schema is existing, because it might be needed within the normalization
            // if not touched it will be removed by removeEmptySchemas
            XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), XmpConstants.NsDC, true);

            // Do the special case fixes within each schema.
            for (var it = xmp.GetRoot().IterateChildren(); it.HasNext();)
            {
                var currSchema = (XmpNode)it.Next();

                switch (currSchema.Name)
                {
                case XmpConstants.NsDC:
                {
                    NormalizeDcArrays(currSchema);
                    break;
                }

                case XmpConstants.NsExif:
                {
                    // Do a special case fix for exif:GPSTimeStamp.
                    FixGpsTimeStamp(currSchema);

                    /*var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                     * if (arrayNode != null)
                     * {
                     *  RepairAltText(arrayNode);
                     * }*/
                    var userComment = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                    if (userComment != null)
                    {
                        if (userComment.Options.IsSimple)
                        {
                            XmpNode newNode = new XmpNode(XmpConstants.ArrayItemName, userComment.Value, userComment.Options);
                            newNode.Parent = userComment;

                            int QualNo = userComment.GetQualifierLength();
                            while (QualNo > 0)
                            {
                                newNode.AddQualifier(userComment.GetQualifier(userComment.GetQualifierLength() - QualNo));
                                --QualNo;
                            }

                            userComment.RemoveQualifiers();
                            if (!newNode.Options.HasLanguage)
                            {
                                var po = new PropertyOptions();
                                po.SetOption(PropertyOptions.HasQualifiersFlag, true);
                                XmpNode langQual = new XmpNode("xml:lang", "x-default", po);
                                newNode.AddQualifier(langQual);
                                newNode.Options.SetOption(PropertyOptions.HasQualifiersFlag, true);
                                newNode.Options.SetOption(PropertyOptions.HasLanguageFlag, true);
                            }
                            userComment.AddChild(newNode);
                            userComment.Options = new PropertyOptions(PropertyOptions.ArrayFlag | PropertyOptions.ArrayOrderedFlag
                                                                      | PropertyOptions.ArrayAltTextFlag | PropertyOptions.ArrayAlternateFlag);
                            userComment.Value = "";
                        }
                        RepairAltText(userComment);
                    }

                    break;
                }

                case XmpConstants.NsDm:
                {
                    // Do a special case migration of xmpDM:copyright to
                    // dc:rights['x-default'].
                    var dmCopyright = XmpNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false);
                    if (dmCopyright != null)
                    {
                        MigrateAudioCopyright(xmp, dmCopyright);
                    }
                    break;
                }

                case XmpConstants.NsXmpRights:
                {
                    var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false);
                    if (arrayNode != null)
                    {
                        RepairAltText(arrayNode);
                    }
                    break;
                }
                }
            }
        }
 /// <summary>Serializes an <c>XMPMeta</c>-object as RDF into a byte buffer.</summary>
 /// <param name="xmp">a metadata implementation object</param>
 /// <param name="options">Options to control the serialization (see <see cref="SerializeOptions"/>).</param>
 /// <returns>Returns a byte buffer containing the serialized RDF.</returns>
 /// <exception cref="XmpException">on serialization errors.</exception>
 public static byte[] SerializeToBuffer(XmpMeta xmp, SerializeOptions options)
 {
     var output = new MemoryStream(2048);
     Serialize(xmp, output, options);
     return output.ToArray();
 }
Beispiel #28
0
        /// <summary>
        /// 7.2.15 resourcePropertyElt
        /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
        /// ws* nodeElement ws
        /// end-element()
        /// This handles structs using an rdf:Description node,
        /// arrays using rdf:Bag/Seq/Alt, and typedNodes.
        /// </summary>
        /// <remarks>
        /// 7.2.15 resourcePropertyElt
        /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
        /// ws* nodeElement ws
        /// end-element()
        /// This handles structs using an rdf:Description node,
        /// arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified
        /// properties written with rdf:Description and rdf:value.
        /// </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="XmpException">thrown on parsing errors</exception>
        private static void Rdf_ResourcePropertyElement(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
        {
            if (isTopLevel && "iX:changes".Equals(xmlNode.Name))
            {
                // Strip old "punchcard" chaff which has on the prefix "iX:".
                return;
            }
            var newCompound = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel);
            // walk through the attributes
            foreach(var attribute in xmlNode.Attributes())
            {
                var prefix = xmlNode.GetPrefixOfNamespace(attribute.Name.Namespace);
                if ("xmlns".Equals(prefix) || (prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }

                var attrLocal = attribute.Name.LocalName;
                var attrNs = attribute.Name.NamespaceName;
                if (XmpConstants.XmlLang.Equals("xml:" + attribute.Name.LocalName))
                {
                    AddQualifierNode(newCompound, XmpConstants.XmlLang, attribute.Value);
                }
                else
                {
                    if ("ID".Equals(attrLocal) && XmpConstants.NsRdf.Equals(attrNs))
                    {
                        continue;
                    }
                    // Ignore all rdf:ID attributes.
                    throw new XmpException("Invalid attribute for resource property element", XmpErrorCode.BadRdf);
                }
            }
            // walk through the children
            var found = false;
            foreach(var currChild in xmlNode.Nodes())
            {
                if (!IsWhitespaceNode(currChild))
                {
                    if (currChild.NodeType == System.Xml.XmlNodeType.Element && !found)
                    {
                        var currChildElem = (XElement)currChild;
                        var isRdf = XmpConstants.NsRdf.Equals(currChildElem.Name.NamespaceName);
                        var childLocal = currChildElem.Name.LocalName;
                        if (isRdf && "Bag".Equals(childLocal))
                        {
                            newCompound.Options.IsArray = true;
                        }
                        else if (isRdf && "Seq".Equals(childLocal))
                        {
                            newCompound.Options.IsArray = true;
                            newCompound.Options.IsArrayOrdered = true;
                        }
                        else if (isRdf && "Alt".Equals(childLocal))
                        {
                            newCompound.Options.IsArray = true;
                            newCompound.Options.IsArrayOrdered = true;
                            newCompound.Options.IsArrayAlternate = true;
                        }
                        else
                        {
                            newCompound.Options.IsStruct = true;
                            if (!isRdf && !"Description".Equals(childLocal))
                            {
                                var typeName = currChildElem.Name.NamespaceName;
                                if (typeName == null)
                                {
                                    throw new XmpException("All XML elements must be in a namespace", XmpErrorCode.BadXmp);
                                }
                                typeName += ':' + childLocal;
                                AddQualifierNode(newCompound, "rdf:type", typeName);
                            }
                        }
                        Rdf_NodeElement(xmp, newCompound, currChildElem, false);
                        if (newCompound.HasValueChild)
                            FixupQualifiedNode(newCompound);
                        else if (newCompound.Options.IsArrayAlternate)
                            XmpNodeUtils.DetectAltText(newCompound);
                        found = true;
                    }
                    else
                    {
                        if (found)
                        {
                            // found second child element
                            throw new XmpException("Invalid child of resource property element", XmpErrorCode.BadRdf);
                        }
                        throw new XmpException("Children of resource property element must be XML elements", XmpErrorCode.BadRdf);
                    }
                }
            }
            if (!found)
            {
                // didn't found any child elements
                throw new XmpException("Missing child of resource property element", XmpErrorCode.BadRdf);
            }
        }
Beispiel #29
0
        /// <summary>Utility to find or create the array used by <c>separateArrayItems()</c>.</summary>
        /// <param name="schemaNs">a the namespace fo the array</param>
        /// <param name="arrayName">the name of the array</param>
        /// <param name="arrayOptions">the options for the array if newly created</param>
        /// <param name="xmp">the xmp object</param>
        /// <returns>Returns the array node.</returns>
        /// <exception cref="XmpException">Forwards exceptions</exception>
        private static XmpNode SeparateFindCreateArray(string schemaNs, string arrayName, PropertyOptions arrayOptions, XmpMeta xmp)
        {
            arrayOptions = XmpNodeUtils.VerifySetOptions(arrayOptions, null);

            if (!arrayOptions.IsOnlyArrayOptions)
            {
                throw new XmpException("Options can only provide array form", XmpErrorCode.BadOptions);
            }

            // Find the array node, make sure it is OK. Move the current children
            // aside, to be readded later if kept.
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            var arrayNode = XmpNodeUtils.FindNode(xmp.GetRoot(), arrayPath, false, null);

            if (arrayNode != null)
            {
                // The array exists, make sure the form is compatible. Zero
                // arrayForm means take what exists.
                var arrayForm = arrayNode.Options;

                if (!arrayForm.IsArray || arrayForm.IsArrayAlternate)
                {
                    throw new XmpException("Named property must be non-alternate array", XmpErrorCode.BadXPath);
                }
                if (arrayOptions.EqualArrayTypes(arrayForm))
                {
                    throw new XmpException("Mismatch of specified and existing array form", XmpErrorCode.BadXPath);
                }
            }
            else
            {
                // *** Right error?
                // The array does not exist, try to create it.
                // don't modify the options handed into the method
                arrayOptions.IsArray = true;
                arrayNode            = XmpNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions);
                if (arrayNode == null)
                {
                    throw new XmpException("Failed to create named array", XmpErrorCode.BadXPath);
                }
            }
            return(arrayNode);
        }
Beispiel #30
0
        /// <summary>
        /// 7.2.13 propertyEltList
        /// ws* ( propertyElt ws* )
        /// </summary>
        /// <param name="xmp">the xmp metadata object that is generated</param>
        /// <param name="xmpParent">the parent xmp node</param>
        /// <param name="xmlParent">the currently processed XML node</param>
        /// <param name="isTopLevel">Flag if the node is a top-level node</param>
        /// <exception cref="XmpException">thrown on parsing errors</exception>
        private static void Rdf_PropertyElementList(XmpMeta xmp, XmpNode xmpParent, XElement xmlParent, bool isTopLevel)
        {
            foreach(var currChild in xmlParent.Nodes())
            {
                if (IsWhitespaceNode(currChild))
                {
                    continue;
                }
                if (currChild.NodeType == System.Xml.XmlNodeType.Comment)
                    continue;

                if (currChild.NodeType != System.Xml.XmlNodeType.Element)
                {
                    throw new XmpException("Expected property element node not found", XmpErrorCode.BadRdf);
                }
                Rdf_PropertyElement(xmp, xmpParent, (XElement)currChild, isTopLevel);
            }
        }
Beispiel #31
0
        /// <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);
                                }
                            }
                        }
                    }
                }
            }
        }
Beispiel #32
0
        /// <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="XmpException">thrown on parsing errors</exception>
        private static void Rdf_NodeElementAttrs(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
        {
            // Used to detect attributes that are mutually exclusive.
            var exclusiveAttrs = 0;
            foreach(var attribute in xmlNode.Attributes())
            {
                // quick hack, ns declarations do not appear in C++
                // ignore "ID" without namespace
                var prefix = xmlNode.GetPrefixOfNamespace(attribute.Name.Namespace);
                if ("xmlns".Equals(prefix) || (prefix == null && "xmlns".Equals(attribute.Name)))
                {
                    continue;
                }
                var attrTerm = GetRdfTermKind(attribute);
                switch (attrTerm)
                {
                    case RdfTerm.Id:
                    case RdfTerm.NodeId:
                    case RdfTerm.About:
                    {
                        if (exclusiveAttrs > 0)
                        {
                            throw new XmpException("Mutally exclusive about, ID, nodeID attributes", XmpErrorCode.BadRdf);
                        }
                        exclusiveAttrs++;
                        if (isTopLevel && (attrTerm == RdfTerm.About))
                        {
                            // 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 (!string.IsNullOrEmpty(xmpParent.Name))
                            {
                                if (!xmpParent.Name.Equals(attribute.Value))
                                {
                                    throw new XmpException("Mismatched top level rdf:about values", XmpErrorCode.BadXmp);
                                }
                            }
                            else
                            {
                                xmpParent.Name = attribute.Value;
                            }
                        }
                        break;
                    }

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

                    default:
                    {
                        throw new XmpException("Invalid nodeElement attribute", XmpErrorCode.BadRdf);
                    }
                }
            }
        }
Beispiel #33
0
 /// <summary>
 /// 7.2.18 parseTypeResourcePropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, parseResource ) )
 /// propertyEltList
 /// end-element()
 /// Add a new struct node with a qualifier for the possible rdf:ID attribute.
 /// </summary>
 /// <remarks>
 /// 7.2.18 parseTypeResourcePropertyElt
 /// start-element ( URI == propertyElementURIs,
 /// attributes == set ( idAttr?, parseResource ) )
 /// propertyEltList
 /// end-element()
 /// Add a new struct node with a qualifier for the possible rdf:ID attribute.
 /// Then process the XML child nodes to get the struct fields.
 /// </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="XmpException">thrown on parsing errors</exception>
 private static void Rdf_ParseTypeResourcePropertyElement(XmpMeta xmp, XmpNode xmpParent, XElement xmlNode, bool isTopLevel)
 {
     var newStruct = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel);
     newStruct.Options.IsStruct = true;
     foreach (var attribute in xmlNode.Attributes())
     {
         var prefix = xmlNode.GetPrefixOfNamespace(attribute.Name.Namespace);
         if ("xmlns".Equals(prefix) || (prefix == null && "xmlns".Equals(attribute.Name)))
         {
             continue;
         }
         var attrLocal = attribute.Name.LocalName;
         var attrNs = attribute.Name.NamespaceName;
         if (XmpConstants.XmlLang.Equals("xml:" + attribute.Name.LocalName))
         {
             AddQualifierNode(newStruct, XmpConstants.XmlLang, attribute.Value);
         }
         else
         {
             if (XmpConstants.NsRdf.Equals(attrNs) && ("ID".Equals(attrLocal) || "parseType".Equals(attrLocal)))
             {
                 continue;
             }
             // The caller ensured the value is "Resource".
             // Ignore all rdf:ID attributes.
             throw new XmpException("Invalid attribute for ParseTypeResource property element", XmpErrorCode.BadRdf);
         }
     }
     Rdf_PropertyElementList(xmp, newStruct, xmlNode, false);
     if (newStruct.HasValueChild)
     {
         FixupQualifiedNode(newStruct);
     }
 }
Beispiel #34
0
 /// <summary>
 /// 7.2.10 nodeElementList<br />
 /// ws* ( nodeElement ws* )
 /// Note: this method is only called from the rdf:RDF-node (top level)
 /// </summary>
 /// <param name="xmp">the xmp metadata object that is generated</param>
 /// <param name="xmpParent">the parent xmp node</param>
 /// <param name="rdfRdfNode">the top-level xml node</param>
 /// <exception cref="XmpException">thrown on parsing errors</exception>
 private static void Rdf_NodeElementList(XmpMeta xmp, XmpNode xmpParent, XElement rdfRdfNode)
 {
     foreach(var child in rdfRdfNode.Nodes())
     {
         // filter whitespaces (and all text nodes)
         if (!IsWhitespaceNode(child))
         {
             Rdf_NodeElement(xmp, xmpParent, (XElement)child, true);
         }
     }
 }
Beispiel #35
0
 private static XmpNode AddChildNode(XmpMeta xmp, XmpNode xmpParent, XName nodeName, string nodeNamespacePrefix, string value, bool isTopLevel)
 {
     var registry = XmpMetaFactory.SchemaRegistry;
     var ns = nodeName.NamespaceName;
     string childName;
     if (ns != string.Empty)
     {
         if (XmpConstants.NsDcDeprecated.Equals(ns))
         {
             // Fix a legacy DC namespace
             ns = XmpConstants.NsDC;
         }
         var prefix = registry.GetNamespacePrefix(ns);
         if (prefix == null)
         {
             prefix = nodeNamespacePrefix ?? DefaultPrefix;
             prefix = registry.RegisterNamespace(ns, prefix);
         }
         childName = prefix + nodeName.LocalName;
     }
     else
     {
         throw new XmpException("XML namespace required for all elements and attributes", XmpErrorCode.BadRdf);
     }
     // create schema node if not already there
     var childOptions = new PropertyOptions();
     var isAlias = false;
     if (isTopLevel)
     {
         // Lookup the schema node, adjust the XMP parent pointer.
         // Incoming parent must be the tree root.
         var schemaNode = XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), ns, DefaultPrefix, true);
         schemaNode.IsImplicit = 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().HasAliases = true;
             schemaNode.HasAliases = true;
         }
     }
     // Make sure that this is not a duplicate of a named node.
     var isArrayItem = "rdf:li".Equals(childName);
     var isValueNode = "rdf:value".Equals(childName);
     // Create XMP node and so some checks
     var newChild = new XmpNode(childName, value, childOptions) { IsAlias = 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.Options.IsStruct)
         {
             throw new XmpException("Misplaced rdf:value element", XmpErrorCode.BadRdf);
         }
         xmpParent.HasValueChild = true;
     }
     if (isArrayItem)
     {
         if (!xmpParent.Options.IsArray)
         {
             throw new XmpException("Misplaced rdf:li element", XmpErrorCode.BadRdf);
         }
         newChild.Name = XmpConstants.ArrayItemName;
     }
     return newChild;
 }