// 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); }
/// <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; } } } }
/// <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); }
/// <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>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 "exact packet size". /// </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 "exact packet size". /// </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 "exact packet size". /// </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()); } }
/// <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; }
/// <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); } } } } } } }
/// <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; }
/// <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); } } } } } } }
/// <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); } } } }
/// <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); } } } }
/// <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; }
/// <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); } }
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); }
/// <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(); }
/// <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); } }
/// <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); }
/// <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); } }
/// <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); } } } } } } }
/// <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); } } } }
/// <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); } }
/// <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); } } }
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; }