/// <summary> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> property. /// </summary> /// <remarks> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> property. An old instance ID usually looks /// like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign /// 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If /// the name looks like a UUID simply move it to <em>xmpMM:InstanceID</em>, /// don't worry about any existing <em>xmpMM:InstanceID</em>. Both will /// only be present when a newer file with the <em>xmpMM:InstanceID</em> /// property is updated by an old app that uses <em>rdf:about</em>. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if tweaking fails.</exception> private static void TweakOldXMP(XMPNode tree) { if (tree.GetName() != null && tree.GetName().Length >= Utils.UuidLength) { string nameStr = tree.GetName().ToLower(); if (nameStr.StartsWith("uuid:")) { nameStr = Sharpen.Runtime.Substring(nameStr, 5); } if (Utils.CheckUUIDFormat(nameStr)) { // move UUID to xmpMM:InstanceID and remove it from the root node XMPPath path = XMPPathParser.ExpandXPath(XMPConstConstants.NsXmpMm, "InstanceID"); XMPNode idNode = XMPNodeUtils.FindNode(tree, path, true, null); if (idNode != null) { idNode.SetOptions(null); // Clobber any existing xmpMM:InstanceID. idNode.SetValue("uuid:" + nameStr); idNode.RemoveChildren(); idNode.RemoveQualifiers(); tree.SetName(null); } else { throw new XMPException("Failure creating xmpMM:InstanceID", XMPErrorConstants.Internalfailure); } } } }
/// <seealso cref="Com.Adobe.Xmp.XMPUtils.AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool)"/> /// <param name="source">The source XMP object.</param> /// <param name="destination">The destination XMP object.</param> /// <param name="doAllProperties">Do internal properties in addition to external properties.</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues">Delete destination values if source property is empty.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void AppendProperties(XMPMeta source, XMPMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) { ParameterAsserts.AssertImplementation(source); ParameterAsserts.AssertImplementation(destination); XMPMetaImpl src = (XMPMetaImpl)source; XMPMetaImpl dest = (XMPMetaImpl)destination; for (Iterator it = src.GetRoot().IterateChildren(); it.HasNext();) { XMPNode sourceSchema = (XMPNode)it.Next(); // Make sure we have a destination schema node XMPNode destSchema = XMPNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.GetName(), false); bool createdSchema = false; if (destSchema == null) { destSchema = new XMPNode(sourceSchema.GetName(), sourceSchema.GetValue(), new PropertyOptions().SetSchemaNode(true)); dest.GetRoot().AddChild(destSchema); createdSchema = true; } // Process the source schema's children. for (Iterator ic = sourceSchema.IterateChildren(); ic.HasNext();) { XMPNode sourceProp = (XMPNode)ic.Next(); if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.GetName(), sourceProp.GetName())) { AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); } } if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) { // Don't create an empty schema / remove empty schema. dest.GetRoot().RemoveChild(destSchema); } } }
/// <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> /// Searches for a field selector in a node: /// [fieldName="value] - an element in an array of structs, chosen by a field value. /// </summary> /// <remarks> /// Searches for a field selector in a node: /// [fieldName="value] - an element in an array of structs, chosen by a field value. /// No implicit nodes are created by field selectors. /// </remarks> /// <param name="arrayNode"/> /// <param name="fieldName"/> /// <param name="fieldValue"/> /// <returns>Returns the index of the field if found, otherwise -1.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static int LookupFieldSelector(XMPNode arrayNode, string fieldName, string fieldValue) { int result = -1; for (int index = 1; index <= arrayNode.GetChildrenLength() && result < 0; index++) { XMPNode currItem = arrayNode.GetChild(index); if (!currItem.GetOptions().IsStruct()) { throw new XMPException("Field selector must be used on array of struct", XMPErrorConstants.Badxpath); } for (int f = 1; f <= currItem.GetChildrenLength(); f++) { XMPNode currField = currItem.GetChild(f); if (!fieldName.Equals(currField.GetName())) { continue; } if (fieldValue.Equals(currField.GetValue())) { result = index; break; } } } return(result); }
/// <summary>Constructor</summary> /// <param name="parentNode">the node which children shall be iterated.</param> /// <param name="parentPath">the full path of the former node without the leaf node.</param> public NodeIteratorChildren(XMPIteratorImpl _enclosing, XMPNode parentNode, string parentPath) : base(_enclosing) { this._enclosing = _enclosing; if (parentNode.GetOptions().IsSchemaNode()) { this._enclosing.SetBaseNS(parentNode.GetName()); } this.parentPath = this.AccumulatePath(parentNode, parentPath, 1); this.childrenIterator = parentNode.IterateChildren(); }
/// <summary> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br /> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. /// </summary> /// <remarks> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br /> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. <br /> /// The <code>xml:lang</code> attribute was dropped from an /// <code>alt-text</code> item if the language was <code>x-default</code>. /// </remarks> /// <param name="dcSchema">the DC schema node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if normalization fails</exception> private static void NormalizeDCArrays(XMPNode dcSchema) { for (int i = 1; i <= dcSchema.GetChildrenLength(); i++) { XMPNode currProp = dcSchema.GetChild(i); PropertyOptions arrayForm = (PropertyOptions)dcArrayForms.Get(currProp.GetName()); if (arrayForm == null) { continue; } else { if (currProp.GetOptions().IsSimple()) { // create a new array and add the current property as child, // if it was formerly simple XMPNode newArray = new XMPNode(currProp.GetName(), arrayForm); currProp.SetName(XMPConstConstants.ArrayItemName); newArray.AddChild(currProp); dcSchema.ReplaceChild(i, newArray); // fix language alternatives if (arrayForm.IsArrayAltText() && !currProp.GetOptions().GetHasLanguage()) { XMPNode newLang = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); currProp.AddQualifier(newLang); } } else { // clear array options and add corrected array form if it has been an array before currProp.GetOptions().SetOption(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText, false); currProp.GetOptions().MergeWith(arrayForm); if (arrayForm.IsArrayAltText()) { // applying for "dc:description", "dc:rights", "dc:title" RepairAltText(currProp); } } } } }
/// <summary> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. /// </summary> /// <remarks> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. Empty schemas are automatically remove /// by <code>XMPNode</code> /// </remarks> /// <param name="schemaNode">a schema node</param> /// <param name="doAllProperties">flag if all properties or only externals shall be removed.</param> /// <returns>Returns true if the schema is empty after the operation.</returns> private static bool RemoveSchemaChildren(XMPNode schemaNode, bool doAllProperties) { for (Iterator it = schemaNode.IterateChildren(); it.HasNext();) { XMPNode currProp = (XMPNode)it.Next(); if (doAllProperties || !Utils.IsInternalProperty(schemaNode.GetName(), currProp.GetName())) { it.Remove(); } } return(!schemaNode.HasChildren()); }
/// <summary>Constructor for the node iterator.</summary> /// <param name="visitedNode">the currently visited node</param> /// <param name="parentPath">the accumulated path of the node</param> /// <param name="index">the index within the parent node (only for arrays)</param> public NodeIterator(XMPIteratorImpl _enclosing, XMPNode visitedNode, string parentPath, int index) { this._enclosing = _enclosing; // EMPTY this.visitedNode = visitedNode; this.state = XMPIteratorImpl.NodeIterator.IterateNode; if (visitedNode.GetOptions().IsSchemaNode()) { this._enclosing.SetBaseNS(visitedNode.GetName()); } // for all but the root node and schema nodes this.path = this.AccumulatePath(visitedNode, parentPath, index); }
/// <summary>Prepares the next node to return if not already done.</summary> /// <seealso cref="Sharpen.Iterator{E}.HasNext()"/> public override bool HasNext() { if (this.GetReturnProperty() != null) { // hasNext has been called before return(true); } else { if (this._enclosing.skipSiblings) { return(false); } else { if (this.childrenIterator.HasNext()) { XMPNode child = (XMPNode)this.childrenIterator.Next(); this.index++; string path = null; if (child.GetOptions().IsSchemaNode()) { this._enclosing.SetBaseNS(child.GetName()); } else { if (child.GetParent() != null) { // for all but the root node and schema nodes path = this.AccumulatePath(child, this.parentPath, this.index); } } // report next property, skip not-leaf nodes in case options is set if (!this._enclosing.GetOptions().IsJustLeafnodes() || !child.HasChildren()) { this.SetReturnProperty(this.CreatePropertyInfo(child, this._enclosing.GetBaseNS(), path)); return(true); } else { return(this.HasNext()); } } else { return(false); } } } }
/// <param name="currNode">the node that will be added to the path.</param> /// <param name="parentPath">the path up to this node.</param> /// <param name="currentIndex">the current array index if an arrey is traversed</param> /// <returns>Returns the updated path.</returns> protected internal virtual string AccumulatePath(XMPNode currNode, string parentPath, int currentIndex) { string separator; string segmentName; if (currNode.GetParent() == null || currNode.GetOptions().IsSchemaNode()) { return(null); } else { if (currNode.GetParent().GetOptions().IsArray()) { separator = string.Empty; segmentName = "[" + currentIndex.ToString() + "]"; } else { separator = "/"; segmentName = currNode.GetName(); } } if (parentPath == null || parentPath.Length == 0) { return(segmentName); } else { if (this._enclosing.GetOptions().IsJustLeafname()) { return(!segmentName.StartsWith("?") ? segmentName : Sharpen.Runtime.Substring(segmentName, 1)); } else { // qualifier return(parentPath + separator + segmentName); } } }
/// <summary>The outermost call is special.</summary> /// <remarks> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </remarks> /// <param name="aliasNode">the alias node</param> /// <param name="baseNode">the base node of the alias</param> /// <param name="outerCall">marks the outer call of the recursion</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void CompareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode, bool outerCall) { if (!aliasNode.GetValue().Equals(baseNode.GetValue()) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } if (!outerCall && (!aliasNode.GetName().Equals(baseNode.GetName()) || !aliasNode.GetOptions().Equals(baseNode.GetOptions()) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } for (Iterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext();) { XMPNode aliasChild = (XMPNode)an.Next(); XMPNode baseChild = (XMPNode)bn.Next(); CompareAliasedSubtrees(aliasChild, baseChild, false); } for (Iterator an_1 = aliasNode.IterateQualifier(), bn_1 = baseNode.IterateQualifier(); an_1.HasNext() && bn_1.HasNext();) { XMPNode aliasQual = (XMPNode)an_1.Next(); XMPNode baseQual = (XMPNode)bn_1.Next(); CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <summary>The outermost call is special.</summary> /// <remarks> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </remarks> /// <param name="aliasNode">the alias node</param> /// <param name="baseNode">the base node of the alias</param> /// <param name="outerCall">marks the outer call of the recursion</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void CompareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode, bool outerCall) { if (!aliasNode.GetValue().Equals(baseNode.GetValue()) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } if (!outerCall && (!aliasNode.GetName().Equals(baseNode.GetName()) || !aliasNode.GetOptions().Equals(baseNode.GetOptions()) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) { throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); } for (Iterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext(); ) { XMPNode aliasChild = (XMPNode)an.Next(); XMPNode baseChild = (XMPNode)bn.Next(); CompareAliasedSubtrees(aliasChild, baseChild, false); } for (Iterator an_1 = aliasNode.IterateQualifier(), bn_1 = baseNode.IterateQualifier(); an_1.HasNext() && bn_1.HasNext(); ) { XMPNode aliasQual = (XMPNode)an_1.Next(); XMPNode baseQual = (XMPNode)bn_1.Next(); CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// </summary> /// <remarks> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// No implicit nodes are created for qualifier selectors, /// except for an alias to an x-default item. /// </remarks> /// <param name="arrayNode">an array node</param> /// <param name="qualName">the qualifier name</param> /// <param name="qualValue">the qualifier value</param> /// <param name="aliasForm"> /// in case the qual selector results from an alias, /// an x-default node is created if there has not been one. /// </param> /// <returns>Returns the index of th</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) { if (XMPConstConstants.XmlLang.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = Com.Adobe.Xmp.Impl.XMPNodeUtils.LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) { XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return(1); } else { return(index); } } else { for (int index = 1; index < arrayNode.GetChildrenLength(); index++) { XMPNode currItem = arrayNode.GetChild(index); for (Iterator it = currItem.IterateQualifier(); it.HasNext();) { XMPNode qualifier = (XMPNode)it.Next(); if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) { return(index); } } } return(-1); } }
/// <summary> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. /// </summary> /// <remarks> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. Empty schemas are automatically remove /// by <code>XMPNode</code> /// </remarks> /// <param name="schemaNode">a schema node</param> /// <param name="doAllProperties">flag if all properties or only externals shall be removed.</param> /// <returns>Returns true if the schema is empty after the operation.</returns> private static bool RemoveSchemaChildren(XMPNode schemaNode, bool doAllProperties) { for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) { XMPNode currProp = (XMPNode)it.Next(); if (doAllProperties || !Utils.IsInternalProperty(schemaNode.GetName(), currProp.GetName())) { it.Remove(); } } return !schemaNode.HasChildren(); }
/// <summary>This is setting the value of a leaf node.</summary> /// <param name="node">an XMPNode</param> /// <param name="value">a value</param> internal static void SetNodeValue(XMPNode node, object value) { string strValue = SerializeNodeValue(value); if (!(node.GetOptions().IsQualifier() && XMPConstConstants.XmlLang.Equals(node.GetName()))) { node.SetValue(strValue); } else { node.SetValue(Utils.NormalizeLangValue(strValue)); } }
/// <summary>Writes all used namespaces of the subtree in node to the output.</summary> /// <remarks> /// Writes all used namespaces of the subtree in node to the output. /// The subtree is recursivly traversed. /// </remarks> /// <param name="node">the root node of the subtree</param> /// <param name="usedPrefixes">a set containing currently used prefixes</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards all writer exceptions.</exception> private void DeclareUsedNamespaces(XMPNode node, HashSet<string> usedPrefixes, int indent) { if (node.GetOptions().IsSchemaNode()) { // The schema node name is the URI, the value is the prefix. string prefix = Sharpen.Runtime.Substring(node.GetValue(), 0, node.GetValue().Length - 1); DeclareNamespace(prefix, node.GetName(), usedPrefixes, indent); } else { if (node.GetOptions().IsStruct()) { for (Iterator it = node.IterateChildren(); it.HasNext(); ) { XMPNode field = (XMPNode)it.Next(); DeclareNamespace(field.GetName(), null, usedPrefixes, indent); } } } for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) { XMPNode child = (XMPNode)it_1.Next(); DeclareUsedNamespaces(child, usedPrefixes, indent); } for (Iterator it_2 = node.IterateQualifier(); it_2.HasNext(); ) { XMPNode qualifier = (XMPNode)it_2.Next(); DeclareNamespace(qualifier.GetName(), null, usedPrefixes, indent); DeclareUsedNamespaces(qualifier, usedPrefixes, indent); } }
/// <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>Recursively handles the "value" for a node.</summary> /// <remarks> /// Recursively handles the "value" for a node. It does not matter if it is a /// top level property, a field of a struct, or an item of an array. The /// indent is that for the property element. An xml:lang qualifier is written /// as an attribute of the property start tag, not by itself forcing the /// qualified property form. The patterns below mostly ignore attribute /// qualifiers like xml:lang. Except for the one struct case, attribute /// qualifiers don't affect the output form. /// <blockquote> /// <pre> /// <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty> /// <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier) /// <rdf:Description> /// ... Fields, same forms as top level properties /// </rdf:Description> /// </ns:UnqualifiedStructProperty> /// <ns:ResourceStructProperty rdf:resource="URI" /// ... Fields as attributes /// > /// <ns:UnqualifiedArrayProperty> /// <rdf:Bag> or Seq or Alt /// ... Array items as rdf:li elements, same forms as top level properties /// </rdf:Bag> /// </ns:UnqualifiedArrayProperty> /// <ns:QualifiedProperty> /// <rdf:Description> /// <rdf:value> ... Property "value" following the unqualified /// forms ... </rdf:value> /// ... Qualifiers looking like named struct fields /// </rdf:Description> /// </ns:QualifiedProperty> /// </pre> /// </blockquote> /// </remarks> /// <param name="node">the property node</param> /// <param name="emitAsRDFValue">property shall be rendered as attribute rather than tag</param> /// <param name="useCanonicalRDF"> /// use canonical form with inner description tag or /// the compact form with rdf:ParseType="resource" attribute. /// </param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards all writer exceptions.</exception> /// <exception cref="Com.Adobe.Xmp.XMPException">If "rdf:resource" and general qualifiers are mixed.</exception> private void SerializeCanonicalRDFProperty(XMPNode node, bool useCanonicalRDF, bool emitAsRDFValue, int indent) { bool emitEndTag = true; bool indentEndTag = true; // Determine the XML element name. Open the start tag with the name and // attribute qualifiers. string elemName = node.GetName(); if (emitAsRDFValue) { elemName = "rdf:value"; } else { if (XMPConstConstants.ArrayItemName.Equals(elemName)) { elemName = "rdf:li"; } } WriteIndent(indent); Write('<'); Write(elemName); bool hasGeneralQualifiers = false; bool hasRDFResourceQual = false; for (Iterator it = node.IterateQualifier(); it.HasNext(); ) { XMPNode qualifier = (XMPNode)it.Next(); if (!RdfAttrQualifier.Contains(qualifier.GetName())) { hasGeneralQualifiers = true; } else { hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName()); if (!emitAsRDFValue) { Write(' '); Write(qualifier.GetName()); Write("=\""); AppendNodeValue(qualifier.GetValue(), true); Write('"'); } } } // Process the property according to the standard patterns. if (hasGeneralQualifiers && !emitAsRDFValue) { // This node has general, non-attribute, qualifiers. Emit using the // qualified property form. // ! The value is output by a recursive call ON THE SAME NODE with // emitAsRDFValue set. if (hasRDFResourceQual) { throw new XMPException("Can't mix rdf:resource and general qualifiers", XMPErrorConstants.Badrdf); } // Change serialization to canonical format with inner rdf:Description-tag // depending on option if (useCanonicalRDF) { Write(">"); WriteNewline(); indent++; WriteIndent(indent); Write(RdfStructStart); Write(">"); } else { Write(" rdf:parseType=\"Resource\">"); } WriteNewline(); SerializeCanonicalRDFProperty(node, useCanonicalRDF, true, indent + 1); for (Iterator it_1 = node.IterateQualifier(); it_1.HasNext(); ) { XMPNode qualifier = (XMPNode)it_1.Next(); if (!RdfAttrQualifier.Contains(qualifier.GetName())) { SerializeCanonicalRDFProperty(qualifier, useCanonicalRDF, false, indent + 1); } } if (useCanonicalRDF) { WriteIndent(indent); Write(RdfStructEnd); WriteNewline(); indent--; } } else { // This node has no general qualifiers. Emit using an unqualified form. if (!node.GetOptions().IsCompositeProperty()) { // This is a simple property. if (node.GetOptions().IsURI()) { Write(" rdf:resource=\""); AppendNodeValue(node.GetValue(), true); Write("\"/>"); WriteNewline(); emitEndTag = false; } else { if (node.GetValue() == null || string.Empty.Equals(node.GetValue())) { Write("/>"); WriteNewline(); emitEndTag = false; } else { Write('>'); AppendNodeValue(node.GetValue(), false); indentEndTag = false; } } } else { if (node.GetOptions().IsArray()) { // This is an array. Write('>'); WriteNewline(); EmitRDFArrayTag(node, true, indent + 1); if (node.GetOptions().IsArrayAltText()) { XMPNodeUtils.NormalizeLangArray(node); } for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) { XMPNode child = (XMPNode)it_1.Next(); SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 2); } EmitRDFArrayTag(node, false, indent + 1); } else { if (!hasRDFResourceQual) { // This is a "normal" struct, use the rdf:parseType="Resource" form. if (!node.HasChildren()) { // Change serialization to canonical format with inner rdf:Description-tag // if option is set if (useCanonicalRDF) { Write(">"); WriteNewline(); WriteIndent(indent + 1); Write(RdfEmptyStruct); } else { Write(" rdf:parseType=\"Resource\"/>"); emitEndTag = false; } WriteNewline(); } else { // Change serialization to canonical format with inner rdf:Description-tag // if option is set if (useCanonicalRDF) { Write(">"); WriteNewline(); indent++; WriteIndent(indent); Write(RdfStructStart); Write(">"); } else { Write(" rdf:parseType=\"Resource\">"); } WriteNewline(); for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) { XMPNode child = (XMPNode)it_1.Next(); SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 1); } if (useCanonicalRDF) { WriteIndent(indent); Write(RdfStructEnd); WriteNewline(); indent--; } } } else { // This is a struct with an rdf:resource attribute, use the // "empty property element" form. for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) { XMPNode child = (XMPNode)it_1.Next(); if (!CanBeRDFAttrProp(child)) { throw new XMPException("Can't mix rdf:resource and complex fields", XMPErrorConstants.Badrdf); } WriteNewline(); WriteIndent(indent + 1); Write(' '); Write(child.GetName()); Write("=\""); AppendNodeValue(child.GetValue(), true); Write('"'); } Write("/>"); WriteNewline(); emitEndTag = false; } } } } // Emit the property element end tag. if (emitEndTag) { if (indentEndTag) { WriteIndent(indent); } Write("</"); Write(elemName); Write('>'); WriteNewline(); } }
/// <summary>Visit all of the top level nodes looking for aliases.</summary> /// <remarks> /// Visit all of the top level nodes looking for aliases. If there is /// no base, transplant the alias subtree. If there is a base and strict /// aliasing is on, make sure the alias and base subtrees match. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <param name="options">th parsing options</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void MoveExplicitAliases(XMPNode tree, ParseOptions options) { if (!tree.GetHasAliases()) { return; } tree.SetHasAliases(false); bool strictAliasing = options.GetStrictAliasing(); for (Iterator schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext();) { XMPNode currSchema = (XMPNode)schemaIt.Next(); if (!currSchema.GetHasAliases()) { continue; } for (Iterator propertyIt = currSchema.IterateChildren(); propertyIt.HasNext();) { XMPNode currProp = (XMPNode)propertyIt.Next(); if (!currProp.IsAlias()) { continue; } currProp.SetAlias(false); // Find the base path, look for the base schema and root node. XMPAliasInfo info = XMPMetaFactory.GetSchemaRegistry().FindAlias(currProp.GetName()); if (info != null) { // find or create schema XMPNode baseSchema = XMPNodeUtils.FindSchemaNode(tree, info.GetNamespace(), null, true); baseSchema.SetImplicit(false); XMPNode baseNode = XMPNodeUtils.FindChildNode(baseSchema, info.GetPrefix() + info.GetPropName(), false); if (baseNode == null) { if (info.GetAliasForm().IsSimple()) { // A top-to-top alias, transplant the property. // change the alias property name to the base name string qname = info.GetPrefix() + info.GetPropName(); currProp.SetName(qname); baseSchema.AddChild(currProp); // remove the alias property propertyIt.Remove(); } else { // An alias to an array item, // create the array and transplant the property. baseNode = new XMPNode(info.GetPrefix() + info.GetPropName(), info.GetAliasForm().ToPropertyOptions()); baseSchema.AddChild(baseNode); TransplantArrayItemAlias(propertyIt, currProp, baseNode); } } else { if (info.GetAliasForm().IsSimple()) { // The base node does exist and this is a top-to-top alias. // Check for conflicts if strict aliasing is on. // Remove and delete the alias subtree. if (strictAliasing) { CompareAliasedSubtrees(currProp, baseNode, true); } propertyIt.Remove(); } else { // This is an alias to an array item and the array exists. // Look for the aliased item. // Then transplant or check & delete as appropriate. XMPNode itemNode = null; if (info.GetAliasForm().IsArrayAltText()) { int xdIndex = XMPNodeUtils.LookupLanguageItem(baseNode, XMPConstConstants.XDefault); if (xdIndex != -1) { itemNode = baseNode.GetChild(xdIndex); } } else { if (baseNode.HasChildren()) { itemNode = baseNode.GetChild(1); } } if (itemNode == null) { TransplantArrayItemAlias(propertyIt, currProp, baseNode); } else { if (strictAliasing) { CompareAliasedSubtrees(currProp, itemNode, true); } propertyIt.Remove(); } } } } } currSchema.SetHasAliases(false); } }
/// <summary>Compares two nodes including its children and qualifier.</summary> /// <param name="leftNode">an <code>XMPNode</code></param> /// <param name="rightNode">an <code>XMPNode</code></param> /// <returns>Returns true if the nodes are equal, false otherwise.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards exceptions to the calling method.</exception> private static bool ItemValuesMatch(XMPNode leftNode, XMPNode rightNode) { PropertyOptions leftForm = leftNode.GetOptions(); PropertyOptions rightForm = rightNode.GetOptions(); if (leftForm.Equals(rightForm)) { return(false); } if (leftForm.GetOptions() == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.GetValue().Equals(rightNode.GetValue())) { return(false); } if (leftNode.GetOptions().GetHasLanguage() != rightNode.GetOptions().GetHasLanguage()) { return(false); } if (leftNode.GetOptions().GetHasLanguage() && !leftNode.GetQualifier(1).GetValue().Equals(rightNode.GetQualifier(1).GetValue())) { return(false); } } else { if (leftForm.IsStruct()) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength()) { return(false); } for (Iterator it = leftNode.IterateChildren(); it.HasNext();) { XMPNode leftField = (XMPNode)it.Next(); XMPNode rightField = XMPNodeUtils.FindChildNode(rightNode, leftField.GetName(), false); if (rightField == null || !ItemValuesMatch(leftField, rightField)) { return(false); } } } else { // Array nodes, see if the "leftNode" values are present in the // "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the // destination for AppendProperties. System.Diagnostics.Debug.Assert(leftForm.IsArray()); for (Iterator il = leftNode.IterateChildren(); il.HasNext();) { XMPNode leftItem = (XMPNode)il.Next(); bool match = false; for (Iterator ir = rightNode.IterateChildren(); ir.HasNext();) { XMPNode rightItem = (XMPNode)ir.Next(); if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return(false); } } } } return(true); }
/// <param name="currNode">the node that will be added to the path.</param> /// <param name="parentPath">the path up to this node.</param> /// <param name="currentIndex">the current array index if an arrey is traversed</param> /// <returns>Returns the updated path.</returns> protected internal virtual string AccumulatePath(XMPNode currNode, string parentPath, int currentIndex) { string separator; string segmentName; if (currNode.GetParent() == null || currNode.GetOptions().IsSchemaNode()) { return null; } else { if (currNode.GetParent().GetOptions().IsArray()) { separator = string.Empty; segmentName = "[" + currentIndex.ToString() + "]"; } else { separator = "/"; segmentName = currNode.GetName(); } } if (parentPath == null || parentPath.Length == 0) { return segmentName; } else { if (this._enclosing.GetOptions().IsJustLeafname()) { return !segmentName.StartsWith("?") ? segmentName : Sharpen.Runtime.Substring(segmentName, 1); } else { // qualifier return parentPath + separator + segmentName; } } }
/// <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); } } } } } } } } }
/// <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); } } } } } } } } }
/// <summary> /// A node can be serialized as RDF-Attribute, if it meets the following conditions: /// <ul> /// <li>is not array item /// <li>don't has qualifier /// <li>is no URI /// <li>is no composite property /// </ul> /// </summary> /// <param name="node">an XMPNode</param> /// <returns>Returns true if the node serialized as RDF-Attribute</returns> private bool CanBeRDFAttrProp(XMPNode node) { return !node.HasQualifier() && !node.GetOptions().IsURI() && !node.GetOptions().IsCompositeProperty() && !XMPConstConstants.ArrayItemName.Equals(node.GetName()); }