/// <summary>Find or create a child node under a given parent node.</summary> /// <remarks> /// Find or create a child node under a given parent node. If the parent node is no /// Returns the found or created child node. /// </remarks> /// <param name="parent">the parent node</param> /// <param name="childName">the node name to find</param> /// <param name="createNodes">flag, if new nodes shall be created.</param> /// <returns>Returns the found or created node or <code>null</code>.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if</exception> internal static XMPNode FindChildNode(XMPNode parent, string childName, bool createNodes) { if (!parent.GetOptions().IsSchemaNode() && !parent.GetOptions().IsStruct()) { if (!parent.IsImplicit()) { throw new XMPException("Named children only allowed for schemas and structs", XMPErrorConstants.Badxpath); } else { if (parent.GetOptions().IsArray()) { throw new XMPException("Named children not allowed for arrays", XMPErrorConstants.Badxpath); } else { if (createNodes) { parent.GetOptions().SetStruct(true); } } } } XMPNode childNode = parent.FindChildByName(childName); if (childNode == null && createNodes) { PropertyOptions options = new PropertyOptions(); childNode = new XMPNode(childName, options); childNode.SetImplicit(true); parent.AddChild(childNode); } System.Diagnostics.Debug.Assert(childNode != null || !createNodes); return(childNode); }
// EMPTY /// <seealso cref="Com.Adobe.Xmp.XMPUtils.CatenateArrayItems(Com.Adobe.Xmp.XMPMeta, string, string, string, string, bool)"/> /// <param name="xmp">The XMP object containing the array to be catenated.</param> /// <param name="schemaNS"> /// The schema namespace URI for the array. Must not be null or /// the empty string. /// </param> /// <param name="arrayName"> /// The name of the array. May be a general path expression, must /// not be null or the empty string. Each item in the array must /// be a simple string value. /// </param> /// <param name="separator"> /// The string to be used to separate the items in the catenated /// string. Defaults to "; ", ASCII semicolon and space /// (U+003B, U+0020). /// </param> /// <param name="quotes"> /// The characters to be used as quotes around array items that /// contain a separator. Defaults to '"' /// </param> /// <param name="allowCommas">Option flag to control the catenation.</param> /// <returns>Returns the string containing the catenated array items.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static string CatenateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string separator, string quotes, bool allowCommas) { ParameterAsserts.AssertSchemaNS(schemaNS); ParameterAsserts.AssertArrayName(arrayName); ParameterAsserts.AssertImplementation(xmp); if (separator == null || separator.Length == 0) { separator = "; "; } if (quotes == null || quotes.Length == 0) { quotes = "\""; } XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; XMPNode arrayNode = null; XMPNode currItem = null; // Return an empty result if the array does not exist, // hurl if it isn't the right form. XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); arrayNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), arrayPath, false, null); if (arrayNode == null) { return(string.Empty); } else { if (!arrayNode.GetOptions().IsArray() || arrayNode.GetOptions().IsArrayAlternate()) { throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badparam); } } // Make sure the separator is OK. CheckSeparator(separator); // Make sure the open and close quotes are a legitimate pair. char openQuote = quotes[0]; char closeQuote = CheckQuotes(quotes, openQuote); // Build the result, quoting the array items, adding separators. // Hurl if any item isn't simple. StringBuilder catinatedString = new StringBuilder(); for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { currItem = (XMPNode)it.Next(); if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Array items must be simple", XMPErrorConstants.Badparam); } string str = ApplyQuotes(currItem.GetValue(), openQuote, closeQuote, allowCommas); catinatedString.Append(str); if (it.HasNext()) { catinatedString.Append(separator); } } return(catinatedString.ToString()); }
/// <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>Make sure the x-default item is first.</summary> /// <remarks> /// Make sure the x-default item is first. Touch up "single value" /// arrays that have a default plus one real language. This case should have /// the same value for both items. Older Adobe apps were hardwired to only /// use the "x-default" item, so we copy that value to the other /// item. /// </remarks> /// <param name="arrayNode">an alt text array node</param> internal static void NormalizeLangArray(XMPNode arrayNode) { if (!arrayNode.GetOptions().IsArrayAltText()) { return; } // check if node with x-default qual is first place for (int i = 2; i <= arrayNode.GetChildrenLength(); i++) { XMPNode child = arrayNode.GetChild(i); if (child.HasQualifier() && XMPConstConstants.XDefault.Equals(child.GetQualifier(1).GetValue())) { // move node to first place try { arrayNode.RemoveChild(i); arrayNode.AddChild(1, child); } catch (XMPException) { // cannot occur, because same child is removed before System.Diagnostics.Debug.Assert(false); } if (i == 2) { arrayNode.GetChild(2).SetValue(child.GetValue()); } break; } } }
/// <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>See if an array is an alt-text array.</summary> /// <remarks> /// See if an array is an alt-text array. If so, make sure the x-default item /// is first. /// </remarks> /// <param name="arrayNode">the array node to check if its an alt-text array</param> internal static void DetectAltText(XMPNode arrayNode) { if (arrayNode.GetOptions().IsArrayAlternate() && arrayNode.HasChildren()) { bool isAltText = false; for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode child = (XMPNode)it.Next(); if (child.GetOptions().GetHasLanguage()) { isAltText = true; break; } } if (isAltText) { arrayNode.GetOptions().SetArrayAltText(true); NormalizeLangArray(arrayNode); } } }
/// <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>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>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); } } } }
/// <summary>Make sure that the array is well-formed AltText.</summary> /// <remarks> /// Make sure that the array is well-formed AltText. Each item must be simple /// and have an "xml:lang" qualifier. If repairs are needed, keep simple /// non-empty items by adding the "xml:lang" with value "x-repair". /// </remarks> /// <param name="arrayNode">the property node of the array to repair.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards unexpected exceptions.</exception> private static void RepairAltText(XMPNode arrayNode) { if (arrayNode == null || !arrayNode.GetOptions().IsArray()) { // Already OK or not even an array. return; } // fix options arrayNode.GetOptions().SetArrayOrdered(true).SetArrayAlternate(true).SetArrayAltText(true); for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode currChild = (XMPNode)it.Next(); if (currChild.GetOptions().IsCompositeProperty()) { // Delete non-simple children. it.Remove(); } else { if (!currChild.GetOptions().GetHasLanguage()) { string childValue = currChild.GetValue(); if (childValue == null || childValue.Length == 0) { // Delete empty valued children that have no xml:lang. it.Remove(); } else { // Add an xml:lang qualifier with the value "x-repair". XMPNode repairLang = new XMPNode(XMPConstConstants.XmlLang, "x-repair", null); currChild.AddQualifier(repairLang); } } } } }
/// <summary>Moves an alias node of array form to another schema into an array</summary> /// <param name="propertyIt">the property iterator of the old schema (used to delete the property)</param> /// <param name="childNode">the node to be moved</param> /// <param name="baseArray">the base array for the array item</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void TransplantArrayItemAlias(Iterator propertyIt, XMPNode childNode, XMPNode baseArray) { if (baseArray.GetOptions().IsArrayAltText()) { if (childNode.GetOptions().GetHasLanguage()) { throw new XMPException("Alias to x-default already has a language qualifier", XMPErrorConstants.Badxmp); } XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); childNode.AddQualifier(langQual); } propertyIt.Remove(); childNode.SetName(XMPConstConstants.ArrayItemName); baseArray.AddChild(childNode); }
/// <summary>Deletes the the given node and its children from its parent.</summary> /// <remarks> /// Deletes the the given node and its children from its parent. /// Takes care about adjusting the flags. /// </remarks> /// <param name="node">the top-most node to delete.</param> internal static void DeleteNode(XMPNode node) { XMPNode parent = node.GetParent(); if (node.GetOptions().IsQualifier()) { // root is qualifier parent.RemoveQualifier(node); } else { // root is NO qualifier parent.RemoveChild(node); } // delete empty Schema nodes if (!parent.HasChildren() && parent.GetOptions().IsSchemaNode()) { parent.GetParent().RemoveChild(parent); } }
/// <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>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>Looks for the appropriate language item in a text alternative array.item</summary> /// <param name="arrayNode">an array node</param> /// <param name="language">the requested language</param> /// <returns>Returns the index if the language has been found, -1 otherwise.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> internal static int LookupLanguageItem(XMPNode arrayNode, string language) { if (!arrayNode.GetOptions().IsArray()) { throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); } for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) { XMPNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) { continue; } else { if (language.Equals(child.GetQualifier(1).GetValue())) { return(index); } } } return(-1); }
/// <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>Serializes a simple property.</summary> /// <param name="node">an XMPNode</param> /// <returns>Returns an array containing the flags emitEndTag and indentEndTag.</returns> /// <exception cref="System.IO.IOException">Forwards the writer exceptions.</exception> private object[] SerializeCompactRDFSimpleProp(XMPNode node) { // This is a simple property. bool emitEndTag = true; bool indentEndTag = true; if (node.GetOptions().IsURI()) { Write(" rdf:resource=\""); AppendNodeValue(node.GetValue(), true); Write("\"/>"); WriteNewline(); emitEndTag = false; } else { if (node.GetValue() == null || node.GetValue().Length == 0) { Write("/>"); WriteNewline(); emitEndTag = false; } else { Write('>'); AppendNodeValue(node.GetValue(), false); indentEndTag = false; } } return new object[] { emitEndTag, indentEndTag }; }
/// <summary>Deletes the the given node and its children from its parent.</summary> /// <remarks> /// Deletes the the given node and its children from its parent. /// Takes care about adjusting the flags. /// </remarks> /// <param name="node">the top-most node to delete.</param> internal static void DeleteNode(XMPNode node) { XMPNode parent = node.GetParent(); if (node.GetOptions().IsQualifier()) { // root is qualifier parent.RemoveQualifier(node); } else { // root is NO qualifier parent.RemoveChild(node); } // delete empty Schema nodes if (!parent.HasChildren() && parent.GetOptions().IsSchemaNode()) { parent.GetParent().RemoveChild(parent); } }
/// <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> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// </summary> /// <remarks> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// <li>[index] - An element of an array. /// <li>[last()] - The last element of an array. /// <li>[qualName="value"] - An element in an array of structs, chosen by a field value. /// <li>[?qualName="value"] - An element in an array, chosen by a qualifier value. /// <li>?qualName - A general qualifier. /// </ul> /// Find the appropriate child node, resolving aliases, and optionally creating nodes. /// </remarks> /// <param name="parentNode">the node to start to start from</param> /// <param name="nextStep">the xpath segment</param> /// <param name="createNodes"></param> /// <returns>returns the found or created XMPPath node</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) { XMPNode nextNode = null; int index = 0; int stepKind = nextStep.GetKind(); if (stepKind == XMPPath.StructFieldStep) { nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.QualifierStep) { nextNode = FindQualifierNode(parentNode, Sharpen.Runtime.Substring(nextStep.GetName(), 1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. if (!parentNode.GetOptions().IsArray()) { throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); } if (stepKind == XMPPath.ArrayIndexStep) { index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.ArrayLastStep) { index = parentNode.GetChildrenLength(); } else { if (stepKind == XMPPath.FieldSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else { if (stepKind == XMPPath.QualSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); } else { throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); } } } } if (1 <= index && index <= parentNode.GetChildrenLength()) { nextNode = parentNode.GetChild(index); } } } return(nextNode); }
/// <summary>The parent is an RDF pseudo-struct containing an rdf:value field.</summary> /// <remarks> /// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the /// XMP data model. The rdf:value node must be the first child, the other /// children are qualifiers. The form, value, and children of the rdf:value /// node are the real ones. The rdf:value node's qualifiers must be added to /// the others. /// </remarks> /// <param name="xmpParent">the parent xmp node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void FixupQualifiedNode(XMPNode xmpParent) { System.Diagnostics.Debug.Assert(xmpParent.GetOptions().IsStruct() && xmpParent.HasChildren()); XMPNode valueNode = xmpParent.GetChild(1); System.Diagnostics.Debug.Assert("rdf:value".Equals(valueNode.GetName())); // Move the qualifiers on the value node to the parent. // Make sure an xml:lang qualifier stays at the front. // Check for duplicate names between the value node's qualifiers and the parent's children. // The parent's children are about to become qualifiers. Check here, between the groups. // Intra-group duplicates are caught by XMPNode#addChild(...). if (valueNode.GetOptions().GetHasLanguage()) { if (xmpParent.GetOptions().GetHasLanguage()) { throw new XMPException("Redundant xml:lang for rdf:value element", XMPErrorConstants.Badxmp); } XMPNode langQual = valueNode.GetQualifier(1); valueNode.RemoveQualifier(langQual); xmpParent.AddQualifier(langQual); } // Start the remaining copy after the xml:lang qualifier. for (int i = 1; i <= valueNode.GetQualifierLength(); i++) { XMPNode qualifier = valueNode.GetQualifier(i); xmpParent.AddQualifier(qualifier); } // Change the parent's other children into qualifiers. // This loop starts at 1, child 0 is the rdf:value node. for (int i_1 = 2; i_1 <= xmpParent.GetChildrenLength(); i_1++) { XMPNode qualifier = xmpParent.GetChild(i_1); xmpParent.AddQualifier(qualifier); } // Move the options and value last, other checks need the parent's original options. // Move the value node's children to be the parent's children. System.Diagnostics.Debug.Assert(xmpParent.GetOptions().IsStruct() || xmpParent.GetHasValueChild()); xmpParent.SetHasValueChild(false); xmpParent.GetOptions().SetStruct(false); xmpParent.GetOptions().MergeWith(valueNode.GetOptions()); xmpParent.SetValue(valueNode.GetValue()); xmpParent.RemoveChildren(); for (Iterator it = valueNode.IterateChildren(); it.HasNext(); ) { XMPNode child = (XMPNode)it.Next(); xmpParent.AddChild(child); } }
/// <summary>Find or create a child node under a given parent node.</summary> /// <remarks> /// Find or create a child node under a given parent node. If the parent node is no /// Returns the found or created child node. /// </remarks> /// <param name="parent">the parent node</param> /// <param name="childName">the node name to find</param> /// <param name="createNodes">flag, if new nodes shall be created.</param> /// <returns>Returns the found or created node or <code>null</code>.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if</exception> internal static XMPNode FindChildNode(XMPNode parent, string childName, bool createNodes) { if (!parent.GetOptions().IsSchemaNode() && !parent.GetOptions().IsStruct()) { if (!parent.IsImplicit()) { throw new XMPException("Named children only allowed for schemas and structs", XMPErrorConstants.Badxpath); } else { if (parent.GetOptions().IsArray()) { throw new XMPException("Named children not allowed for arrays", XMPErrorConstants.Badxpath); } else { if (createNodes) { parent.GetOptions().SetStruct(true); } } } } XMPNode childNode = parent.FindChildByName(childName); if (childNode == null && createNodes) { PropertyOptions options = new PropertyOptions(); childNode = new XMPNode(childName, options); childNode.SetImplicit(true); parent.AddChild(childNode); } System.Diagnostics.Debug.Assert(childNode != null || !createNodes); return childNode; }
/// <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()); }
/// <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> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// </summary> /// <remarks> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// <li>[index] - An element of an array. /// <li>[last()] - The last element of an array. /// <li>[qualName="value"] - An element in an array of structs, chosen by a field value. /// <li>[?qualName="value"] - An element in an array, chosen by a qualifier value. /// <li>?qualName - A general qualifier. /// </ul> /// Find the appropriate child node, resolving aliases, and optionally creating nodes. /// </remarks> /// <param name="parentNode">the node to start to start from</param> /// <param name="nextStep">the xpath segment</param> /// <param name="createNodes"></param> /// <returns>returns the found or created XMPPath node</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) { XMPNode nextNode = null; int index = 0; int stepKind = nextStep.GetKind(); if (stepKind == XMPPath.StructFieldStep) { nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.QualifierStep) { nextNode = FindQualifierNode(parentNode, Sharpen.Runtime.Substring(nextStep.GetName(), 1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. if (!parentNode.GetOptions().IsArray()) { throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); } if (stepKind == XMPPath.ArrayIndexStep) { index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.ArrayLastStep) { index = parentNode.GetChildrenLength(); } else { if (stepKind == XMPPath.FieldSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else { if (stepKind == XMPPath.QualSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); } else { throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); } } } } if (1 <= index && index <= parentNode.GetChildrenLength()) { nextNode = parentNode.GetChild(index); } } } return nextNode; }
/// <summary> /// <ol> /// <li>Look for an exact match with the specific language. /// </summary> /// <remarks> /// <ol> /// <li>Look for an exact match with the specific language. /// <li>If a generic language is given, look for partial matches. /// <li>Look for an "x-default"-item. /// <li>Choose the first item. /// </ol> /// </remarks> /// <param name="arrayNode">the alt text array node</param> /// <param name="genericLang">the generic language</param> /// <param name="specificLang">the specific language</param> /// <returns> /// Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> internal static object[] ChooseLocalizedText(XMPNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.GetOptions().IsArrayAltText()) { throw new XMPException("Localized text array is not alt-text", XMPErrorConstants.Badxpath); } else { if (!arrayNode.HasChildren()) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltNoValues, null }); } } int foundGenericMatches = 0; XMPNode resultNode = null; XMPNode xDefault = null; // Look for the first partial match with the generic language. for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode currItem = (XMPNode)it.Next(); // perform some checks on the current item if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); } else { if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) { throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); } } string currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSpecificMatch, currItem }); } else { if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else { if (XMPConstConstants.XDefault.Equals(currLang)) { xDefault = currItem; } } } } // evaluate loop if (foundGenericMatches == 1) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSingleGeneric, resultNode }); } else { if (foundGenericMatches > 1) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltMultipleGeneric, resultNode }); } else { if (xDefault != null) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltXdefault, xDefault }); } else { // Everything failed, choose the first item. return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltFirstItem, arrayNode.GetChild(1) }); } } } }
/// <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>Make sure the x-default item is first.</summary> /// <remarks> /// Make sure the x-default item is first. Touch up "single value" /// arrays that have a default plus one real language. This case should have /// the same value for both items. Older Adobe apps were hardwired to only /// use the "x-default" item, so we copy that value to the other /// item. /// </remarks> /// <param name="arrayNode">an alt text array node</param> internal static void NormalizeLangArray(XMPNode arrayNode) { if (!arrayNode.GetOptions().IsArrayAltText()) { return; } // check if node with x-default qual is first place for (int i = 2; i <= arrayNode.GetChildrenLength(); i++) { XMPNode child = arrayNode.GetChild(i); if (child.HasQualifier() && XMPConstConstants.XDefault.Equals(child.GetQualifier(1).GetValue())) { // move node to first place try { arrayNode.RemoveChild(i); arrayNode.AddChild(1, child); } catch (XMPException) { // cannot occur, because same child is removed before System.Diagnostics.Debug.Assert(false); } if (i == 2) { arrayNode.GetChild(2).SetValue(child.GetValue()); } break; } } }
/// <summary>Looks for the appropriate language item in a text alternative array.item</summary> /// <param name="arrayNode">an array node</param> /// <param name="language">the requested language</param> /// <returns>Returns the index if the language has been found, -1 otherwise.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> internal static int LookupLanguageItem(XMPNode arrayNode, string language) { if (!arrayNode.GetOptions().IsArray()) { throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); } for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) { XMPNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) { continue; } else { if (language.Equals(child.GetQualifier(1).GetValue())) { return index; } } } return -1; }
/// <summary>Creates a property info object from an <code>XMPNode</code>.</summary> /// <param name="node">an <code>XMPNode</code></param> /// <param name="baseNS">the base namespace to report</param> /// <param name="path">the full property path</param> /// <returns>Returns a <code>XMPProperty</code>-object that serves representation of the node.</returns> protected internal virtual XMPPropertyInfo CreatePropertyInfo(XMPNode node, string baseNS, string path) { string value = node.GetOptions().IsSchemaNode() ? null : node.GetValue(); return new _XMPPropertyInfo_450(node, baseNS, path, value); }
/// <summary>Moves an alias node of array form to another schema into an array</summary> /// <param name="propertyIt">the property iterator of the old schema (used to delete the property)</param> /// <param name="childNode">the node to be moved</param> /// <param name="baseArray">the base array for the array item</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards XMP errors</exception> private static void TransplantArrayItemAlias(Iterator propertyIt, XMPNode childNode, XMPNode baseArray) { if (baseArray.GetOptions().IsArrayAltText()) { if (childNode.GetOptions().GetHasLanguage()) { throw new XMPException("Alias to x-default already has a language qualifier", XMPErrorConstants.Badxmp); } XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); childNode.AddQualifier(langQual); } propertyIt.Remove(); childNode.SetName(XMPConstConstants.ArrayItemName); baseArray.AddChild(childNode); }
/// <summary>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); }
/// <summary> /// The internals for SetProperty() and related calls, used after the node is /// found or created. /// </summary> /// <param name="node">the newly created node</param> /// <param name="value">the node value, can be <code>null</code></param> /// <param name="newOptions">options for the new node, must not be <code>null</code>.</param> /// <param name="deleteExisting">flag if the existing value is to be overwritten</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thrown if options and value do not correspond</exception> internal virtual void SetNode(XMPNode node, object value, PropertyOptions newOptions, bool deleteExisting) { if (deleteExisting) { node.Clear(); } // its checked by SetOptions(), if the merged result is a valid options set node.GetOptions().MergeWith(newOptions); if (!node.GetOptions().IsCompositeProperty()) { // This is setting the value of a leaf node. XMPNodeUtils.SetNodeValue(node, value); } else { if (value != null && value.ToString().Length > 0) { throw new XMPException("Composite nodes can't have values", XMPErrorConstants.Badxpath); } node.RemoveChildren(); } }
/// <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>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; }
/// <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>See if an array is an alt-text array.</summary> /// <remarks> /// See if an array is an alt-text array. If so, make sure the x-default item /// is first. /// </remarks> /// <param name="arrayNode">the array node to check if its an alt-text array</param> internal static void DetectAltText(XMPNode arrayNode) { if (arrayNode.GetOptions().IsArrayAlternate() && arrayNode.HasChildren()) { bool isAltText = false; for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) { XMPNode child = (XMPNode)it.Next(); if (child.GetOptions().GetHasLanguage()) { isAltText = true; break; } } if (isAltText) { arrayNode.GetOptions().SetArrayAltText(true); NormalizeLangArray(arrayNode); } } }
/// <summary> /// Evaluates a raw node value to the given value type, apply special /// conversions for defined types in XMP. /// </summary> /// <param name="valueType">an int indicating the value type</param> /// <param name="propNode">the node containing the value</param> /// <returns>Returns a literal value for the node.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> private object EvaluateNodeValue(int valueType, XMPNode propNode) { object value; string rawValue = propNode.GetValue(); switch (valueType) { case ValueBoolean: { value = XMPUtils.ConvertToBoolean(rawValue); break; } case ValueInteger: { value = XMPUtils.ConvertToInteger(rawValue); break; } case ValueLong: { value = XMPUtils.ConvertToLong(rawValue); break; } case ValueDouble: { value = XMPUtils.ConvertToDouble(rawValue); break; } case ValueDate: { value = XMPUtils.ConvertToDate(rawValue); break; } case ValueCalendar: { XMPDateTime dt = XMPUtils.ConvertToDate(rawValue); value = dt.GetCalendar(); break; } case ValueBase64: { value = XMPUtils.DecodeBase64(rawValue); break; } case ValueString: default: { // leaf values return empty string instead of null // for the other cases the converter methods provides a "null" // value. // a default value can only occur if this method is made public. value = rawValue != null || propNode.GetOptions().IsCompositeProperty() ? rawValue : string.Empty; break; } } return value; }
/// <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; }
/// <summary>Follow an expanded path expression to find or create a node.</summary> /// <param name="xmpTree">the node to begin the search.</param> /// <param name="xpath">the complete xpath</param> /// <param name="createNodes"> /// flag if nodes shall be created /// (when called by <code>setProperty()</code>) /// </param> /// <param name="leafOptions"> /// the options for the created leaf nodes (only when /// <code>createNodes == true</code>). /// </param> /// <returns>Returns the node if found or created or <code>null</code>.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"> /// An exception is only thrown if an error occurred, /// not if a node was not found. /// </exception> internal static XMPNode FindNode(XMPNode xmpTree, XMPPath xpath, bool createNodes, PropertyOptions leafOptions) { // check if xpath is set. if (xpath == null || xpath.Size() == 0) { throw new XMPException("Empty XMPPath", XMPErrorConstants.Badxpath); } // Root of implicitly created subtree to possible delete it later. // Valid only if leaf is new. XMPNode rootImplicitNode = null; XMPNode currNode = null; // resolve schema step currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XMPPath.StepSchema).GetName(), createNodes); if (currNode == null) { return(null); } else { if (currNode.IsImplicit()) { currNode.SetImplicit(false); // Clear the implicit node bit. rootImplicitNode = currNode; } } // Save the top most implicit node. // Now follow the remaining steps of the original XMPPath. try { for (int i = 1; i < xpath.Size(); i++) { currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes); if (currNode == null) { if (createNodes) { // delete implicitly created nodes DeleteNode(rootImplicitNode); } return(null); } else { if (currNode.IsImplicit()) { // clear the implicit node flag currNode.SetImplicit(false); // if node is an ALIAS (can be only in root step, auto-create array // when the path has been resolved from a not simple alias type if (i == 1 && xpath.GetSegment(i).IsAlias() && xpath.GetSegment(i).GetAliasForm() != 0) { currNode.GetOptions().SetOption(xpath.GetSegment(i).GetAliasForm(), true); } else { // "CheckImplicitStruct" in C++ if (i < xpath.Size() - 1 && xpath.GetSegment(i).GetKind() == XMPPath.StructFieldStep && !currNode.GetOptions().IsCompositeProperty()) { currNode.GetOptions().SetStruct(true); } } if (rootImplicitNode == null) { rootImplicitNode = currNode; } } } } } catch (XMPException e) { // Save the top most implicit node. // if new notes have been created prior to the error, delete them if (rootImplicitNode != null) { DeleteNode(rootImplicitNode); } throw; } if (rootImplicitNode != null) { // set options only if a node has been successful created currNode.GetOptions().MergeWith(leafOptions); currNode.SetOptions(currNode.GetOptions()); } return(currNode); }
/// <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>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); } }
/// <seealso cref="Com.Adobe.Xmp.XMPUtils.RemoveProperties(Com.Adobe.Xmp.XMPMeta, string, string, bool, bool)"/> /// <param name="xmp">The XMP object containing the properties to be removed.</param> /// <param name="schemaNS"> /// Optional schema namespace URI for the properties to be /// removed. /// </param> /// <param name="propName">Optional path expression for the property to be removed.</param> /// <param name="doAllProperties"> /// Option flag to control the deletion: do internal properties in /// addition to external properties. /// </param> /// <param name="includeAliases"> /// Option flag to control the deletion: Include aliases in the /// "named schema" case above. /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException">If metadata processing fails</exception> public static void RemoveProperties(XMPMeta xmp, string schemaNS, string propName, bool doAllProperties, bool includeAliases) { ParameterAsserts.AssertImplementation(xmp); XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; if (propName != null && propName.Length > 0) { // Remove just the one indicated property. This might be an alias, // the named schema might not actually exist. So don't lookup the // schema node. if (schemaNS == null || schemaNS.Length == 0) { throw new XMPException("Property name requires schema namespace", XMPErrorConstants.Badparam); } XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); XMPNode propNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), expPath, false, null); if (propNode != null) { if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment(XMPPath.StepSchema).GetName(), expPath.GetSegment(XMPPath.StepRootProp).GetName())) { XMPNode parent = propNode.GetParent(); parent.RemoveChild(propNode); if (parent.GetOptions().IsSchemaNode() && !parent.HasChildren()) { // remove empty schema node parent.GetParent().RemoveChild(parent); } } } } else { if (schemaNS != null && schemaNS.Length > 0) { // Remove all properties from the named schema. Optionally include // aliases, in which case // there might not be an actual schema node. // XMP_NodePtrPos schemaPos; XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmpImpl.GetRoot(), schemaNS, false); if (schemaNode != null) { if (RemoveSchemaChildren(schemaNode, doAllProperties)) { xmpImpl.GetRoot().RemoveChild(schemaNode); } } if (includeAliases) { // We're removing the aliases also. Look them up by their // namespace prefix. // But that takes more code and the extra speed isn't worth it. // Lookup the XMP node // from the alias, to make sure the actual exists. XMPAliasInfo[] aliases = XMPMetaFactory.GetSchemaRegistry().FindAliases(schemaNS); for (int i = 0; i < aliases.Length; i++) { XMPAliasInfo info = aliases[i]; XMPPath path = XMPPathParser.ExpandXPath(info.GetNamespace(), info.GetPropName()); XMPNode actualProp = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), path, false, null); if (actualProp != null) { XMPNode parent = actualProp.GetParent(); parent.RemoveChild(actualProp); } } } } else { // Remove all appropriate properties from all schema. In this case // we don't have to be // concerned with aliases, they are handled implicitly from the // actual properties. for (Iterator it = xmpImpl.GetRoot().IterateChildren(); it.HasNext();) { XMPNode schema = (XMPNode)it.Next(); if (RemoveSchemaChildren(schema, doAllProperties)) { it.Remove(); } } } } }
/// <summary>Serializes an array property.</summary> /// <param name="node">an XMPNode</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards the writer exceptions.</exception> /// <exception cref="Com.Adobe.Xmp.XMPException">If qualifier and element fields are mixed.</exception> private void SerializeCompactRDFArrayProp(XMPNode node, int indent) { // This is an array. Write('>'); WriteNewline(); EmitRDFArrayTag(node, true, indent + 1); if (node.GetOptions().IsArrayAltText()) { XMPNodeUtils.NormalizeLangArray(node); } SerializeCompactRDFElementProps(node, indent + 2); EmitRDFArrayTag(node, false, indent + 1); }
/// <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> /// <ol> /// <li>Look for an exact match with the specific language. /// </summary> /// <remarks> /// <ol> /// <li>Look for an exact match with the specific language. /// <li>If a generic language is given, look for partial matches. /// <li>Look for an "x-default"-item. /// <li>Choose the first item. /// </ol> /// </remarks> /// <param name="arrayNode">the alt text array node</param> /// <param name="genericLang">the generic language</param> /// <param name="specificLang">the specific language</param> /// <returns> /// Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> internal static object[] ChooseLocalizedText(XMPNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.GetOptions().IsArrayAltText()) { throw new XMPException("Localized text array is not alt-text", XMPErrorConstants.Badxpath); } else { if (!arrayNode.HasChildren()) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltNoValues, null }; } } int foundGenericMatches = 0; XMPNode resultNode = null; XMPNode xDefault = null; // Look for the first partial match with the generic language. for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) { XMPNode currItem = (XMPNode)it.Next(); // perform some checks on the current item if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); } else { if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) { throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); } } string currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSpecificMatch, currItem }; } else { if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else { if (XMPConstConstants.XDefault.Equals(currLang)) { xDefault = currItem; } } } } // evaluate loop if (foundGenericMatches == 1) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSingleGeneric, resultNode }; } else { if (foundGenericMatches > 1) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltMultipleGeneric, resultNode }; } else { if (xDefault != null) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltXdefault, xDefault }; } else { // Everything failed, choose the first item. return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltFirstItem, arrayNode.GetChild(1) }; } } } }
/// <summary>Creates a property info object from an <code>XMPNode</code>.</summary> /// <param name="node">an <code>XMPNode</code></param> /// <param name="baseNS">the base namespace to report</param> /// <param name="path">the full property path</param> /// <returns>Returns a <code>XMPProperty</code>-object that serves representation of the node.</returns> protected internal virtual XMPPropertyInfo CreatePropertyInfo(XMPNode node, string baseNS, string path) { string value = node.GetOptions().IsSchemaNode() ? null : node.GetValue(); return(new _XMPPropertyInfo_450(node, baseNS, path, value)); }
/// <summary>Make sure that the array is well-formed AltText.</summary> /// <remarks> /// Make sure that the array is well-formed AltText. Each item must be simple /// and have an "xml:lang" qualifier. If repairs are needed, keep simple /// non-empty items by adding the "xml:lang" with value "x-repair". /// </remarks> /// <param name="arrayNode">the property node of the array to repair.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards unexpected exceptions.</exception> private static void RepairAltText(XMPNode arrayNode) { if (arrayNode == null || !arrayNode.GetOptions().IsArray()) { // Already OK or not even an array. return; } // fix options arrayNode.GetOptions().SetArrayOrdered(true).SetArrayAlternate(true).SetArrayAltText(true); for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) { XMPNode currChild = (XMPNode)it.Next(); if (currChild.GetOptions().IsCompositeProperty()) { // Delete non-simple children. it.Remove(); } else { if (!currChild.GetOptions().GetHasLanguage()) { string childValue = currChild.GetValue(); if (childValue == null || childValue.Length == 0) { // Delete empty valued children that have no xml:lang. it.Remove(); } else { // Add an xml:lang qualifier with the value "x-repair". XMPNode repairLang = new XMPNode(XMPConstConstants.XmlLang, "x-repair", null); currChild.AddQualifier(repairLang); } } } } }
/// <summary>Writes the array start and end tags.</summary> /// <param name="arrayNode">an array node</param> /// <param name="isStartTag">flag if its the start or end tag</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">forwards writer exceptions</exception> private void EmitRDFArrayTag(XMPNode arrayNode, bool isStartTag, int indent) { if (isStartTag || arrayNode.HasChildren()) { WriteIndent(indent); Write(isStartTag ? "<rdf:" : "</rdf:"); if (arrayNode.GetOptions().IsArrayAlternate()) { Write("Alt"); } else { if (arrayNode.GetOptions().IsArrayOrdered()) { Write("Seq"); } else { Write("Bag"); } } if (isStartTag && !arrayNode.HasChildren()) { Write("/>"); } else { Write(">"); } WriteNewline(); } }