// EMPTY /// <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="Com.Adobe.Xmp.XMPException">Collects all severe processing errors.</exception> internal static XMPMeta Process(XMPMetaImpl xmp, ParseOptions options) { XMPNode tree = xmp.GetRoot(); TouchUpDataModel(xmp); MoveExplicitAliases(tree, options); TweakOldXMP(tree); DeleteEmptySchemas(tree); return xmp; }
/// <summary>Static method to serialize the metadata object.</summary> /// <remarks> /// Static method to serialize the metadata object. For each serialisation, a new XMPSerializer /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to /// serialialize the same XMPMeta objects in two threads. /// </remarks> /// <param name="xmp">a metadata implementation object</param> /// <param name="out">the output stream to serialize to</param> /// <param name="options">serialization options, can be <code>null</code> for default.</param> /// <exception cref="Com.Adobe.Xmp.XMPException"/> public static void Serialize(XMPMetaImpl xmp, OutputStream @out, SerializeOptions options) { options = options != null ? options : new SerializeOptions(); // sort the internal data model on demand if (options.GetSort()) { xmp.Sort(); } new XMPSerializerRDF().Serialize(xmp, @out, options); }
/// <summary>Serializes an <code>XMPMeta</code>-object as RDF into a string.</summary> /// <remarks> /// Serializes an <code>XMPMeta</code>-object as RDF into a string. /// <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="Com.Adobe.Xmp.Options.SerializeOptions"/> /// ). /// </param> /// <returns>Returns a string containing the serialized RDF.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">on serializsation errors.</exception> public static string SerializeToString(XMPMetaImpl xmp, SerializeOptions options) { // forces the encoding to be UTF-16 to get the correct string length options = options != null ? options : new SerializeOptions(); options.SetEncodeUTF16BE(true); ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); Serialize(xmp, @out, options); try { return @out.ToString(options.GetEncoding()); } catch (UnsupportedEncodingException) { // cannot happen as UTF-8/16LE/BE is required to be implemented in // Java return @out.ToString(); } }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { XMPNode newStruct = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); newStruct.GetOptions().SetStruct(true); for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes.Item(i); if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } string attrLocal = attribute.LocalName; string attrNS = attribute.NamespaceURI; if (XMPConstConstants.XmlLang.Equals(attribute.Name)) { AddQualifierNode(newStruct, XMPConstConstants.XmlLang, attribute.Value); } else { if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "parseType".Equals(attrLocal))) { continue; } else { // The caller ensured the value is "Resource". // Ignore all rdf:ID attributes. throw new XMPException("Invalid attribute for ParseTypeResource property element", XMPErrorConstants.Badrdf); } } } Rdf_PropertyElementList(xmp, newStruct, xmlNode, false); if (newStruct.GetHasValueChild()) { 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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode rdfRdfNode) { for (int i = 0; i < rdfRdfNode.ChildNodes.Count; i++) { XmlNode child = rdfRdfNode.ChildNodes.Item(i); // filter whitespaces (and all text nodes) if (!IsWhitespaceNode(child)) { Rdf_NodeElement(xmp, xmpParent, child, true); } } }
/// <summary> /// 7.2.7 propertyAttributeURIs /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) /// 7.2.11 nodeElement /// start-element ( URI == nodeElementURIs, /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) /// propertyEltList /// end-element() /// Process the attribute list for an RDF node element. /// </summary> /// <remarks> /// 7.2.7 propertyAttributeURIs /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) /// 7.2.11 nodeElement /// start-element ( URI == nodeElementURIs, /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) /// propertyEltList /// end-element() /// Process the attribute list for an RDF node element. A property attribute URI is /// anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, /// as are rdf:about attributes on inner nodes. /// </remarks> /// <param name="xmp">the xmp metadata object that is generated</param> /// <param name="xmpParent">the parent xmp node</param> /// <param name="xmlNode">the currently processed XML node</param> /// <param name="isTopLevel">Flag if the node is a top-level node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { // Used to detect attributes that are mutually exclusive. int exclusiveAttrs = 0; for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes.Item(i); // quick hack, ns declarations do not appear in C++ // ignore "ID" without namespace if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } int attrTerm = GetRDFTermKind(attribute); switch (attrTerm) { case RdftermId: case RdftermNodeId: case RdftermAbout: { if (exclusiveAttrs > 0) { throw new XMPException("Mutally exclusive about, ID, nodeID attributes", XMPErrorConstants.Badrdf); } exclusiveAttrs++; if (isTopLevel && (attrTerm == RdftermAbout)) { // This is the rdf:about attribute on a top level node. Set // the XMP tree name if // it doesn't have a name yet. Make sure this name matches // the XMP tree name. if (xmpParent.GetName() != null && xmpParent.GetName().Length > 0) { if (!xmpParent.GetName().Equals(attribute.Value)) { throw new XMPException("Mismatched top level rdf:about values", XMPErrorConstants.Badxmp); } } else { xmpParent.SetName(attribute.Value); } } break; } case RdftermOther: { AddChildNode(xmp, xmpParent, attribute, attribute.Value, isTopLevel); break; } default: { throw new XMPException("Invalid nodeElement attribute", XMPErrorConstants.Badrdf); } } } }
/// <summary>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="Com.Adobe.Xmp.XMPException">Occurs if the parsing fails for any reason.</exception> internal static XMPMetaImpl Parse(XmlNode xmlRoot) { XMPMetaImpl xmp = new XMPMetaImpl(); Rdf_RDF(xmp, xmlRoot); return xmp; }
/// <summary>Utility to find or create the array used by <code>SeparateArrayItems()</code>.</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="Com.Adobe.Xmp.XMPException">Forwards exceptions</exception> private static XMPNode SeparateFindCreateArray(string schemaNS, string arrayName, PropertyOptions arrayOptions, XMPMetaImpl xmp) { arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null); if (!arrayOptions.IsOnlyArrayOptions()) { throw new XMPException("Options can only provide array form", XMPErrorConstants.Badoptions); } // Find the array node, make sure it is OK. Move the current children // aside, to be readded later if kept. XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); XMPNode 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. PropertyOptions arrayForm = arrayNode.GetOptions(); if (!arrayForm.IsArray() || arrayForm.IsArrayAlternate()) { throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badxpath); } if (arrayOptions.EqualArrayTypes(arrayForm)) { throw new XMPException("Mismatch of specified and existing array form", XMPErrorConstants.Badxpath); } } else { // *** Right error? // The array does not exist, try to create it. // don't modify the options handed into the method arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions.SetArray(true)); if (arrayNode == null) { throw new XMPException("Failed to create named array", XMPErrorConstants.Badxpath); } } return arrayNode; }
/// <summary>Visit all schemas to do general fixes and handle special cases.</summary> /// <param name="xmp">the metadata object implementation</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if the normalisation fails.</exception> private static void TouchUpDataModel(XMPMetaImpl 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(), XMPConstConstants.NsDc, true); // Do the special case fixes within each schema. for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) { XMPNode currSchema = (XMPNode)it.Next(); if (XMPConstConstants.NsDc.Equals(currSchema.GetName())) { NormalizeDCArrays(currSchema); } else { if (XMPConstConstants.NsExif.Equals(currSchema.GetName())) { // Do a special case fix for exif:GPSTimeStamp. FixGPSTimeStamp(currSchema); XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "exif:UserComment", false); if (arrayNode != null) { RepairAltText(arrayNode); } } else { if (XMPConstConstants.NsDm.Equals(currSchema.GetName())) { // Do a special case migration of xmpDM:copyright to // dc:rights['x-default']. XMPNode dmCopyright = XMPNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false); if (dmCopyright != null) { MigrateAudioCopyright(xmp, dmCopyright); } } else { if (XMPConstConstants.NsXmpRights.Equals(currSchema.GetName())) { XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false); if (arrayNode != null) { RepairAltText(arrayNode); } } } } } } }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { if (isTopLevel && "iX:changes".Equals(xmlNode.Name)) { // Strip old "punchcard" chaff which has on the prefix "iX:". return; } XMPNode newCompound = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); // walk through the attributes for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes.Item(i); if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } string attrLocal = attribute.LocalName; string attrNS = attribute.NamespaceURI; if (XMPConstConstants.XmlLang.Equals(attribute.Name)) { AddQualifierNode(newCompound, XMPConstConstants.XmlLang, attribute.Value); } else { if ("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)) { continue; } else { // Ignore all rdf:ID attributes. throw new XMPException("Invalid attribute for resource property element", XMPErrorConstants.Badrdf); } } } // walk through the children XmlNode currChild = null; bool found = false; int i_1; for (i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++) { currChild = xmlNode.ChildNodes.Item(i_1); if (!IsWhitespaceNode(currChild)) { if (currChild.NodeType == System.Xml.XmlNodeType.Element && !found) { bool isRDF = XMPConstConstants.NsRdf.Equals(currChild.NamespaceURI); string childLocal = currChild.LocalName; if (isRDF && "Bag".Equals(childLocal)) { newCompound.GetOptions().SetArray(true); } else { if (isRDF && "Seq".Equals(childLocal)) { newCompound.GetOptions().SetArray(true).SetArrayOrdered(true); } else { if (isRDF && "Alt".Equals(childLocal)) { newCompound.GetOptions().SetArray(true).SetArrayOrdered(true).SetArrayAlternate(true); } else { newCompound.GetOptions().SetStruct(true); if (!isRDF && !"Description".Equals(childLocal)) { string typeName = currChild.NamespaceURI; if (typeName == null) { throw new XMPException("All XML elements must be in a namespace", XMPErrorConstants.Badxmp); } typeName += ':' + childLocal; AddQualifierNode(newCompound, "rdf:type", typeName); } } } } Rdf_NodeElement(xmp, newCompound, currChild, false); if (newCompound.GetHasValueChild()) { FixupQualifiedNode(newCompound); } else { if (newCompound.GetOptions().IsArrayAlternate()) { XMPNodeUtils.DetectAltText(newCompound); } } found = true; } else { if (found) { // found second child element throw new XMPException("Invalid child of resource property element", XMPErrorConstants.Badrdf); } else { throw new XMPException("Children of resource property element must be XML elements", XMPErrorConstants.Badrdf); } } } } if (!found) { // didn't found any child elements throw new XMPException("Missing child of resource property element", XMPErrorConstants.Badrdf); } }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlParent, bool isTopLevel) { for (int i = 0; i < xmlParent.ChildNodes.Count; i++) { XmlNode currChild = xmlParent.ChildNodes.Item(i); if (IsWhitespaceNode(currChild)) { continue; } else { if (currChild.NodeType != System.Xml.XmlNodeType.Element) { throw new XMPException("Expected property element node not found", XMPErrorConstants.Badrdf); } else { Rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel); } } } }
/// <summary>Adds a child node.</summary> /// <param name="xmp">the xmp metadata object that is generated</param> /// <param name="xmpParent">the parent xmp node</param> /// <param name="xmlNode">the currently processed XML node</param> /// <param name="value">Node value</param> /// <param name="isTopLevel">Flag if the node is a top-level node</param> /// <returns>Returns the newly created child node.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static XMPNode AddChildNode(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, string value, bool isTopLevel) { XMPSchemaRegistry registry = XMPMetaFactory.GetSchemaRegistry(); string @namespace = xmlNode.NamespaceURI; string childName; if (@namespace != null) { if (XMPConstConstants.NsDcDeprecated.Equals(@namespace)) { // Fix a legacy DC namespace @namespace = XMPConstConstants.NsDc; } string prefix = registry.GetNamespacePrefix(@namespace); if (prefix == null) { prefix = xmlNode.Prefix != null ? xmlNode.Prefix : DefaultPrefix; prefix = registry.RegisterNamespace(@namespace, prefix); } childName = prefix + xmlNode.LocalName; } else { throw new XMPException("XML namespace required for all elements and attributes", XMPErrorConstants.Badrdf); } // create schema node if not already there PropertyOptions childOptions = new PropertyOptions(); bool isAlias = false; if (isTopLevel) { // Lookup the schema node, adjust the XMP parent pointer. // Incoming parent must be the tree root. XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), @namespace, DefaultPrefix, true); schemaNode.SetImplicit(false); // Clear the implicit node bit. // need runtime check for proper 32 bit code. xmpParent = schemaNode; // If this is an alias set the alias flag in the node // and the hasAliases flag in the tree. if (registry.FindAlias(childName) != null) { isAlias = true; xmp.GetRoot().SetHasAliases(true); schemaNode.SetHasAliases(true); } } // Make sure that this is not a duplicate of a named node. bool isArrayItem = "rdf:li".Equals(childName); bool isValueNode = "rdf:value".Equals(childName); // Create XMP node and so some checks XMPNode newChild = new XMPNode(childName, value, childOptions); newChild.SetAlias(isAlias); // Add the new child to the XMP parent node, a value node first. if (!isValueNode) { xmpParent.AddChild(newChild); } else { xmpParent.AddChild(1, newChild); } if (isValueNode) { if (isTopLevel || !xmpParent.GetOptions().IsStruct()) { throw new XMPException("Misplaced rdf:value element", XMPErrorConstants.Badrdf); } xmpParent.SetHasValueChild(true); } if (isArrayItem) { if (!xmpParent.GetOptions().IsArray()) { throw new XMPException("Misplaced rdf:li element", XMPErrorConstants.Badrdf); } newChild.SetName(XMPConstConstants.ArrayItemName); } return newChild; }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { int nodeTerm = GetRDFTermKind(xmlNode); if (!IsPropertyElementName(nodeTerm)) { throw new XMPException("Invalid property element name", XMPErrorConstants.Badrdf); } // remove the namespace-definitions from the list XmlAttributeCollection attributes = xmlNode.Attributes; IList nsAttrs = null; for (int i = 0; i < attributes.Count; i++) { XmlNode attribute = attributes.Item(i); if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { if (nsAttrs == null) { nsAttrs = new ArrayList(); } nsAttrs.Add(attribute.Name); } } if (nsAttrs != null) { for (Iterator it = nsAttrs.Iterator(); it.HasNext(); ) { string ns = (string)it.Next(); attributes.RemoveNamedItem(ns); } } 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! for (int i_1 = 0; i_1 < attributes.Count; i_1++) { XmlNode attribute = attributes.Item(i_1); string attrLocal = attribute.LocalName; string attrNS = attribute.NamespaceURI; string attrValue = attribute.Value; if (!(XMPConstConstants.XmlLang.Equals(attribute.Name) && !("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)))) { if ("datatype".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)) { Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else { if (!("parseType".Equals(attrLocal) && XMPConstConstants.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(); } } } } } return; } } // 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.HasChildNodes) { for (int i_2 = 0; i_2 < xmlNode.ChildNodes.Count; i_2++) { XmlNode currChild = xmlNode.ChildNodes.Item(i_2); if (currChild.NodeType != System.Xml.XmlNodeType.Text) { Rdf_ResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); return; } } Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else { Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } } }
/// <summary>Serializes an <code>XMPMeta</code>-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="Com.Adobe.Xmp.Options.SerializeOptions"/> /// ). /// </param> /// <returns>Returns a byte buffer containing the serialized RDF.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">on serializsation errors.</exception> public static sbyte[] SerializeToBuffer(XMPMetaImpl xmp, SerializeOptions options) { ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); Serialize(xmp, @out, options); return @out.ToByteArray(); }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> internal static void Rdf_RDF(XMPMetaImpl xmp, XmlNode rdfRdfNode) { if (rdfRdfNode.HasAttributes()) { Rdf_NodeElementList(xmp, xmp.GetRoot(), rdfRdfNode); } else { throw new XMPException("Invalid attributes of rdf:RDF element", XMPErrorConstants.Badrdf); } }
/// <summary>Constructor with optionsl initial values.</summary> /// <remarks> /// Constructor with optionsl initial values. If <code>propName</code> is provided, /// <code>schemaNS</code> 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 redurce to this property within the <code>schemaNS</code></param> /// <param name="options"> /// advanced iteration options, see /// <see cref="Com.Adobe.Xmp.Options.IteratorOptions"/> /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException">If the node defined by the paramters is not existing.</exception> public XMPIteratorImpl(XMPMetaImpl xmp, string schemaNS, string propPath, IteratorOptions options) { // make sure that options is defined at least with defaults this.options = options != null ? options : new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XMPNode startNode = null; string initialPath = null; bool baseSchema = schemaNS != null && schemaNS.Length > 0; bool baseProperty = propPath != null && propPath.Length > 0; if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.GetRoot(); } else { if (baseSchema && baseProperty) { // Schema and property node provided XMPPath path = XMPPathParser.ExpandXPath(schemaNS, propPath); // base path is the prop path without the property leaf XMPPath basePath = new XMPPath(); for (int i = 0; i < path.Size() - 1; i++) { basePath.Add(path.GetSegment(i)); } startNode = XMPNodeUtils.FindNode(xmp.GetRoot(), path, false, null); baseNS = 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", XMPErrorConstants.Badschema); } } } // create iterator if (startNode != null) { if (!this.options.IsJustChildren()) { nodeIterator = new XMPIteratorImpl.NodeIterator(this, startNode, initialPath, 1); } else { nodeIterator = new XMPIteratorImpl.NodeIteratorChildren(this, startNode, initialPath); } } else { // create null iterator nodeIterator = Sharpen.Collections.EmptyList().Iterator(); } }
/// <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: /// <ol> /// <li> If there is an rdf:value attribute then this is a simple property /// with a text value. /// All other attributes are qualifiers. /// <li> If there is an rdf:resource attribute then this is a simple property /// with a URI value. /// All other attributes are qualifiers. /// <li> If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID /// then this is a simple /// property with an empty value. /// <li> Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, /// or rdf:nodeID are fields. /// </ol> /// </remarks> /// <param name="xmp">the xmp metadata object that is generated</param> /// <param name="xmpParent">the parent xmp node</param> /// <param name="xmlNode">the currently processed XML node</param> /// <param name="isTopLevel">Flag if the node is a top-level node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_EmptyPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { bool hasPropertyAttrs = false; bool hasResourceAttr = false; bool hasNodeIDAttr = false; bool hasValueAttr = false; XmlNode valueNode = null; // ! Can come from rdf:value or rdf:resource. if (xmlNode.HasChildNodes) { throw new XMPException("Nested content not allowed with rdf:resource or property attributes", XMPErrorConstants.Badrdf); } // First figure out what XMP this maps to and remember the XML node for a simple value. for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes.Item(i); if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } int attrTerm = GetRDFTermKind(attribute); switch (attrTerm) { case RdftermId: { // Nothing to do. break; } case RdftermResource: { if (hasNodeIDAttr) { throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf); } else { if (hasValueAttr) { throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp); } } hasResourceAttr = true; if (!hasValueAttr) { valueNode = attribute; } break; } case RdftermNodeId: { if (hasResourceAttr) { throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf); } hasNodeIDAttr = true; break; } case RdftermOther: { if ("value".Equals(attribute.LocalName) && XMPConstConstants.NsRdf.Equals(attribute.NamespaceURI)) { if (hasResourceAttr) { throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp); } hasValueAttr = true; valueNode = attribute; } else { if (!XMPConstConstants.XmlLang.Equals(attribute.Name)) { hasPropertyAttrs = true; } } break; } default: { throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.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. XMPNode childNode = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); bool childIsStruct = false; if (hasValueAttr || hasResourceAttr) { childNode.SetValue(valueNode != null ? valueNode.Value : string.Empty); if (!hasValueAttr) { // ! Might have both rdf:value and rdf:resource. childNode.GetOptions().SetURI(true); } } else { if (hasPropertyAttrs) { childNode.GetOptions().SetStruct(true); childIsStruct = true; } } for (int i_1 = 0; i_1 < xmlNode.Attributes.Count; i_1++) { XmlNode attribute = xmlNode.Attributes.Item(i_1); if (attribute == valueNode || "xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } // Skip the rdf:value or rdf:resource attribute holding the value. int attrTerm = GetRDFTermKind(attribute); switch (attrTerm) { case RdftermId: case RdftermNodeId: { break; } case RdftermResource: { // Ignore all rdf:ID and rdf:nodeID attributes. AddQualifierNode(childNode, "rdf:resource", attribute.Value); break; } case RdftermOther: { if (!childIsStruct) { AddQualifierNode(childNode, attribute.Name, attribute.Value); } else { if (XMPConstConstants.XmlLang.Equals(attribute.Name)) { AddQualifierNode(childNode, XMPConstConstants.XmlLang, attribute.Value); } else { AddChildNode(xmp, childNode, attribute, attribute.Value, false); } } break; } default: { throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf); } } } }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_LiteralPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { XMPNode newChild = AddChildNode(xmp, xmpParent, xmlNode, null, isTopLevel); for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes.Item(i); if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } string attrNS = attribute.NamespaceURI; string attrLocal = attribute.LocalName; if (XMPConstConstants.XmlLang.Equals(attribute.Name)) { AddQualifierNode(newChild, XMPConstConstants.XmlLang, attribute.Value); } else { if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "datatype".Equals(attrLocal))) { continue; } else { // Ignore all rdf:ID and rdf:datatype attributes. throw new XMPException("Invalid attribute for literal property element", XMPErrorConstants.Badrdf); } } } string textValue = string.Empty; for (int i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++) { XmlNode child = xmlNode.ChildNodes.Item(i_1); if (child.NodeType == System.Xml.XmlNodeType.Text) { textValue += child.Value; } else { throw new XMPException("Invalid child of literal property element", XMPErrorConstants.Badrdf); } } newChild.SetValue(textValue); }
/// <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="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void Rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) { int nodeTerm = GetRDFTermKind(xmlNode); if (nodeTerm != RdftermDescription && nodeTerm != RdftermOther) { throw new XMPException("Node element must be rdf:Description or typed node", XMPErrorConstants.Badrdf); } else { if (isTopLevel && nodeTerm == RdftermOther) { throw new XMPException("Top level typed node not allowed", XMPErrorConstants.Badxmp); } else { Rdf_NodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel); Rdf_PropertyElementList(xmp, xmpParent, xmlNode, isTopLevel); } } }
/// <seealso cref="AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool, bool)"/> /// <param name="destXMP">The destination XMP object.</param> /// <param name="sourceNode">the source node</param> /// <param name="destParent">the parent of the destination node</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues"> /// flag if properties with empty values should be deleted /// in the destination object. /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException"/> private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !sourceNode.HasChildren(); } if (deleteEmptyValues && valueIsEmpty) { if (destNode != null) { destParent.RemoveChild(destNode); } } else { if (destNode == null) { // The one easy case, the destination does not exist. destParent.AddChild((XMPNode)sourceNode.Clone()); } else { if (replaceOldValues) { // The destination exists and should be replaced. destXMP.SetNode(destNode, sourceNode.GetValue(), sourceNode.GetOptions(), true); destParent.RemoveChild(destNode); destNode = (XMPNode)sourceNode.Clone(); destParent.AddChild(destNode); } else { // The destination exists and is not totally replaced. Structs and // arrays are merged. PropertyOptions sourceForm = sourceNode.GetOptions(); PropertyOptions destForm = destNode.GetOptions(); if (sourceForm != destForm) { return; } if (sourceForm.IsStruct()) { // To merge a struct process the fields recursively. E.g. add simple missing fields. // The recursive call to AppendSubtree will handle deletion for fields with empty // values. for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) { XMPNode sourceField = (XMPNode)it.Next(); AppendSubtree(destXMP, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else { if (sourceForm.IsArrayAltText()) { // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. // Make a special check for deletion of empty values. Meaningful in AltText arrays // because the "xml:lang" qualifier provides unambiguous source/dest correspondence. for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) { XMPNode sourceItem = (XMPNode)it.Next(); if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName())) { continue; } int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue()); if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0)) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else { if (destIndex == -1) { // Not replacing, keep the existing item. if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions()); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } } else { if (sourceForm.IsArray()) { // Merge other arrays by item values. Don't worry about order or duplicates. Source // items with empty values do not cause deletion, that conflicts horribly with // merging. for (Iterator @is = sourceNode.IterateChildren(); @is.HasNext(); ) { XMPNode sourceItem = (XMPNode)@is.Next(); bool match = false; for (Iterator id = destNode.IterateChildren(); id.HasNext(); ) { XMPNode destItem = (XMPNode)id.Next(); if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XMPNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } } } } } }
// UTF-8 /// <summary>The actual serialization.</summary> /// <param name="xmp">the metadata object to be serialized</param> /// <param name="out">outputStream the output stream to serialize to</param> /// <param name="options">the serialization options</param> /// <exception cref="Com.Adobe.Xmp.XMPException">If case of wrong options or any other serialization error.</exception> public virtual void Serialize(XMPMeta xmp, OutputStream @out, SerializeOptions options) { try { outputStream = new CountOutputStream(@out); writer = new OutputStreamWriter(outputStream, options.GetEncoding()); this.xmp = (XMPMetaImpl)xmp; this.options = options; this.padding = options.GetPadding(); writer = new OutputStreamWriter(outputStream, 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 string tailStr = SerializeAsRDF(); writer.Flush(); // adds padding AddPadding(tailStr.Length); // writes the tail Write(tailStr); writer.Flush(); outputStream.Close(); } catch (IOException) { throw new XMPException("Error writing to the OutputStream", XMPErrorConstants.Unknown); } }