/// <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); } } }
// 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>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> /// 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>Remove all empty schemas from the metadata tree that were generated during the rdf parsing.</summary> /// <param name="tree">the root of the metadata tree</param> private static void DeleteEmptySchemas(XMPNode tree) { // Delete empty schema nodes. Do this last, other cleanup can make empty // schema. for (Iterator it = tree.IterateChildren(); it.HasNext();) { XMPNode schema = (XMPNode)it.Next(); if (!schema.HasChildren()) { it.Remove(); } } }
/// <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 = baseNode.IterateChildren(); an_1.HasNext() && bn.HasNext();) { XMPNode aliasQual = (XMPNode)an_1.Next(); XMPNode baseQual = (XMPNode)bn.Next(); CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <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>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> /// Recursively handles the "value" for a node that must be written as an RDF /// property element. /// </summary> /// <remarks> /// Recursively handles the "value" for a node that must be written as an RDF /// property element. 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. The patterns bwlow ignore attribute qualifiers such as /// xml:lang, they don't affect the output form. /// <blockquote> /// <pre> /// <ns:UnqualifiedStructProperty-1 /// ... The fields as attributes, if all are simple and unqualified /// /> /// <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource"> /// ... The fields as elements, if none are simple and unqualified /// </ns:UnqualifiedStructProperty-2> /// <ns:UnqualifiedStructProperty-3> /// <rdf:Description /// ... The simple and unqualified fields as attributes /// > /// ... The compound or qualified fields as elements /// </rdf:Description> /// </ns:UnqualifiedStructProperty-3> /// <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:parseType="Resource"> /// <rdf:value> ... Property "value" /// following the unqualified forms ... </rdf:value> /// ... Qualifiers looking like named struct fields /// </ns:QualifiedProperty> /// </pre> /// </blockquote> /// *** Consider numbered array items, but has compatibility problems. /// Consider qualified form with rdf:Description and attributes. /// </remarks> /// <param name="parentNode">the parent node</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards writer exceptions</exception> /// <exception cref="Com.Adobe.Xmp.XMPException">If qualifier and element fields are mixed.</exception> private void SerializeCompactRDFElementProps(XMPNode parentNode, int indent) { for (Iterator it = parentNode.IterateChildren(); it.HasNext(); ) { XMPNode node = (XMPNode)it.Next(); if (CanBeRDFAttrProp(node)) { continue; } bool emitEndTag = true; bool indentEndTag = true; // Determine the XML element name, write the name part of the start tag. Look over the // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute // qualifiers at the same time. string elemName = node.GetName(); if (XMPConstConstants.ArrayItemName.Equals(elemName)) { elemName = "rdf:li"; } WriteIndent(indent); Write('<'); Write(elemName); bool hasGeneralQualifiers = false; bool hasRDFResourceQual = false; for (Iterator iq = node.IterateQualifier(); iq.HasNext(); ) { XMPNode qualifier = (XMPNode)iq.Next(); if (!RdfAttrQualifier.Contains(qualifier.GetName())) { hasGeneralQualifiers = true; } else { hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName()); Write(' '); Write(qualifier.GetName()); Write("=\""); AppendNodeValue(qualifier.GetValue(), true); Write('"'); } } // Process the property according to the standard patterns. if (hasGeneralQualifiers) { SerializeCompactRDFGeneralQualifier(indent, node); } else { // This node has only attribute qualifiers. Emit as a property element. if (!node.GetOptions().IsCompositeProperty()) { object[] result = SerializeCompactRDFSimpleProp(node); emitEndTag = ((bool)result[0]); indentEndTag = ((bool)result[1]); } else { if (node.GetOptions().IsArray()) { SerializeCompactRDFArrayProp(node, indent); } else { emitEndTag = SerializeCompactRDFStructProp(node, indent, hasRDFResourceQual); } } } // Emit the property element end tag. if (emitEndTag) { if (indentEndTag) { WriteIndent(indent); } Write("</"); Write(elemName); Write('>'); WriteNewline(); } } }
/// <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>Serializes a struct property.</summary> /// <param name="node">an XMPNode</param> /// <param name="indent">the current indent level</param> /// <param name="hasRDFResourceQual">Flag if the element has resource qualifier</param> /// <returns>Returns true if an end flag shall be emitted.</returns> /// <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 bool SerializeCompactRDFStructProp(XMPNode node, int indent, bool hasRDFResourceQual) { // This must be a struct. bool hasAttrFields = false; bool hasElemFields = false; bool emitEndTag = true; for (Iterator ic = node.IterateChildren(); ic.HasNext(); ) { XMPNode field = (XMPNode)ic.Next(); if (CanBeRDFAttrProp(field)) { hasAttrFields = true; } else { hasElemFields = true; } if (hasAttrFields && hasElemFields) { break; } } // No sense looking further. if (hasRDFResourceQual && hasElemFields) { throw new XMPException("Can't mix rdf:resource qualifier and element fields", XMPErrorConstants.Badrdf); } if (!node.HasChildren()) { // Catch an empty struct as a special case. The case // below would emit an empty // XML element, which gets reparsed as a simple property // with an empty value. Write(" rdf:parseType=\"Resource\"/>"); WriteNewline(); emitEndTag = false; } else { if (!hasElemFields) { // All fields can be attributes, use the // emptyPropertyElt form. SerializeCompactRDFAttrProps(node, indent + 1); Write("/>"); WriteNewline(); emitEndTag = false; } else { if (!hasAttrFields) { // All fields must be elements, use the // parseTypeResourcePropertyElt form. Write(" rdf:parseType=\"Resource\">"); WriteNewline(); SerializeCompactRDFElementProps(node, indent + 1); } else { // Have a mix of attributes and elements, use an inner rdf:Description. Write('>'); WriteNewline(); WriteIndent(indent + 1); Write(RdfStructStart); SerializeCompactRDFAttrProps(node, indent + 2); Write(">"); WriteNewline(); SerializeCompactRDFElementProps(node, indent + 1); WriteIndent(indent + 1); Write(RdfStructEnd); WriteNewline(); } } } return emitEndTag; }
/// <summary> /// Serializes one schema with all contained properties in pretty-printed /// manner.<br /> /// Each schema's properties are written to a single /// rdf:Description element. /// </summary> /// <remarks> /// Serializes one schema with all contained properties in pretty-printed /// manner.<br /> /// Each schema's properties are written to a single /// rdf:Description element. All of the necessary namespaces are declared in /// the rdf:Description element. The baseIndent is the base level for the /// entire serialization, that of the x:xmpmeta element. An xml:lang /// qualifier is written as an attribute of the property start tag, not by /// itself forcing the qualified property form. /// <blockquote> /// <pre> /// <rdf:Description rdf:about="TreeName" xmlns:ns="URI" ... > /// ... The actual properties of the schema, see SerializePrettyRDFProperty /// <!-- ns1:Alias is aliased to ns2:Actual --> ... If alias comments are wanted /// </rdf:Description> /// </pre> /// </blockquote> /// </remarks> /// <param name="schemaNode">a schema node</param> /// <param name="level"></param> /// <exception cref="System.IO.IOException">Forwarded writer exceptions</exception> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private void SerializeCanonicalRDFSchema(XMPNode schemaNode, int level) { // Write each of the schema's actual properties. for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) { XMPNode propNode = (XMPNode)it.Next(); SerializeCanonicalRDFProperty(propNode, options.GetUseCanonicalFormat(), false, level + 2); } }
/// <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>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>Write each of the parent's simple unqualified properties as an attribute.</summary> /// <remarks> /// Write each of the parent's simple unqualified properties as an attribute. Returns true if all /// of the properties are written as attributes. /// </remarks> /// <param name="parentNode">the parent property node</param> /// <param name="indent">the current indent level</param> /// <returns>Returns true if all properties can be rendered as RDF attribute.</returns> /// <exception cref="System.IO.IOException"/> private bool SerializeCompactRDFAttrProps(XMPNode parentNode, int indent) { bool allAreAttrs = true; for (Iterator it = parentNode.IterateChildren(); it.HasNext(); ) { XMPNode prop = (XMPNode)it.Next(); if (CanBeRDFAttrProp(prop)) { WriteNewline(); WriteIndent(indent); Write(prop.GetName()); Write("=\""); AppendNodeValue(prop.GetValue(), true); Write('"'); } else { allAreAttrs = false; } } return allAreAttrs; }
/// <summary>Remove all empty schemas from the metadata tree that were generated during the rdf parsing.</summary> /// <param name="tree">the root of the metadata tree</param> private static void DeleteEmptySchemas(XMPNode tree) { // Delete empty schema nodes. Do this last, other cleanup can make empty // schema. for (Iterator it = tree.IterateChildren(); it.HasNext(); ) { XMPNode schema = (XMPNode)it.Next(); if (!schema.HasChildren()) { it.Remove(); } } }
/// <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>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>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>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> /// 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(); }
/// <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>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>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> /// <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>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); } }